Как улучшить преобразование YUV420P в RGB на C / C ++? - PullRequest
2 голосов
/ 12 июля 2020

Я пытаюсь записать YUV420P в RGB888, когда у меня все это как один гигантский буфер с Y (размером width*height), затем Cr (размером width*height/4), затем Cb (размер width*height/4). На выходе должен быть буфер RGB с размером width*height*3.

Я думаю, что моя функция ниже очень бесполезна. Например, я использую функцию потолка (разве она не должна возвращать int? В моем случае она возвращает double, почему?), И я никогда не видел, чтобы какая-либо функция преобразования цвета использовала эту функцию. Но именно так я нашел соответствующие Cr и Cb для каждого Y.

JNIEXPORT void JNICALL Java_com_example_mediacodecdecoderexample_YuvToRgb_YUVtoRBGA2(JNIEnv * env, jobject obj, jbyteArray yuv420sp, jint width, jint height, jbyteArray rgbOut)
{
    //ITU-R BT.601 conversion
    //
    //     R = 1.164*(Y-16)+1.596*(Cr-128)
    //     G = 1.164*(Y-16)-0.392*(Cb-128)-0.813*(Cr-128)
    //     B = 1.164*(Y-16)+2.017*(Cb-128)
    //
    int Y;
    int Cr;
    int Cb;
    int R;
    int G;
    int B;
    int size = width * height;
    //After width*height luminance values we have the Cr values
    size_t CrBase = size;
    //After width*height luminance values + width*height/4 we have the Cb values
    size_t CbBase = size + width*height/4;
    jbyte *rgbData = (jbyte*) ((*env)->GetPrimitiveArrayCritical(env, rgbOut, 0));
    jbyte* yuv = (jbyte*) (*env)->GetPrimitiveArrayCritical(env, yuv420sp, 0);

    for (int i=0; i<size; i++) {
        Y  = rgbData[i] - 16;
        Cr = rgbData[CrBase + ceil(i/4)]  - 128;
        Cb = rgbData[CbBase + ceil(i/4)]  - 128;
        R = 1.164*Y+1.596*Cr;
        G = 1.164*Y-0.392*Cb-0.813*Cr;
        B = 1.164*Y+2.017*Cb;
        yuv[i*3] = R;
        yuv[i*3+1] = G;
        yuv[i*3+2] = B;
    }

    (*env)->ReleasePrimitiveArrayCritical(env, rgbOut, rgbData, 0);
    (*env)->ReleasePrimitiveArrayCritical(env, yuv420sp, yuv, 0);
}

Я делаю это, потому что не нашел функции, которая делает именно это и мне нужен один для буфера декодирования MediaCode c. Но даже если он есть, я хотел бы знать, что можно сделать для улучшения моей функции, просто чтобы узнать.

ОБНОВЛЕНИЕ:

Я изменил код на основе в ответе ниже, чтобы он работал с ByteBuffer:

JNIEXPORT void JNICALL Java_com_lucaszanella_mediacodecdecoderexample_YuvToRgb_YUVtoRBGA2(JNIEnv * env, jobject obj, jobject yuv420sp, jint width, jint height, jobject rgbOut)
{
    //ITU-R BT.601 conversion
    //
    //     R = 1.164*(Y-16)+1.596*(Cr-128)
    //     G = 1.164*(Y-16)-0.392*(Cb-128)-0.813*(Cr-128)
    //     B = 1.164*(Y-16)+2.017*(Cb-128)
    //

    char *rgbData = (char*)(*env)->GetDirectBufferAddress(env, rgbOut);
    char *yuv = (char*)(*env)->GetDirectBufferAddress(env, yuv420sp);

    const int size = width * height;

    //After width*height luminance values we have the Cr values
    const size_t CrBase = size;

    //After width*height luminance values + width*height/4 we have the Cb values
    const size_t CbBase = size + width*height/4;

    for (int i=0; i<size; i++) {
        int Y  = yuv[i] - 16;
        int Cr = yuv[CrBase + i/4]  - 128;
        int Cb = yuv[CbBase + i/4]  - 128;

        double R = 1.164*Y+1.596*Cr;
        double G = 1.164*Y-0.392*Cb-0.813*Cr;
        double B = 1.164*Y+2.017*Cb;

        rgbData[i*3] = (R > 255) ? 255 : ((R < 0) ? 0 : R);
        rgbData[i*3+1] = (G > 255) ? 255 : ((G < 0) ? 0 : G);
        rgbData[i*3+2] = (B > 255) ? 255 : ((B < 0) ? 0 : B);
    }
}

, однако он дает сбой. Я не вижу, чтобы что-то писалось за границами. У кого-нибудь есть идеи?

ОБНОВЛЕНИЕ:

Код выше работает, если мы вызываем его с прямым байтовым буфером. Не будет работать, если буфер не прямой.

Добавлен

    if (rgbData==NULL) {
        __android_log_print(ANDROID_LOG_ERROR, "TRACKERS", "%s", "RGB data null");
    }

    if (yuv==NULL) {
        __android_log_print(ANDROID_LOG_ERROR, "TRACKERS", "%s", "yuv data null");
    }
    if (rgbData==NULL || yuv==NULL) {
        return;
    }

для безопасности.

В любом случае цвет неверный:

введите описание изображения здесь

Ответы [ 4 ]

1 голос
/ 12 июля 2020

Это только я, но разве вы не должны читать из массива yuv и записывать в массив rgbData? В вашей реализации все наоборот.

Нет необходимости вызывать ceil для целочисленного выражения, такого как i/4. И когда вы реализуете маршрут обработки изображений, вызов функции для каждого пикселя просто убивает производительность (был там, сделал это). Может быть, компилятор сможет его оптимизировать, но зачем рисковать.

Итак, измените это:

    Cr = rgbData[CrBase + ceil(i/4)]  - 128;
    Cb = rgbData[CbBase + ceil(i/4)]  - 128;

На это:

    Cr = rgbData[CrBase + i/4]  - 128;
    Cb = rgbData[CbBase + i/4]  - 128;

будьте осторожны, вы можете захотеть зафиксировать R, G и B, чтобы они находились в 8-битном диапазоне байтов, прежде чем возвращать их в массив yuv. Эти математические уравнения могут давать результаты < 0 и > 255.

Еще одна микро-оптимизация - объявить все ваши переменные в блоке for-l oop, чтобы у компилятора было больше подсказок по оптимизации, как временные. И объявив некоторые из ваших других констант как const, я могу предложить:

JNIEXPORT void JNICALL Java_com_example_mediacodecdecoderexample_YuvToRgb_YUVtoRBGA2(JNIEnv * env, jobject obj, jbyteArray yuv420sp, jint width, jint height, jbyteArray rgbOut)
{
    //ITU-R BT.601 conversion
    //
    //     R = 1.164*(Y-16)+1.596*(Cr-128)
    //     G = 1.164*(Y-16)-0.392*(Cb-128)-0.813*(Cr-128)
    //     B = 1.164*(Y-16)+2.017*(Cb-128)
    //
    const int size = width * height;
    //After width*height luminance values we have the Cr values

    const size_t CrBase = size;
    //After width*height luminance values + width*height/4 we have the Cb values

    const size_t CbBase = size + width*height/4;

    jbyte *rgbData = (jbyte*) ((*env)->GetPrimitiveArrayCritical(env, rgbOut, 0));
    jbyte* yuv= (jbyte*) (*env)->GetPrimitiveArrayCritical(env, yuv420sp, 0);

    for (int i=0; i<size; i++) {
        int Y  = yuv[i] - 16;
        int Cr = yuv[CrBase + i/4]  - 128;
        int Cb = yuv[CbBase + i/4]  - 128;

        int R = 1.164*Y+1.596*Cr;
        int G = 1.164*Y-0.392*Cb-0.813*Cr;
        int B = 1.164*Y+2.017*Cb;

        rgbData[i*3] = (R > 255) ? 255 : ((R < 0) ? 0 : R);
        rgbData[i*3+1] = (G > 255) ? 255 : ((G < 0) ? 0 : G);
        rgbData[i*3+2] = (B > 255) ? 255 : ((B < 0) ? 0 : B);
    }

    (*env)->ReleasePrimitiveArrayCritical(env, rgbOut, rgbData, 0);
    (*env)->ReleasePrimitiveArrayCritical(env, yuv420sp, yuv, 0);
}

Тогда остается только скомпилировать с максимальной оптимизацией. Об остальном позаботится компилятор.

После этого исследуются оптимизации SIMD, которые некоторые компиляторы предлагают в качестве переключателя компилятора (или активируются с помощью прагмы).

0 голосов
/ 16 июля 2020

Не делай сам! Не делайте этого прямо в C ++! Единственный правильный подход - использовать для этого аппаратное ускорение. Вы сэкономите много заряда батареи.

В основном вы можете использовать OpenGL для этого, и он будет использовать оборудование от вашего имени.

Долгое время go Я сделал это для iOS и Я уверен, что решение для Android будет очень похожим. К сожалению, я оставил код (в старой компании), поэтому я не могу предоставить вам пример кода. Если найду что-нибудь полезное, то обновлю этот ответ. В моем коде YUV (и пара других цветовых форматов) визуализировался непосредственно в представлении openGL, и OpenGL выполнил необходимое преобразование.

Теперь я просто указываю пальцем на OpenGL, поскольку другие ответы делают это непосредственно на CPU, что является плохой выбор, поскольку он будет сильно расходовать батарею, и вы никогда не достигнете желаемой производительности таким образом.

Изменить : Я нашел аналогичный вопрос по SO с некоторым примером: { ссылка }

Отказ от ответственности: не проверено, что этот пример является лучшим подходом, но это хороший способ начать поиск лучших решений.


Если по какой-то причине вам нужно сделать это в любом случае в коде C ++, а затем отказаться от операций с плавающей запятой в пользу операций с целыми типами.
0 голосов
/ 13 июля 2020

относительно:

*I use the ceiling function (shouldn't it return an int? In my case it's returning a double, why?)*

вот синтаксис:

double ceil(double x);

Обратите внимание, что возвращаемый тип double

Страница MAN для ceil ( )

0 голосов
/ 12 июля 2020

Небольшая модификация ответа selb ie, в котором используется ByteBuffer, что более полезно, так как это то, что Java производит при декодировании.

JNIEXPORT void JNICALL Java_com_example_mediacodecdecoderexample_YuvToRgb_YUVtoRBGA2(JNIEnv * env, jobject obj, jobject yuv420sp, jint width, jint height, jobject rgbOut)
{
    //ITU-R BT.601 conversion
    //
    //     R = 1.164*(Y-16)+1.596*(Cr-128)
    //     G = 1.164*(Y-16)-0.392*(Cb-128)-0.813*(Cr-128)
    //     B = 1.164*(Y-16)+2.017*(Cb-128)
    //
    const int size = width * height;
    
    //After width*height luminance values we have the Cr values
    const size_t CrBase = size;
    
    //After width*height luminance values + width*height/4 we have the Cb values
    const size_t CbBase = size + width*height/4;

    jbyte *rgbData = (*env)->GetDirectBufferAddress(env, rgbOut);
    jbyte *yuv = (*env)->GetDirectBufferAddress(env, yuv420sp);

    for (int i=0; i<size; i++) {
        int Y  = yuv[i] - 16;
        int Cr = yuv[CrBase + i/4]  - 128;
        int Cb = yuv[CbBase + i/4]  - 128;

        int R = 1.164*Y+1.596*Cr;
        int G = 1.164*Y-0.392*Cb-0.813*Cr;
        int B = 1.164*Y+2.017*Cb;

        rgbData[i*3] = (R > 255) ? 255 : ((R < 0) ? 0 : R);
        rgbData[i*3+1] = (G > 255) ? 255 : ((G < 0) ? 0 : G);
        rgbData[i*3+2] = (B > 255) ? 255 : ((B < 0) ? 0 : B);
    }
}
...