博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android使用Camera2获取预览数据
阅读量:4635 次
发布时间:2019-06-09

本文共 5495 字,大约阅读时间需要 18 分钟。

一、Camera2简介

Camera2是Google在Android 5.0后推出的一个全新的相机API,Camera2和Camera没有继承关系,是完全重新设计的,且Camera2支持的功能也更加丰富,但是提供了更丰富的功能的同时也增加了使用的难度。Google的官方Demo:

二、Camera2 VS Camera

以下分别是使用Camera2和Camera打开相机进行预览并获取预览数据的流程图。

 
Camera2 API使用流程
 
Camera API使用流程

可以看到,和Camera相比,Camera2的调用明显复杂得多,但同时也提供了更强大的功能:

  • 支持在非UI线程获取预览数据
  • 可以获取更多的预览帧
  • 对相机的控制更加完备
  • 支持更多格式的预览数据
  • 支持高速连拍

但是具体能否使用还要看设备的厂商有无实现。

三、如何使用Camera2

  • 获取预览数据

一般情况下,大多设备其实只支持ImageFormat.YUV_420_888ImageFormat.JPEG格式的预览数据,而ImageFormat.JPEG是压缩格式,一般适用于拍照的场景,而不适合直接用于算法检测,因此我们一般取ImageFormat.YUV_420_888作为我们获取预览数据的格式,对于YUV不太了解的同学可以戳。

mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(),                ImageFormat.YUV_420_888, 2);mImageReader.setOnImageAvailableListener(               new OnImageAvailableListenerImpl(), mBackgroundHandler);

其中OnImageAvailableListenerImpl的实现如下

private class OnImageAvailableListenerImpl implements ImageReader.OnImageAvailableListener {        private byte[] y;        private byte[] u;        private byte[] v;        private ReentrantLock lock = new ReentrantLock();        @Override        public void onImageAvailable(ImageReader reader) {            Image image = reader.acquireNextImage();            // Y:U:V == 4:2:2            if (camera2Listener != null && image.getFormat() == ImageFormat.YUV_420_888) {                Image.Plane[] planes = image.getPlanes();                // 加锁确保y、u、v来源于同一个Image                lock.lock();                // 重复使用同一批byte数组,减少gc频率                if (y == null) {                    y = new byte[planes[0].getBuffer().limit() - planes[0].getBuffer().position()];                    u = new byte[planes[1].getBuffer().limit() - planes[1].getBuffer().position()];                    v = new byte[planes[2].getBuffer().limit() - planes[2].getBuffer().position()];                }                if (image.getPlanes()[0].getBuffer().remaining() == y.length) {                    planes[0].getBuffer().get(y);                    planes[1].getBuffer().get(u);                    planes[2].getBuffer().get(v);                    camera2Listener.onPreview(y, u, v, mPreviewSize, planes[0].getRowStride());                }                lock.unlock();            }            image.close();        }    }

  

  • 注意事项

1. 图像格式问题

经过在多台设备上测试,明明设置的预览数据格式是ImageFormat.YUV_420_888(4个Y对应一组UV,即平均1个像素占1.5个byte,12位),但是拿到的数据却都是YUV_422格式(2个Y对应一组UV,即平均1个像素占2个byte,16位),且UV的长度都少了一些(在Oneplus 5和Samsung Tab s3上长度都少了1),也就是:
(u.length == v.length) && (y.length / 2 > u.length) && (y.length / 2 ≈ u.length)
YUV_420_888数据的YUV关系应该是:
y.length / 4 == u.length == v.length
且系统API中android.graphics.ImageFormat类的getBitsPerPixel方法可说明上述Y、U、V数据比例不对的问题,内容如下:

public static int getBitsPerPixel(int format) {        switch (format) {            ...            case YUV_420_888:                return 12;            case YUV_422_888:                return 16;            ...        }        return -1;    }

以及android.media.ImageUtils类的imageCopy(Image src, Image dst)函数中有这么一段注释说明的确可能会有部分像素丢失:

public static void imageCopy(Image src, Image dst) {                ...                for (int row = 0; row < effectivePlaneSize.getHeight(); row++) {                    if (row == effectivePlaneSize.getHeight() - 1) {                        // Special case for NV21 backed YUV420_888: need handle the last row                        // carefully to avoid memory corruption. Check if we have enough bytes to                        // copy.                        int remainingBytes = srcBuffer.remaining() - srcOffset;                        if (srcByteCount > remainingBytes) {                            srcByteCount = remainingBytes;                        }                    }                    directByteBufferCopy(srcBuffer, srcOffset, dstBuffer, dstOffset, srcByteCount);                    srcOffset += srcRowStride;                    dstOffset += dstRowStride;                }                ...    }

 

2. 图像宽度不一定为stride(步长)

在有些设备上,回传的图像的rowStride不一定为previewSize.getWidth(),比如在OPPO K3手机上,选择的分辨率为1520x760,但是回传的图像数据的rowStride却是1536,且总数据少了16个像素(Y少了16,U和V分别少了8)

3. 当心数组越界

上述说到,Camera2设置的预览数据格式是ImageFormat.YUV_420_888时,回传的Y,U,V的关系一般是
(u.length == v.length) && (y.length / 2 > u.length) && (y.length / 2 ≈ u.length)
UV是有部分缺失的,因此我们在进行数组操作时需要注意越界问题,示例如下:

/**     * 将Y:U:V == 4:2:2的数据转换为nv21     *     * @param y      Y 数据     * @param u      U 数据     * @param v      V 数据     * @param nv21   生成的nv21,需要预先分配内存     * @param stride 步长     * @param height 图像高度     */    public static void yuv422ToYuv420sp(byte[] y, byte[] u, byte[] v, byte[] nv21, int stride, int height) {        System.arraycopy(y, 0, nv21, 0, y.length);        // 注意,若length值为 y.length * 3 / 2 会有数组越界的风险,需使用真实数据长度计算        int length = y.length + u.length / 2 + v.length / 2;        int uIndex = 0, vIndex = 0;        for (int i = stride * height; i < length; i += 2) {            nv21[i] = v[vIndex];            nv21[i + 1] = u[uIndex];            vIndex += 2;            uIndex += 2;        }    }

 

4. 避免频繁创建对象

若选择的图像格式是ImageFormat.YUV_420_888,那么相机回传的Image数据包将含3个plane,分别代表Y,U,V,但是一般情况下我们可能需要的是其组合的结果,如NV21I420等。由于Java的gc会影响性能,在从plane中获取Y、U、V数据和Y、U、V转换为其他数据的过程中,我们需要注意对象的创建频率,我们可以创建一次对象重复使用。不仅是Y,U,V这三个对象,组合的对象,如NV21,也可以用同样的方式处理,但若有将 NV21传出当前线程,用于异步处理的操作,则需要做深拷贝,避免异步处理时引用数据被修改

四、示例代码

  • 示例代码
  • demo功能
    • 演示Camera2的使用
    • 获取预览帧数据并隔一段时间将原始画面和处理过的画面显示到UI上
    • 将预览的YUV数据转换为NV21,再转换为Bitmap并显示到控件上,同时也将该Bitmap转换为相机预览效果的Bitmap显示到控件上,便于了解原始数据和预览画面的关系
  • 运行效果
     
    效果图

最后,推荐给大家一个比较好用的开源安卓人脸识别sdk:

转载于:https://www.cnblogs.com/feishixin123/p/11156256.html

你可能感兴趣的文章
kettle变量(param命名参数)
查看>>
EXCEL使用技巧
查看>>
HDU 2586 How far away ?【LCA】
查看>>
新安装数据库sqlserver2008r2,使用javaweb连接不上问题处理
查看>>
数据结构学习方法
查看>>
地大信工成果快报
查看>>
win10 php7+apache2.4的配置以及遇到的问题及解决
查看>>
透明明兼容
查看>>
大三了,计算机专业学生的困惑。 [转]
查看>>
tinyfox for linux 独立版 fox.sh
查看>>
Codeforces 1045. A. Last chance(网络流 + 线段树优化建边)
查看>>
hdu 1233 最小生成树
查看>>
js作用域与作用域链
查看>>
javascript 反斜杠\
查看>>
iOS linker command failed with exit code 1 (use -v to see invocation)多种解决方案汇总
查看>>
Android学习第十五天----notification应用
查看>>
【GOF23设计模式】迭代器模式
查看>>
递归锁
查看>>
Unix_03_文件系统介绍_2
查看>>
向前插入迭代器
查看>>