Эффективный способ чтения набора 3-канальных изображений из Python в двумерный массив для использования в C - PullRequest
0 голосов
/ 29 апреля 2018

Я работаю над проектом, включающим обнаружение объектов посредством глубокого изучения, с базовым кодом обнаружения, написанным на C. Из-за требований проекта этот код имеет оболочку Python, которая взаимодействует с требуемыми функциями C через ctypes. Изображения читаются из Python, а затем передаются в C для обработки в виде пакета.

В своем текущем состоянии код очень неоптимизирован: изображения (640x360x3 каждое) читаются с использованием cv2.imread, а затем укладываются в массив. Например, для размера партии 16 размеры этого массива равны (16 360 640,3). Как только это сделано, указатель на этот массив передается через ctypes в C, где массив анализируется, значения пикселей нормализуются и переставляются в двумерный массив. Размеры двумерного массива составляют 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, который вызывает эту функцию: (массив является исходным массивом 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)

На данный момент эта реализация ctypes занимает около 35 мс для пакета из 16 изображений. Я работаю над конвейером обработки критических изображений с очень высокой частотой кадров, поэтому существует ли более эффективный способ выполнения этих операций? В частности:

  1. Могу ли я прочитать изображение непосредственно как «пошаговый» одномерный массив в Python с диска, что позволит избежать итеративного доступа и копирования?
  2. Я изучил такие недалёкие операции, как: np.ascontiguousarray(img.transpose(2,0,1).flat, dtype=float)/255., который должен достичь чего-то похожего, но на самом деле это занимает больше времени, возможно, из-за того, что он вызывается в Python.
  3. Поможет ли Cython где-нибудь во время операции чтения?

1 Ответ

0 голосов
/ 30 апреля 2018

Что касается метода ascontiguousarray, я предполагаю, что он довольно медленный, поскольку python должен выполнить некоторые операции с памятью, чтобы вернуть C-подобный непрерывный массив.

РЕДАКТИРОВАТЬ 1: Я видел этот ответ , очевидно, функция imread openCV уже должна возвращать непрерывный массив.

Я не очень знаком с ctypes, но случайно использую библиотеку PyBind и могу только рекомендовать ее использовать. Он реализует буферный протокол Python , что позволяет вам взаимодействовать с данными Python практически без затрат.

Я ответил на вопрос , объясняющий, как передать массив numpy из Python в C / C ++, сделать с ним что-то глупое в C ++ и вернуть динамически созданный массив обратно в Python.

РЕДАКТИРОВАТЬ 2: Я добавил простой пример, который получает массив Numpy, отправляет его в C и печатает его из C. Вы можете найти его здесь . Надеюсь, это поможет!

РЕДАКТИРОВАТЬ 3: Чтобы ответить на ваш последний комментарий, да, вы определенно можете это сделать. Вы можете изменить свой код так, чтобы (1) создать экземпляр двумерного массива в C ++, (2) передать ссылку на данные в вашу функцию C, которая изменит их вместо объявления матрицы, и (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(...){}...

Это должно сработать, хотя я не проверял:)

...