为什么在 Android NDK 图像中将 YUV_420_888 转换为 RGBA_8888 旋转 90 度?
Why during conversion YUV_420_888 to RGBA_8888 in Android NDK image rotate 90 degree?
我在本地方法中通过 NDK 中的此方法将图像阅读器格式转换为 yug 格式再转换为 Rgba 格式:
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);
}
我建议您阅读this related discussion(也有不错的屏幕截图,不要错过)。
TL;NR:ImageReader 始终 returns 横向图像,与 相同。
如果将相机连接到纹理表面,显示将比您的程序可以执行的 YUV➤RGB 转换更有效,但我知道有很多情况需要 RGB 数据进行图像处理, 有时您希望图像处理的结果显示为实时预览。
本质上,这就是 OpenCV 处理 Android 相机和实时预览的方式(不幸的是,官方 OpenCV 不使用 camera2 API,但我已找到 one tutorial that shows how this can be done).
强烈建议使用Renderscript进行YUV➤RGB转换。它不仅速度快得多,而且与 CPU.
相比还可以显着节省电池电量
您的代码的一个完全不同的问题是它假设显示 window 宽高比与您从相机接收到的图像相同,不是吗?
你不应该依赖这个。即使在你固定 90° 旋转后(如果 window 是纵向的),你也可以 see the image distorted. This is not special for camera2, same can happen with the deprecated camera API.
不过,对于您的情况,解决方案可能有所不同。您可以跳过一些输入像素以保持纵横比正确,而不是调整用于显示实时预览的 window 的几何形状。
简而言之,你需要
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 的像素,您可能会执行 预期的前置摄像头,不是吗?
我在本地方法中通过 NDK 中的此方法将图像阅读器格式转换为 yug 格式再转换为 Rgba 格式:
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);
}
我建议您阅读this related discussion(也有不错的屏幕截图,不要错过)。
TL;NR:ImageReader 始终 returns 横向图像,与
如果将相机连接到纹理表面,显示将比您的程序可以执行的 YUV➤RGB 转换更有效,但我知道有很多情况需要 RGB 数据进行图像处理, 有时您希望图像处理的结果显示为实时预览。
本质上,这就是 OpenCV 处理 Android 相机和实时预览的方式(不幸的是,官方 OpenCV 不使用 camera2 API,但我已找到 one tutorial that shows how this can be done).
强烈建议使用Renderscript进行YUV➤RGB转换。它不仅速度快得多,而且与 CPU.
相比还可以显着节省电池电量您的代码的一个完全不同的问题是它假设显示 window 宽高比与您从相机接收到的图像相同,不是吗?
你不应该依赖这个。即使在你固定 90° 旋转后(如果 window 是纵向的),你也可以 see the image distorted. This is not special for camera2, same can happen with the deprecated camera API.
不过,对于您的情况,解决方案可能有所不同。您可以跳过一些输入像素以保持纵横比正确,而不是调整用于显示实时预览的 window 的几何形状。
简而言之,你需要
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 的像素,您可能会执行