Масштабирование изображения YUV420 с использованием libyuv приводит к странным выводам - PullRequest
0 голосов
/ 29 августа 2018

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

Фон

Я снимаю изображение в формате YUV_420_888 , возвращенном методом ARCore frame.acquireCameraImage(). Поскольку я установил конфигурацию камеры на разрешение 1920 * 1080, мне нужно уменьшить ее до 224 * 224, чтобы передать ее в реализацию tenorflow-lite. Я делаю это с помощью библиотеки LibYuv через Android NDK.

Осуществление

Подготовка кадров изображения

    //Figure out the source image dimensions
    int y_size = srcWidth * srcHeight;

    //Get dimensions of the desired output image
    int out_size = destWidth * destHeight;

    //Generate input frame
    i420_input_frame.width = srcWidth;
    i420_input_frame.height = srcHeight;
    i420_input_frame.data = (uint8_t*) yuvArray;
    i420_input_frame.y = i420_input_frame.data;
    i420_input_frame.u = i420_input_frame.y + y_size;
    i420_input_frame.v = i420_input_frame.u + (y_size / 4);

    //Generate output frame
    free(i420_output_frame.data);
    i420_output_frame.width = destWidth;
    i420_output_frame.height = destHeight;
    i420_output_frame.data = new unsigned char[out_size * 3 / 2];
    i420_output_frame.y = i420_output_frame.data;
    i420_output_frame.u = i420_output_frame.y + out_size;
    i420_output_frame.v = i420_output_frame.u + (out_size / 4);

Я масштабирую свое изображение, используя I420Scale метод Либюва

libyuv::FilterMode mode = libyuv::FilterModeEnum::kFilterBox;
jint result = libyuv::I420Scale(i420_input_frame.y, i420_input_frame.width,
                                i420_input_frame.u, i420_input_frame.width / 2,
                                i420_input_frame.v, i420_input_frame.width / 2,
                                i420_input_frame.width, i420_input_frame.height,
                                i420_output_frame.y, i420_output_frame.width,
                                i420_output_frame.u, i420_output_frame.width / 2,
                                i420_output_frame.v, i420_output_frame.width / 2,
                                i420_output_frame.width, i420_output_frame.height,
                                mode);

и верните его в Java

    //Create a new byte array to return to the caller in Java
    jbyteArray outputArray = env -> NewByteArray(out_size * 3 / 2);
    env -> SetByteArrayRegion(outputArray, 0, out_size, (jbyte*) i420_output_frame.y);
    env -> SetByteArrayRegion(outputArray, out_size, out_size / 4, (jbyte*) i420_output_frame.u);
    env -> SetByteArrayRegion(outputArray, out_size + (out_size / 4), out_size / 4, (jbyte*) i420_output_frame.v);

Фактическое изображение: enter image description here

Как это выглядит после масштабирования:

enter image description here

Как это выглядит, если я создаю изображение из i420_input_frame без масштабирования:

enter image description here

Поскольку при масштабировании все время портятся цвета, тензор потока не может правильно распознать объекты. (Это правильно распознается в их примере приложения) Что я делаю не так, чтобы испортить цвета?

Ответы [ 2 ]

0 голосов
/ 15 ноября 2018

Проблема с цветом вызвана тем, что вы работаете с другим форматом YUV. Формат YUV, который используют рамки камеры, - это YUV NV21. Этот формат (NV21) является стандартным форматом изображения для предварительного просмотра камеры Android. Плоское изображение YUV 4: 2: 0 с 8-битными выборками Y, за которым следует чередующаяся плоскость V / U с 8-битными выборками цветности 2x2 с субдискретизацией.

Если ваши цвета инвертированы, это означает:

  • Вы работаете с YUV NV12 (плоскость U - это V, а V - это U).
  • Одна из ваших цветовых плоскостей делает что-то странное.

Для правильной работы с libyuv Я предлагаю вам преобразовать вывод вашей камеры в YUV I420, используя метод transformI420 и отправив формат по параметру:

return libyuv::ConvertToI420(src, srcSize, //src data
                             dstY, dstWidth, //dst planes
                             dstU, dstWidth / 2,
                             dstV, dstWidth / 2,
                             cropLeft, cropTop, //crop start
                             srcWidth, srcHeight, //src dimensions
                             cropRight - cropLeft, cropBottom - cropTop, //dst dimensions
                             rotationMode,
                             libyuv::FOURCC_NV21); //libyuv::FOURCC_NV12

После этого преобразования вы сможете правильно работать с libyuv, используя все I420scale, I420rotate ... и так далее. Ваш метод масштабирования должен выглядеть следующим образом:

JNIEXPORT jint JNICALL
Java_com_aa_project_images_yuv_myJNIcl_scaleI420(JNIEnv *env, jclass type,
                                                 jobject srcBufferY,
                                                 jobject srcBufferU,
                                                 jobject srcBufferV,
                                                 jint srcWidth, jint srcHeight,
                                                 jobject dstBufferY,
                                                 jobject dstBufferU,
                                                 jobject dstBufferV,
                                                 jint dstWidth, jint dstHeight,
                                                 jint filterMode) {

    const uint8_t *srcY = static_cast<uint8_t *>(env->GetDirectBufferAddress(srcBufferY));
    const uint8_t *srcU = static_cast<uint8_t *>(env->GetDirectBufferAddress(srcBufferU));
    const uint8_t *srcV = static_cast<uint8_t *>(env->GetDirectBufferAddress(srcBufferV));
    uint8_t *dstY = static_cast<uint8_t *>(env->GetDirectBufferAddress(dstBufferY));
    uint8_t *dstU = static_cast<uint8_t *>(env->GetDirectBufferAddress(dstBufferU));
    uint8_t *dstV = static_cast<uint8_t *>(env->GetDirectBufferAddress(dstBufferV));

    return libyuv::I420Scale(srcY, srcWidth,
                             srcU, srcWidth / 2,
                             srcV, srcWidth / 2,
                             srcWidth, srcHeight,
                             dstY, dstWidth,
                             dstU, dstWidth / 2,
                             dstV, dstWidth / 2,
                             dstWidth, dstHeight,
                             static_cast<libyuv::FilterMode>(filterMode));
}

Если вы хотите преобразовать это изображение в JPEG после всего процесса. Вы можете использовать метод I420toNV21 и после этого использовать преобразование Android в YUV в JPEG. Также вы можете использовать libJpegTurbo , которая является дополнительной библиотекой для подобных ситуаций.

Надеюсь, что это вам помогло!

0 голосов
/ 03 октября 2018

Либо я делал что-то не так (что я не смог исправить), либо LibYuv неправильно обрабатывает цвета при работе с изображениями YUV с Android.

Ссылка на официальную ошибку, опубликованную в библиотеке Libyuv: https://bugs.chromium.org/p/libyuv/issues/detail?id=815&can=1&q=&sort=-id

Они предложили сначала использовать метод Android420ToI420(), а затем применить любые необходимые мне преобразования. В конце концов я использовал Android420ToI420(), затем Масштабирование, затем преобразование в RGB. В конце концов, результат был немного лучше, чем изображение чашки, размещенное выше, но искаженные цвета все еще присутствовали. В итоге я использовал OpenCV, чтобы уменьшить изображение и преобразовать его в форматы RGBA или RGB.

// The camera image received is in YUV YCbCr Format at preview dimensions
// so we will scale it down to 224x224 size using OpenCV
// Y plane (0) non-interleaved => stride == 1; U/V plane interleaved => stride == 2
// Refer : https://developer.android.com/reference/android/graphics/ImageFormat.html#YUV_420_888
val cameraPlaneY = cameraImage.planes[0].buffer
val cameraPlaneUV = cameraImage.planes[1].buffer

// Create a new Mat with OpenCV. One for each plane - Y and UV
val y_mat = Mat(cameraImage.height, cameraImage.width, CvType.CV_8UC1, cameraPlaneY)
val uv_mat = Mat(cameraImage.height / 2, cameraImage.width / 2, CvType.CV_8UC2, cameraPlaneUV)
var mat224 = Mat()
var cvFrameRGBA = Mat()

// Retrieve an RGBA frame from the produced YUV
Imgproc.cvtColorTwoPlane(y_mat, uv_mat, cvFrameRGBA, Imgproc.COLOR_YUV2BGRA_NV21)


//Then use this frame to retrieve all RGB channel data
//Iterate over all pixels and retrieve information of RGB channels
  for(rows in 1 until cvFrameRGBA.rows())
      for(cols in 1 until cvFrameRGBA.cols()) {
          val imageData = cvFrameRGBA.get(rows, cols)
          // Type of Mat is 24
          // Channels is 4
          // Depth is 0
          rgbBytes.put(imageData[0].toByte())
          rgbBytes.put(imageData[1].toByte())
          rgbBytes.put(imageData[2].toByte())
      }
...