相机本质上就是一个"光子计数器"——镜头收集光子,传感器统计光子,ISP 处理光子统计结果,最终形成一张你看到的照片。
这一章不讲代码,不讲 HAL,只讲一件事:光是怎么变成数字图像的。
你可能觉得这些是"摄影知识",和做 Camera 驱动开发无关。但恰恰相反——你后面学到的每一个 HAL 概念(AE 算法、ISP 管线、Bayer 处理、YUV 格式),根都在这一章。不理解曝光三角,你就无法理解 AE 算法在干什么。不理解 Bayer 排列,你就看不懂 ISP 的 Demosaic 模块在做什么。
所以,这章是整门课程的地基。

光子 (来自被摄物体)
│
▼
① 镜头 ———— 聚焦光线,光圈控制进光量
│
▼
② 快门 ———— 控制光线照射传感器的时间
│
▼
③ 传感器 —— 把光子转换成电子(光电效应)
│
▼
④ ADC ———— 把模拟电荷量转换成数字值
│
▼
⑤ 数字图像 — RAW 数据(还没变成你看到的"照片")
│
▼
⑥ ISP ———— Demosaic → 颜色校正 → Gamma → 锐化 → YUV
│
▼
⑦ 编码 ———— JPEG / H.264(你最终看到的照片/视频)
这就是整门课程要讲的事情的全貌。每一章都会落在这个链条上的某个环节。
💡 一个过来人的视角
做了几年 Camera 之后你会发现,大多数"疑难杂症"的根因,都出在这条链的前半段——传感器、曝光、Bayer。图像偏色了?先查 AWB,但根因可能是 sensor 的 R/G/B 响应不均衡。帧率不稳?先看 AE,但根因可能是曝光时间太长。
底层概念理解得越深,上层问题定位得越快。
曝光是相机成像的第一个核心问题:控制多少光落到传感器上。
这由三个参数共同决定——光圈、快门、ISO。它们构成了一个三角形,制约着最终图像的亮度、清晰度和噪点。
光圈是镜头中的一个可调节孔径,控制进光量:
F-number = 焦距 / 孔径直径
───────────────
│ │
F 值越小 F 值越大
孔径越大 孔径越小
进光越多 进光越少
景深越浅 景深越深
| F 值 | 进光量(相对值) | 典型场景 |
|---|---|---|
| F/1.4 | 16x | 暗光、人像(背景虚化) |
| F/2.0 | 8x | 室内 |
| F/4.0 | 2x | 户外日常 |
| F/5.6 | 1x(基准) | 风景(全清晰) |
💡 手机的特殊之处
手机镜头的光圈是固定的——因为可变光圈机构太大,塞不进手机模组。所以你的
CameraCharacteristics.LENS_INFO_AVAILABLE_APERTURES通常只返回一个值。这意味着手机上的 AE 算法只能调快门和 ISO,不能调光圈。这是一条重要的约束——HAL 层的 AE 策略必须知道这一点。
光圈与手机相机开发的关系:
// Camera2 API:读取光圈值
float[] apertures = cameraCharacteristics.get(
CameraCharacteristics.LENS_INFO_AVAILABLE_APERTURES);
// 手机通常返回: [2.0] — 只有 F/2.0,固定光圈
// 如果数组长度 > 1,说明有可变光圈(如三星、华为部分旗舰)
快门决定传感器感光的时间长度。但手机相机没有机械快门——所谓"快门速度",实际上就是传感器曝光时间。
快门速度 曝光量 运动模糊
1/1000s 少 冻结运动
1/250s 中 日常手持
1/60s 多 安全快门下限
1/30s 更多 容易糊
1/10s 大量 必须三脚架
卷帘快门效应——手机特有的问题。
手机传感器用的是卷帘快门(Rolling Shutter),不是全局快门(Global Shutter)。
全局快门:
┌──────────────────┐
│ 整帧同时曝光 │ ← 所有行同时开始,同时结束
└──────────────────┘
时间 →
卷帘快门(手机):
┌──────────────────┐
│ 第 1 行 ████████│ ← 每行依次开始、依次结束
│ 第 2 行 ████████│
│ 第 3 行 ████████│
│ ... │
└──────────────────┘
时间 →
每行之间有微小的时间差
为什么卷帘快门重要?
因为卷帘快门导致果冻效应(Jello Effect)——拍摄快速移动的物体时,物体会倾斜变形。
实际物体(竖直线条) 卷帘快门拍摄结果
│ ╲
│ ╲
│ ╲
静止拍摄 快速移动时
→ 正常 → 线条倾斜
💡 工程影响
卷帘快门不是 bug,它是手机 CMOS 传感器的固有特性。但它在两个场景下会成为问题:
- HDR 多帧合成:连续几帧的卷帘位置不同,融合时会产生"鬼影"
- EIS 电子防抖:需要同时用陀螺仪数据 + 卷帘位置来做稳像(第 15 章会讲)
所以当你看到 ISP 调试中有人讨论"rolling shutter correction",他们就是在处理这个。
曝光时间和帧率的直接关系——这一点做相机 HAL 的人必须刻在脑子里:
如果 exposureTime = 33ms
那么最大帧率 = 1000ms / 33ms ≈ 30fps
如果 exposureTime = 66ms
那么最大帧率 = 1000ms / 66ms ≈ 15fps
为什么?
因为每帧的曝光时间不能超过帧间隔——传感器需要时间曝光。
如果你设了 33ms 曝光,30fps 的 33ms 帧间隔就刚好用满。
如果你要 60fps(16ms 帧间隔),曝光时间就必须 <= 16ms。
// Camera2 API:手动控制曝光
CaptureRequest.Builder builder =
cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_MANUAL);
// 必须先关掉自动曝光,手动值才会生效
builder.set(CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_OFF);
// 曝光时间:33ms = 33000000ns → 约 30fps
builder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, 33_000_000L);
// ISO 感光度
builder.set(CaptureRequest.SENSOR_SENSITIVITY, 200);
ISO 的本质是信号放大倍数。
传感器输出信号 → 放大器 → ADC → 数字值
↑
ISO 控制放大倍数
| ISO | 信号放大 | 副作用 | 使用场景 |
|---|---|---|---|
| 100 | 1x | 无噪点 | 白天户外 |
| 400 | 4x | 轻微噪点 | 室内 |
| 1600 | 16x | 明显噪点 | 暗光 |
| 6400 | 64x | 严重噪点 | 极暗 |
💡 ISO 不是"感光度"
严格来说,ISO 不改变传感器的物理感光能力。它只是把已经收集到的信号放大。问题在于:噪声也被一起放大了。
传感器的原始信号包含两部分:
- 有效信号(光子转换的电子)→ 你想要的
- 噪声信号(热噪声、读出噪声)→ 你不想要的
ISO 放大倍数 = N,信号放大 N 倍,噪声也放大 N 倍。信噪比(SNR)不变。
所以"高 ISO 噪点多"的本质是:信号不够,放大来凑,但噪声也跟着被凑进来了。
三个参数互相制约:
亮度 ⬆
▲
/|\
/ | \
光圈(F) 快门(t) ISO
↓ ↓ ↓
景深浅 运动模糊 噪点多
AE 算法在做什么?
AE(Auto Exposure)算法就是在每一帧里,找到一个最优的光圈(如果有)、快门、ISO 组合,使得:
实际手机只有快门和 ISO 两个自由度(光圈固定),AE 就是在这两者之间做权衡。
曝光控制的是"有多少光进来"。传感器要做的,是把这些光转换成数字信号。
复位 → 曝光 → 读出 → ADC → 输出 RAW
↑ │
└────────────────────────┘
下一帧循环
每帧的完整过程:
① 复位:清空像素中的残留电荷
─ 耗时极短(~μs 级别)
② 曝光:光子进入像素,产生光电子
─ 这就是 exposureTime(几 ms 到几十 ms)
─ 曝光长 → 电荷多 → 信号强
─ 曝光过长 → 过曝(像素饱和,白成一片)
③ 读出:逐行读取电荷值
─ 卷帘快门模式下,读出一行开始下一行的曝光
─ 读出速度决定了最大帧率
④ ADC:模拟电荷值 → 数字 RAW 值
─ 10bit ADC → 0~1023
─ 12bit ADC → 0~4095
─ 14bit ADC → 0~16383
做 Camera HAL 的人,必须读得懂 sensor datasheet 上的这几个参数:
| 参数 | 含义 | 为什么重要 |
|---|---|---|
| 像素尺寸 | 单个像素的物理边长(μm) | 越大感光越好,越暗的场景越有优势 |
| 分辨率 | 像素总数(MP) | 决定了 RAW 数据的量,影响 ISP 带宽 |
| 帧率 | 每秒最多能出多少帧 | 决定了预览和录像的上限 |
| 位深 | ADC 的精度(10/12/14bit) | 位深越大,动态范围越大,但数据量也越大 |
| 动态范围 | 最亮和最暗的比值(dB) | 决定了高光和阴影细节的保留能力 |
💡 一个经典的权衡:像素尺寸 vs 分辨率
同样大小的传感器(比如 1/1.28"),有两种设计方案:
- 方案 A(大像素):12MP,每个像素 1.4μm → 每个像素感光面积大 → 暗光好
- 方案 B(高像素):48MP,每个像素 0.8μm → 分辨率高 → 白天细节多
方案 B 通常用 像素合并(binning) 技术——4 个 0.8μm 像素合并成 1 个 1.6μm 像素来用。白天用 48MP,暗光用 12MP(第 28 章的 Remosaic 会详细讲)。
这就是为什么"同样 48MP,有的暗光好有的差"——真正决定暗光性能的,是 单像素尺寸 和 合并后的等效像素尺寸。
传感器经过 ADC 后输出的就是 RAW 数据。它是"裸"的——没有经过任何 ISP 处理。
RAW 的本质:
每个 pixel 的 RAW 值 = 光子数 × 转换增益 × ISO 放大倍数 + 黑电平偏移
然后 ADC 把这个模拟值量化成数字:
10bit RAW: 0~1023
12bit RAW: 0~4095
RAW 的两个重要特性:
① 线性:RAW 值与光强成正比
── 1000lux → RAW值 800
── 500lux → RAW值 400(一半的光,一半的值)
── 这是因为光子→电子的转换是线性的
── 但人眼感知不是线性的(Gamma 校正就在做这个)
② 未处理:保留了传感器上的所有信息
── 包括噪声、坏点、暗角
── 这些需要 ISP 后续处理(第 15 章)
// Camera2 API:检查是否支持 RAW 输出
StreamConfigurationMap map = characteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
int[] formats = map.getOutputFormats();
boolean hasRaw = Arrays.asList(formats).contains(ImageFormat.RAW_SENSOR);
// 如果 hasRaw == true,说明这台设备支持 RAW 输出
// 大多数设备的 CameraCharacteristics 会包含 RAW_SENSOR
// 因为 CTS 测试要求支持 RAW

单个像素只记录"有多少光子进来了",不记录"这是什么颜色的光子"。要得到彩色图像,必须在传感器表面覆盖一层彩色滤光片阵列(CFA, Color Filter Array)。
最主流的设计是 Bayer 排列。
Bayer 排列的特点是:每个像素只感光一种颜色。
一个 4×4 的 Bayer 区域:
R Gr R Gr
Gb B Gb B
R Gr R Gr
Gb B Gb B
R = Red pixel
Gr = Green pixel(在 Red 行)
Gb = Green pixel(在 Blue 行)
B = Blue pixel
为什么绿色像素是红/蓝色的两倍?
因为人眼对 550nm 左右的绿光最敏感。多放绿色像素,是为了让图像的亮度分辨率更高。
💡 Gr 和 Gb 为什么分开命名?
虽然 Gr 和 Gb 都是绿色像素,但它们所处的"行"不同——Gr 和 R 在同一行,Gb 和 B 在同一行。由于传感器制造工艺的差异,Gr 和 Gb 的光电响应可能略有不同。
这种差异会在图像中表现为绿色固定模式噪声——在均匀光照下,Gr 行和 Gb 行的亮度稍有差异,肉眼看起来就是"横条纹"。
ISP 的 LSC(Lens Shading Correction)模块会做 Gr/Gb 平衡校正。
Bayer 的每个像素只有一种颜色。要得到完整的 RGB 三通道图像,必须插值。这个过程叫 Demosaic(去马赛克)。
以一个 R 像素位置为例,它缺 G 和 B 值:
原始 Bayer(以 R₅ 位置为中心):
G₂ ? G₄
? R₅ ?
G₇ ? G₉
R₅ 位置缺 G 值:
G₅ = (G₂ + G₄ + G₇ + G₉) / 4
R₅ 位置缺 B 值:
B₅ = (B₁ + B₃ + B₇ + B₉) / 4 (从相邻 B 像素取)
这是最简单的平均插值。但实际 ISP 中的 Demosaic 要复杂得多:
简单平均的问题:
┌──────────────────────┐
│ 方向不明,边缘模糊 │ → 斜线会变成锯齿
└──────────────────────┘
ISP 实际用的方法(以高通 IFE/BPS 为例):
① 检测边缘方向(水平、垂直、对角线)
② 沿着边缘方向插值(不跨边缘)
③ 伪彩色抑制(避免彩色边缘出现假色)
💡 Demosaic 是 ISP 中计算量最大的模块之一
因为它对每个像素都要做方向检测 + 插值。一个 12MP 的图像,1200 万个像素,每个像素做一次,这就是 Demosaic 为什么用硬件实现(IFE/BPS),而不是软件。
Bayer RGGB 是主流,但不是唯一:
| 类型 | 排列 | 特点 | 代表 |
|---|---|---|---|
| Bayer RGGB | R / Gr / Gb / B | 经典方案,几乎所有传感器 | 大多数手机 |
| Quadra CFA | 2×2 同色簇 | 支持 binning + remosaic | 48MP+ 传感器 |
| RYYB | R / Y / Y / B | Y(黄色)比 G 感光更宽→进光量+40% | 华为 P 系列 |
| X-Trans | 6×6 不规则 | 减少摩尔纹 | 富士相机 |
RYYB 的代价
用 Y 代替 G 确实进光量更大,但颜色精度会下降——因为 Y 覆盖了 R+G 的波段,导致 R 和 G 通道之间的分离度变差。这就是为什么 RYYB 传感器的手机有时偏色。这是物理限制,算法只能部分补偿。

这条流水线在 ISP 硬件中完成,贯穿 IFE → IPE(预览主路径跳过 BPS;BPS 用于拍照/快照的 demosaic,详见第 15 章),这里只看数据形态的变化:
传感器输出 → RAW(10/12/14bit,线性,Bayer)
│
预处理(黑电平校正、LSC、坏点校正)
│
▼
校正后 RAW
│
Demosaic(Bayer → RGB)
│
▼
RGB 图像
│
ISP 处理(AWB、CCM、Gamma、去噪、锐化)
│
▼
YUV 图像
│
编码(JPEG / H.264)
│
▼
照片(.jpg) / 视频(.mp4)
以 12MP(4000×3000)传感器为例:
| 阶段 | 格式 | 每帧大小 | 30fps 带宽 |
|---|---|---|---|
| RAW 输出 | RAW10 (10bit) | ~15MB | 450MB/s |
| ISP 内部 | RGB (8bit × 3) | ~36MB | 1.08GB/s |
| ISP 输出 | YUV420 (NV12) | ~18MB | 540MB/s |
| 编码后 | JPEG | ~3MB | 90MB/s |
💡 ISP 为什么用硬件实现?
看上表。ISP 内部处理 RGB 时每帧 36MB,30fps 就是 1GB/s 以上的带宽。这是在手机 SoC 上,不是服务器。如果用 CPU 做,功耗会大到手机发烫,帧率可能只有个位数。
所以 Qualcomm Spectra ISP 是硬件加速——IFE、BPS、IPE 都是硬件模块。CamX 的 HWL 层(
camx/src/hwl/)就是封装这些硬件模块的寄存器接口。只有 EIS、SWMF 等算法因为太复杂、更新太频繁,才放在 CPU 上做 SW Node(
camx/src/swl/)。
搞 Camera 的,最终都是在和这几个问题打交道:
根因:卷帘快门逐行曝光,行与行之间有延时。
拍摄一根快速摆动的杆:
实际 拍出来
时间→ │ ╲
第 1 行曝光 → 杆在左边 │ ╲
第 2 行曝光 → 杆往右移 │ ╲
第 3 行曝光 → 杆更右了 │ ╲
怎么办? ——EIS 算法把陀螺仪数据和卷帘位置结合起来做修正。但最好的办法是物理上的——用全局快门(手机做不了)。
根因:镜头边缘的进光量比中心少。
中心区域:光垂直进入 → 进光量 100%
边缘区域:光斜着进入 → 进光量 ~50%(甚至更少)
怎么办? ——LSC(Lens Shading Correction)。为每个像素位置存一个增益值,边缘的增益大于中心。LSC 表存在 sensor 的 OTP 里(第 20 章)。
根因:拍摄规则纹理(如格子衬衫)时,Bayer 排列的频率和纹理频率产生"差拍"。
怎么办? ——光学低通滤波器(OPLF,在镜头中)或 ISP 内的去摩尔纹算法。
噪声来源分类:
| 来源 | 性质 | 怎么抑制 |
|---|---|---|
| 光子噪声(Shot Noise) | 光的量子特性,必然存在 | 增加进光量(无法根除) |
| 读出噪声(Read Noise) | 电路噪声 | 降低 sensor 温度 |
| 热噪声(Dark Current) | 温度越高越严重 | 降低曝光时间 |
| ISO 放大噪声 | 信号放大时噪声同比例放大 | 尽量用低 ISO |
💡 做 Camera 越久,越觉得"信噪比"是所有问题的核心
暗光降噪(MFNR)做的就是在"信号不够"的条件下,通过多帧融合来提升 SNR。HDR 做的也是在"动态范围不够"的条件下,通过多帧不同曝光来扩展 DR。
所有计算摄影技术(多帧降噪、HDR、超级夜景),本质上都是在和"物理限制"做斗争——传感器就那么小,光子就那么少,怎么利用多帧、算法来补?
一条主线:光子 → 像素 → 照片
光子进入镜头 → 光圈控制进光量 → 快门控制曝光时间
→ 传感器光电转换 → ADC 量化 → RAW 数据(Bayer)
→ ISP 处理(Demosaic → AWB → CCM → Gamma → 去噪)
→ YUV 图像 → JPEG/H.264 编码
学完这章之后,你应该能回答这几个问题:
最大 FPS = 1000ms / exposureTime执行 adb shell dumpsys media.camera,找到后摄的:
SENSOR_INFO_EXPOSURE_TIME_RANGE — 最短/最长曝光时间LENS_INFO_AVAILABLE_APERTURES — 光圈值(手机通常只有一个)根据最短曝光时间,计算理论最大帧率。
打开相机对着快速移动的物体(如风扇)拍一段视频——观察是否有果冻效应。
下章预告:第 2 章「Android Camera 系统全景」——从 App 到硬件,5 层架构。
这章我们看了"光走的路",下章看"请求走的路"——你按下快门那一瞬间,数据包怎样穿越 App → Framework → HAL → Kernel → Hardware,从 App 一层层走到 ISP。