Search code examples
pythonarraysgpuarrayfire

How to use ArrayFire batched 2D convolution


Reading through ArrayFire documentation, I noticed that the library supports batched operations when using 2D convolution. Therefore, I need to apply N filters to an image using the C++ API.

For easy testing, I decided to create a simple Python script to assert the convolution results. However, I couldn't get proper results when using >1 filters and comparing them to OpenCV's 2D convolution separately. Following is my Python script:

import arrayfire as af
import cv2
import numpy as np
 
np.random.seed(1)
 
np.set_printoptions(precision=3)
af.set_backend('cuda')
 
n_kernels = 2
 
image = np.random.randn(512,512).astype(np.float32)
 
kernels_list = [np.random.randn(7,7).astype(np.float32) for _ in range(n_kernels)]
 
conv_cv_list = [cv2.filter2D(image, -1, cv2.flip(kernel,-1), borderType=cv2.BORDER_CONSTANT) for kernel in kernels_list]
 
image_gpu = af.array.Array(image.ctypes.data, image.shape, image.dtype.char)
 
kernels = np.stack(kernels_list, axis=-1) if n_kernels > 1 else kernels_list[0]
kernels_gpu = af.array.Array(kernels.ctypes.data, kernels.shape, kernels.dtype.char)
 
conv_af_gpu = af.convolve2(image_gpu, kernels_gpu)
conv_af = conv_af_gpu.to_ndarray()
 
if n_kernels == 1:
    conv_af = conv_af[..., None]
 
for kernel_idx in range(n_kernels):
    print("CV conv:", conv_cv_list[kernel_idx][0, 0])
    print("AF conv", conv_af[0, 0, kernel_idx])

That said, I would like to know how properly use ArrayFire batched support.


Solution

  • ArrayFire is column-major, whereas OpenCV and NumPy are row-major. This can cause issues. We provide an interop function to handle this for you, as follows.

    import arrayfire as af
    import cv2
    import numpy as np
     
    np.random.seed(1)
     
    np.set_printoptions(precision=3)
    af.set_backend('cuda')
     
    n_kernels = 2
     
    image = np.random.randn(512,512).astype(np.float32)
     
    kernels_list = [np.random.randn(7,7).astype(np.float32) for _ in range(n_kernels)]
     
    conv_cv_list = [cv2.filter2D(image, -1, cv2.flip(kernel,-1), borderType=cv2.BORDER_CONSTANT) for kernel in kernels_list]
     
    image_gpu = af.interop.from_ndarray(image) # CHECK OUT THIS
     
    kernels = np.stack(kernels_list, axis=-1) if n_kernels > 1 else kernels_list[0]
    kernels_gpu = af.interop.from_ndarray(kernels) # CHECK OUT THIS
     
    conv_af_gpu = af.convolve2(image_gpu, kernels_gpu)
    conv_af = conv_af_gpu.to_ndarray()
     
    if n_kernels == 1:
        conv_af = conv_af[..., None]
     
    for kernel_idx in range(n_kernels):
        print("CV conv:", conv_cv_list[kernel_idx][0, 0])
        print("AF conv", conv_af[0, 0, kernel_idx])
    

    We are working on a newer version of ArrayFire Python which will address this issue.

    Good luck!