Je développe une application pour appareil photo basée sur Camera API 2
et j'ai rencontré plusieurs problèmes lors de l'utilisation de libyuv . Je souhaite convertir des images YUV_420_888
extraites d'un ImageReader, mais je rencontre des problèmes de redimensionnement une surface retraitable.
Essentiellement: les images apparaissent avec des tons de vert au lieu d’avoir les tons correspondants (j’exporte les fichiers .yuv et je les vérifie avec http://rawpixels.net/ ).
Vous pouvez voir un exemple d'entrée ici:
Et ce que je reçois après avoir effectué la mise à l'échelle:
Je pense que je fais quelque chose de mal avec les progrès, ou de fournir un format YUV non valide (peut-être que je dois transformer l'image en un autre format?). Cependant, je ne peux pas savoir où se trouve l'erreur car je ne sais pas comment corréler la couleur verte à l'algorithme de mise à l'échelle.
C’est le code de conversion que j’utilise, vous pouvez ignorer le retour NULL car il existe un traitement supplémentaire qui n’est pas lié au problème.
#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;
}
Les images vertes ont été causées par l’un des avions remplis de 0. Cela signifie qu'un des avions était vide. Cela était dû au fait que je convertissais à partir de YUV NV21 au lieu de YUV I420. Les images du cadre de la caméra dans Android vient comme I420 YUV.
Nous devons les convertir en YUV I420 pour fonctionner correctement avec Libyuv. Après cela, nous pouvons commencer à utiliser les multiples opérations proposées par la bibliothèque. Comme rotation, échelle etc.
Voici la capture sur l'apparence de la méthode de mise à l'échelle:
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));
}
Vous avez un problème avec la taille d'entrée du cadre:
CA devrait etre:
int input_array_size = env->GetArrayLength(yuvByteArray_);
int input_size = input_array_size * 2 / 3; //This is the frame size
Par exemple, si vous avez un cadre 6x4
Chanel y taille: 6 * 4 = 24
1 2 3 4 5 6
_ _ _ _ _ _
|_|_|_|_|_|_| 1
|_|_|_|_|_|_| 2
|_|_|_|_|_|_| 3
|_|_|_|_|_|_| 4
Chanel u taille: 3 * 2 = 6
1 2 3
_ _ _ _ _ _
| | | |
|_ _|_ _|_ _| 1
| | | |
|_ _|_ _|_ _| 2
Chanel v taille: 3 * 2 = 6
1 2 3
_ _ _ _ _ _
| | | |
|_ _|_ _|_ _| 1
| | | |
|_ _|_ _|_ _| 2
Taille du tableau = 6 * 4 + 3 * 2 + 3 * 2 = 36
Mais Taille de l’image réelle = canal y Taille = 36 * 2/3 = 24
Vous pouvez essayer ce code qu'il utilise le y_size
au lieu de la taille complète de votre tableau.
...
//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;
...
probablement votre code est basé sur https://github.com/begeekmyfriend/yasea/blob/master/library/src/main/libenc/jni/libenc.cc et, en fonction de ce code, vous devez utiliser le y_size
gmetax est presque correct.
Vous utilisez la taille de l'ensemble du tableau où vous devriez utiliser la taille du composant Y, qui est src_width * src_height
.
la réponse de gmetax est fausse car il a mis y_size
à la place de out_size
lors de la définition du cadre de sortie. Je crois que le bon extrait de code devrait ressembler à ceci:
//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;
Vous essayez de redimensionner votre image YUV422 comme si c’était YUV420, il n’est donc pas étonnant que les couleurs soient toutes dégradées. Tout d’abord, vous devez déterminer le format exact de votre tampon d’entrée YUV. La documentation de YUV_422_888 donne l’impression que cela peut représenter aussi bien des formats plans que des formats entrelacés (si la foulée de pixels n’est pas 1). D'après vos résultats, il semble que votre source soit planaire et que le traitement du plan Y soit correct, mais votre erreur est liée à la gestion des plans U et V. Pour réussir la mise à l'échelle:
U
et V
sont entrelacés ou Plans. Très probablement, ils sont également planaires.ScalePlane
de libyuv pour mettre à l'échelle U
et V
séparément. Peut-être que Si vous entrez dans I420Scale
, il appelle ScalePlane
pour des avions Individuels. Faites de même, mais utilisez des lignes de lignes correctes pour vos plans U
et V
(Chacun est deux fois plus grand que ce que I420Scale attend).Quelques conseils pour savoir si vous avez U
et V
planes ou entrelacées: essayez d'éviter de redimensionner votre image et de l'enregistrer, afin de vous assurer d'obtenir un résultat correct (identique à la source). Ensuite, essayez de mettre à zéro les images U
ou V
et de voir ce que vous obtenez. Si U
et V
sont plans et que vous memset U
plan à zéro, vous devriez voir l’image entière changer de couleur. S'ils sont entrelacés, la moitié de l'image change et l'autre reste inchangée. De la même manière, vous pouvez vérifier vos hypothèses sur les tailles, les tailles de ligne et les décalages de vos avions. Une fois que vous êtes sûr de votre format et de votre mise en page YUV, vous pouvez mettre à l'échelle des plans individuels si votre entrée est plane, ou si vous avez une entrée entrelacée en premier, vous devez désentrelacer les plans et les redimensionner ensuite.
Vous pouvez également utiliser libswscale à partir de ffmpeg/libav et essayer différents formats pour en trouver un, puis utiliser libyuv.