camera2提供了获取Depth16深度图的api,不过并不是所有设备都支持获取深度图,可以通过以下的方式检测设备是否支持:
CameraManager manager = (CameraManager) getContext().getSystemService(Context.CAMERA_SERVICE);
//遍历设备上的摄像头
for (String cameraId : manager.getCameraIdList()) {
//获取每个摄像头的参数信息管理对象
characteristics = manager.getCameraCharacteristics(cameraId);
//获取摄像头参数信息
int[] capabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
//遍历参数信息
for (int capability : capabilities) {
if(capability ==CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT){
//说明此摄像头支持获取深度图
break;
}
}
//判断是否支持同时输出深度图和jpeg主图,false:支持 true:不支持
boolean isExclusive = characteristics.get(CameraCharacteristics.DEPTH_DEPTH_IS_EXCLUSIVE);
}
通过上面的判断若设备支持获取深度图,我们可以通过一下方式拿到一张深度图:
ImageReader mImageReaderDepth = ImageReader.newInstance(480,640,ImageFormat.DEPTH16, 1);
mImageReaderDepth.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);
//会在此回调方法里返回图片信息
private final ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
Image image = reader.acquireNextImage();
switch (image.getFormat()) {
case ImageFormat.JPEG:
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
byte[] bytes = new byte[buffer.capacity()];
buffer.get(bytes);
Bitmap bitmapImage = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
break;
case ImageFormat.DEPTH_JPEG:
break;
case ImageFormat.DEPTH16:
//此image对象就包含了图片信息
Bitmap bitmapDepth = convertToRGBBitmap(image);
break;
default:
break;
}
image.close();
}
};
到此,我们拿到了depth16格式的图片,通过查看官方文档ImageFormat.DEPTH16,我们能得知一下信息:
且深度值单位是:毫米。
到此,我们根据官方文档提供的方法可以得到每个像素点的深度值和置信度值,但遗憾的是这种格式的Image对象并不能像ImageFormat.JPEG格式的Image对象能直接生成Bitmap位图,而对于此种格式如何进行可视化操作,官方文档中也没有提及,可能官方也没想到会有这种需求吧。幸运的是在调研如何获取深度图时,还有一种方案是使用ARCore的api获取,在查看文档时捕捉到一个关键信息:即ImageFormat.DEPTH16的深度信息值为0~8191mm
,后来发现8191是2进制13位的值,也就是文档中提到的16位数据中,高3位是置信度值,低13位是深度值。
既然如此深度值有了范围区间,我们就可以把深度值映射到rgb值,从而得到一张图片了,以下是转换操作:
private Bitmap convertToRGBBitmap(Image image) {
//得到对应宽高
int width = image.getWidth();
int height = image.getHeight();
//读出所有深度信息,因为每个点的深度信息都是用16位表示的,所以用short[]接收
ShortBuffer shortDepthBuffer = image.getPlanes()[0].getBuffer().asShortBuffer();
short[] shorts = new short[shortDepthBuffer.capacity()];
shortDepthBuffer.get(shorts);
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
int res[] = new int[shorts.length];
for (int i = 0; i < shorts.length; i++) {
//根据官方文档,去低13位得到深度信息
short depth = (short) (shorts[i] & 0x1FFF);
if (depth == 0) { //若深度信息为0,直接赋值黑色色值(计算优化)
res[i] = 0xFF000000;
} else {
//depth是上一步计算出的深度值,所以将深度值映射到颜色值的公式是:
// int color = 深度值 ÷ 深度值区间上限(8191) x 255
// int color = depth / 8191f * 255
// 优化一 :浮点数运算开销大,优化为整数运算:int color = depth * 255 / 8191
// 优化二:既然这样干脆直接使用位运算 depth * 255 = depth << 8 depth / 8191 = depth >> 13
int color = depth >> 5;
//将 r、g、b通道设为相同的值(0~255),得到一张灰度图
int grey = Color.argb(255, color, color, color);
res[i] = grey;
}
}
bitmap.setPixels(res, 0, width, 0, 0, width, height);
return bitmap;
}
最后附一张转换得到的深度图: