将 Android camera2 api YUV_420_888 转换为 RGB
Posted
技术标签:
【中文标题】将 Android camera2 api YUV_420_888 转换为 RGB【英文标题】:Convert Android camera2 api YUV_420_888 to RGB 【发布时间】:2015-08-11 05:15:41 【问题描述】:我正在编写一个应用程序,该应用程序获取相机信息,将其转换为 rgb,以便进行一些处理。
它在使用 NV21 Yuv 格式的旧相机实现上运行良好。 我遇到的问题是新的 Yuv 格式 YUV_420_888。在发送 YUV_420_888 yuv 格式而不是 NV21 (YUV_420_SP) 格式的新 Camera2 Api 中,图像不再正确转换为 RGB。
谁能告诉我应该如何将 YUV_420_888 转换为 RGB?
谢谢
【问题讨论】:
那是针对较旧的相机实施,这对我没有帮助。不过谢谢。 有人把YUV_420_888转成NV21(YUV_420_SP)吗? @ConstantinGeorgiu,上述问题你解决了吗? 我的解决方案将 media.image 作为输入并返回位图 ***.com/a/35994288/5148048)。 看看这个link我用这个方法解决我的问题。 【参考方案1】:Camera2 YUV_420_888 到 Java 中的 RGB Mat(opencv)
@Override
public void onImageAvailable(ImageReader reader)
Image image = null;
try
image = reader.acquireLatestImage();
if (image != null)
byte[] nv21;
ByteBuffer yBuffer = mImage.getPlanes()[0].getBuffer();
ByteBuffer uBuffer = mImage.getPlanes()[1].getBuffer();
ByteBuffer vBuffer = mImage.getPlanes()[2].getBuffer();
int ySize = yBuffer.remaining();
int uSize = uBuffer.remaining();
int vSize = vBuffer.remaining();
nv21 = new byte[ySize + uSize + vSize];
//U and V are swapped
yBuffer.get(nv21, 0, ySize);
vBuffer.get(nv21, ySize, vSize);
uBuffer.get(nv21, ySize + vSize, uSize);
Mat mRGB = getYUV2Mat(nv21);
catch (Exception e)
Log.w(TAG, e.getMessage());
finally
image.close();// don't forget to close
public Mat getYUV2Mat(byte[] data)
Mat mYuv = new Mat(image.getHeight() + image.getHeight() / 2, image.getWidth(), CV_8UC1);
mYuv.put(0, 0, data);
Mat mRGB = new Mat();
cvtColor(mYuv, mRGB, Imgproc.COLOR_YUV2RGB_NV21, 3);
return mRGB;
【讨论】:
这是我找到的最快的方法。其他转换有 for 循环,这会减慢实时处理处理方案的速度。 请注意,这仅在底层缓冲区为 NV21 时有效,即当 uBuffer 和 vBuffer 重叠时,请参阅***.com/a/52740776/192373。 非常适合将 android CameraX 图像分析帧 (YUV) 转换为 RBG,谢谢。【参考方案2】:在我的方法中,我使用 OpenCV Mat 和脚本 https://gist.github.com/camdenfullmer/dfd83dfb0973663a7974
首先,使用上面链接中的代码将 YUV_420_888 图像转换为 Mat。
*mImage 是我在 ImageReader.OnImageAvailableListener 中获得的 Image 对象
Mat mYuvMat = imageToMat(mImage);
public static Mat imageToMat(Image image)
ByteBuffer buffer;
int rowStride;
int pixelStride;
int width = image.getWidth();
int height = image.getHeight();
int offset = 0;
Image.Plane[] planes = image.getPlanes();
byte[] data = new byte[image.getWidth() * image.getHeight() * ImageFormat.getBitsPerPixel(ImageFormat.YUV_420_888) / 8];
byte[] rowData = new byte[planes[0].getRowStride()];
for (int i = 0; i < planes.length; i++)
buffer = planes[i].getBuffer();
rowStride = planes[i].getRowStride();
pixelStride = planes[i].getPixelStride();
int w = (i == 0) ? width : width / 2;
int h = (i == 0) ? height : height / 2;
for (int row = 0; row < h; row++)
int bytesPerPixel = ImageFormat.getBitsPerPixel(ImageFormat.YUV_420_888) / 8;
if (pixelStride == bytesPerPixel)
int length = w * bytesPerPixel;
buffer.get(data, offset, length);
if (h - row != 1)
buffer.position(buffer.position() + rowStride - length);
offset += length;
else
if (h - row == 1)
buffer.get(rowData, 0, width - pixelStride + 1);
else
buffer.get(rowData, 0, rowStride);
for (int col = 0; col < w; col++)
data[offset++] = rowData[col * pixelStride];
Mat mat = new Mat(height + height / 2, width, CvType.CV_8UC1);
mat.put(0, 0, data);
return mat;
我们有 1 个通道的 YUV Mat。为 BGR(还不是 RGB)图像定义新的 Mat:
Mat bgrMat = new Mat(mImage.getHeight(), mImage.getWidth(),CvType.CV_8UC4);
我刚开始学习 OpenCV,所以这不一定是 4 通道 Mat,而是可以是 3 通道,但它适用于我。 现在我使用转换颜色方法将我的 yuv Mat 更改为 bgr Mat。
Imgproc.cvtColor(mYuvMat, bgrMat, Imgproc.COLOR_YUV2BGR_I420);
现在我们可以进行所有的图像处理,比如寻找轮廓、颜色、圆圈等。要将图像打印回屏幕上,我们需要将其转换为位图:
Mat rgbaMatOut = new Mat();
Imgproc.cvtColor(bgrMat, rgbaMatOut, Imgproc.COLOR_BGR2RGBA, 0);
final Bitmap bitmap = Bitmap.createBitmap(bgrMat.cols(), bgrMat.rows(), Bitmap.Config.ARGB_8888);
Utils.matToBitmap(rgbaMatOut, bitmap);
我在单独的线程中处理所有图像,因此要设置我的 ImageView,我需要在 UI 线程上执行此操作。
runOnUiThread(new Runnable()
@Override
public void run()
if(bitmap != null)
mImageView.setImageBitmap(bitmap);
);
【讨论】:
【参考方案3】:您是否尝试过使用此脚本?这是yydcdut 在this 问题上发布的答案
https://github.com/pinguo-yuyidong/Camera2/blob/master/camera2/src/main/rs/yuv2rgb.rs
【讨论】:
【参考方案4】:Use Shyam Kumar 的答案不适用于我的手机,但 Daniel Więcek 的答案是正确的。我调试它,发现 planes[i].getRowStride() 是 1216,planes[i].getPixelStride() 是 2。而图像宽度和高度都是1200。
因为我的声誉是 3,所以我不能发表评论,只能发布答案。
【讨论】:
您可以在此处找到从 Image 到 NV21(可以馈送到 OpenCV)的优化 Java 转换器:***.com/a/52740776/192373。请注意,在大多数情况下,renderscript conversion 会比 CPU 更快,并且不需要 OpenCV。【参考方案5】:比上面提到的“imageToMat”快大约 10 倍 - 上面的函数是这段代码:
Image image = reader.acquireLatestImage();
...
Mat yuv = new Mat(image.getHeight() + image.getHeight() / 2, image.getWidth(), CvType.CV_8UC1);
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
final byte[] data = new byte[buffer.limit()];
buffer.get(data);
yuv.put(0, 0, data);
...
image.close();
【讨论】:
我看不出您的答案如何正确...imageToMat 和我测试过的更简单的函数会产生两个不同的字节数组。如果 OpenCV 可以从 Camera2 api 处理这种新的 YUV 格式,那就太好了。但是下一个转换: Imgproc.cvtColor(mYuvMat, bgrMat, mgproc.COLOR_YUV2BGR_I420);至关重要,对于我的所有测试,只有 imageToMat 产生了良好的结果。如果有 YUV2BGR_CAMERA2_API 你的答案会更好。也许现在是。现在是两个月后;) 这可能适用于某些设备,其中 yBuffer(即image.getPlanes(0)
)实际上是完整的 NV21 直接缓冲区。这种情况很少见。【参考方案6】:
所以我遇到了完全相同的问题,我有一个代码从 OnPreviewFrame() 中获取旧的 YUV_420_SP 格式字节 [] 数据并将其转换为 RGB。
这里的关键是 byte[] 中的“旧”数据为 YYYYYY...CrCbCrCbCrCb,而来自 Camera2 API 的“新”数据分为 3 个平面:0=Y,1=Cb, 2=Cr.,您可以从中获取每个字节[]s。因此,您需要做的就是将新数据重新排序为与“旧”格式匹配的单个数组,您可以将其传递给现有的 toRGB() 函数:
Image.Plane[] planes = image.getPlanes(); // in YUV220_888 format
int acc = 0, i;
ByteBuffer[] buff = new ByteBuffer[planes.length];
for (i = 0; i < planes.length; i++)
buff[i] = planes[i].getBuffer();
acc += buff[i].capacity();
byte[] data = new byte[acc],
tmpCb = new byte[buff[1].capacity()] , tmpCr = new byte[buff[2].capacity()];
buff[0].get(data, 0, buff[0].capacity()); // Y
acc = buff[0].capacity();
buff[2].get(tmpCr, 0, buff[2].capacity()); // Cr
buff[1].get(tmpCb, 0, buff[1].capacity()); // Cb
for (i=0; i<tmpCb.length; i++)
data[acc] = tmpCr[i];
data[acc + 1] = tmpCb[i];
acc++;
..现在 data[] 的格式与旧的 YUV_420_SP 一样。
(希望它对某人有所帮助,尽管已经过去了很多年......)
【讨论】:
您的答案看起来很有希望,确实让我免于崩溃到转换后的位图。可悲的是,生成的位图只有绿色和灰色的步幅...... 请注意,这只适用于底层缓冲区为 YV12 的情况,即当 uBuffer 和 vBuffer 的 pixelStride=1 时。以上是关于将 Android camera2 api YUV_420_888 转换为 RGB的主要内容,如果未能解决你的问题,请参考以下文章
使用新的 Android camera2 api 从 YUV_420_888 进行 JPEG 编码时的绿色图像
在Android camera2下将YUV_420_888转换为位图的图像不正确
使用 OpenCV 从 android Camera2 将 YUV 转换为 RGB ImageReader 时出现问题,输出图像为灰度