web-dev-qa-db-ja.com

libyuvライブラリを使用してYUV画像をスケーリングする際の問題

Camera API 2に基づいてカメラアプリを開発していますが、 libyuv を使用するといくつかの問題が見つかりました。 ImageReaderから取得したYUV_420_888画像を変換したいのですが、再処理可能なサーフェスでのスケーリングに問題があります。

本質的に:画像は、対応するトーンではなく、緑のトーンで出力されます(.yuvファイルをエクスポートし、 http://rawpixels.net/ を使用してチェックしています)。

ここに入力例を見ることができます: enter image description here

そして、スケーリングを実行した後に得られるもの: enter image description here

ストライドで何か問題が発生しているか、無効なYUV形式を提供していると思います(画像を別の形式に変換する必要があるかもしれません)。ただし、緑色をスケーリングアルゴリズムに関連付ける方法がわからないため、エラーがどこにあるのかわかりません。

これは私が使用している変換コードです。問題に関係のない処理がさらにあるため、戻り値のNULLは無視してかまいません。

#include <jni.h>
#include <stdint.h>
#include <Android/log.h>
#include <inc/libyuv/scale.h>
#include <inc/libyuv.h>
#include <stdio.h>


#define  LOG_TAG    "libyuv-jni"

#define unused(x) UNUSED_ ## x __attribute__((__unused__))
#define  LOGD(...)  __Android_log_print(Android_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define  LOGE(...)  __Android_log_print(Android_LOG_ERROR, LOG_TAG, __VA_ARGS_)

struct YuvFrame {
    int width;
    int height;
    uint8_t *data;
    uint8_t *y;
    uint8_t *u;
    uint8_t *v;
};

static struct YuvFrame i420_input_frame;
static struct YuvFrame i420_output_frame;

extern "C" {

JNIEXPORT jbyteArray JNICALL
Java_com_Android_camera3_camera_hardware_session_output_photo_yuv_YuvJniInterface_scale420YuvByteArray(
        JNIEnv *env, jclass /*clazz*/, jbyteArray yuvByteArray_, jint src_width, jint src_height,
        jint out_width, jint out_height) {

    jbyte *yuvByteArray = env->GetByteArrayElements(yuvByteArray_, NULL);

    //Get input and output length
    int input_size = env->GetArrayLength(yuvByteArray_);
    int out_size = out_height * out_width;

    //Generate input frame
    i420_input_frame.width = src_width;
    i420_input_frame.height = src_height;
    i420_input_frame.data = (uint8_t *) yuvByteArray;
    i420_input_frame.y = i420_input_frame.data;
    i420_input_frame.u = i420_input_frame.y + input_size;
    i420_input_frame.v = i420_input_frame.u + input_size / 4;

    //Generate output frame
    free(i420_output_frame.data);
    i420_output_frame.width = out_width;
    i420_output_frame.height = out_height;
    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;
    libyuv::FilterMode mode = libyuv::FilterModeEnum::kFilterBilinear;

    int result = 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);
    LOGD("Image result %d", result);
    env->ReleaseByteArrayElements(yuvByteArray_, yuvByteArray, 0);
    return NULL;
}

緑の画像は、飛行機の1つが0でいっぱいになっていることが原因です。これは、飛行機の1つが空だったことを意味します。これは、YUVI420ではなくYUVNV21から変換していたことが原因でした。 Androidのカメラのフレームワークからの画像はI420YUVとして提供されます。

Libyuvで正しく動作するには、それらをYUVI420に変換する必要があります。その後、ライブラリが提供する複数の操作の使用を開始できます。回転、スケールなどのように。

スケーリング方法がどのように見えるかについての抜粋は次のとおりです。

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));
}

フレームの入力サイズに問題があります。

そのはず:

int input_array_size = env->GetArrayLength(yuvByteArray_);
int input_size = input_array_size * 2 / 3; //This is the frame size

たとえば、6x4のフレームがある場合

シャネルyサイズ:6 * 4 = 24

 1 2 3 4 5 6
 _ _ _ _ _ _
|_|_|_|_|_|_| 1
|_|_|_|_|_|_| 2
|_|_|_|_|_|_| 3
|_|_|_|_|_|_| 4

シャネルサイズ:3 * 2 = 6

  1   2   3 
 _ _ _ _ _ _
|   |   |   | 
|_ _|_ _|_ _| 1
|   |   |   | 
|_ _|_ _|_ _| 2

シャネルvサイズ:3 * 2 = 6

  1   2   3 
 _ _ _ _ _ _
|   |   |   | 
|_ _|_ _|_ _| 1
|   |   |   | 
|_ _|_ _|_ _| 2

配列サイズ= 6 * 4 + 3 * 2 + 3 * 2 = 36
ただし、実際のフレームサイズ=チャネルyサイズ= 36 * 2/3 = 24

1
Rama

配列のフルサイズの代わりにy_sizeを使用するコードを試すことができます。

    ...
    //Get input and output length
    int input_size = env->GetArrayLength(yuvByteArray_);
    int y_size = src_width * src_height;
    int out_size = out_height * out_width;

    //Generate input frame
    i420_input_frame.width = src_width;
    i420_input_frame.height = src_height;
    i420_input_frame.data = (uint8_t *) yuvByteArray;
    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 = out_width;
    i420_output_frame.height = out_height;
    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;
    ...

おそらくあなたのコードはそれに基づいています https://github.com/begeekmyfriend/yasea/blob/master/library/src/main/libenc/jni/libenc.cc そしてあなたが持っているそのコードによるとy_sizeを使用するには

1
gmetax

gmetaxはほぼ正しいです。

Yコンポーネントのサイズであるsrc_width * src_heightを使用する必要がある配列全体のサイズを使用しています。

gmetaxの答えは、出力フレームを定義するときにy_sizeの代わりにout_sizeを配置したという点で間違っています。正しいコードスニペットは、次のようになると思います。

//Get input and output length
int input_size = env->GetArrayLength(yuvByteArray_);
int y_size = src_width * src_height;
int out_size = out_height * out_width;

//Generate input frame
i420_input_frame.width = src_width;
i420_input_frame.height = src_height;
i420_input_frame.data = (uint8_t *) yuvByteArray;
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 = out_width;
i420_output_frame.height = out_height;
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;
0
Dave

YUV422画像をYUV420のように拡大縮小しようとしていますが、色がすべて台無しになっているのも不思議ではありません。まず最初に、YUV入力バッファの正確なフォーマットを把握する必要があります。 YUV_422_888 のドキュメントから、平面形式とインターリーブ形式を表しているようです(ピクセルストライドが1でない場合)。結果から、ソースは平面であり、Y平面の処理は問題ないように見えますが、エラーはU平面とV平面の処理にあります。スケーリングを正しく行うには:

  • U平面とV平面がインターリーブされているか平面であるかを把握する必要があります。ほとんどの場合、それらも平面です。
  • LibyuvのScalePlaneを使用して、UVを別々にスケーリングします。おそらく、I420Scaleに足を踏み入れると、個々の飛行機に対してScalePlaneが呼び出されます。同じことを行いますが、U平面とV平面に正しい線サイズを使用します(それぞれがI420Scaleの予想より2倍大きい)。

平面またはインターリーブされたUVがあるかどうかを判断するためのヒント:画像のスケーリングと保存をスキップして、正しい結果(ソースと同じ)が得られるようにしてください。 。次に、UフレームまたはVフレームをゼロにして、何が得られるかを確認します。 UVが平面で、U平面をゼロに設定すると、画像全体の色が変わるはずです。それらがインターリーブされている場合、画像の半分が変化し、もう一方は同じままになります。同じ方法で、平面のサイズ、線のサイズ、およびオフセットに関する仮定を確認できます。 YUVのフォーマットとレイアウトが決まったら、入力が平面の場合は個々の平面をスケーリングできます。または、最初に入力をインターリーブした場合は、平面をデインターリーブしてからスケーリングする必要があります。

または、ffmpeg/libavのlibswscaleを使用し、さまざまな形式を試して正しい形式を見つけてから、libyuvを使用することもできます。

0
Pavel P