15、Camera 多帧处理:BurstCapture实现、Burst回调处理、多帧降噪原理、HDR+多帧合成流程
多帧处理,说白了就是让相机连续拍好几张照片,然后通过算法把它们合成一张更好的。这个技术现在几乎成了旗舰手机的标配。我个人觉得,理解多帧处理是吃透Camera Framework的关键一步。
你想想看,单帧拍照受限于传感器和光线,动态范围、噪点控制总有天花板。但多帧不一样,它用时间换质量。今天我们就来拆解一下,Android里BurstCapture是怎么实现的,回调怎么处理,以及多帧降噪和HDR+背后的原理。
15.1 BurstCapture 实现机制
BurstCapture,就是连拍。用户按住快门不松手,相机咔咔咔拍一堆。在Framework层,这其实是通过CaptureRequest的CONTROL_AE_MODE和CONTROL_AF_MODE配合CaptureSession的setRepeatingRequest或captureBurst来实现的。
我记得在Android 5.0刚引入Camera2 API时,BurstCapture的实现还比较原始。那时候我们得手动管理一个请求队列。现在好多了,系统直接提供了captureBurst方法。
核心思路:一次性提交多个CaptureRequest,每个请求对应一帧。HAL层按顺序处理,返回对应的CaptureResult。
来看一个典型的BurstCapture调用代码:
// 构建连拍请求列表
List<CaptureRequest> burstRequests = new ArrayList<>();
for (int i = 0; i < 10; i++) {
CaptureRequest.Builder builder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
builder.addTarget(surface);
// 可以针对每一帧设置不同的参数,比如曝光补偿
if (i % 2 == 0) {
builder.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, 0);
} else {
builder.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, -2);
}
burstRequests.add(builder.build());
}
// 提交连拍
cameraCaptureSession.captureBurst(burstRequests, new CaptureCallback() {
@Override
public void onCaptureCompleted(CaptureSession session, CaptureRequest request, TotalCaptureResult result) {
// 每一帧完成时回调
Log.d("Burst", "Frame completed: " + result.getSequenceId());
}
}, handler);
这里有个细节:captureBurst会保证请求的顺序。HAL层必须按提交顺序返回结果。我在项目中遇到过一个问题,某些OEM的HAL实现不遵守这个顺序,导致合成时帧错乱。嗯,后来我们加了一个帧序号校验才解决。
15.2 Burst 回调处理
回调处理是BurstCapture里最容易踩坑的地方。为什么?因为回调频率太高了。你连拍10张,每张都有onCaptureStarted、onCaptureProgressed、onCaptureCompleted三个回调。如果处理不当,UI线程直接卡死。
我建议的做法是:
- 不要在回调里做耗时操作。比如写文件、做图像处理,统统扔到子线程。
- 使用队列缓冲。回调里只把数据塞进队列,后台线程慢慢消费。
- 注意SequenceId。每个Burst请求序列有唯一的SequenceId,可以用来判断当前属于哪一次连拍。
小技巧:如果你需要知道连拍什么时候全部结束,可以在最后一个请求的onCaptureCompleted里设置一个标志位。或者更优雅的方式:用TotalCaptureResult的getSequenceId()和getFrameNumber()来判断。
我曾经在调试一个连拍卡顿的问题时发现,某些设备在连拍过程中会丢失回调。后来查了HAL层的实现,原来是buffer queue满了,HAL直接丢弃了部分帧。解决方案是降低连拍速度,或者增大buffer池。
15.3 多帧降噪原理
多帧降噪,说白了就是拍多张照片,然后平均。但直接平均会糊掉,所以需要先对齐。
核心流程分三步:
- 图像对齐:用光流法或块匹配算法,找到每帧之间的偏移量,然后做仿射变换或透视变换,把多帧对齐到同一坐标系。
- 时域滤波:对齐后,对每个像素点做加权平均。运动区域的权重低,静止区域的权重高。
- 空域降噪:时域滤波后可能还有残留噪声,再用双边滤波或BM3D做一次空域降噪。
你想想看,为什么多帧降噪效果好?因为噪声是随机的,信号是确定的。多帧平均后,噪声的方差会降低到原来的1/N(N是帧数)。这就是多帧降噪的数学基础。
注意:多帧降噪对运动物体不友好。如果场景里有快速移动的物体,对齐会失败,结果就是鬼影。所以很多手机在夜景模式下会检测运动,如果运动太大就自动切回单帧。
我记得在Pixel 2的HDR+实现里,Google用了最多15帧做降噪。但实际效果取决于对齐算法的精度。对齐做得不好,15帧还不如3帧。
15.4 HDR+ 多帧合成流程
HDR+是Google在Pixel系列上推出的技术。它和普通HDR不一样。普通HDR是拍几张不同曝光的照片,然后合成。HDR+是拍多张相同曝光的照片(通常是欠曝),然后合成。
为什么用欠曝?因为欠曝可以保留高光细节,暗部噪声通过多帧降噪来消除。这样动态范围和信噪比都能提升。
HDR+的完整流程是这样的:
| 步骤 | 操作 | 说明 |
|---|---|---|
| 1 | Burst Capture | 连续拍8-15帧,全部用相同的欠曝参数 |
| 2 | 帧选择 | 剔除模糊、过暗、过亮的帧,选出质量最好的N帧 |
| 3 | 图像对齐 | 以第一帧为参考,用金字塔光流法对齐其他帧 |
| 4 | 多帧合成 | 对齐后做加权平均,权重由像素的亮度和运动程度决定 |
| 5 | 色调映射 | 把合成后的线性数据映射到sRGB空间,提升暗部、压缩高光 |
| 6 | 后处理 | 锐化、去马赛克、颜色校正等 |
这里最核心的是第4步——多帧合成。Google用的是基于方差的加权平均。每个像素的权重由它的局部方差决定。方差大的区域(边缘、纹理)权重高,方差小的区域(平坦区域)权重低。这样既能保留细节,又能有效降噪。
我曾经在项目中复现过HDR+的简化版。说实话,最难的不是算法本身,而是性能优化。多帧对齐和合成都是计算密集型操作,在手机上要做到毫秒级响应,需要大量的NEON优化和GPU加速。
关键点:HDR+的成功在于它把「多帧降噪」和「HDR合成」融合成了一个流程。先通过多帧降噪提升信噪比,再通过色调映射扩展动态范围。两者相辅相成,缺一不可。
嗯,这里要注意一点:HDR+的帧选择很重要。如果选到了模糊帧,合成结果会糊。Google的做法是计算每帧的拉普拉斯方差,方差越大说明越清晰。低于阈值的帧直接丢弃。
最后,我们来看一张HDR+多帧合成的流程图:
这张图把HDR+的六个步骤串起来了。从Burst Capture开始,到最终图像输出。每一步都有明确的输入输出。我个人觉得,理解了这个流程,你就掌握了现代手机相机多帧处理的核心。
避坑指南:我曾经在实现HDR+时,忽略了帧选择这一步,结果合成出来的照片总有几张是糊的。后来加上拉普拉斯方差检测,问题就解决了。记住:不是所有帧都值得合成,质量差的帧要果断丢弃。
好了,多帧处理的内容就讲到这里。BurstCapture的实现、回调处理、多帧降噪原理、HDR+合成流程,这些知识点在实际项目中都会用到。尤其是HDR+,它代表了目前手机相机多帧处理的最高水平。理解它,对你深入Camera Framework会有很大帮助。
公众号:蓝海资料掘金营,微信deep3321