使用新的Android camera2 api从YUV_420_888进行JPEG编码时的绿色图像

我正在尝试使用新的相机api。 突发捕捉速度太慢了,所以我在ImageReader中使用了YUV_420_888格式,稍后再做一个JPEG格式的转换,就像在下面的文章中提到的那样:

Android camera2捕捉突发太慢

问题是,当我尝试使用RenderScript从YUV_420_888编码JPEG时,出现绿色图像,如下所示:

RenderScript rs = RenderScript.create(mContext); ScriptIntrinsicYuvToRGB yuvToRgbIntrinsic = ScriptIntrinsicYuvToRGB.create(rs, Element.RGBA_8888(rs)); Type.Builder yuvType = new Type.Builder(rs, Element.YUV(rs)).setX(width).setY(height).setYuvFormat(ImageFormat.YUV_420_888); Allocation in = Allocation.createTyped(rs, yuvType.create(), Allocation.USAGE_SCRIPT); Type.Builder rgbaType = new Type.Builder(rs, Element.RGBA_8888(rs)).setX(width).setY(height); Allocation out = Allocation.createTyped(rs, rgbaType.create(), Allocation.USAGE_SCRIPT); in.copyFrom(data); yuvToRgbIntrinsic.setInput(in); yuvToRgbIntrinsic.forEach(out); Bitmap bmpout = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); out.copyTo(bmpout); ByteArrayOutputStream baos = new ByteArrayOutputStream(); bmpout.compress(Bitmap.CompressFormat.JPEG, 100, baos); byte[] jpegBytes = baos.toByteArray(); 

数据variables(YUV_420_888数据)从以下获得:

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

我在做什么错误的JPEG编码,只有在绿色的图像?

提前致谢

编辑:这是我获得的绿色图像的一个例子:

https://drive.google.com/file/d/0B1yCC7QDeEjdaXF2dVp6NWV6eWs/view?usp=sharing

Solutions Collecting From Web of "使用新的Android camera2 api从YUV_420_888进行JPEG编码时的绿色图像"

所以这个问题有几层答案。

首先,我不相信有一种直接的方法来将YUV_420_888数据的图像复制到RS分配中,即使分配格式为YUV_420_888。

所以,如果您不使用图像进行其他任何操作,则可以使用Allocation#getSurface和Allocation#ioReceive直接使用Allocation作为摄像机的输出。 然后你可以执行你的YUV-> RGB转换并读出位图。

但是,请注意,JPEG文件实际上存储了YUV数据,因此当您压缩JPEG时,位图将保存文件时将执行另一个RGB-> YUV转换。 为了获得最大的效率,您需要将YUV数据直接送入可接受的JPEG编码器,并完全避免额外的转换步骤。 不幸的是,这是不可能通过公共的API,所以你必须下降到JNI代码,并包括自己的libjpeg副本或等效的JPEG编码库。

如果不需要快速保存JPEG文件,可以将YUV_420_888数据转换为NV21字节[],然后使用YuvImage,但需要注意YUV_420_888数据的像素和行跨度,并将其正确映射到NV21 – YUV_420_888是灵活的,可以代表几种不同的内存布局(包括NV21),并可能在不同的设备上有所不同。 因此,在将布局修改为NV21时,确保正确执行映射至关重要。

我设法得到这个工作,panonski提供的答案是不完全正确的,而一个大问题是,这个YUV_420_888格式涵盖了NV21格式非常具体的许多不同的内存布局(我不知道为什么默认格式是这样改变,对我来说没有意义)

请注意,由于几个原因,此方法可能会非常慢。

  1. 由于NV21交织色度通道,而YUV_420_888包含具有非交错色度通道的格式,所以唯一可靠的select(我知道)是逐字节复制。 我有兴趣知道是否有一个技巧来加速这个过程,我怀疑有一个。 我提供了一个灰度级选项,因为该部分是非常快速的逐行副本。

  2. 当从相机抓取帧时,其字节将被标记为受保护,这意味着不可能直接访问,并且它们必须被复制以被直接操纵。

  3. 图像似乎以反向字节顺序存储,所以在转换之后,最终的数组需要被反转。 这可能只是我的相机,我怀疑这里还有另外一个技巧可以加快这个速度。

无论如何这里是代码:

 private byte[] getRawCopy(ByteBuffer in) { ByteBuffer rawCopy = ByteBuffer.allocate(in.capacity()); rawCopy.put(in); return rawCopy.array(); } private void fastReverse(byte[] array, int offset, int length) { int end = offset + length; for (int i = offset; i < offset + (length / 2); i++) { array[i] = (byte)(array[i] ^ array[end - i - 1]); array[end - i - 1] = (byte)(array[i] ^ array[end - i - 1]); array[i] = (byte)(array[i] ^ array[end - i - 1]); } } private ByteBuffer convertYUV420ToN21(Image imgYUV420, boolean grayscale) { Image.Plane yPlane = imgYUV420.getPlanes()[0]; byte[] yData = getRawCopy(yPlane.getBuffer()); Image.Plane uPlane = imgYUV420.getPlanes()[1]; byte[] uData = getRawCopy(uPlane.getBuffer()); Image.Plane vPlane = imgYUV420.getPlanes()[2]; byte[] vData = getRawCopy(vPlane.getBuffer()); // NV21 stores a full frame luma (y) and half frame chroma (u,v), so total size is // size(y) + size(y) / 2 + size(y) / 2 = size(y) + size(y) / 2 * 2 = size(y) + size(y) = 2 * size(y) int npix = imgYUV420.getWidth() * imgYUV420.getHeight(); byte[] nv21Image = new byte[npix * 2]; Arrays.fill(nv21Image, (byte)127); // 127 -> 0 chroma (luma will be overwritten in either case) // Copy the Y-plane ByteBuffer nv21Buffer = ByteBuffer.wrap(nv21Image); for(int i = 0; i < imgYUV420.getHeight(); i++) { nv21Buffer.put(yData, i * yPlane.getRowStride(), imgYUV420.getWidth()); } // Copy the u and v planes interlaced if(!grayscale) { for (int row = 0; row < imgYUV420.getHeight() / 2; row++) { for (int cnt = 0, upix = 0, vpix = 0; cnt < imgYUV420.getWidth() / 2; upix += uPlane.getPixelStride(), vpix += vPlane.getPixelStride(), cnt++) { nv21Buffer.put(uData[row * uPlane.getRowStride() + upix]); nv21Buffer.put(vData[row * vPlane.getRowStride() + vpix]); } } fastReverse(nv21Image, npix, npix); } fastReverse(nv21Image, 0, npix); return nv21Buffer; } 

如果我正确理解你的描述,我可以看到你的代码中至less有两个问题:

  1. 看起来你只是将你的图像的Y部分传递给YUV-> RGB转换代码,因为它看起来像只使用ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();的第一个平面ByteBuffer buffer = mImage.getPlanes()[0].getBuffer(); ,忽略了U和V飞机。

  2. 我还不熟悉这些Renderscripttypes,但它看起来像Element.RGBA_8888和Bitmap.Config.ARGB_8888指的是略有不同的字节顺序,所以你可能需要做一些重新sorting的工作。

这两个问题都可能是产生的图像的绿色的原因。

你必须使用RenderScript? 如果没有,您可以将图像从YUV转换为N21,然后从N21转换为JPEG,而不需要任何花哨的结构。 首先你乘坐0和2飞机得到N21:

 private byte[] convertYUV420ToN21(Image imgYUV420) { byte[] rez = new byte[0]; ByteBuffer buffer0 = imgYUV420.getPlanes()[0].getBuffer(); ByteBuffer buffer2 = imgYUV420.getPlanes()[2].getBuffer(); int buffer0_size = buffer0.remaining(); int buffer2_size = buffer2.remaining(); rez = new byte[buffer0_size + buffer2_size]; buffer0.get(rez, 0, buffer0_size); buffer2.get(rez, buffer0_size, buffer2_size); return rez; } 

然后你可以使用YuvImage的内置方法压缩成JPEG格式。 wh参数是图像文件的宽度和高度。

 private byte[] convertN21ToJpeg(byte[] bytesN21, int w, int h) { byte[] rez = new byte[0]; YuvImage yuv_image = new YuvImage(bytesN21, ImageFormat.NV21, w, h, null); Rect rect = new Rect(0, 0, w, h); ByteArrayOutputStream output_stream = new ByteArrayOutputStream(); yuv_image.compressToJpeg(rect, 100, output_stream); rez = output_stream.toByteArray(); return rez; } 

这是一个答案/问题。 在几个类似的post,build议使用这个脚本: https : //github.com/pinguo-yuyidong/Camera2/blob/master/camera2/src/main/rs/yuv2rgb.rs

但我不知道如何使用它。 build议,欢迎。

上述转换是否奏效? 因为我通过复制第一个和最后一个平面来使用renderscript来尝试它,而且我仍然收到了一个像上面那样的绿色过滤图像。