如何将android.media.Image转换为位图对象?

32
在Android中,我从这个相机教程中获取了一个Image对象。但我现在想循环遍历像素值,有人知道我该如何做吗? 我需要将其转换为其他格式吗?如果需要,我该怎么做?
谢谢
9个回答

41
如果您想循环遍历每个像素,那么您需要先将其转换为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);

获取位图对象后,您现在可以迭代所有像素。


3
它会将图像从缓冲中加载到内存中的字节数组中。因此,“get”是从图像缓冲区检索字节的操作。 - Rod_Algonquin
6
为什么你只获取第一个平面?其他平面中没有相关数据吗? - hellowill89
4
好的,明白了。这个只适用于JPEG图像。相机通常会输出使用3个平面的YUV图像。那么,在这种情况下,这些平面会被依次附加吗? - hellowill89
6
IllegalStateException: BitmapFactory.decodeByteArray(bytes, 0, bytes.size, null) 的返回值不应为null。 - Someone Somewhere
2
当转换DEPTH16图像时,遇到与位图为空相同的问题:尝试在空对象引用上调用虚拟方法'boolean android.graphics.Bitmap.compress(android.graphics.Bitmap$CompressFormat, int, java.io.OutputStream)' - zhangxaochen
显示剩余6条评论

5

不要忘记添加 val yuvToRgbConverter= YuvToRgbConverter(requireContext()) - Tom Xuan

2
Actually you have two questions in one 1) 如何循环遍历android.media.Image像素 2) 如何将android.media.image转换为Bitmap 第一个问题很简单。请注意,从相机获得的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应用程序(Linux)打开它以检查格式。
第二个问题有点复杂。为了获得Bitmap对象,我从TensorFlow项目here中找到了一些代码示例。对于您来说最有趣的函数是“convertImageToBitmap”,它将返回RGB值。
要将它们转换为真正的Bitmap,请执行以下操作:
  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() 代码。 - Someone Somewhere
2
@SomeoneSomewhere 这不是我的项目,所以我提供了项目链接。请在那里查看。 - Arkady

1

Java转换方法

ImageAnalysis imageAnalysis = new ImageAnalysis.Builder()
                .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888)
                .build();

imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(this), new ImageAnalysis.Analyzer() {
    @Override
    public void analyze(@NonNull ImageProxy image) {
         // call toBitmap function
         Bitmap bitmap = toBitmap(image);
         image.close();
    }
});

private Bitmap bitmapBuffer;
private Bitmap toBitmap(@NonNull ImageProxy image) {
   if(bitmapBuffer == null){
       bitmapBuffer = Bitmap.createBitmap(image.getWidth(),image.getHeight(),Bitmap.Config.ARGB_8888);
   }
   bitmapBuffer.copyPixelsFromBuffer(image.getPlanes()[0].getBuffer());
   return bitmapBuffer;
}

即使在 Kotlin 中也可以正常工作,例如:imageAnalyzer = ImageAnalysis.Builder() .... .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888) 然后进行分析:myBitmap.copyPixelsFromBuffer(image.planes[0].buffer) - Gabor Szigeti
失败。在copyPixelsFromBuffer上崩溃了。 - dcarl661
@CsabaToth 输入数据已经是RGBA_8888格式,这里只需要复制内存。当它是yuv格式时,会有其他平面。 - cheungxiongwei
@dcarl661 上面的代码片段,我在安卓上通常使用它。你的函数输入数据是其他格式吗? - cheungxiongwei
@cheungxiongwei 我在Android上添加了我的用法,从分析器imageProxy到使用我从BitMapUtils改编的静态函数转换为BitMap。 - dcarl661
显示剩余2条评论

0

0

我假设您已经拥有由相机提供的YUV(YUV_420_888)图像。使用这个有趣的如何在Android中使用YUV(YUV_420_888)图像教程,我可以提出以下解决方案将图像转换为位图。 使用以下方法将YUV图像转换为位图:

    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;
    }

0

从分析器开始使用imageProxy

@Override
        public void analyze(@NonNull ImageProxy imageProxy)
        {
            Image mediaImage     = imageProxy.getImage();
            if (mediaImage      != null)
            {
                toBitmap(mediaImage);  
            }
            imageProxy.close();
        }

然后转换为位图

private Bitmap toBitmap(Image image)
{
    if (image.getFormat() != ImageFormat.YUV_420_888)
    {
        throw new IllegalArgumentException("Invalid image format");
    }
    byte[] nv21b      =  yuv420ThreePlanesToNV21BA(image.getPlanes(), image.getWidth(), image.getHeight());
    YuvImage yuvImage = new YuvImage(nv21b, ImageFormat.NV21, image.getWidth(), image.getHeight(), null);

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    yuvImage.compressToJpeg      (new Rect(0, 0,
                                 yuvImage.getWidth(),
                                 yuvImage.getHeight()),
                                 mQuality, baos);
    mFrameBuffer               = baos;

    //byte[] imageBytes = baos.toByteArray();
    //Bitmap bm         = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);

    return null;
}

这是我用过的静态函数:
public static byte [] yuv420ThreePlanesToNV21BA(Plane[] yuv420888planes, int width, int height)
{
    int imageSize = width * height;
    byte[] out = new byte[imageSize + 2 * (imageSize / 4)];

    if (areUVPlanesNV21(yuv420888planes, width, height)) {
        // Copy the Y values.
        yuv420888planes[0].getBuffer().get(out, 0, imageSize);

        ByteBuffer uBuffer = yuv420888planes[1].getBuffer();
        ByteBuffer vBuffer = yuv420888planes[2].getBuffer();
        // Get the first V value from the V buffer, since the U buffer does not contain it.
        vBuffer.get(out, imageSize, 1);
        // Copy the first U value and the remaining VU values from the U buffer.
        uBuffer.get(out, imageSize + 1, 2 * imageSize / 4 - 1);
    }
    else
    {
        // Fallback to copying the UV values one by one, which is slower but also works.
        // Unpack Y.
        unpackPlane(yuv420888planes[0], width, height, out, 0, 1);
        // Unpack U.
        unpackPlane(yuv420888planes[1], width, height, out, imageSize + 1, 2);
        // Unpack V.
        unpackPlane(yuv420888planes[2], width, height, out, imageSize, 2);
    }
    return out;
}

-3

bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image);

位图 = BitmapFactory.decodeResource(getResources(), R.drawable.image);


-5

1-将图像文件的路径存储为字符串变量。要解码图像文件的内容,您需要将文件路径存储在作为字符串的代码内部。请使用以下语法作为指南:

String picPath = "/mnt/sdcard/Pictures/mypic.jpg";

2-创建位图对象并使用BitmapFactory:

Bitmap picBitmap;
Bitmap picBitmap = BitmapFactory.decodeFile(picPath);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接