Camera API 2
に基づいてカメラアプリを開発していますが、 libyuv を使用するといくつかの問題が見つかりました。 ImageReaderから取得したYUV_420_888
画像を変換したいのですが、再処理可能なサーフェスでのスケーリングに問題があります。
本質的に:画像は、対応するトーンではなく、緑のトーンで出力されます(.yuvファイルをエクスポートし、 http://rawpixels.net/ を使用してチェックしています)。
ストライドで何か問題が発生しているか、無効な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
配列のフルサイズの代わりに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
を使用するには
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;
YUV422画像をYUV420のように拡大縮小しようとしていますが、色がすべて台無しになっているのも不思議ではありません。まず最初に、YUV入力バッファの正確なフォーマットを把握する必要があります。 YUV_422_888 のドキュメントから、平面形式とインターリーブ形式を表しているようです(ピクセルストライドが1でない場合)。結果から、ソースは平面であり、Y平面の処理は問題ないように見えますが、エラーはU平面とV平面の処理にあります。スケーリングを正しく行うには:
U
平面とV
平面がインターリーブされているか平面であるかを把握する必要があります。ほとんどの場合、それらも平面です。ScalePlane
を使用して、U
とV
を別々にスケーリングします。おそらく、I420Scale
に足を踏み入れると、個々の飛行機に対してScalePlane
が呼び出されます。同じことを行いますが、U
平面とV
平面に正しい線サイズを使用します(それぞれがI420Scaleの予想より2倍大きい)。平面またはインターリーブされたU
とV
があるかどうかを判断するためのヒント:画像のスケーリングと保存をスキップして、正しい結果(ソースと同じ)が得られるようにしてください。 。次に、U
フレームまたはV
フレームをゼロにして、何が得られるかを確認します。 U
とV
が平面で、U
平面をゼロに設定すると、画像全体の色が変わるはずです。それらがインターリーブされている場合、画像の半分が変化し、もう一方は同じままになります。同じ方法で、平面のサイズ、線のサイズ、およびオフセットに関する仮定を確認できます。 YUVのフォーマットとレイアウトが決まったら、入力が平面の場合は個々の平面をスケーリングできます。または、最初に入力をインターリーブした場合は、平面をデインターリーブしてからスケーリングする必要があります。
または、ffmpeg/libavのlibswscaleを使用し、さまざまな形式を試して正しい形式を見つけてから、libyuvを使用することもできます。