Camera2是Google在Android 5.0后推出的一个全新的相机API,Camera2和Camera没有继承关系,是彻底从新设计的,且Camera2支持的功能也更加丰富,可是提供了更丰富的功能的同时也增长了使用的难度。Google的官方Demo:https://github.com/googlesamples/android-Camera2Basichtml
如下分别是使用Camera2和Camera打开相机进行预览并获取预览数据的流程图。android
能够看到,和Camera相比,Camera2的调用明显复杂得多,但同时也提供了更强大的功能:git
可是具体可否使用还要看设备的厂商有无实现。github
通常状况下,大多设备其实只支持ImageFormat.YUV_420_888
和ImageFormat.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位),且U
和V
的长度都少了一些(在Oneplus 5和Samsung Tab s3上长度都少了1),也就是:
(u.length == v.length) && (y.length / 2 > u.length) && (y.length / 2 ≈ u.length)
;
而YUV_420_888
数据的Y
、U
、V
关系应该是:
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)
函数中有这么一段注释说明的确可能会有部分像素丢失:ide
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)
;
U
和V
是有部分缺失的,所以咱们在进行数组操做时须要注意越界问题,示例以下:
/** * 将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
,可是通常状况下咱们可能须要的是其组合的结果,如NV21
、I420
等。因为Java的gc会影响性能,在从plane中获取Y、U、V
数据和Y、U、V
转换为其余数据的过程当中,咱们须要注意对象的建立频率,咱们能够建立一次对象重复使用。不只是Y
,U
,V
这三个对象,组合的对象,如NV21
,也能够用一样的方式处理,但如有将 NV21传出当前线程,用于异步处理的操做,则须要作深拷贝,避免异步处理时引用数据被修改
。
最后,推荐给你们一个比较好用的开源安卓人脸识别sdk:
https://ai.arcsoft.com.cn/ucenter/user/reg?utm_source=cnblogs&utm_medium=referral