Объяснение выборки изображения ARM Neon

Я пытаюсь написать лучшую версию cv::resize() OpenCV, и я наткнулся на код, который находится здесь: https://github.com/rmaz/NEON-Image-Downscaling/blob/master/ImageResize/BDPViewController.m Код предназначен для понижения разрешения изображения на 2, но я не могу понять алгоритм. Я хотел бы сначала преобразовать этот алгоритм в C, а затем попытаться изменить его для целей обучения. Легко ли также преобразовать его в даунсэмплинг любого размера?

Функция:

static void inline resizeRow(uint32_t *dst, uint32_t *src, uint32_t pixelsPerRow)
{
    const uint32_t * rowB = src + pixelsPerRow;

    // force the number of pixels per row to a multiple of 8
    pixelsPerRow = 8 * (pixelsPerRow / 8);

    __asm__ volatile("Lresizeloop: \n" // start loop
                     "vld1.32 {d0-d3}, [%1]! \n" // load 8 pixels from the top row
                     "vld1.32 {d4-d7}, [%2]! \n" // load 8 pixels from the bottom row
                     "vhadd.u8 q0, q0, q2 \n" // average the pixels vertically
                     "vhadd.u8 q1, q1, q3 \n"
                     "vtrn.32 q0, q2 \n" // transpose to put the horizontally adjacent pixels in different registers
                     "vtrn.32 q1, q3 \n"
                     "vhadd.u8 q0, q0, q2 \n" // average the pixels horizontally
                     "vhadd.u8 q1, q1, q3 \n"
                     "vtrn.32 d0, d1 \n" // fill the registers with pixels
                     "vtrn.32 d2, d3 \n"
                     "vswp d1, d2 \n"
                     "vst1.64 {d0-d1}, [%0]! \n" // store the result
                     "subs %3, %3, #8 \n" // subtract 8 from the pixel count
                     "bne Lresizeloop \n" // repeat until the row is complete
: "=r"(dst), "=r"(src), "=r"(rowB), "=r"(pixelsPerRow)
: "0"(dst), "1"(src), "2"(rowB), "3"(pixelsPerRow)
: "q0", "q1", "q2", "q3", "cc"
);
}

To call it:

 // downscale the image in place
    for (size_t rowIndex = 0; rowIndex < height; rowIndex+=2)
    {
        void *sourceRow = (uint8_t *)buffer + rowIndex * bytesPerRow;
        void *destRow = (uint8_t *)buffer + (rowIndex / 2) * bytesPerRow;
        resizeRow(destRow, sourceRow, width);
    }

person Ahmed Saleh    schedule 13.03.2013    source источник
comment
Вы нашли довольно плохой пример: 1) Он усекается с каждым половинным сложением, поэтому результат менее точен. 2) Это тратит впустую ценные циклы со всеми этими транспонированиями, а также сбивает с толку. С VPADD и VPADAL вместо половинных сложений функция будет работать намного быстрее (транспонирование исчезнет) и точнее. (обрезается только один раз)   -  person Jake 'Alquimista' LEE    schedule 23.06.2013


Ответы (1)


Алгоритм довольно прост. Он считывает 8 пикселей из текущей строки и 8 из строки ниже. Затем он использует команду vhadd (сложение пополам) для усреднения 8 пикселей по вертикали. Затем он изменяет положение пикселей таким образом, что пары соседних по горизонтали пикселей теперь находятся в отдельных регистрах (расположенных вертикально). Затем он выполняет еще один набор сложений пополам, чтобы усреднить их вместе. Затем результат снова преобразуется, чтобы поместить их в исходное положение и записать в место назначения. Этот алгоритм можно было бы переписать, чтобы обрабатывать различные интегральные размеры масштабирования, но в том виде, в котором он написан, он может выполнять только уменьшение 2x2 до 1 с усреднением. Вот эквивалент кода C:

static void inline resizeRow(uint32_t *dst, uint32_t *src, uint32_t pixelsPerRow)
{
    uint8_t * pSrc8 = (uint8_t *)src;
    uint8_t * pDest8 = (uint8_t *)dst;
    int stride = pixelsPerRow * sizeof(uint32_t);
    int x;
    int r, g, b, a;

    for (x=0; x<pixelsPerRow; x++)
    {
       r = pSrc8[0] + pSrc8[4] + pSrc8[stride+0] + pSrc8[stride+4];
       g = pSrc8[1] + pSrc8[5] + pSrc8[stride+1] + pSrc8[stride+5];
       b = pSrc8[2] + pSrc8[6] + pSrc8[stride+2] + pSrc8[stride+6];
       a = pSrc8[3] + pSrc8[7] + pSrc8[stride+3] + pSrc8[stride+7];
       pDest8[0] = (uint8_t)((r + 2)/4); // average with rounding
       pDest8[1] = (uint8_t)((g + 2)/4);
       pDest8[2] = (uint8_t)((b + 2)/4);
       pDest8[3] = (uint8_t)((a + 2)/4);
       pSrc8 += 8; // skip forward 2 source pixels
       pDest8 += 4; // skip forward 1 destination pixel
    }
person BitBank    schedule 13.03.2013
comment
Удивительный ответ. Как пиксели усредняются по вертикали? два пикселя, затем два пикселя? и Что необходимо для поддержки разного размера масштабирования? Я попробовал это на бумаге, но до сих пор не понимаю операцию транспонирования и соседние по горизонтали пиксели: / - person Ahmed Saleh; 13.03.2013
comment
Инструкция vhadd эквивалентна c = (a+b+1)/2. Первая часть кода усредняет пиксели по вертикали, потому что регистры NEON, содержащие верхнюю и нижнюю строки, усредняются вместе. Поскольку нет vhadd, который работает горизонтально через элементы вектора NEON, значения необходимо транспонировать так, чтобы соседние по горизонтали элементы помещались в отдельные регистры. После транспонирования они снова усредняются (усреднение пикселей по горизонтали). См. описание VTRN здесь: infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0489c/ - person BitBank; 13.03.2013
comment
Это правильно, как написано. Я записываю 4 байта, затем перемещаю указатель назначения на 4 байта. - person BitBank; 14.03.2013
comment
Хм? 2D массив? Нет, это простой указатель на одномерный ряд пикселей. Не знаю, откуда взялось ваше замешательство. - person BitBank; 14.03.2013