如何在不转换为位图的情况下裁剪包含 Y'(亮度)的 byte[] 数组
How to crop a byte[] array containing Y' (luma) without converting to Bitmap
编辑:已解决!见下文。
我需要裁剪从 Camera2 的 onImageAvailable 侦听器获得的图像(YUV422888 颜色 space)。我不想也不需要将它转换为位图,因为它会极大地影响性能,而且我实际上对亮度感兴趣而不是 RGB 信息(包含在图像的平面 0 中)。
我提出了以下解决方案:
- 获取侦听器中 Camera2 可用的图像对象的平面 0 中包含的 Y' 信息。
- 将 Y' 平面转换为 byte[] 数组 in.
- 将 byte[] 数组转换为 2d byte[][] 数组以便裁剪。
- 使用一些 for 循环在所需的左、右、上、下坐标处裁剪。
- 将 2d byte[][] 数组折叠回 1d byte[] 数组 out,包含裁剪后的亮度 Y' 信息。
不幸的是,第 4 点产生了损坏的图像。 我做错了什么?
在Camera2的onImageAvailableListener中(请注意,虽然我计算的是位图,但只是看看发生了什么,因为我对Bitmap/RGB数据):
Image.Plane[] planes = image.getPlanes();
ByteBuffer buffer = planes[0].getBuffer(); // Grab just the Y' Plane.
buffer.rewind();
byte[] data = new byte[buffer.capacity()];
buffer.get(data);
Bitmap bitmap = cropByteArray(data, image.getWidth(), image.getHeight()); // Just for preview/sanity check purposes. The bitmap is **corrupt**.
runOnUiThread(new bitmapRunnable(bitmap) {
@Override
public void run() {
image_view_preview.setImageBitmap(this.bitmap);
}
});
cropByteArray 函数需要修复。它输出损坏的位图,应该输出类似于 in 的 out byte[] 数组,但仅包含裁剪区域:
public Bitmap cropByteArray(byte[] in, int inw, int inh) {
int l = 100; // left crop start
int r = 400; // right crop end
int t = 400; // top crop start
int b = 700; // top crop end
int outw = r-l;
int outh = b-t;
byte[][] in2d = new byte[inw][inh]; // input width and height are 1080 x 1920.
byte[] out = new byte[outw*outh];
int[] pixels = new int[outw*outh];
i = 0;
for(int col = 0; col < inw; col++) {
for(int row = 0; row < inh; row++) {
in2d[col][row] = in[i++];
}
}
i = 0;
for(int col = l; col < r; col++) {
for(int row = t; row < b; row++) {
//out[i++] = in2d[col][row]; // out is the desired output of the function, but for now we output a bitmap instead
int grey = in2d[col][row] & 0xff;
pixels[i++] = 0xFF000000 | (grey * 0x00010101);
}
}
return Bitmap.createBitmap(pixels, inw, inh, Bitmap.Config.ARGB_8888);
}
编辑 由于 Eddy Talvala 的建议,已解决。以下代码将生成裁剪到所需坐标的 Y'(来自 ImageReader 的亮度平面 0)。裁剪后的数据在 out 字节数组中。生成位图只是为了确认。我还在下面附加了方便的 YUVtoGrayscale() 函数。
Image.Plane[] planes = image.getPlanes();
ByteBuffer buffer = planes[0].getBuffer();
int stride = planes[0].getRowStride();
buffer.rewind();
byte[] Y = new byte[buffer.capacity()];
buffer.get(Y);
int t=200; int l=600;
int out_h = 600; int out_w = 600;
byte[] out = new byte[out_w*out_h];
int firstRowOffset = stride * t + l;
for (int row = 0; row < out_h; row++) {
buffer.position(firstRowOffset + row * stride);
buffer.get(out, row * out_w, out_w);
}
Bitmap bitmap = YUVtoGrayscale(out, out_w, out_h);
这里是 YUVtoGrayscale().
public Bitmap YUVtoGrayscale(byte[] yuv, int width, int height) {
int[] pixels = new int[yuv.length];
for (int i = 0; i < yuv.length; i++) {
int grey = yuv[i] & 0xff;
pixels[i] = 0xFF000000 | (grey * 0x00010101);
}
return Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888);
}
还有一些遗留问题。我使用的是前置摄像头,虽然预览方向在 TextureView 中是正确的,但 ImageViewer 返回的图像顺时针旋转并垂直翻转(一个人在预览中躺在他们的右脸颊上,只有右脸颊是左脸颊,因为垂直翻转)在我的设备上,传感器方向为 270 度。 是否有公认的解决方案可以使用 Camera2 使预览和保存的照片在相同、正确的方向上?
干杯。
如果您描述图像是如何损坏的,这将会很有帮助 - 您是否看到有效图像但它被扭曲了,或者它只是完全垃圾,还是完全黑色?
但我猜你没有注意 Y 平面的行步幅 (https://developer.android.com/reference/android/media/Image.Plane.html#getRowStride() ),这通常会导致图像倾斜(垂直线变成斜线)。
访问Y平面时,像素(x,y)的字节索引为:
y * rowStride + x
没有
y * width + x
因为行步幅可能大于宽度。
我也尽量避免抄袭;你真的不需要二维数组,图像的大 byte[] 也会浪费内存。
您可以改用 seek() 到每个输出行的开头,然后只读取需要直接复制到目标字节 [] 中的字节 ByteBuffer.get(byte[], offset,长度)。
看起来像
int stride = planes[0].getRowStride();
ByteBuffer img = planes[0].getBuffer();
int firstRowOffset = stride * t + l;
for (int row = 0; row < outh; row++) {
img.position(firstRowOffset + row * stride);
img.get(out, row * outw, outw);
}
编辑:已解决!见下文。 我需要裁剪从 Camera2 的 onImageAvailable 侦听器获得的图像(YUV422888 颜色 space)。我不想也不需要将它转换为位图,因为它会极大地影响性能,而且我实际上对亮度感兴趣而不是 RGB 信息(包含在图像的平面 0 中)。 我提出了以下解决方案:
- 获取侦听器中 Camera2 可用的图像对象的平面 0 中包含的 Y' 信息。
- 将 Y' 平面转换为 byte[] 数组 in.
- 将 byte[] 数组转换为 2d byte[][] 数组以便裁剪。
- 使用一些 for 循环在所需的左、右、上、下坐标处裁剪。
- 将 2d byte[][] 数组折叠回 1d byte[] 数组 out,包含裁剪后的亮度 Y' 信息。
不幸的是,第 4 点产生了损坏的图像。 我做错了什么?
在Camera2的onImageAvailableListener中(请注意,虽然我计算的是位图,但只是看看发生了什么,因为我对Bitmap/RGB数据):
Image.Plane[] planes = image.getPlanes();
ByteBuffer buffer = planes[0].getBuffer(); // Grab just the Y' Plane.
buffer.rewind();
byte[] data = new byte[buffer.capacity()];
buffer.get(data);
Bitmap bitmap = cropByteArray(data, image.getWidth(), image.getHeight()); // Just for preview/sanity check purposes. The bitmap is **corrupt**.
runOnUiThread(new bitmapRunnable(bitmap) {
@Override
public void run() {
image_view_preview.setImageBitmap(this.bitmap);
}
});
cropByteArray 函数需要修复。它输出损坏的位图,应该输出类似于 in 的 out byte[] 数组,但仅包含裁剪区域:
public Bitmap cropByteArray(byte[] in, int inw, int inh) {
int l = 100; // left crop start
int r = 400; // right crop end
int t = 400; // top crop start
int b = 700; // top crop end
int outw = r-l;
int outh = b-t;
byte[][] in2d = new byte[inw][inh]; // input width and height are 1080 x 1920.
byte[] out = new byte[outw*outh];
int[] pixels = new int[outw*outh];
i = 0;
for(int col = 0; col < inw; col++) {
for(int row = 0; row < inh; row++) {
in2d[col][row] = in[i++];
}
}
i = 0;
for(int col = l; col < r; col++) {
for(int row = t; row < b; row++) {
//out[i++] = in2d[col][row]; // out is the desired output of the function, but for now we output a bitmap instead
int grey = in2d[col][row] & 0xff;
pixels[i++] = 0xFF000000 | (grey * 0x00010101);
}
}
return Bitmap.createBitmap(pixels, inw, inh, Bitmap.Config.ARGB_8888);
}
编辑 由于 Eddy Talvala 的建议,已解决。以下代码将生成裁剪到所需坐标的 Y'(来自 ImageReader 的亮度平面 0)。裁剪后的数据在 out 字节数组中。生成位图只是为了确认。我还在下面附加了方便的 YUVtoGrayscale() 函数。
Image.Plane[] planes = image.getPlanes();
ByteBuffer buffer = planes[0].getBuffer();
int stride = planes[0].getRowStride();
buffer.rewind();
byte[] Y = new byte[buffer.capacity()];
buffer.get(Y);
int t=200; int l=600;
int out_h = 600; int out_w = 600;
byte[] out = new byte[out_w*out_h];
int firstRowOffset = stride * t + l;
for (int row = 0; row < out_h; row++) {
buffer.position(firstRowOffset + row * stride);
buffer.get(out, row * out_w, out_w);
}
Bitmap bitmap = YUVtoGrayscale(out, out_w, out_h);
这里是 YUVtoGrayscale().
public Bitmap YUVtoGrayscale(byte[] yuv, int width, int height) {
int[] pixels = new int[yuv.length];
for (int i = 0; i < yuv.length; i++) {
int grey = yuv[i] & 0xff;
pixels[i] = 0xFF000000 | (grey * 0x00010101);
}
return Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888);
}
还有一些遗留问题。我使用的是前置摄像头,虽然预览方向在 TextureView 中是正确的,但 ImageViewer 返回的图像顺时针旋转并垂直翻转(一个人在预览中躺在他们的右脸颊上,只有右脸颊是左脸颊,因为垂直翻转)在我的设备上,传感器方向为 270 度。 是否有公认的解决方案可以使用 Camera2 使预览和保存的照片在相同、正确的方向上?
干杯。
如果您描述图像是如何损坏的,这将会很有帮助 - 您是否看到有效图像但它被扭曲了,或者它只是完全垃圾,还是完全黑色?
但我猜你没有注意 Y 平面的行步幅 (https://developer.android.com/reference/android/media/Image.Plane.html#getRowStride() ),这通常会导致图像倾斜(垂直线变成斜线)。
访问Y平面时,像素(x,y)的字节索引为:
y * rowStride + x
没有
y * width + x
因为行步幅可能大于宽度。
我也尽量避免抄袭;你真的不需要二维数组,图像的大 byte[] 也会浪费内存。
您可以改用 seek() 到每个输出行的开头,然后只读取需要直接复制到目标字节 [] 中的字节 ByteBuffer.get(byte[], offset,长度)。
看起来像
int stride = planes[0].getRowStride();
ByteBuffer img = planes[0].getBuffer();
int firstRowOffset = stride * t + l;
for (int row = 0; row < outh; row++) {
img.position(firstRowOffset + row * stride);
img.get(out, row * outw, outw);
}