Qcom Camx全栈开发

Android Camera 全栈课程(Qcom Camx):第 1 章:相机成像基础


本章导读

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

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

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

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


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

image.png

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

光子 (来自被摄物体)
  │
  ▼
① 镜头 ———— 聚焦光线,光圈控制进光量
  │
  ▼
② 快门 ———— 控制光线照射传感器的时间
  │
  ▼
③ 传感器 —— 把光子转换成电子(光电效应)
  │
  ▼
④ 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.416x暗光、人像(背景虚化)
F/2.08x室内
F/4.02x户外日常
F/5.61x(基准)风景(全清晰)

💡 手机的特殊之处

手机镜头的光圈是固定的——因为可变光圈机构太大,塞不进手机模组。所以你的 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.Builderbuilder=
    cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_MANUAL);

// 必须先关掉自动曝光,手动值才会生效
builder.set(CaptureRequest.CONTROL_AE_MODE
            CaptureRequest.CONTROL_AE_MODE_OFF);

// 曝光时间:33ms = 33000000ns → 约 30fps
builder.set(CaptureRequest.SENSOR_EXPOSURE_TIME33_000_000L);

// ISO 感光度
builder.set(CaptureRequest.SENSOR_SENSITIVITY200);

1.2.3 ISO

ISO 的本质是信号放大倍数

传感器输出信号 → 放大器 → ADC → 数字值
                    ↑
               ISO 控制放大倍数
ISO信号放大副作用使用场景
1001x无噪点白天户外
4004x轻微噪点室内
160016x明显噪点暗光
640064x严重噪点极暗

💡 ISO 不是"感光度"

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

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

  • 有效信号(光子转换的电子)→ 你想要的

  • 噪声信号(热噪声、读出噪声)→ 你不想要的

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

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

1.2.4 曝光三角的权衡

三个参数互相制约:

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

AE 算法在做什么?

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

  • 画面亮度适中(目标亮度)

  • 帧率达标(曝光时间不能太长)

  • 噪点可接受(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 输出
StreamConfigurationMapmap=characteristics.get(
    CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);

int[] formats=map.getOutputFormats();
booleanhasRaw=Arrays.asList(formats).contains(ImageFormat.RAW_SENSOR);
// 如果 hasRaw == true,说明这台设备支持 RAW 输出
// 大多数设备的 CameraCharacteristics 会包含 RAW_SENSOR
// 因为 CTS 测试要求支持 RAW

1.4 Bayer 排列与 CFA

image.png

现在你有了 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 RGGBR / Gr / Gb / B经典方案,几乎所有传感器大多数手机
Quadra CFA2×2 同色簇支持 binning + remosaic48MP+ 传感器
RYYBR / Y / Y / BY(黄色)比 G 感光更宽→进光量+40%华为 P 系列
X-Trans6×6 不规则减少摩尔纹富士相机

RYYB 的代价

用 Y 代替 G 确实进光量更大,但颜色精度会下降——因为 Y 覆盖了 R+G 的波段,导致 R 和 G 通道之间的分离度变差。这就是为什么 RYYB 传感器的手机有时偏色。这是物理限制,算法只能部分补偿。


1.5 从 RAW 到照片——ISP 管线

image.png

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

这条流水线在 ISP 硬件中完成,���穿 IFE → BPS → IPE(第 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)~15MB450MB/s
ISP 内部RGB (8bit × 3)~36MB1.08GB/s
ISP 输出YUV420 (NV12)~18MB540MB/s
编码后JPEG~3MB90MB/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 压缩


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

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


(课程相关内容,会陆续在知识星球上面更新,感兴趣的同学可以扫码加入)

图片

《更多交流,欢迎加入知识星球》

推荐阅读:

关于博主 

采用v4l2loopback来实现 虚拟Camera

Camera基础及一些基本概念

Android Camera 学习路线 | 个人推荐

Android Camera开发系列(干货满满)

Camera Hal|如何学习一个新平台

一篇文章带你了解Android 最新Camera框架

学习完Camera入门课程视频,可以去找工作了?




欢迎扫码关注「小驰行动派」公众号 10 年Camera开发 | Camera技术干货 | 行业洞察 | Camera实战分享
小驰行动派公众号 扫一扫关注
分享到: 复制链接
← 上一篇 Android Camera 全栈课程(Qcom Camx):第 2 章:Android Camera 系统全景 下一篇 → 选Camera方向到底值不值?2026年岗位趋势分析

相关文章

推荐课程

想系统学习 Camera 开发?看看这些课程

评论 (0)

暂无评论,快来抢沙发吧