前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android UVC Camera获取的yuv转Mat

Android UVC Camera获取的yuv转Mat

作者头像
zinyan.com
发布2023-07-14 11:06:48
6110
发布2023-07-14 11:06:48
举报
文章被收录于专栏:zinyan

1. 前言

碰见一种特殊情况,Android 设备没有默认集成Camera摄像头。只好选择了 usb 摄像头。

一开始临时拿了个比较老的usb摄像头(ps:标注1080p,但是清晰度不太好)。插入设备的USB口之后,通过Android相机可以正确唤起设备。

也就是系统本身自动加载了该相机。之后在开发过程中直接通过CameraX 可以加载这个USB摄像头。

但是有两种问题:

  1. 设备经常在使用和关闭切换过程中,出现相机错误,无法使用的问题。
  2. 摄像头发现速度比较慢。

考虑了之后,打算换个高清点的摄像头。就买了一个2K的高清USB摄像头。

结果发现CameraX发现不了设备了。

没办法,系统改不了的情况下。选择了UVC协议加载USB摄像头。

1.1 UVC Camera

还好在Android平台上有大佬提供了UVC 加载USB摄像头的开源库。https://github.com/jiangdongguo/AndroidUSBCamera

依赖该库之后,可以正常加载和显示USB摄像头的画面了。

以下基于AndroidUSBCamera 3.2.10版本 因为不想用多路相机,同时3.3.x之后部分api进行了比较大的改动。

同时,根据项目的readme介绍文档,找到了获取Camera的实时回调的 yuv 数据

代码语言:javascript
复制
        //获取 相机原始数据 yuv
        cameraClient.addPreviewDataCallBack(new IPreviewDataCallBack() {
            @Override
            public void onPreviewData(@Nullable byte[] bytes, @NonNull DataFormat dataFormat) {
//                Log.e("TAG", "请求得到 YUV数据");
                assert bytes != null;
//                Log.e("TAG", "YUV数据长度" + bytes.length);

            }
        });

得到 byte[]的yuv数据之后。(PS:该yuv是 NV21格式的)根据我的业务需求。

我需要将yuv数组转为Mat用于OpenCV的计算。

然后中间出现了各种异常和问题。本篇内容就是记录一下,我碰见的各种情况和最后的解决方法。

2. 转换yuv byte 转 Bitmap

笨办法可以先将yuv转Bitmap,然后再使用OpenCV提供的Utils.btimapToMat转换成Mat。

但是很明显,中间的转换过程可以进行优化。或者我们直接使用AndroidUSBCamera 库中的cameraClient.captureImage直接得到图片算了。(ps:这个方法会将相机数据输出为本地文件存储。)然后再转换。

2.1 方法一

将yuv byte[] 转Bitmap 的步骤如下:

代码语言:javascript
复制
byte[] imageInBuffer ;// 这个是我们的byte数组
FrameMetadata frameMetadata = new FrameMetadata.Builder().setHeight(previewHeight).setWidth(previewWidth).setRotation(0).build(); //是我们的图片对的宽高信息和旋转角度。

try {
            YuvImage image =
                    new YuvImage(
                            imageInBuffer, ImageFormat.NV21, metadata.getWidth(), metadata.getHeight(), null);
            ByteArrayOutputStream stream = new ByteArrayOutputStream();
            image.compressToJpeg(new Rect(0, 0, metadata.getWidth(), metadata.getHeight()), 80, stream);

            Bitmap bmp = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size());

            stream.close();
            return rotateBitmap(bmp, metadata.getRotation(), false, false);
        } catch (Exception e) {
            Log.e("VisionProcessorBase", "Error: " + e.getMessage());
        }

然后我们就能得到Bitmap bmp了。只需要将该bmp转换为Mat就可以了。

代码语言:javascript
复制
import org.opencv.android.Utils;

Mat yuv = null;
Utils.bitmapToMat(bitmap, yuv);

2.2 方法二

除了上诉的的方法以外,我们还可以使用Android提供的ScriptIntrinsicYuvToRGB进行转换。

这种方式转换Bitmap的效率要比上面通过YuvImage进行转换的速度快。

代码语言:javascript
复制
public class FastYUVtoRGB {
    private RenderScript rs;
    private ScriptIntrinsicYuvToRGB yuvToRgbIntrinsic;
    private Type.Builder yuvType, rgbaType;
    private Allocation in, out;

    public FastYUVtoRGB(Context context) {
        rs = RenderScript.create(context);
        yuvToRgbIntrinsic = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs));
    }


    public Bitmap convertYUVtoRGB(byte[] yuvData, int width, int height) {
        if (yuvType == null) {
            yuvType = new Type.Builder(rs, Element.U8(rs)).setX(yuvData.length);
            in = Allocation.createTyped(rs, yuvType.create(), Allocation.USAGE_SCRIPT);

            rgbaType = new Type.Builder(rs, Element.RGBA_8888(rs)).setX(width).setY(height);
            out = Allocation.createTyped(rs, rgbaType.create(), Allocation.USAGE_SCRIPT);
        }
        in.copyFrom(yuvData);
        yuvToRgbIntrinsic.setInput(in);
        yuvToRgbIntrinsic.forEach(out);
        Bitmap bmpout = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        out.copyTo(bmpout);
        return bmpout;
    }
}

PS:AndroidUSBCamera库中返回的byte数据是NV21格式的。同时该接口是异步线程。所以我们转成Bitmap之后进行显示时需要注意线程切换。

3. yuv byte [] 转 Mat

上面的转换过程都先进行了Bitmap转换,但是OpenCV现在可以直接将yuv数据填充到Mat中。

如果是处理好的yuv数组,我们应该是可以直接使用:

代码语言:javascript
复制
Mat yuv_mat = new Mat(height + (height / 2), width, CvType.CV_8UC1);
yuv_mat.put(0, 0, bytes);
Mat bgr_i420 = new Mat();
Imgproc.cvtColor(yuv_mat, bgr_i420, Imgproc.COLOR_YUV2RGBA_I420, 4);

Bitmap bitmap1 = Bitmap.createBitmap(bgr_i420.width(), bgr_i420.height(), Bitmap.Config.ARGB_8888);
Utils.matToBitmap(bgr_i420, bitmap1); //将 mat转bitmap
viewBinding.imSitArea.setImageBitmap(bitmap1);  //使用ImageView 显示该bitmap

按照上面的代码直接使用后,我们显示的图片是灰度图。原因在哪里,原因在于我格式配置错误了。

因为我们的数据是YUV NV21也就是YUV420sp。图像数据比值关系是4:2:0

所以,我们如果想将相机得到的yuv数据,转换为Mat只需要写为:

代码语言:javascript
复制
Mat yuv_mat = new Mat(480 + (480 / 2), 640, CvType.CV_8UC1);
yuv_mat.put(0, 0, bytes);
Mat rgb_mat = new Mat();
Imgproc.cvtColor(yuv_mat, rgb_mat, Imgproc.COLOR_YUV420sp2RGB);

就能得到一个彩色的Mat对象了。之后再根据我们的需求进行处理Mat就可以了。

3.1 yuv 数据格式

在得到的yuv的 byte[]的数据的数组长度应该是:width*height*3/2

验证一下:

代码语言:javascript
复制
cameraClient.addPreviewDataCallBack(new IPreviewDataCallBack() {
    @Override
    public void onPreviewData(@Nullable byte[] bytes, @NonNull DataFormat dataFormat) {
//Log.e("TAG", "请求得到 YUV数据");
       assert bytes != null;
       Log.e("TAG", "YUV数据长度" + bytes.length);
}
//输出: YUV数据长度460800

已知我的宽高为640*480 。那么代入到上面的计算方法中: 640*480*3/2= 460800。完全符合输出的数组长度。

yuv 中,Y代表的亮度值,而UV是颜色值。NV21属于YUV420格式 。也就是4:2:0的关系。

每四个Y值对应一个点U和一个点V。如果不太能理解的话:

还是用上面计算的进行拆分介绍:

代码语言:javascript
复制
Y = 640*480 = 307200
// 每四个Y 对应一个U和V那么可算出U和V的数量:
U =76800
V =76800
Y+U+V =307200+76800+76800 = 460800 

到这里我们就能理解了吧,byte[]数组中,每个值其实就代表了Y,U,V 中某个信息值。那么我们如何去区分数组中哪些值是Y,哪些值是U哪些值是V。

就需要知道YUV格式了,也就是上面介绍的NV21了。

在YUV420格式中width*height 代表的是Y的值,而后面的就是UV的值了。

NV12类型的YUV420格式的数据效果如下:

代码语言:javascript
复制
[               
	Y Y Y Y      
	Y Y Y Y    
	U V U V 
] 

NV21的数据格式,就刚好和NV12相反了:

代码语言:javascript
复制
[
    Y Y Y Y
    Y Y Y Y
    V U V U
]               

通过分析,我们如果直接从byte中提取到指定长度应该是能够正常显示灰度图的。所以,我们验证一下:

代码语言:javascript
复制
byte[] s ;// 这个是相机返回的 yuv420数据
Mat yuv_mat = new Mat(480, 640, CvType.CV_8UC1);
yuv_mat.put(0, 0, s);

我们如果直接显示该yuv_mat 就会得到一个灰度图的效果。说明逻辑和方法是正确的。

那么,Mat是怎么识别byte数组中的nv的顺序的呢?很简单,通过我们后面

Imgproc.cvtColor(yuv_mat, rgb_mat, Imgproc.COLOR_YUV420sp2RGB); 方法中的 Imgproc.COLOR_YUV420sp2RGB判断的。

上面这个代码的作用是,将yuv_mat中的数据采用YUV420sp格式转换为RGB格式,并赋值给rgb_mat

因为YUV NV21或者 NV12格式数据,在Mat中识别为了YUV420sp,我们可以统一使用YUV420sp将NV21或NV12格式的yuv数据组成的Mat转换为其他的Mat数据。

4.小结

到这里,转换就算结束了。希望对于转换过程中出现问题的小伙伴们,有一点点参考价值。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-03-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 zinyan 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 前言
    • 1.1 UVC Camera
    • 2. 转换yuv byte 转 Bitmap
      • 2.1 方法一
        • 2.2 方法二
        • 3. yuv byte [] 转 Mat
          • 3.1 yuv 数据格式
          • 4.小结
          相关产品与服务
          文件存储
          文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档