曝光控制:让画面亮暗由你说了算
大家好,我是你们的Android摄像头开发课讲师。今天我们来聊聊曝光控制——说白了,就是怎么让相机拍出你想要的亮度。
我记得刚入行那会儿,总觉得曝光是相机自动搞定的事。直到有一次做扫码项目,在强光下二维码死活扫不出来,我才意识到:曝光控制,其实是摄像头开发里最容易被忽视、却又最关键的一环。
测光模式:相机怎么判断「亮不亮」?
相机要决定曝光,首先得知道场景有多亮。这个「知道」的过程,就是测光。
Android Camera2 API 提供了三种测光模式,我一个个说。
1. 矩阵测光(MeteringMode.MATRIX)
这是最常用的模式。相机把画面分成几十个区域,分别测光,然后加权平均。说白了,就是「大家投票决定曝光值」。
适合风景、日常拍照。我自己的习惯是,拍大场景时优先用矩阵测光,省心。
2. 中心权重测光(MeteringMode.CENTER_WEIGHTED)
画面中心区域权重更高,边缘区域权重低。适合主体在画面中央的场景,比如人像。
嗯,这里要注意:如果你的主体不在中央,用这个模式容易翻车。我曾经拍一张侧脸人像,背景太亮,结果人脸全黑了——就是因为用了中心权重测光。
3. 点测光(MeteringMode.SPOT)
只取画面中一个很小的点(通常1%-5%的区域)来测光。适合逆光、舞台、月亮等极端场景。
为什么会有这种模式?你想想看,拍月亮时如果还用矩阵测光,整个画面会被平均成灰蒙蒙一片,月亮根本拍不清楚。点测光只盯着月亮测,就能把月亮拍得又亮又清晰。
曝光补偿:手动微调亮度
自动测光再聪明,也有犯傻的时候。比如雪景,相机觉得太亮会自动压暗,结果拍出来雪是灰色的。这时候就需要曝光补偿。
曝光补偿的单位是EV(Exposure Value)。每+1EV,亮度翻倍;每-1EV,亮度减半。
| 场景 | 建议补偿值 | 原因 |
|---|---|---|
| 雪景/白色背景 | +1.0 ~ +2.0 EV | 相机容易过暗,需要提亮 |
| 黑色物体/夜景 | -0.5 ~ -1.0 EV | 相机容易过亮,需要压暗 |
| 逆光人像 | +0.7 ~ +1.3 EV | 人脸需要补光 |
在Camera2 API里,设置曝光补偿的代码很简单:
// 获取曝光补偿范围
Range<Integer> compensationRange = characteristics.get(
CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE);
int min = compensationRange.getLower();
int max = compensationRange.getUpper();
// 设置曝光补偿(单位:EV的1/8步进)
CaptureRequest.Builder builder = cameraDevice.createCaptureRequest(
CameraDevice.TEMPLATE_PREVIEW);
builder.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, 6); // +0.75 EV
AE Bracketing:多拍几张,总有一张对的
AE Bracketing,全称是自动曝光包围。说白了就是:按一次快门,相机自动拍多张不同曝光的照片。
为什么要这么做?
因为有些场景太复杂,一张照片根本搞不定。比如拍日落,天空很亮、地面很暗。你曝光调亮了,天空过曝;调暗了,地面死黑。这时候AE Bracketing就派上用场了。
典型的Bracketing设置:
- 张数:3张或5张
- 步进:±0.7 EV 或 ±1.0 EV
- 顺序:欠曝 → 正常 → 过曝
在Camera2 API里,实现Bracketing需要用到CaptureRequest的重复请求:
// 创建三个不同曝光的请求
List<CaptureRequest> bracketRequests = new ArrayList<>();
// 欠曝 -1.0 EV
CaptureRequest.Builder builder1 = cameraDevice.createCaptureRequest(
CameraDevice.TEMPLATE_STILL_CAPTURE);
builder1.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, -8);
bracketRequests.add(builder1.build());
// 正常曝光
CaptureRequest.Builder builder2 = cameraDevice.createCaptureRequest(
CameraDevice.TEMPLATE_STILL_CAPTURE);
builder2.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, 0);
bracketRequests.add(builder2.build());
// 过曝 +1.0 EV
CaptureRequest.Builder builder3 = cameraDevice.createCaptureRequest(
CameraDevice.TEMPLATE_STILL_CAPTURE);
builder3.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, 8);
bracketRequests.add(builder3.build());
// 发送Burst请求
captureSession.captureBurst(bracketRequests, captureCallback, null);
REQUEST_AVAILABLE_CAPABILITIES中是否包含BURST_CAPTURE。我曾经在低端设备上踩过坑,Burst请求发出去后只返回了一张照片。
HDR原理:把多张照片合成一张
AE Bracketing拍完多张照片后,下一步就是合成HDR。
HDR的全称是High Dynamic Range,高动态范围。它的核心思想很简单:
- 从欠曝照片里提取高光细节(天空、灯光等)
- 从过曝照片里提取暗部细节(阴影、地面等)
- 把两者合成到一张照片里
说白了,就是「取长补短」。
Android从4.0开始就内置了HDR模式。在Camera2 API里,开启HDR的方式有两种:
- 场景模式:
CONTROL_SCENE_MODE_HDR,系统自动处理Bracketing和合成 - 手动Bracketing + 第三方合成:自己拍多张,然后用OpenCV或RenderScript合成
第一种最简单,但可控性差。第二种灵活,但需要自己写合成算法。
我个人建议:如果只是做普通拍照App,用系统自带的HDR就够了。但如果你在做专业相机App,比如拍星空、拍产品,那手动Bracketing + 自定义合成才是王道。
findTransformECC函数可以搞定。
知识体系总览
下面这张图,把今天讲的内容串起来了:
从这张图可以看得很清楚:测光模式决定了「看哪里」,曝光补偿决定了「调多少」,AE Bracketing决定了「拍几张」,HDR决定了「怎么合」。四者环环相扣,缺一不可。
写在最后
曝光控制这块,说难不难,说简单也不简单。我见过很多开发者,花大量时间研究图像算法,却忽略了最基础的曝光控制。结果拍出来的照片,要么过曝、要么欠曝,算法再好也救不回来。
记住一句话:曝光是照片的骨架,算法只是血肉。骨架歪了,血肉再丰满也没用。
好了,今天的内容就到这里。下一章我们聊聊白平衡——为什么有时候拍出来的照片偏黄、偏蓝?怎么手动校正?到时候见。
公众号:蓝海资料掘金营,微信deep3321