Почему при преобразовании YUV_420_888 в RGBA_8888 в Android NDK изображение поворачивается на 90 градусов?

Я делаю преобразование нативным методом из imagereader в формат yug в формат Rgba через этот метод в NDK:

size_t bufferSize = buffer.width * buffer.height * (size_t)4;
uint8_t * outPtr = reinterpret_cast<uint8_t *>(buffer.bits);
for (size_t y = 0; y < srcHeight; y++)
{
    uint8_t * Y_rowPtr = srcYPtr + y * Y_rowStride;
    uint8_t * U_rowPtr = srcUPtr + (y >> 1) * U_rowStride;
    uint8_t * V_rowPtr = srcVPtr + (y >> 1) * V_rowStride;

    for (size_t x = 0; x < srcWidth; x++)
    {
        uint8_t Y = Y_rowPtr[x];
        uint8_t U = U_rowPtr[(x >> 1)];
        uint8_t V = V_rowPtr[(x >> 1)];

        double R = (Y + (V - 128) * 1.40625);
        double G = (Y - (U - 128) * 0.34375 - (V - 128) * 0.71875);
        double B = (Y + (U - 128) * 1.765625);

        *(outPtr + (--bufferSize)) = 255; // gamma for RGBA_8888
        *(outPtr + (--bufferSize)) = (uint8_t) (B > 255 ? 255 : (B < 0 ? 0 : B));
        *(outPtr + (--bufferSize)) = (uint8_t) (G > 255 ? 255 : (G < 0 ? 0 : G));
        *(outPtr + (--bufferSize)) = (uint8_t) (R > 255 ? 255 : (R < 0 ? 0 : R));

    }
}

Почему изображение повернуто на 90 градусов?

ОБНОВИТЬ:

Я использую это преобразование: https://www.fourcc.org/fccyvrgb.php, но изображение остается повернутым от оригинала на 90.

ОБНОВЛЕНИЕ2:

       @Override
    public void onImageAvailable(ImageReader reader) {
        // ottiene il nuovo frame
        Image image = reader.acquireNextImage();

        if (image == null) {
            return;
        }


        //preparazione per RGBA output
        Image.Plane Y_plane = image.getPlanes()[0];
        int Y_rowStride = Y_plane.getRowStride();
        Image.Plane U_plane = image.getPlanes()[1];
        int UV_rowStride = U_plane.getRowStride();  //nelle immagini YUV, uPlane.getRowStride() == vPlane.getRowStride()
        Image.Plane V_plane = image.getPlanes()[2];
        JNIUtils.RGBADisplay(image.getWidth(), image.getHeight(), Y_rowStride, Y_plane.getBuffer(), UV_rowStride, U_plane.getBuffer(), UV_rowStride, V_plane.getBuffer(), surface);


        image.close();
    }

ОБНОВЛЕНИЕ 3: код в native.cpp

Java_com_ndkvideoimagecapture_JNIUtils_RGBADisplay(
    JNIEnv *env, //env per consentire il passaggio di dati per riferimento
    jobject obj,
    jint srcWidth,
    jint srcHeight,
    jint Y_rowStride,
    jobject Y_Buffer,
    jint U_rowStride,
    jobject U_Buffer,
    jint V_rowStride,
    jobject V_Buffer,
    jobject surface) {

uint8_t *srcYPtr = reinterpret_cast<uint8_t *>(env->GetDirectBufferAddress(Y_Buffer));
uint8_t *srcUPtr = reinterpret_cast<uint8_t *>(env->GetDirectBufferAddress(U_Buffer));
uint8_t *srcVPtr = reinterpret_cast<uint8_t *>(env->GetDirectBufferAddress(V_Buffer));

ANativeWindow *window = ANativeWindow_fromSurface(env, surface);
ANativeWindow_acquire(window);
ANativeWindow_Buffer buffer;

ANativeWindow_setBuffersGeometry(window, srcWidth, srcHeight, WINDOW_FORMAT_RGBA_8888);

if (int32_t err = ANativeWindow_lock(window, &buffer, NULL)) {
    LOGE("ANativeWindow_lock failed with error code: %d\n", err);
    ANativeWindow_release(window);
}

    size_t bufferSize = buffer.width * buffer.height * (size_t)4;
uint8_t * outPtr = reinterpret_cast<uint8_t *>(buffer.bits);
for (size_t y = 0; y < srcHeight; y++)
{
    uint8_t * Y_rowPtr = srcYPtr + y * Y_rowStride;
    uint8_t * U_rowPtr = srcUPtr + (y >> 1) * U_rowStride;
    uint8_t * V_rowPtr = srcVPtr + (y >> 1) * V_rowStride;

    for (size_t x = 0; x < srcWidth; x++)
    {
        uint8_t Y = Y_rowPtr[x];
        uint8_t U = U_rowPtr[(x >> 1)];
        uint8_t V = V_rowPtr[(x >> 1)];

        double R = (Y + (V - 128) * 1.40625);
        double G = (Y - (U - 128) * 0.34375 - (V - 128) * 0.71875);
        double B = (Y + (U - 128) * 1.765625);

        *(outPtr + (--bufferSize)) = 255; // gamma for RGBA_8888
        *(outPtr + (--bufferSize)) = (uint8_t) (B > 255 ? 255 : (B < 0 ? 0 : B));
        *(outPtr + (--bufferSize)) = (uint8_t) (G > 255 ? 255 : (G < 0 ? 0 : G));
        *(outPtr + (--bufferSize)) = (uint8_t) (R > 255 ? 255 : (R < 0 ? 0 : R));

    }
}

ANativeWindow_unlockAndPost(window);
ANativeWindow_release(window);
}

person Enda Azi    schedule 15.11.2017    source источник
comment
Повернуто по сравнению с чем? Если вы используете камеру в портретной ориентации, вы можете правильно настроить предварительный просмотр в реальном времени. Но byte[], возвращаемый onPreviewFrame(), всегда находится в исходном альбомном порядке.   -  person Alex Cohn    schedule 05.12.2017
comment
Изображение в конверсии меняет ориентацию, а не приложение.   -  person Enda Azi    schedule 05.12.2017
comment
Сохраните исходное изображение YUV на диск и откройте его как изображение в градациях серого в программе просмотра изображений на вашем ПК. Вы увидите, что он уже «повернут».   -  person Alex Cohn    schedule 05.12.2017
comment
Цель состоит в том, чтобы увидеть изображение в камере предварительного просмотра, не сохраняя фотографии на диск.   -  person Enda Azi    schedule 06.12.2017
comment
Подождите секунду, вы конвертируете в RGB для предварительного просмотра? Пожалуйста, поймите, что я писал о сохранении буфера YUV на диск только в целях отладки.   -  person Alex Cohn    schedule 06.12.2017
comment
Я конвертировал кадр в режиме предварительного просмотра из YUV в RGBA. Как я могу сделать то, что вы просите?   -  person Enda Azi    schedule 06.12.2017
comment
Объясните, пожалуйста, подробнее, откуда вы берете YUV_420_888 src?Ptr. Я не понимаю, почему вы заполняете свой outPtr задом наперед, но это перевернет вывод, а не повернет его на 90°. Если ваше изображение повернуто на 90°, вы должны увидеть, что фактическая ширина равна ожидаемой высоте, и наоборот.   -  person Alex Cohn    schedule 06.12.2017
comment
Я вставил код обновления в первый пост   -  person Enda Azi    schedule 06.12.2017


Ответы (1)


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

TL;NR: ImageReader всегда возвращает горизонтальное изображение, как onPreviewFrame().

Если вы подключите камеру к поверхности текстуры, отображение будет намного эффективнее, чем преобразование YUV➤RGB, которое может выполнить ваша программа, но я понимаю, что есть много ситуаций, когда вам нужны данные RGB для обработки изображения, и иногда вы хотите результат обработки изображения будет отображаться в виде предварительного просмотра в реальном времени.

Это, по сути, то, как OpenCV обрабатывает камеру Android и предварительный просмотр в реальном времени (к сожалению, официальный OpenCV не использует API camera2, но я нашел одно руководство, которое показывает, как это может быть сделано).

Я настоятельно рекомендую использовать Renderscript для преобразования YUV➤RGB. Это не только намного быстрее, но и дает значительную экономию заряда батареи по сравнению с работой на процессоре.

Совершенно другая проблема с вашим кодом заключается в том, что он предполагает, что соотношение сторон окна дисплея такое же, как и изображение, которое вы получаете с камеры, не так ли?

Вы не должны полагаться на это. Даже после исправления поворота на 90° (если окно портретное), вы можете видеть искаженное изображение. Это не относится к камере2, то же самое может произойти с устаревшим API камеры.

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

Короче говоря, вам нужно

float srcAspectRatio = (float)srcWidth/srcHeight;
float outAspectRatio = (float)buffer.width/buffer.height;

int clippedSrcWidth =  outAspectRatio > srcAspectRatio ? srcWidth : (int)(0.5 + srcHeight*outAspectRatio);
int clippedSrcHeight = outAspectRatio < srcAspectRatio ? srcHeight : (int)(0.5 + srcWidth/outAspectRatio);

ANativeWindow_setBuffersGeometry(window, clippedSrcWidth, clippedSrcHeight, WINDOW_FORMAT_RGBA_8888);

и

for (size_t y = (srcHeight-clippedSrcHeight)/2; y < srcHeight - (srcHeight-clippedSrcHeight)/2; y++)

и так далее.

И используя обратный счетчик для доступа к пикселям outPtr, вы, вероятно, выполняете зеркалирование, ожидаемое для фронтальная камера, не так ли?

person Alex Cohn    schedule 06.12.2017