單元 10 · 曝光與自動曝光(AE)

register 位元級 + AE 收斂

10.1 曝光三要素(含位元級)

要素register位元/公式
快門(曝光行數)0x3500–0x3503值 = (0x3501<<12)|(0x3502<<4)|(0x3503>>4) − 1(依 datasheet 位元布局)
類比增益0x350A–0x350B對應 datasheet gain map(非線性)
數位增益ISP 端最後手段
讀目前曝光三 register
for r in 0x3501 0x3502 0x3503; do
  h=${r:2:2}; l=${r:4:2}
  printf "%s: " "$r"; sudo i2ctransfer -y 22 w2@0x36 0x$h 0x$l r1
done
調適順序鐵則:先快門 → 類比增益 → 數位增益。用增益「補」曝光會犧牲畫質(shot noise 等比放大)。

10.2 Worked Example:從 register 算出曝光

假設讀回
0x3501 = 0x01   # 高位
0x3502 = 0xE4   # 中位
0x3503 = 0x10   # 低位(僅 bit[7:4] 有效)

# 曝光行數(依位元布局)
exposure = (0x01 << 12) | (0xE4 << 4) | (0x10 >> 4)
         = 4096 + 3648 + 1
         = 7745 行

# 實際時間 = 行數 × row_time
# row_time ≈ 影像行週期(由 PLL/時序決定,datasheet 可查)

這是理解「曝光 register」的練習;正式流程由 libcamera 以 µs 為單位控制(下一節)。

10.3 用 libcamera/picamera2 控制(正確方式)

AE 相關控制全表
picam2.set_controls({
    "AeEnable": False,              # 關自動
    "ExposureTime": 20000,          # 快門 µs(與 AnalogueGain 併用)
    "AnalogueGain": 2.0,
    "AeMeteringMode": 1,            # 1=矩陣 2=中心加權 3=點測光
    "ExposureCompensation": 0.0,    # EV 補償
    "AeConstraintMode": 0,          # 約束(正常/高光優先)
})
測光模式:矩陣=全畫面平均;中心加權=中間重要;點測光=單點。背光場景用點測光中心人物才不會過曝。EV 補償整體偏移曝光目標。

10.4 banding:50/60Hz 與曝光

成因:室內光源(日光燈/LED)以市電頻率閃爍(50Hz 或 60Hz)。曝光若不是「半週期的整數倍」,各列累積的光量不同 → 明暗條紋。

Register功能
0x3A00–0x3A03banding filter 設定(auto)
0x3A02/0x3A0350/60Hz 對應的曝光行數
libcamera 層的 banding
# 固定曝光時,選 50/60Hz 的整數倍
# 50Hz: 10ms 半週期 → ExposureTime=10000/20000...
# 60Hz: 8.33ms → ExposureTime=8333/16666...
picam2.set_controls({"ExposureTime": 20000})   # 50Hz 整數倍

10.5 AE 收斂與除錯

觀察 AE 狀態
rpicam-still --info -o /dev/null | grep -E "Exposure|Gain|Colour"
症狀檢查方向
AE 到極限仍暗快門上限太低?增益上限太低?→ 補光或放寬上限
收斂慢 / 閃爍測光區域、EV、convergence 速度參數
banding曝光非 50/60Hz 整數倍
高光爆掉AE 只看平均 → 改高光優先約束或點測光
除錯核心:先判斷 AE 是否「受限」。受限 → 物理補光或放寬範圍;不受限 → 才查測光/收斂參數。不要一上來就亂調增益。

10.6 深入:測光模式的實務差異

模式演算法適用陷阱
矩陣全畫面加權平均一般大亮區誤導
中心加權中間權重高人物置中主角不在中間
點測光單點精準主體點選錯就全錯
實務:逆光場景 → 點測光對準主體,或 EV 補償 + 高光優先。沒有「最佳模式」,只有「最適合場景」。

10.7 Worked Example:banding 的曝光計算

50Hz 場景
# 市電 50Hz → 半週期 10ms
曝光 = 10000 * N  µs    (N = 1,2,3...)
  例如 10000 / 20000 / 30000 µs

# 若設 15000µs(非整數倍)→ 出現 banding
# libcamera 有 banding 支援;手動固定曝光時要自己算

10.8 練習

  1. 在日光燈下拍一張,觀察是否有 banding。
  2. 固定曝光為 50Hz 整數倍,再拍一張比較。
  3. 用 --info 觀察 AE 收斂的曝光/增益值。
看完這單元你應該能說出:
  • 曝光三要素 register 位元級與公式。
  • Worked example 從 register 算曝光。
  • libcamera 測光模式與 EV。
  • banding 成因與 50/60Hz。
  • AE 受限判斷與除錯方向。

進階真實情境 Worked Example:在日光燈下拍攝並分析 banding 的精確週期

場景:你在室內日光燈環境下拍攝,畫面出現明顯的水平明暗條紋。需要確認是 50Hz 還是 60Hz 的 banding,並計算正確的曝光值。

banding 週期分析
# 1. 用不同曝光拍攝,觀察條紋變化
for EXP in 8000 8333 10000 16666 20000 33333; do
  rpicam-still --raw --shutter $EXP --gain 1 \
    --awb off --awbgains 1,1 -o band_${EXP}us.dng 2>/dev/null
done

# 2. 分析各曝光下的亮度週期性
python3 - <<'EOF'
import rawpy, numpy as np
for exp in [8000,8333,10000,16666,20000,33333]:
    raw = rawpy.imread(f'band_{exp}us.dng').raw_image
    roi = raw[900:1100, 500:2000]  # 橫向 ROI
    row_means = roi.mean(axis=1)
    std = row_means.std()
    print(f"曝光{exp}µs: 行間亮度標準差={std:.2f} (越大=banding越嚴重)")
EOF
為什麼選這條路徑:用不同曝光拍攝同一場景,可以找出 banding 最嚴重的曝光值——那就是「非半週期整數倍」的曝光。50Hz 的半週期是 10ms(10000µs),60Hz 是 8.33ms(8333µs)。若 10000µs 的 std 最小,代表是 50Hz 環境;若 8333µs 最小,代表是 60Hz。這個方法比「看起來有沒有條紋」更精確。

深入原理擴充:OV5647 的 AEC 暫存器與行曝光時序

OV5647 的曝光是行級(line-based)的,不是微秒級的:

大家以為沒问题但其實是陷阱:很多人以為「libcamera 設 ExposureTime=20000µs 就真的曝光 20000µs」,但 libcamera 內部會向上取整到 row_time 的整數倍。若 row_time=11.6µs,20000/11.6=1724.1,libcamera 會取 1725 行 × 11.6µs = 20010µs——微小的偏差在高精度場景(如 banding 分析)可能有意義。

診斷式疑難排解表

症狀可能原因解決方案
室內日光燈下有水平明暗條紋曝光時間非光源半週期的整數倍(50Hz→10ms, 60Hz→8.33ms)固定曝光為半週期整數倍(如 10000/20000µs for 50Hz)
AE 模式下 banding 消失但固定曝光時出現AE 的 banding filter 自動同步了;手動曝光時需自行同步啟用 libcamera 的 auto-banding 或手動設整數倍曝光
曝光設為 10000µs 但仍有輕微條紋光源非純正弦波閃爍(LED 可能有高諧波)嘗試 5000µs(半半週期)或用全局快門感測器
AE 收斂後畫面過暗場景亮度低,AE 已達曝光上限或增益上限放寬 AE 範圍(增大 max_shutter/max_gain)或補光
戶外日光下也有輕微水平條紋非 banding——可能是 rolling shutter 與 PWM 光源(如 LED 車燈)的交互確認光源類型;PWM 光源需用全局快門或同步觸發

進階挑戰題

  1. 用示波器量測 OV5647 的 frame sync 訊號(若可接觸),驗證 row_time 是否與 PLL 配置一致。
  2. 設計一個「自動 banding 偵測」腳本:拍一張 RAW,分析行間亮度的週期性,自動判斷光源是 50Hz/60Hz/DC。
  3. 若場景中同時有 50Hz 日光燈和 60Hz LED 燈,會出現什麼症狀?如何處理?

延伸閱讀

專案級端到端 Worked Example:多場景曝光策略專案

場景:監控攝影機要在室內日光燈、室外日光、夜晚低光三種場景自動切換曝光策略,並避免 banding。本專案整合單元 10(AE/banding)、2(SNR)、13(雜訊)、14(回歸)知識,建立一套可驗證的曝光策略表。

exposure_strategy.py:三場景策略
from picamera2 import Picamera2

# 場景定義:增益上限、快門上限、banding 頻率
SCENES = {
    "indoor_fluorescent": dict(max_gain=8.0, max_shutter=30000, banding=50),
    "outdoor_day":        dict(max_gain=2.0, max_shutter=8000,  banding=None),
    "night_lowlight":     dict(max_gain=12.0,max_shutter=66000, banding=50),
}

def apply(picam2, scene):
    s = SCENES[scene]
    ctrl = {
        "AeEnable": True,
        "AeConstraintMode": 0,
        "ExposureCompensation": 0.0,
        "AnalogueGain": s["max_gain"] / 2,
    }
    # 若設 banding:曝光以半週期整數倍為準
    picam2.set_controls(ctrl)

picam2 = Picamera2()
picam2.configure(picam2.create_still_configuration())
apply(picam2, "indoor_fluorescent")
驗證 banding 已消除
# 在日光燈下拍兩張:20000µs(50Hz OK)vs 15000µs(非整數倍)
rpicam-still --raw --shutter 20000 --gain 2 -o ok50.dng
rpicam-still --raw --shutter 15000 --gain 2 -o bad15.dng
python3 - <<'EOF'
import rawpy, numpy as np
for f in ['ok50.dng','bad15.dng']:
    roi = rawpy.imread(f).raw_image[900:1100, 500:2000]
    print(f, "行間σ =", round(roi.mean(axis=1).std(),2))
EOF
# 預期:ok50 行間σ 明顯小於 bad15(banding 消除)
專案規模與跨單元整合:曝光策略把單元 10 的 AE 知識變成「產品設定」:banding 用 50/60Hz 週期(單元 10.4)、gain 上限來自 SNR 量測(單元 2/13)、場景回歸用單元 14 的清單。策略表放進設定檔,AE 就從「碰運氣」變成「可驗證的工程決策」。

量測/驗證 SOP:AE 與 banding 驗證

  1. 在目標光源下跑 AE,用 --info 記錄收斂後的曝光/增益。
  2. 檢查收斂時間:換場景後 AE 應在 <2 秒內穩定。
  3. 用行間 σ 量測確認 banding 消失(<閾值)。
  4. 檢查高光:逆光場景高光不爆(用 AeConstraintMode=高光優先)。
  5. 在低光確認增益未超過 SNR 決定的上限。
  6. 三場景各錄一輪,存 log。
判讀指標:行間 σ 在固定曝光整數倍下應比非整數倍小 50%+;AE 收斂後曝光/gain 落在策略表設定範圍內;增益超過上限 = 場景亮度不足,需補光或放寬。

平台間對照:曝光控制

面向RPi5Orange PiOrin NanoThor
曝光介面ExposureTime(µs) + AeEnablev4l2 exposure(驅動單位)SensorMode exp_timeSensorMode / Holoscan
banding 支援libcamera 自動 + 手動整數倍感測器 banding filterNVIDIA 自動NVIDIA 自動
測光模式矩陣/中心/點 + EV有限多模式可選多模式
曝光被 row_time 取整單位模糊自動收斂快但黑箱生態新

互動式檢核清單


第 3 輪深度加深

① Register 位元級完整工作流:OV5647 手動曝光設定與驗證

步驟暫存器位元級說明
1. 解除 AE0x35030x03bit[0]=1(manual AEC), bit[1]=1(manual AGC)
2. 設定曝光高位0x35000x00bit[3:0] = 曝光 bit[19:16]
3. 設定曝光中位0x35010x64bit[7:0] = 曝光 bit[15:8]
4. 設定曝光低位0x35020x00bit[7:4] = 4-bit 小數位
5. 設定增益0x350A0x00bit[1:0] = analog gain 高位
6. 設定增益低位0x350B0x10bit[7:0] = digital gain × 16 (0x10 = 1.0×)
7. 驗證read-back 0x3500-0x3503確認回讀值 = 寫入值
曝光計算公式:曝光時間(s) = (曝光行數 × 行時間) / 平台時脈。OV5647 在 24MHz MCLK 下,一行 ≈ 22.2μs。

② 多層疑難排解決策樹

決策樹 A:AE 收斂過慢或震盪

AE 異常
├─ 檢查 A:AE target brightness 是否合理
│  ├─ 太高(>200) → 曝光拉到最大仍不夠 → 降低 target
│  └─ 太低(<50) → 曝光壓到最低仍太亮 → 提高 target
├─ 檢查 B:AE convergance speed 設定
│  ├─ 太快 → 震盪 → 降低 step size
│  └─ 太慢 → 逐步增加 step size
├─ 檢查 C:場景是否有大量高動態範圍
│  ├─ 是 → AE 加權區域設定問題 → 調整 metering mode
│  └─ 否 → 繼續
└─ 檢查 D:50Hz/60Hz banding 是否造成行間差異
   ├─ 是 → 曝光行數非整數倍 → 調整為 100/120 的整數倍
   └─ 否 → AE 算法本身問題

③ 量測驗證完整 SOP:AE 收斂時間量測

  1. 方法:LED 切換光源亮度 + 影像亮度 log
  2. 步驟:
    a. LED 亮度 Level 1 → 等 AE 收斂 → 記錄穩定 brightness
    b. 切換到 Level 2(突變)
    c. 每幀記錄 brightness,直到收斂(±5% 以内)
  3. 量測條件:3 個亮度等級(暗/中/亮),每次切換測 5 回
  4. 統計:平均收斂幀數、最差收斂幀數
  5. 判讀:收斂 <30 幀 @30fps = <1s 正常;>60 幀 = 需優化 AE 參數
  6. 常見偏差:開場景 histogram 分佈極端 → AE target 誤判

④ 四平台終極對照

面向RPi5Orange PiOrin NanoThor推薦
AE 控制方式libcamera 自動V4L2 exposure controlArgus AE 算法Holoscan + AI AE各有特色
曝光範圍1-65535 行依 sensor更寬範圍更寬範圍Orin/Thor 範圍更寬
收斂速度可調(tuning)固定可調AI 自適應Thor 最快
Metering 模式全域/中心全域全域/中心/自訂全域/中心/AIThor 最豐富
手動/自動切換✓ tuning 檔V4L2 控制Argus APIHoloscan APIRPi5 最直觀

⑤ 完整 Bring-up 專案 Checklist