← 返回课程

成像基础

Camera全栈开发(Qcom Camx) 第 3 / 30 节

第 1 章:相机成像基础


本章导读

相机本质上就是一个"光子计数器"——镜头收集光子,传感器统计光子,ISP 处理光子统计结果,最终形成一张你看到的照片。

这一章不讲代码,不讲 HAL,只讲一件事:光是怎么变成数字图像的

你可能觉得这些是"摄影知识",和做 Camera 驱动开发无关。但恰恰相反——你后面学到的每一个 HAL 概念(AE 算法、ISP 管线、Bayer 处理、YUV 格式),根都在这一章。不理解曝光三角,你就无法理解 AE 算法在干什么。不理解 Bayer 排列,你就看不懂 ISP 的 Demosaic 模块在做什么。

所以,这章是整门课程的地基。


1.1 从光子到像素:一条线串起来

光走的路,比你想象的长:

光子 (来自被摄物体)
  │
  ▼
① 镜头 ———— 聚焦光线,光圈控制进光量
  │
  ▼
② 快门 ———— 控制光线照射传感器的时间
  │
  ▼
③ 传感器 —— 把光子转换成电子(光电效应)
  │
  ▼
④ ADC ———— 把模拟电荷量转换成数字值
  │
  ▼
⑤ 数字图像 — RAW 数据(还没变成你看到的"照片")
  │
  ▼
⑥ ISP ———— Demosaic → 颜色校正 → Gamma → 锐化 → YUV
  │
  ▼
⑦ 编码 ———— JPEG / H.264(你最终看到的照片/视频)

这就是整门课程要讲的事情的全貌。每一章都会落在这个链条上的某个环节。

💡 一个过来人的视角

做了几年 Camera 之后你会发现,大多数"疑难杂症"的根因,都出在这条链的前半段——传感器、曝光、Bayer。图像偏色了?先查 AWB,但根因可能是 sensor 的 R/G/B 响应不均衡。帧率不稳?先看 AE,但根因可能是曝光时间太长。

底层概念理解得越深,上层问题定位得越快。


1.2 曝光三角

曝光是相机成像的第一个核心问题:控制多少光落到传感器上

这由三个参数共同决定——光圈、快门、ISO。它们构成了一个三角形,制约着最终图像的亮度、清晰度和噪点。

1.2.1 光圈

光圈是镜头中的一个可调节孔径,控制进光量:

             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.2.2 快门

快门决定传感器感光的时间长度。但手机相机没有机械快门——所谓"快门速度",实际上就是传感器曝光时间

快门速度         曝光量         运动模糊
1/1000s         少             冻结运动
1/250s          中             日常手持
1/60s           多             安全快门下限
1/30s           更多           容易糊
1/10s           大量           必须三脚架

卷帘快门效应——手机特有的问题

手机传感器用的是卷帘快门(Rolling Shutter),不是全局快门(Global Shutter)。

全局快门:
  ┌──────────────────┐
  │ 整帧同时曝光      │  ← 所有行同时开始,同时结束
  └──────────────────┘
  时间 →

卷帘快门(手机):
  ┌──────────────────┐
  │ 第 1 行  ████████│  ← 每行依次开始、依次结束
  │ 第 2 行   ████████│
  │ 第 3 行    ████████│
  │ ...               │
  └──────────────────┘
  时间 →
  每行之间有微小的时间差

为什么卷帘快门重要?

因为卷帘快门导致果冻效应(Jello Effect)——拍摄快速移动的物体时,物体会倾斜变形。

实际物体(竖直线条)      卷帘快门拍摄结果
     │                    ╲
     │                     ╲
     │                      ╲
  静止拍摄                 快速移动时
  → 正常                   → 线条倾斜

💡 工程影响

卷帘快门不是 bug,它是手机 CMOS 传感器的固有特性。但它在两个场景下会成为问题:

  1. HDR 多帧合成:连续几帧的卷帘位置不同,融合时会产生"鬼影"
  2. 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);

1.2.3 ISO

ISO 的本质是信号放大倍数

传感器输出信号 → 放大器 → ADC → 数字值
                    ↑
               ISO 控制放大倍数
ISO 信号放大 副作用 使用场景
100 1x 无噪点 白天户外
400 4x 轻微噪点 室内
1600 16x 明显噪点 暗光
6400 64x 严重噪点 极暗

💡 ISO 不是"感光度"

严格来说,ISO 不改变传感器的物理感光能力。它只是把已经收集到的信号放大。问题在于:噪声也被一起放大了

传感器的原始信号包含两部分:

  • 有效信号(光子转换的电子)→ 你想要的
  • 噪声信号(热噪声、读出噪声)→ 你不想要的

ISO 放大倍数 = N,信号放大 N 倍,噪声也放大 N 倍。信噪比(SNR)不变。

所以"高 ISO 噪点多"的本质是:信号不够,放大来凑,但噪声也跟着被凑进来了。

1.2.4 曝光三角的权衡

三个参数互相制约:

              亮度 ⬆
                 ▲
                /|\
               / | \
        光圈(F) 快门(t)  ISO
         ↓        ↓       ↓
       景深浅    运动模糊   噪点多

AE 算法在做什么?

AE(Auto Exposure)算法就是在每一帧里,找到一个最优的光圈(如果有)、快门、ISO 组合,使得:

实际手机只有快门和 ISO 两个自由度(光圈固定),AE 就是在这两者之间做权衡。


1.3 传感器——把光变成电

曝光控制的是"有多少光进来"。传感器要做的,是把这些光转换成数字信号

1.3.1 传感器的工作流程

复位 → 曝光 → 读出 → ADC → 输出 RAW
  ↑                        │
  └────────────────────────┘
       下一帧循环

每帧的完整过程:

① 复位:清空像素中的残留电荷
        ─ 耗时极短(~μs 级别)

② 曝光:光子进入像素,产生光电子
        ─ 这就是 exposureTime(几 ms 到几十 ms)
        ─ 曝光长 → 电荷多 → 信号强
        ─ 曝光过长 → 过曝(像素饱和,白成一片)

③ 读出:逐行读取电荷值
        ─ 卷帘快门模式下,读出一行开始下一行的曝光
        ─ 读出速度决定了最大帧率

④ ADC:模拟电荷值 → 数字 RAW 值
        ─ 10bit ADC → 0~1023
        ─ 12bit ADC → 0~4095
        ─ 14bit ADC → 0~16383

1.3.2 传感器的关键参数

做 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,有的暗光好有的差"——真正决定暗光性能的,是 单像素尺寸合并后的等效像素尺寸

1.3.3 RAW——传感器最原始的输出

传感器经过 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

1.4 Bayer 排列与 CFA

现在你有了 RAW 数据。但有个问题:传感器不知道颜色。

单个像素只记录"有多少光子进来了",不记录"这是什么颜色的光子"。要得到彩色图像,必须在传感器表面覆盖一层彩色滤光片阵列(CFA, Color Filter Array)

最主流的设计是 Bayer 排列

1.4.1 Bayer RGGB 排列

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 平衡校正。

1.4.2 Demosaic——从 Bayer 到全彩色

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),而不是软件。

1.4.3 其他 CFA 排列

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 传感器的手机有时偏色。这是物理限制,算法只能部分补偿。


1.5 从 RAW 到照片——ISP 管线

RAW 数据拿到手了,但这个数据还不能直接用——它偏暗、颜色不对、有噪声、有暗角。需要经过 ISP 处理。

这条流水线在 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/)。


1.6 常见成像问题

搞 Camera 的,最终都是在和这几个问题打交道:

果冻效应(Rolling Shutter)

根因:卷帘快门逐行曝光,行与行之间有延时。

拍摄一根快速摆动的杆:
                        实际            拍出来
 时间→                   │                ╲
  第 1 行曝光 → 杆在左边  │                ╲
  第 2 行曝光 → 杆往右移  │                 ╲
  第 3 行曝光 → 杆更右了  │                  ╲

怎么办? ——EIS 算法把陀螺仪数据和卷帘位置结合起来做修正。但最好的办法是物理上的——用全局快门(手机做不了)。

暗角(Vignetting)

根因:镜头边缘的进光量比中心少。

中心区域:光垂直进入 → 进光量 100%
边缘区域:光斜着进入 → 进光量 ~50%(甚至更少)

怎么办? ——LSC(Lens Shading Correction)。为每个像素位置存一个增益值,边缘的增益大于中心。LSC 表存在 sensor 的 OTP 里(第 20 章)。

摩尔纹(Moiré Pattern)

根因:拍摄规则纹理(如格子衬衫)时,Bayer 排列的频率和纹理频率产生"差拍"。

怎么办? ——光学低通滤波器(OPLF,在镜头中)或 ISP 内的去摩尔纹算法。

噪点(Noise)

噪声来源分类:

来源 性质 怎么抑制
光子噪声(Shot Noise) 光的量子特性,必然存在 增加进光量(无法根除)
读出噪声(Read Noise) 电路噪声 降低 sensor 温度
热噪声(Dark Current) 温度越高越严重 降低曝光时间
ISO 放大噪声 信号放大时噪声同比例放大 尽量用低 ISO

💡 做 Camera 越久,越觉得"信噪比"是所有问题的核心

暗光降噪(MFNR)做的就是在"信号不够"的条件下,通过多帧融合来提升 SNR。HDR 做的也是在"动态范围不够"的条件下,通过多帧不同曝光来扩展 DR。

所有计算摄影技术(多帧降噪、HDR、超级夜景),本质上都是在和"物理限制"做斗争——传感器就那么小,光子就那么少,怎么利用多帧、算法来补?


1.7 本章总结

一条主线:光子 → 像素 → 照片

光子进入镜头 → 光圈控制进光量 → 快门控制曝光时间
  → 传感器光电转换 → ADC 量化 → RAW 数据(Bayer)
    → ISP 处理(Demosaic → AWB → CCM → Gamma → 去噪)
      → YUV 图像 → JPEG/H.264 编码

学完这章之后,你应该能回答这几个问题:

  1. 曝光时间(exposureTime)和帧率(FPS)的关系?—— 最大 FPS = 1000ms / exposureTime
  2. 为什么手机传感器用卷帘快门?它带来了什么问题?——逐行扫描,导致果冻效应
  3. Bayer 排列中为什么绿色像素是红色/蓝色的两倍?——人眼对绿光敏感
  4. ISP 为什么要做 Demosaic?——Bayer 每个像素只有一种颜色,需要插值出完整的 RGB
  5. RAW 和 JPEG 谁大?为什么?——RAW 不压缩且位深高,JPEG 有损 8bit 压缩


动手验证

  1. 执行 adb shell dumpsys media.camera,找到后摄的:

    • SENSOR_INFO_EXPOSURE_TIME_RANGE — 最短/最长曝光时间
    • LENS_INFO_AVAILABLE_APERTURES — 光圈值(手机通常只有一个)
  2. 根据最短曝光时间,计算理论最大帧率。

  3. 打开相机对着快速移动的物体(如风扇)拍一段视频——观察是否有果冻效应。

常见误解


下章预告:第 2 章「Android Camera 系统全景」——从 App 到硬件,5 层架构。

这章我们看了"光走的路",下章看"请求走的路"——你按下快门那一瞬间,数据包怎样穿越 App → Framework → HAL → Kernel → Hardware,从 App 一层层走到 ISP。