从Python中读取一组3通道图像到C中使用的二维数组的有效方法

时间:2018-04-29 18:43:27

标签: python c arrays numpy ctypes

我正在开发一个涉及通过深度学习进行对象检测的项目,底层检测代码用C语言编写。由于项目的要求,这段代码有一个Python包装器,它通过它连接所需的C函数ctypes的。图像从Python读取,然后传输到C中作为批处理。

在当前状态下,代码非常不优化:使用cv2.imread读取图像(每个640x360x3),然后堆叠成numpy数组。例如,对于批量大小为16,此阵列的尺寸为(16,360,640,3)。完成此操作后,指向此数组的指针将通过ctypes传递到C,在此处解析数组,将像素值标准化并重新排列为2D数组。 2D阵列的尺寸为16x691200(16x(640 * 360 * 3)),排列如下。

row [0]: Image 0: (B)r0(B)r1(B)r2.... (G)r0(G)r1(G)r2.... (R)r0(R)r1(R)r2....
row [1]: Image 1: (B)r0(B)r1(B)r2.... (G)r0(G)r1(G)r2.... (R)r0(R)r1(R)r2....
.
.
row [15]: Image 15: (B)r0(B)r1(B)r2.... (G)r0(G)r1(G)r2.... (R)r0(R)r1(R)r2....

`

用于执行此操作的C代码当前看起来像这样,其中通过步幅访问像素值并按顺序排列每个图像。 nb是批次中的图像总数(通常为16); h,w,c分别为360,640和3。

matrix ndarray_to_matrix(unsigned char* src, long* shape, long* strides)
{
int nb = shape[0];
int h = shape[1];
int w = shape[2];
int c = shape[3];
matrix X = make_matrix(nb, h*w*c);

int step_b = strides[0];
int step_h = strides[1];
int step_w = strides[2];
int step_c = strides[3];

int b, i, j, k;
int index1, index2 = 0;

for(b = 0; b < nb ; ++b) {
    for(i = 0; i < h; ++i) {
        for(k= 0; k < c; ++k) {
            for(j = 0; j < w; ++j) {
                index1 = k*w*h + i*w + j;
                index2 = step_b*b + step_h*i + step_w*j + step_c*k;
                X.vals[b][index1] = src[index2]/255.;
            }
        }
    }
}
return X;
}

调用此函数的相应Python代码:( array是原始的numpy数组)

for i in range(start, end):
    imgName = imgDir + '/' + allImageName[i]
    img = cv2.imread(imgName, 1)
    batchImageData[i-start,:,:] = img[:,:]

data = batchImageData.ctypes.data_as(POINTER(c_ubyte))
resmatrix = self.ndarray_to_matrix(data, batchImageData.ctypes.shape, batchImageData.ctypes.strides)

截至目前,对于一批16张图像,此ctypes实现大约需要35毫秒。我正在开发一个非常FPS的关键图像处理管道,那么有更有效的方法来执行这些操作吗?具体做法是:

  1. 我可以直接将图像作为一个跨步的图像来阅读吗? Python中的一维数组来自磁盘,从而避免了迭代访问和复制?
  2. 我研究过numpy操作,例如: np.ascontiguousarray(img.transpose(2,0,1).flat, dtype=float)/255.应该实现类似的东西,但这实际上花费了更多的时间,因为它是用Python调用的。
  3. 在阅读操作期间,Cython会在任何地方提供帮助吗?

1 个答案:

答案 0 :(得分:2)

关于ascontiguousarray方法,我假设它非常慢,因为python必须做一些内存工作才能返回类似C的连续数组。

编辑1: 我看到this answer,显然openCV的imread函数应该已经返回一个连续的数组。

我对ctypes不太熟悉,但碰巧使用PyBind library并且只能推荐使用它。它实现了Python的buffer protocol,因此允许您与python数据进行交互,几乎没有开销。

我已经回答了question解释如何将numpy数组从Python传递到C / C ++,在C ++中做一些虚拟的事情并将动态创建的数组返回给Python。

编辑2:我添加了一个接收Numpy数组的简单示例,将其发送到C并从C打印出来。您可以找到它here。希望它有所帮助!

编辑3: 要回答你的最后评论,是的,你绝对可以做到这一点。 您可以修改代码,以便(1)在C ++中实例化2D numpy数组,(2)将对数据的引用传递给C函数,该函数将修改它而不是声明Matrix并且(3)通过引用将该实例返回给Python

你的职能将成为:

void ndarray_to_matrix(unsigned char* src, double * x, long* shape, long* strides)
{
int nb = shape[0];
int h = shape[1];
int w = shape[2];
int c = shape[3];

int step_b = strides[0];
int step_h = strides[1];
int step_w = strides[2];
int step_c = strides[3];

int b, i, j, k;
int index1, index2 = 0;

for(b = 0; b < nb ; ++b) {
    for(i = 0; i < h; ++i) {
        for(k= 0; k < c; ++k) {
            for(j = 0; j < w; ++j) {
                index1 = k*w*h + i*w + j;
                index2 = step_b*b + step_h*i + step_w*j + step_c*k;
                X.vals[b][index1] = src[index2]/255.;
            }
        }
    }
}
}

您要在C ++包装器代码中添加

// Instantiate the output array, assuming we know b, h, c,w
py::array_t<double> x = py::array_t<double>(b*h*c*w);
py::buffer_info bufx = x.request();
double*ptrx = (double *) bufx.ptr;

// Call to your C function with ptrx as input
ndarray_to_matrix(src, ptrx, shape, strides);

// now reshape x
x.reshape({b, h*c*w});

不要忘记修改C ++包装函数的原型以返回numpy数组,如:

py::array_t<double> read_matrix(...){}...

这应该有用,但我没有测试过它:)