【问题标题】:Yuv (NV21) image converting to bitmap [duplicate]Yuv(NV21)图像转换为位图[重复]
【发布时间】:2016-03-06 12:14:57
【问题描述】:

我正在尝试从相机预览中捕获图像并在其上进行一些绘图。问题是,我只有大约 3-4 fps 的绘图,一半的帧处理时间是从相机预览接收和解码 NV21 图像并转换为位图。我有一个代码来完成这个任务,我在另一个堆栈问题上找到了它。它似乎并不快,但我不知道如何优化它。在三星 Note 3 上大约需要 100-150 毫秒,图像尺寸为 1920x1080。我怎样才能让它更快地工作?

代码:

public Bitmap curFrameImage(byte[] data, Camera camera)
{
    Camera.Parameters parameters = camera.getParameters();
    int imageFormat = parameters.getPreviewFormat();

    if (imageFormat == ImageFormat.NV21)
    {
        YuvImage img = new YuvImage(data, ImageFormat.NV21, prevSizeW, prevSizeH, null);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        img.compressToJpeg(new android.graphics.Rect(0, 0, img.getWidth(), img.getHeight()), 50, out);
        byte[] imageBytes = out.toByteArray();
        return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
    }
    else
    {
        Log.i(TAG, "Preview image not NV21");
        return null;
    }
}

图像的最终格式必须是位图,所以我可以对其进行处理。我尝试将 Camera.Parameters.setPreviewFormat 设置为 RGB_565,但无法将相机参数分配给相机,我还读到 NV21 是唯一可用的格式。我不确定是否可以在这些格式更改中找到解决方案。

提前谢谢你。

【问题讨论】:

  • 最快的方法是使用 RenderScript 内在函数,但问题是为什么每秒需要这么多位图?绘制这些位图或存储它们将成为您流程的下一个瓶颈,速度为 10 FPS 或更低
  • 谢谢。我的任务是在相机预览中围绕文本字段绘制矩形,因此当相机移动时,我希望保持文本字段周围的边界也移动,并在新找到的文本字段周围查找和绘制矩形(因此此处无法进行跟踪) .这是我第一次做这样的事情,比如在相机预览上实时绘图,所以也许你有什么建议或意见,也许我在这里做错了什么?
  • 考虑通过 OpenGL 渲染你的矩形;您无法显示 SurfaceTexture 来渲染相机预览,因为您的矩形将显示有一些延迟(识别和跟踪算法)。最好使用着色器手动渲染 YUV 预览帧,与您的矩形完美同步。也许您的识别和跟踪算法也可能被调整为依赖 YUV 数据而不是 RGB?
  • “你不能显示 SurfaceTexture 来渲染相机预览”——我不是。我只是在 onPreviewFrame 函数中从这个函数接收图像,在其他 SurfaceView 上的画布上进行处理和绘制线条(通过处理找到它们的坐标),就像这样 canvas.drawLine(... (如果我正确理解你的陈述)。实际上,我认为调整算法以支持 YUV 图像的想法非常好,但我仍然对优化这种转换感兴趣,因为这看起来很糟糕。谢谢你,很抱歉花了太长时间回复,我很忙。
  • 在 GoogleSource 上查看 example:使用 RenderScript 内部函数进行一些图像处理和 YUV 到 RGB 转换来显示它。

标签: java android bitmap camera yuv


【解决方案1】:

感谢 Alex Cohn 帮助我加快转换速度。我实现了您建议的方法(RenderScript 内在函数)。此代码使用 RenderScript 内部函数编写,将 YUV 图像转换为位图的速度大约快了 5 倍。之前的代码需要 100-150 毫秒。在三星 Note 3 上,这需要 15-30 左右。 如果有人需要执行相同的任务,代码如下:

这些将被使用:

private RenderScript rs;
private ScriptIntrinsicYuvToRGB yuvToRgbIntrinsic;
private Type.Builder yuvType, rgbaType;
private Allocation in, out;

在我初始化的创建函数中..:

rs = RenderScript.create(this);
yuvToRgbIntrinsic = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs));

整个onPreviewFrame看起来是这样的(这里我接收并转换图像):

if (yuvType == null)
{
    yuvType = new Type.Builder(rs, Element.U8(rs)).setX(dataLength);
    in = Allocation.createTyped(rs, yuvType.create(), Allocation.USAGE_SCRIPT);

    rgbaType = new Type.Builder(rs, Element.RGBA_8888(rs)).setX(prevSizeW).setY(prevSizeH);
    out = Allocation.createTyped(rs, rgbaType.create(), Allocation.USAGE_SCRIPT);
}

in.copyFrom(data);

yuvToRgbIntrinsic.setInput(in);
yuvToRgbIntrinsic.forEach(out);

Bitmap bmpout = Bitmap.createBitmap(prevSizeW, prevSizeH, Bitmap.Config.ARGB_8888);
out.copyTo(bmpout);

【讨论】:

  • 谢谢,从基本方法 (stackoverflow.com/questions/9192982/displaying-yuv-image-in-android#9330203) 用你的方法将 1080p 图像处理到 ~0.08 秒 ~1 秒 :)
  • 有些行应该只调用一次,而且在预览中检查 null 看起来真的很难看stackoverflow.com/a/43551798/4548520 答案要好得多
【解决方案2】:

您可以获得更快的速度(使用 JellyBean 4.3、API18 或更高版本): 相机预览模式必须为 NV21 !

在“onPreviewFrame()”上只做

aIn.copyFrom(data);

yuvToRgbIntrinsic.forEach(aOut);

aOut.copyTo(bmpout);  // and of course, show the bitmap or whatever

不要在这里创建任何对象。

所有其他的东西(创建 rs、yuvToRgbIntrinsic、分配、位图)都可以 在 onCreate() 方法中在开始相机预览之前

rs = RenderScript.create(this);
yuvToRgbIntrinsic = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs));

// you don´t need Type.Builder objects , with cameraPreviewWidth and cameraPreviewHeight do:
int yuvDatalength = cameraPreviewWidth*cameraPreviewHeight*3/2;  // this is 12 bit per pixel  
aIn = Allocation.createSized(rs, Element.U8(rs), yuvDatalength);

// of course you will need the Bitmap
bmpout = Bitmap.createBitmap(cameraPreviewWidth, cameraPreviewHeight, Bitmap.Config.ARGB_8888);

// create output allocation from bitmap
aOut = Allocation.createFromBitmap(rs,bmpout);  // this simple !

// set the script´s in-allocation, this has to be done only once
yuvToRgbIntrinsic.setInput(aIn);

在 Nexus 7(2013,JellyBean 4.3)上,全高清 (1920x1080) 相机预览转换大约需要 0.007 秒(是,7 毫秒)。

【讨论】:

【解决方案3】:

OpenCV-JNI 从 Nv21 数据为 4160x3120 大小的图像构造 Mat 似乎比 renderscript 快 2 倍(38 毫秒)(68 毫秒,不包括初始化时间)。如果我们需要缩小构建位图的大小,OpenCV-JNI 似乎是更好的方法,因为我们只对 Y 数据使用全尺寸。仅在构建 OpenCV 垫子时,将调整 CbCr 数据的大小以缩小您的数据。

更好的方法是将 NV21 字节数组和 int 像素数组传递给 Jni。 Jni 端可能不需要数组副本。然后,使用开源 libyuv 库(https://chromium.googlesource.com/libyuv/libyuv/)将 NV21 转换为 ARGB。在 Java 中,我们使用传递的像素数组来构造位图。在 Jni 中,对于 arm64 平台上的 4160x3120 大小的字节数组,从 NV21 到 ARGB 的转换仅需约 4ms。

【讨论】:

  • byte[] NV21 到 OpenCV Mat,然后 Mat 到 Bitmap 并不比 stackoverflow.com/a/43551798/4548520 快,所以 RenderScript 稍微快一点
  • 附注作者的回答比 Matti81 的stackoverflow.com/a/43551798/4548520
  • mb 你没有尝试过 Matti81 的回答
  • 你对 lubyuv 也有误解,它比 renderscript 慢 1-3 毫秒 s8.postimg.cc/uvl85emx1/on_Preview.png s8.postimg.cc/5csvsdg7p/lubyuv_jni.png method renderScript 14 method lubyuv 14 method renderScript 12 method lubyuv 12 method renderScript 12 method lubyuv 15 method renderScript 12 method lubyuv 13 method renderScript 11 method lubyuv 14 method renderScript 12 method lubyuv 12
  • 还有 mb 您忘记将 ARGB byte[] 转换为 Bitmap 并认为它是最快的?我们使用 Matti81 的答案将 NV21 字节数组转换为位图,而不仅仅是 ARGB 字节数组
猜你喜欢
  • 2011-08-23
  • 1970-01-01
  • 2012-09-10
  • 2014-10-26
  • 2017-06-19
  • 1970-01-01
  • 2015-11-23
  • 1970-01-01
  • 2015-07-25
相关资源
最近更新 更多