24. 多摄像头协同:多CameraDevice管理、多路预览同步、多摄像头数据融合
多摄像头协同,说白了就是让手机上的两个甚至三个摄像头同时工作。你想想看,现在的旗舰机哪个不是三摄四摄?但让它们各自独立拍照容易,让它们协同工作,嗯,这里面的坑可不少。
我记得第一次做双摄项目时,天真地以为开两个CameraDevice就行。结果一跑起来,预览画面一个快一个慢,颜色还不一样。当时我就意识到——多摄像头管理,绝不是简单的「开两个Camera」那么简单。
24.1 多CameraDevice的管理策略
Android从API 21开始支持同时打开多个摄像头。但有个限制:同一时间只能打开一个CameraDevice实例。想同时用广角和长焦?你得分别创建两个CameraDevice。
我个人习惯用CameraManager来管理所有摄像头。先获取所有摄像头ID,再根据需求筛选。
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
String[] cameraIds = manager.getCameraIdList();
// 筛选广角和长焦
String wideId = null;
String teleId = null;
for (String id : cameraIds) {
CameraCharacteristics chars = manager.getCameraCharacteristics(id);
Integer facing = chars.get(CameraCharacteristics.LENS_FACING);
if (facing != CameraCharacteristics.LENS_FACING_BACK) continue;
// 通过焦距判断
float[] focalLengths = chars.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS);
if (focalLengths != null && focalLengths.length > 0) {
if (focalLengths[0] < 2.0f) wideId = id;
else teleId = id;
}
}
核心要点:每个CameraDevice独立管理自己的生命周期。打开、关闭、异常处理都要各自维护。我建议用一个Map<String, CameraDevice>来统一管理,key就是摄像头ID。
24.2 多路预览同步的挑战
多路预览同步,这是最头疼的部分。两个摄像头各自跑各自的帧率,画面不同步是常态。
为什么会这样?因为每个CameraDevice有自己的帧率控制、曝光时间、增益调整。哪怕你设置同样的fps,实际输出时间戳也可能差几毫秒到几十毫秒。
我在项目中遇到过这样的问题:广角和长焦的画面叠加显示时,边缘总是有重影。后来发现是两路画面的时间戳差了30ms。解决办法是——以主摄像头的时间戳为基准,副摄像头做帧对齐。
// 帧对齐示例
class FrameSynchronizer {
private long baseTimestamp = -1;
private final Queue<Image> syncQueue = new LinkedList<>();
public void onFrameAvailable(Image image, long timestamp) {
if (baseTimestamp == -1) {
baseTimestamp = timestamp;
syncQueue.offer(image);
return;
}
// 计算时间差
long diff = timestamp - baseTimestamp;
if (Math.abs(diff) < 16_000_000) { // 16ms以内算同步
processSyncedFrames(image);
} else {
// 丢弃或等待
image.close();
}
}
}
小技巧:如果硬件支持,可以用CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES中的SYNC能力。但说实话,大部分中低端设备不支持硬件同步,还是得靠软件对齐。
24.3 多摄像头数据融合
数据融合,这是多摄像头的终极目标。常见的融合方式有三种:
| 融合方式 | 原理 | 适用场景 |
|---|---|---|
| 画面拼接 | 将广角和长焦的画面拼成一张全景图 | 全景拍照、超广角模式 |
| 深度融合 | 利用两个摄像头的视差计算深度信息 | 人像模式、AR测距 |
| 多帧合成 | 同时采集多帧,合成高动态范围或低噪点图像 | 夜景模式、HDR |
我个人最常用的是深度融合。原理很简单:两个摄像头位置不同,看到的画面有视差。通过匹配两幅图像中的特征点,就能算出每个像素的深度。
// 深度融合伪代码
public DepthMap computeDepth(Image wideImage, Image teleImage) {
// 1. 特征点提取
List<KeyPoint> keyPoints1 = extractORBFeatures(wideImage);
List<KeyPoint> keyPoints2 = extractORBFeatures(teleImage);
// 2. 特征点匹配
List<Match> matches = matchFeatures(keyPoints1, keyPoints2);
// 3. 计算基础矩阵
Mat fundamentalMat = findFundamentalMat(matches);
// 4. 三角测量得到深度
DepthMap depth = triangulate(matches, fundamentalMat);
return depth;
}
注意:深度融合对摄像头标定要求极高。两个摄像头的内参、外参(相对位置)必须精确。我曾经因为标定参数差了0.5毫米,导致深度图全是噪点。建议用OpenCV的calibrateCamera做标定,或者直接用手机厂商提供的标定文件。
24.4 实战中的避坑指南
做多摄像头项目,有几个坑我踩过,分享给你:
- 内存爆炸:两路预览同时开,每路都是1080p,内存占用直接翻倍。建议用SurfaceView而不是TextureView,减少内存拷贝。
- 发热严重:两个ISP同时工作,发热量不是1+1=2,而是1+1=3。我建议在非必要场景下,副摄像头用低分辨率预览。
- 权限问题:Android 10以上,同时打开两个摄像头需要额外的CAMERA权限声明。别忘了在manifest里加。
- 兼容性:不是所有设备都支持同时打开两个后置摄像头。建议先检查CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES中的LOGICAL_MULTI_CAMERA能力。
我曾经在一个项目里,因为没检查兼容性,结果在小米某款手机上直接崩溃。后来加了个运行时检测,才解决问题。
24.5 多摄像头协同的架构设计
最后说说架构。我推荐用生产者-消费者模式来管理多路数据流。
这个架构的好处是:每个摄像头独立采集,帧同步器统一对齐,数据融合模块只处理对齐后的帧。各模块职责清晰,出了问题也好排查。
嗯,多摄像头协同这块内容确实不少。但只要你把CameraDevice管理、帧同步、数据融合这三个核心点吃透,大部分场景都能应对。我在实际项目中,这套架构已经稳定运行在好几款机型上了,效果还不错。