当前位置: 首页 > 知识库问答 >
问题:

Android使用渲染脚本将ImageReader图像转换为YCbCr_420_SP(NV21)字节数组?

谢志文
2023-03-14

我目前正在使用Javacv,它使用public void onPreviewFrame(Byte[]data,Camera Camera)Camera函数。

因为相机是不推荐的,我一直在研究camera2和MediaProjection。这两个库都使用了ImageReader类。

目前,我用以下代码实例化了这样一个ImageReader:

 private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
            = new ImageReader.OnImageAvailableListener() {

        @Override
        public void onImageAvailable(ImageReader reader) {

            mBackgroundHandler.post(new processImage(reader.acquireNextImage()));
        }

我已经尝试使用RGBA_8888格式作为以下线程的Javacv:https://github.com/bytedeco/Javacv/issues/298,但这对我不起作用。

因此,我考虑使用呈现脚本将这些图像转换为NV21(YUV_420_SP)格式(这是OnPreviewFrame函数中camera的默认输出),就像我使用camera库一样。

我也读了像这个和这个网站这样的帖子来做转换,但这些对我不起作用,我担心他们会太慢。此外,我对C语言的了解非常有限。基本上,我想要https://developer.android.com/reference/android/renderscript/scriptIntrinSiCyuVtorgb.html的反向操作

那么,如何将图像转换为与OnPreviewFrame函数输出匹配的字节数组,即NV21(YUV_420_SP)格式?最好使用Renderscript,因为它更快。

我尝试使用imageformat.yuv_420_888,但是没有用。我不断收到类似生成器输出缓冲区格式0x1与ImageReader配置的缓冲区格式不匹配这样的错误。我切换回PixelFormat.rgba_8888,发现ImageObject中只有一个平面。这个平面的字节缓冲器的大小为宽*高*4(分别为R、G、B、A的一个字节)。因此,我尝试将其转换为NV21格式。

我从这个答案中修改了代码,以产生以下函数:

void RGBtoNV21(byte[] yuv420sp, byte[] argb, int width, int height) {
    final int frameSize = width * height;

    int yIndex = 0;
    int uvIndex = frameSize;

    int A, R, G, B, Y, U, V;
    int index = 0;
    int rgbIndex = 0;

    for (int i = 0; i < height; i++) {
        for (int j = 0; j < width; j++) {

            R = argb[rgbIndex++];
            G = argb[rgbIndex++];
            B = argb[rgbIndex++];
            A = argb[rgbIndex++];

            // RGB to YUV conversion according to
            // https://en.wikipedia.org/wiki/YUV#Y.E2.80.B2UV444_to_RGB888_conversion
            Y = ((66 * R + 129 * G + 25 * B + 128) >> 8) + 16;
            U = ((-38 * R - 74 * G + 112 * B + 128) >> 8) + 128;
            V = ((112 * R - 94 * G - 18 * B + 128) >> 8) + 128;

            // NV21 has a plane of Y and interleaved planes of VU each sampled by a factor
            // of 2 meaning for every 4 Y pixels there are 1 V and 1 U.
            // Note the sampling is every other pixel AND every other scanline.
            yuv420sp[yIndex++] = (byte) ((Y < 0) ? 0 : ((Y > 255) ? 255 : Y));
            if (i % 2 == 0 && index % 2 == 0) {
                yuv420sp[uvIndex++] = (byte) ((V < 0) ? 0 : ((V > 255) ? 255 : V));
                yuv420sp[uvIndex++] = (byte) ((U < 0) ? 0 : ((U > 255) ? 255 : U));
            }
            index++;
        }
    }
}
int mWidth = mImage.getWidth();
int mHeight = mImage.getHeight();

byte[] rgbaBytes = new byte[mWidth * mHeight * 4];
mImage.getPlanes()[0].getBuffer().get(rgbaBytes);
mImage.close();

byte[] yuv = new byte[mWidth * mHeight * 3 / 2];
RGBtoNV21(yuv, rgbaBytes, mWidth, mHeight);

然而,这产生了一个类似于这幅图像的结果:

显然是畸形的。似乎我的转换是关闭的,但我不知道到底是什么。

共有1个答案

危宜
2023-03-14
@TargetApi(19)
public static byte[] yuvImageToByteArray(Image image) {

    assert(image.getFormat() == ImageFormat.YUV_420_888);

    int width = image.getWidth();
    int height = image.getHeight();

    Image.Plane[] planes = image.getPlanes();
    byte[] result = new byte[width * height * 3 / 2];

    int stride = planes[0].getRowStride();
    assert (1 == planes[0].getPixelStride());
    if (stride == width) {
        planes[0].getBuffer().get(result, 0, width*height);
    }
    else {
        for (int row = 0; row < height; row++) {
            planes[0].getBuffer().position(row*stride);
            planes[0].getBuffer().get(result, row*width, width);
        }
    }

    stride = planes[1].getRowStride();
    assert (stride == planes[2].getRowStride());
    int pixelStride = planes[1].getPixelStride();
    assert (pixelStride == planes[2].getPixelStride());
    byte[] rowBytesCb = new byte[stride];
    byte[] rowBytesCr = new byte[stride];

    for (int row = 0; row < height/2; row++) {
        int rowOffset = width*height + width/2 * row;
        planes[1].getBuffer().position(row*stride);
        planes[1].getBuffer().get(rowBytesCb);
        planes[2].getBuffer().position(row*stride);
        planes[2].getBuffer().get(rowBytesCr);

        for (int col = 0; col < width/2; col++) {
            result[rowOffset + col*2] = rowBytesCr[col*pixelStride];
            result[rowOffset + col*2 + 1] = rowBytesCb[col*pixelStride];
        }
    }
    return result;
}

我已经发布了另一个具有类似需求的函数。这个新的实现试图利用这样一个事实,即YUV_420_888通常只是伪装的NV21。

 类似资料:
  • 问题内容: 我正在尝试从相机预览中捕获图像并在其上进行一些绘制。问题是,我只有大约3-4 fps的绘图,并且一半的帧处理时间是从摄像机预览接收和解码NV21图像并将其转换为 位图 。我有一个执行此任务的代码,是在另一个堆栈问题上找到的。它似乎并不快,但我不知道如何优化它。在Samsung Note 3(图像尺寸1920x1080)上大约需要100-150毫秒。我怎样才能使其更快地工作? 代码: 图

  • 问题内容: 我正在尝试通过和不通过将字节数组写入磁盘来验证字节数组,然后使用来打开它。我看了and方法,但是我需要图像的大小(只有将字节流转换为图像时才能得到)。 我的读取功能如下所示: 然后作为基本测试,我尝试将字节数组转换为图像: 如果有人知道我在做什么错,或者有一种更优雅的方法将这些字节转换为对我有真正帮助的图像。 PS:我以为我需要字节数组,因为我对字节进行了处理(使它们出现图像错误)。这

  • 问题内容: 我有一个PIL图像格式的图像。我需要将其转换为字节数组。 现在我需要作为字节数组。 问题答案: 感谢大家的帮助。 终于解决了!! 这样,我不必将裁剪后的图像保存在硬盘中,并且能够从PIL裁剪后的图像中检索字节数组。

  • 我有一个byte[],它是通过从某个表中提取数据返回的。我想把这个字节数组转换成png图像。注意:我没有像其他地方类似的例子中那样从图像中获得字节数组。

  • 如何根据由按钮触发的布尔状态变量显示图像?我不希望重新加载整个页面,而只是重新加载特定的图像。 Facelets页面: 背衬类: 知道这里有什么问题吗?

  • 本文向大家介绍使用Java脚本将图像转换为Base64数据URL,包括了使用Java脚本将图像转换为Base64数据URL的使用技巧和注意事项,需要的朋友参考一下 若要使用javascript将图像从HTML页面标签转换为数据URI,首先需要创建一个canvas元素,将其宽度和高度设置为与图像相同,在其上绘制图像,最后在其上调用toDataURL方法。 这将返回图像的base64编码数据URI。例