Быстрое преобразование RGB => YUV в OpenCL - PullRequest
5 голосов
/ 12 февраля 2011

Я знаю, что следующую формулу можно использовать для преобразования изображений RGB в изображения YUV.В следующей формуле R, G, B, Y, U, V - все 8-разрядные целые числа без знака, а промежуточные значения - 16-разрядные целые числа без знака.

Y = ( (  66 * R + 129 * G +  25 * B + 128) >> 8) +  16  
U = ( ( -38 * R -  74 * G + 112 * B + 128) >> 8) + 128  
V = ( ( 112 * R -  94 * G -  18 * B + 128) >> 8) + 128

Но когда формула используется в OpenCL, это другая история.
1. Доступ к записи в 8-битную память является необязательным расширением, что означает, что некоторые реализации OpenCL могут не поддерживать его.
2. поддерживается даже указанное выше расширение, оно смертельно медленное по сравнению с 32-битным доступом для записи.

Чтобы повысить производительность, каждые 4 пикселя будут обрабатываться одновременно, поэтому ввод равен 128-разрядные целые числа, а на выходе - 3 32-разрядных целых числа без знака (первое означает 4 выборки Y, второе - 4 U выборки, последнее - 4 В.).

MyВопрос в том, как получить эти 3 32-битных целых числа напрямую из 12 8-битных целых чисел?Существует ли формула для получения этих 3-х 32-битных целых чисел, или мне просто нужно использовать старую формулу, чтобы получить 12 8-битных целочисленных результатов (4 Y, 4 U, 4 В) и построить 3 32-битных целых с битоперация?

Ответы [ 3 ]

9 голосов
/ 31 июля 2013

Несмотря на то, что этот вопрос был задан 2 года назад, я думаю, что здесь поможет какой-то рабочий код.С точки зрения первоначальной обеспокоенности по поводу плохой производительности при прямом доступе к 8-битным значениям, лучше по возможности выполнять 32-битный прямой доступ.

Некоторое время назад я разработал и использовал следующее ядро ​​OpenCL для преобразованияARGB (типичное расположение пикселов растрового изображения Windows) для макета памяти y-плоскости (полноразмерный) и полуплоскости u / v (размер четверти) в качестве входных данных для кодирования libx264.

__kernel void ARGB2YUV ( 
                            __global  unsigned int * sourceImage,
                            __global unsigned int * destImage,
            unsigned int srcHeight,
            unsigned int srcWidth,
            unsigned int yuvStride // must be srcWidth/4 since we pack 4 pixels into 1 Y-unit (with 4 y-pixels)
            )
{
    int i,j;
    unsigned int RGBs [ 4 ];
    unsigned int posSrc, RGB, Value4 = 0, Value, yuvStrideHalf, srcHeightHalf, yPlaneOffset, posOffset;
    unsigned char red, green, blue;

    unsigned int posX = get_global_id(0);
    unsigned int posY = get_global_id(1);

    if ( posX < yuvStride ) {
        // Y plane - pack 4 y's within each work item
        if ( posY >= srcHeight )
            return;

        posSrc = (posY * srcWidth) + (posX * 4);

        RGBs [ 0 ] = sourceImage [ posSrc ];
        RGBs [ 1 ] = sourceImage [ posSrc + 1 ];
        RGBs [ 2 ] = sourceImage [ posSrc + 2 ];
        RGBs [ 3 ] = sourceImage [ posSrc + 3 ];

        for ( i=0; i<4; i++ ) {
            RGB = RGBs [ i ];

            blue = RGB & 0xff; green = (RGB >> 8) & 0xff; red = (RGB >> 16) & 0xff;

            Value = ( ( 66 * red + 129 * green + 25 * blue ) >> 8 ) + 16;
            Value4 |= (Value << (i * 8));
        }

        destImage [ (posY * yuvStride) + posX ] = Value4;
        return;
    }

    posX -= yuvStride;
    yuvStrideHalf = yuvStride >> 1;

    // U plane - pack 4 u's within each work item
    if ( posX >= yuvStrideHalf )
        return;

    srcHeightHalf = srcHeight >> 1; 
    if ( posY < srcHeightHalf ) {
        posSrc = ((posY * 2) * srcWidth) + (posX * 8);

        RGBs [ 0 ] = sourceImage [ posSrc ];
        RGBs [ 1 ] = sourceImage [ posSrc + 2 ];
        RGBs [ 2 ] = sourceImage [ posSrc + 4 ];
        RGBs [ 3 ] = sourceImage [ posSrc + 6 ];

        for ( i=0; i<4; i++ ) {
            RGB = RGBs [ i ];

            blue = RGB & 0xff; green = (RGB >> 8) & 0xff; red = (RGB >> 16) & 0xff;
            Value = ( ( -38 * red + -74 * green + 112 * blue ) >> 8 ) + 128;
            Value4 |= (Value << (i * 8));
        }
        yPlaneOffset = yuvStride * srcHeight;
        posOffset = (posY * yuvStrideHalf) + posX;
        destImage [ yPlaneOffset + posOffset ] = Value4;
        return;
    }

    posY -= srcHeightHalf;
    if ( posY >= srcHeightHalf )
        return;

    // V plane - pack 4 v's within each work item
    posSrc = ((posY * 2) * srcWidth) + (posX * 8);

    RGBs [ 0 ] = sourceImage [ posSrc ];
    RGBs [ 1 ] = sourceImage [ posSrc + 2 ];
    RGBs [ 2 ] = sourceImage [ posSrc + 4 ];
    RGBs [ 3 ] = sourceImage [ posSrc + 6 ];

    for ( i=0; i<4; i++ ) {
        RGB = RGBs [ i ];

        blue = RGB & 0xff; green = (RGB >> 8) & 0xff; red = (RGB >> 16) & 0xff;

        Value = ( ( 112 * red + -94 * green + -18 * blue ) >> 8 ) + 128;
        Value4 |= (Value << (i * 8));
    }

    yPlaneOffset = yuvStride * srcHeight;
    posOffset = (posY * yuvStrideHalf) + posX;

    destImage [ yPlaneOffset + (yPlaneOffset >> 2) + posOffset ] = Value4;
    return;
}

Этот код выполняет толькоглобальный 32-битный доступ к памяти, в то время как 8-битная обработка происходит внутри каждого рабочего элемента.

Oh .. и соответствующий код для вызова ядра

unsigned int width = 1024;
unsigned int height = 768;

unsigned int frameSize = width * height;
const unsigned int argbSize = frameSize * 4; // ARGB pixels

const unsigned int yuvSize = frameSize + (frameSize >> 1); // Y,U,V planes

const unsigned int yuvStride = width >> 2; // since we pack 4 RGBs into "one" YYYY

// Allocates ARGB buffer
ocl_rgb_buffer = clCreateBuffer ( context, CL_MEM_READ_WRITE, argbSize, 0, &error );
// ... error handling ...

ocl_yuv_buffer = clCreateBuffer ( context, CL_MEM_READ_WRITE, yuvSize, 0, &error );
// ... error handling ...

error = clSetKernelArg  ( kernel, 0, sizeof(cl_mem), &ocl_rgb_buffer );
error |= clSetKernelArg ( kernel, 1, sizeof(cl_mem), &ocl_yuv_buffer );

error |= clSetKernelArg ( kernel, 2, sizeof(unsigned int), &height);
error |= clSetKernelArg ( kernel, 3, sizeof(unsigned int), &width);

error |= clSetKernelArg ( kernel, 4, sizeof(unsigned int), &yuvStride);
// ... error handling ...

const size_t local_ws[] = { 16, 16 };
const size_t global_ws[] = { yuvStride + (yuvStride >> 1), height };

error = clEnqueueNDRangeKernel ( queue, kernel, 2, NULL, global_ws, local_ws, 0, NULL, NULL );
// ... error handling ...

Примечание: посмотрите нарасчеты рабочего элемента.Необходимо добавить некоторый дополнительный код (например, используя мод, чтобы добавить достаточное количество запасных элементов), чтобы убедиться, что размеры рабочих элементов соответствуют локальным рабочим размерам.

2 голосов
/ 28 февраля 2011

Наряду с спецификацией opencl тип данных int3 не существует.

Страница 123:

Поддерживаются следующие значения n: 2, 4, 8 и 16 ...

В переменных вашего ядра rgb, R, G, B и yuv должны быть не менее __private int4.

В OpenCL 1.1 добавлена ​​поддержка typen, где n = 3.Тем не менее, я настоятельно рекомендую вам не использовать его.Различные реализации вендоров имеют разные ошибки, и это ничего не спасет.

2 голосов
/ 15 февраля 2011

Как это?Используйте int4, если ваша платформа не может использовать int3.Также вы можете упаковать 5 пикселей в int16, тратя впустую 1/16 вместо 1/4 пропускной способности памяти.

__kernel void rgb2yuv( __global int3* input, __global int3* output){


rgb = input[get_global_id(0)];
R = rgb.x;
G = rgb.y;
B = rgb.z;    

yuv.x = ( (  66 * R + 129 * G +  25 * B + 128) >> 8) +  16; 
yuv.y = ( ( -38 * R -  74 * G + 112 * B + 128) >> 8) + 128; 
yuv.z = ( ( 112 * R -  94 * G -  18 * B + 128) >> 8) + 128;

output[get_global_id(0)] = yuv;
}
...