【发布时间】:2017-01-21 05:12:37
【问题描述】:
在 android 中,我从这里得到一个 Image 对象https://inducesmile.com/android/android-camera2-api-example-tutorial/ 这个相机教程。但我现在想循环遍历像素值,有人知道我该怎么做吗?我需要把它转换成别的东西吗?我该怎么做?
谢谢
【问题讨论】:
在 android 中,我从这里得到一个 Image 对象https://inducesmile.com/android/android-camera2-api-example-tutorial/ 这个相机教程。但我现在想循环遍历像素值,有人知道我该怎么做吗?我需要把它转换成别的东西吗?我该怎么做?
谢谢
【问题讨论】:
如果要循环整个像素,则需要先将其转换为Bitmap 对象。现在由于我在源代码中看到它返回一个Image,您可以直接将字节转换为位图。
Image image = reader.acquireLatestImage();
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
byte[] bytes = new byte[buffer.capacity()];
buffer.get(bytes);
Bitmap bitmapImage = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, null);
然后,一旦获得位图对象,您现在就可以遍历所有像素。
【讨论】:
get 是从图像缓冲区中检索字节。
IllegalStateException: BitmapFactory.decodeByte…tes, 0, bytes.size, null) must not be null
YuvToRgbConverter 对于从 Image 到 Bitmap 的转换很有用。
使用示例。
val bmp = Bitmap.createBitmap(image.width, image.height, Bitmap.Config.ARGB_8888)
yuvToRgbConverter.yuvToRgb(image, bmp)
【讨论】:
val yuvToRgbConverter= YuvToRgbConverter(requireContext())
其实你有两个问题合二为一 1)你如何循环抛出 android.media.Image 像素 2) 如何将android.media.image 转换为位图
1-st 很容易。请注意,您从相机获得的 Image 对象只是一个 YUV 帧,其中 Y 和 U+V 分量位于不同的平面中。在许多图像处理案例中,您只需要 Y 平面,即图像的灰色部分。为了得到它,我建议这样的代码:
Image.Plane[] planes = image.getPlanes();
int yRowStride = planes[0].getRowStride();
byte[] yImage = new byte[yRowStride];
planes[0].getBuffer().get(yImage);
yImage 字节缓冲区实际上是帧的灰色像素。 以同样的方式,你可以得到 U+V 部分。请注意,它们可以是 U 首先,V 之后,或者 V 和 U 之后,并且可能是交替存在的(这是 Camera2 API 的常见情况)。所以你得到了 UVUV....
出于调试目的,我经常将框架写入文件,并尝试使用Vooya app (Linux) 打开它以检查格式。
第二个问题有点复杂。 为了获得一个位图对象,我从 TensorFlow 项目here 中找到了一些代码示例。对您来说最有趣的函数是“convertImageToBitmap”,它将返回 RGB 值。
要将它们转换为真正的位图,请执行以下操作:
Bitmap rgbFrameBitmap;
int[] cachedRgbBytes;
cachedRgbBytes = ImageUtils.convertImageToBitmap(image, cachedRgbBytes, cachedYuvBytes);
rgbFrameBitmap = Bitmap.createBitmap(image.getWidth(), image.getHeight(), Bitmap.Config.ARGB_8888);
rgbFrameBitmap.setPixels(cachedRgbBytes,0,image.getWidth(), 0, 0,image.getWidth(), image.getHeight());
注意:将 YUV 转换为 RGB 帧的选项更多,因此如果您需要像素值,也许 Bitmap 不是最佳选择,因为它可能会消耗比您需要的更多的内存来获取 RGB 值
【讨论】:
ImageUtils.convertImageToBitmap()
https://docs.oracle.com/javase/1.5.0/docs/api/java/nio/ByteBuffer.html#get%28byte[]%29
根据 java 文档:buffer.get 方法将此缓冲区中的字节传输到给定的目标数组中。 src.get(a) 形式的此方法的调用与调用的行为方式完全相同
src.get(a, 0, a.length)
【讨论】:
我假设您有相机提供的 YUV (YUV (YUV_420_888) 图像。使用这个有趣的How to use YUV (YUV_420_888) Image in Android 教程,我可以提出以下解决方案将图像转换为位图。 使用它来将 YUV Image 转换为 Bitmap:
private Bitmap yuv420ToBitmap(Image image, Context context) {
RenderScript rs = RenderScript.create(SpeedMeasurementActivity.this);
ScriptIntrinsicYuvToRGB script = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs));
// Refer the logic in a section below on how to convert a YUV_420_888 image
// to single channel flat 1D array. For sake of this example I'll abstract it
// as a method.
byte[] yuvByteArray = image2byteArray(image);
Type.Builder yuvType = new Type.Builder(rs, Element.U8(rs)).setX(yuvByteArray.length);
Allocation in = Allocation.createTyped(rs, yuvType.create(), Allocation.USAGE_SCRIPT);
Type.Builder rgbaType = new Type.Builder(rs, Element.RGBA_8888(rs))
.setX(image.getWidth())
.setY(image.getHeight());
Allocation out = Allocation.createTyped(rs, rgbaType.create(), Allocation.USAGE_SCRIPT);
// The allocations above "should" be cached if you are going to perform
// repeated conversion of YUV_420_888 to Bitmap.
in.copyFrom(yuvByteArray);
script.setInput(in);
script.forEach(out);
Bitmap bitmap = Bitmap.createBitmap(image.getWidth(), image.getHeight(), Bitmap.Config.ARGB_8888);
out.copyTo(bitmap);
return bitmap;
}
以及一个支持将 3 平面 YUV 图像转换为 1 维字节数组的功能:
private byte[] image2byteArray(Image image) {
if (image.getFormat() != ImageFormat.YUV_420_888) {
throw new IllegalArgumentException("Invalid image format");
}
int width = image.getWidth();
int height = image.getHeight();
Image.Plane yPlane = image.getPlanes()[0];
Image.Plane uPlane = image.getPlanes()[1];
Image.Plane vPlane = image.getPlanes()[2];
ByteBuffer yBuffer = yPlane.getBuffer();
ByteBuffer uBuffer = uPlane.getBuffer();
ByteBuffer vBuffer = vPlane.getBuffer();
// Full size Y channel and quarter size U+V channels.
int numPixels = (int) (width * height * 1.5f);
byte[] nv21 = new byte[numPixels];
int index = 0;
// Copy Y channel.
int yRowStride = yPlane.getRowStride();
int yPixelStride = yPlane.getPixelStride();
for(int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
nv21[index++] = yBuffer.get(y * yRowStride + x * yPixelStride);
}
}
// Copy VU data; NV21 format is expected to have YYYYVU packaging.
// The U/V planes are guaranteed to have the same row stride and pixel stride.
int uvRowStride = uPlane.getRowStride();
int uvPixelStride = uPlane.getPixelStride();
int uvWidth = width / 2;
int uvHeight = height / 2;
for(int y = 0; y < uvHeight; ++y) {
for (int x = 0; x < uvWidth; ++x) {
int bufferIndex = (y * uvRowStride) + (x * uvPixelStride);
// V channel.
nv21[index++] = vBuffer.get(bufferIndex);
// U channel.
nv21[index++] = uBuffer.get(bufferIndex);
}
}
return nv21;
}
【讨论】:
1-将图像文件的路径存储为字符串变量。要解码图像文件的内容,您需要将文件路径作为字符串存储在代码中。使用以下语法作为指导:
String picPath = "/mnt/sdcard/Pictures/mypic.jpg";
2-创建Bitmap对象并使用BitmapFactory:
Bitmap picBitmap;
Bitmap picBitmap = BitmapFactory.decodeFile(picPath);
【讨论】: