【发布时间】:2017-12-13 03:40:09
【问题描述】:
注意:我帖子中的所有信息仅适用于三星 Galaxy S7 设备。我不知道模拟器和其他设备的行为如何。
在 onImageAvailable 中,我将每个图像连续转换为 NV21 字节数组,并将其转发到期望原始 NV21 格式的 API。
这是我初始化图像阅读器和接收图像的方式:
private void openCamera() {
...
mImageReader = ImageReader.newInstance(WIDTH, HEIGHT,
ImageFormat.YUV_420_888, 1); // only 1 for best performance
mImageReader.setOnImageAvailableListener(
mOnImageAvailableListener, mBackgroundHandler);
...
}
private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
= new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
Image image = reader.acquireLatestImage();
if (image != null) {
byte[] data = convertYUV420ToNV21_ALL_PLANES(image); // this image is turned 90 deg using front cam in portrait mode
byte[] data_rotated = rotateNV21_working(data, WIDTH, HEIGHT, 270);
ForwardToAPI(data_rotated); // image data is being forwarded to api and received later on
image.close();
}
}
};
将图像转换为原始 NV21 (from here) 的函数,工作正常,在纵向模式下使用前置摄像头时,图像(由于 android?)旋转了 90 度: (我根据 Alex Cohn 的 cmets 稍微修改了一下)
private byte[] convertYUV420ToNV21_ALL_PLANES(Image imgYUV420) {
byte[] rez;
ByteBuffer buffer0 = imgYUV420.getPlanes()[0].getBuffer();
ByteBuffer buffer1 = imgYUV420.getPlanes()[1].getBuffer();
ByteBuffer buffer2 = imgYUV420.getPlanes()[2].getBuffer();
// actually here should be something like each second byte
// however I simply get the last byte of buffer 2 and the entire buffer 1
int buffer0_size = buffer0.remaining();
int buffer1_size = buffer1.remaining(); // / 2 + 1;
int buffer2_size = 1;//buffer2.remaining(); // / 2 + 1;
byte[] buffer0_byte = new byte[buffer0_size];
byte[] buffer1_byte = new byte[buffer1_size];
byte[] buffer2_byte = new byte[buffer2_size];
buffer0.get(buffer0_byte, 0, buffer0_size);
buffer1.get(buffer1_byte, 0, buffer1_size);
buffer2.get(buffer2_byte, buffer2_size-1, buffer2_size);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
// swap 1 and 2 as blue and red colors are swapped
outputStream.write(buffer0_byte);
outputStream.write(buffer2_byte);
outputStream.write(buffer1_byte);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
rez = outputStream.toByteArray();
return rez;
}
因此需要轮换“数据”。使用这个函数 (from here),我得到一个奇怪的 3-times interlaced 图片 错误:
public static byte[] rotateNV21(byte[] input, int width, int height, int rotation) {
byte[] output = new byte[input.length];
boolean swap = (rotation == 90 || rotation == 270);
// **EDIT:** in portrait mode & front cam this needs to be set to true:
boolean yflip = true;// (rotation == 90 || rotation == 180);
boolean xflip = (rotation == 270 || rotation == 180);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
int xo = x, yo = y;
int w = width, h = height;
int xi = xo, yi = yo;
if (swap) {
xi = w * yo / h;
yi = h * xo / w;
}
if (yflip) {
yi = h - yi - 1;
}
if (xflip) {
xi = w - xi - 1;
}
output[w * yo + xo] = input[w * yi + xi];
int fs = w * h;
int qs = (fs >> 2);
xi = (xi >> 1);
yi = (yi >> 1);
xo = (xo >> 1);
yo = (yo >> 1);
w = (w >> 1);
h = (h >> 1);
// adjust for interleave here
int ui = fs + (w * yi + xi) * 2;
int uo = fs + (w * yo + xo) * 2;
// and here
int vi = ui + 1;
int vo = uo + 1;
output[uo] = input[ui];
output[vo] = input[vi];
}
}
return output;
}
结果成这张图:
注意:它仍然是同一个杯子,但是你看到了 3-4 次。
使用另一个建议的旋转函数from here 会得到正确的结果:
public static byte[] rotateNV21_working(final byte[] yuv,
final int width,
final int height,
final int rotation)
{
if (rotation == 0) return yuv;
if (rotation % 90 != 0 || rotation < 0 || rotation > 270) {
throw new IllegalArgumentException("0 <= rotation < 360, rotation % 90 == 0");
}
final byte[] output = new byte[yuv.length];
final int frameSize = width * height;
final boolean swap = rotation % 180 != 0;
final boolean xflip = rotation % 270 != 0;
final boolean yflip = rotation >= 180;
for (int j = 0; j < height; j++) {
for (int i = 0; i < width; i++) {
final int yIn = j * width + i;
final int uIn = frameSize + (j >> 1) * width + (i & ~1);
final int vIn = uIn + 1;
final int wOut = swap ? height : width;
final int hOut = swap ? width : height;
final int iSwapped = swap ? j : i;
final int jSwapped = swap ? i : j;
final int iOut = xflip ? wOut - iSwapped - 1 : iSwapped;
final int jOut = yflip ? hOut - jSwapped - 1 : jSwapped;
final int yOut = jOut * wOut + iOut;
final int uOut = frameSize + (jOut >> 1) * wOut + (iOut & ~1);
final int vOut = uOut + 1;
output[yOut] = (byte)(0xff & yuv[yIn]);
output[uOut] = (byte)(0xff & yuv[uIn]);
output[vOut] = (byte)(0xff & yuv[vIn]);
}
}
return output;
}
现在结果很好:
上图显示了使用纹理视图的表面并将其添加到 captureRequestBuilder 的直接流。下图为旋转后的原始图像数据。
问题是:
- “convertYUV420ToNV21_ALL_PLANES”中的这个 hack 是否适用于任何 设备/模拟器?
- 为什么 rotateNV21 不工作,而 rotateNV21_working 工作正常。
编辑:镜像问题已修复,请参阅代码注释。挤压问题已修复,它是由它被转发的 API 引起的。 实际的未解决问题是一个适当的不太昂贵的功能,将图像转换和旋转为在任何设备上工作的原始 NV21。
【问题讨论】:
-
从表面上看,旋转后的图像应该让杯子侧放,不是吗?
-
@AlexCohn:杯子不应该像底部图片那样被“挤压”。如果我显示“数据”,我的图片与顶部的图片完全相同,但旋转了 90 度。因此,我使用的是“rotateNV21”,但存在图片被挤压的问题,即宽度太高而高度太低。这是我需要解决的问题。
-
@AlexCohn:查看我编辑的帖子(图片)。 Android 在纵向模式下的前置摄像头存在问题,原始帧(即 NV21 字节数组)旋转 90 度(中间图片)。因此我使用函数“rotateNV21”。然而,这会改变图片的宽度和高度,从而产生不好的结果(下图)。它应该看起来像上面的图片。所以 rotateNV21 需要调整,而不是交换宽度和高度。我的问题是如何做到这一点。
-
旋转代码看起来是正确的(虽然不是最优的)。为了安全起见,请验证
rotateNV21()的输出长度是否等于width*height*3/2。我怀疑问题出在您用于将 NV21 转换为位图以进行显示的功能上。 -
在 Nexus 5 设备上,我得到相同的缓冲区大小。实际上,这里的plane[2] 与plane[1] 相同,只是偏移了1 个字节。结果是,如果您读取平面[1] 中的每个第二个字节,您就会得到 U,正如 developer.android.com/reference/android/graphics/… 中所解释的那样;如果你读取平面[2] 中的每一第二个字节,你会得到 V。但如果你只是连接平面 [0] 和平面 [1],你会得到一个 NV21 图像(没有最后一个 U 像素)。归根结底,您的
convertYUV420ToNV21_ALL_PLANES()只能靠运气才能生成正确的图像。
标签: android rotation android-camera2 portrait front-camera