2015-11-05 114 views
8

我试图将图像从YUV_420_888转换为rgb,并且在输出图像时遇到了一些麻烦。在ImageReader中,我以YUV_420_888格式(使用相机2 api获取此图像预览)获取图像。camera2 api将yuv420转换为rgb绿色

imageReader = ImageReader.newInstance(1920,1080,ImageFormat.YUV_420_888,10); 

在为YuvImage类写作Android SDK中,该YuvImage只使用NV21,YUY2。

我们可以看到差异N21和YUV420之间并不大,我尝试数据转换为N21

YUV420: enter image description here

N21: enter image description here

onImageAvailable我分别得到每架飞机,并把它们放在正确的地方(如图)

ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 

ByteBuffer bufferY = image.getPlanes()[0].getBuffer(); 
byte[] data0 = new byte[bufferY.remaining()]; 
bufferY.get(data0); 

ByteBuffer bufferU = image.getPlanes()[1].getBuffer(); 
byte[] data1 = new byte[bufferU.remaining()]; 
bufferU.get(data1); 

ByteBuffer bufferV = image.getPlanes()[2].getBuffer(); 
byte[] data2 = new byte[bufferV.remaining()]; 
bufferV.get(data2); 
... 
outputStream.write(data0); 
for (int i=0;i<bufferV.remaining();i++) { 
    outputStream.write(data1[i]); 
    outputStream.write(data2[i]); 
} 

后创建YuvImage,转换为位图,查看和保存

final YuvImage yuvImage = new YuvImage(outputStream.toByteArray(), ImageFormat.NV21, 1920,1080, null); 
ByteArrayOutputStream outBitmap = new ByteArrayOutputStream(); 

yuvImage.compressToJpeg(new Rect(0, 0,1920, 1080), 95, outBitmap); 

byte[] imageBytes = outBitmap.toByteArray(); 

final Bitmap imageBitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length); 
mImageView.setImageBitmap(imageBitmap); 
... 
imageBitmap.compress(Bitmap.CompressFormat.JPEG, 95, out); 

但我保存的图像是绿色和粉红色: capture image capture image

我错过了什么?

+2

小点:为NV21图其实是错误的,并描述了NV12编码。除了交换U和V之外,NV21是相同的,即VUVUVUVUV而不是UVUVUVUVUV。 – samgak

回答

2

bufferV.get(data2)增加了字节缓冲区的位置。这就是为什么循环for (int i=0;i<bufferV.remaining();i++)产生0次迭代。您可以轻松地把它改写为

for (int i=0; i<data1.length; i++) { 
    outputStream.write(data1[i]); 
    outputStream.write(data2[i]); 
} 
+0

我改变这个......但是一样。 我查了一下,里面有数组** ** **,** data2 **和他们差不多。数组长度为518400,并且从这个长度为517440是0的值。 可能只能使用960? –

+0

这可能是您设备上camera2 API的错误实现。 –

3

有您的转换的尝试两个主要问题:

  1. 我们不能假定U和V平面是孤立的,它们可能包含交错的数据(例如U-PLANE = {U1, V1, U2, V2, ...} )。事实上,它甚至可能已经是NV21格式的交错了。这里的关键是看飞机的row stridepixel stride,并检查我们可以假设的关于YUV_420_888 format
  2. 事实上,你已经评论过大部分U和V平面数据都是零,这表明你正在经历Android bug on the generation of images in YUV_420_888。这意味着,即使您获得了转换权限,如果受到仅在Android 5.1.1及更高版本中修复的bug的影响,该图像仍然显示为绿色,因此除了检查您正在使用的版本之外,还值得一看修复代码。
+0

感谢与Android的错误链接,我检查我的版本,我也有5.0.1。所以我会尝试更新我的android,并会看到有什么变化 –

3

我已经实现了YUV_420逻辑(完全按照上面的图中示出)中的renderScript,看到完整的代码在这里:

Conversion YUV_420 _888 to Bitmap, complete code

它产生完美bimaps为API 22,但对于API 21它显示了“绿色田园诗”。从这我可以确认,你找到的结果。正如上面Silvaren已经提到的那样,原因似乎是API 21中的一个Android bug。看看我的rs代码,很明显,如果缺少U和V信息(即零),G(reen)ARGB组件在转换。

我在我的Galaxy S5(仍然是API 21)上看到类似的绿色图片 - 这里甚至颠倒了;-)。我怀疑API 21中的大多数设备目前还没有将Camera2用于设备相机应用程序。有一个名为“手动相机兼容性”的免费应用程序,可以对此进行测试。从这我看到,确实S5/API21仍然不使用Camera2 ...幸运的是...

2

我得到了ImageFormat.YUV_420_888的图像,并成功保存到JPEG文件,并可以在Windows上正确地查看它。
我分享这里:

private final Image mImage; 
private final File mFile; 
private final int mImageFormat; 


ByteArrayOutputStream outputbytes = new ByteArrayOutputStream(); 

ByteBuffer bufferY = mImage.getPlanes()[0].getBuffer(); 
byte[] data0 = new byte[bufferY.remaining()]; 
bufferY.get(data0); 

ByteBuffer bufferU = mImage.getPlanes()[1].getBuffer(); 
byte[] data1 = new byte[bufferU.remaining()]; 
bufferU.get(data1); 

ByteBuffer bufferV = mImage.getPlanes()[2].getBuffer(); 
byte[] data2 = new byte[bufferV.remaining()]; 
bufferV.get(data2); 

try 
{ 
    outputbytes.write(data0); 
    outputbytes.write(data2); 
    outputbytes.write(data1); 


    final YuvImage yuvImage = new YuvImage(outputbytes.toByteArray(), ImageFormat.NV21, mImage.getWidth(),mImage.getHeight(), null); 
    ByteArrayOutputStream outBitmap = new ByteArrayOutputStream(); 

    yuvImage.compressToJpeg(new Rect(0, 0,mImage.getWidth(), mImage.getHeight()), 95, outBitmap); 


    FileOutputStream outputfile = null; 
    outputfile = new FileOutputStream(mFile); 
    outputfile.write(outBitmap.toByteArray()); 
} 
catch (IOException e) 
{ 
    e.printStackTrace(); 
} 
finally 
{ 
    mImage.close(); 
}