shading 校正
鏡頭邊緣光量低於中心(cos⁴),造成四周變暗;廣角鏡頭常角落偏色。
| 路線 | 做法 | 可行度 |
|---|---|---|
| 平台 ISP(RK3588) | Rockchip LSC 網格 | ✅(需 Rockchip 工具) |
| 感測器端 | OV5640 內建 shading(有限) | ⚠️ 範圍小 |
| 後處理 | OpenCV 增益圖 | ✅ 通用非即時 |
python3 - <<'EOF'
import cv2, numpy as np
gray = cv2.imread('graycard.png', 0) # 均勻灰
gain = gray.mean() / (gray + 1e-6) # 逐像素增益(需平滑)
# 套用到影像 frame_out = frame * gain
EOFpython3 - <<'EOF' import numpy as np # raw = 讀取的 RAW(分通道 R/Gr/Gb/B) # center = 中央區均值;gain = center / cell(四周>1) # 需分通道各自計算;對純灰卡才準 EOF
鏡頭邊緣變暗不是「故障」而是 光學定律。點光源經鏡頭成像,照度隨離軸角 θ 依 cos⁴θ 衰減:
cos⁴(30°) ≈ 0.56——差將近一半。# 固定曝光避免過曝,關 AWB 讓四通道可比
v4l2-ctl -d /dev/video0 --set-fmt-video=width=1280,height=720,pixelformat=SRGGB10 \
--stream-mmap=3 --stream-count=1 --stream-to=flat.rawpython3 - <<'EOF'
import numpy as np
raw = np.fromfile('flat.raw', dtype=np.uint16).reshape(720,1280)
chans = {'R':raw[0::2,0::2], 'Gr':raw[0::2,1::2],
'Gb':raw[1::2,0::2], 'B':raw[1::2,1::2]}
for name, c in chans.items():
center = c[100:260, 200:440].mean()
gain = center / (c + 1e-6) # 逐像素增益
np.save(f'gain_{name}.npy', gain)
print(name, 'corner gain ~', np.percentile(gain, 5))
EOFpython3 - <<'EOF'
import numpy as np, cv2
raw = np.fromfile('shot.raw', dtype=np.uint16).reshape(720,1280)
out = raw.copy().astype(float)
for name, sl in {'R':(slice(0,None,2),slice(0,None,2)),
'Gr':(slice(0,None,2),slice(1,None,2)),
'Gb':(slice(1,None,2),slice(0,None,2)),
'B':(slice(1,None,2),slice(1,None,2))}.items():
g = np.load(f'gain_{name}.npy')
out[sl] = out[sl] * g
out = np.clip(out, 0, 1023).astype(np.uint16)
out.tofile('shot_lsc.raw')
EOF| 現象 | 方向 |
|---|---|
| 角落仍暗 | 增益圖範圍不足或亮場不均 → 換更 diffuse 光源 |
| 角落偏色 | 只用單通道增益 → 分 R/Gr/Gb/B 各自算 |
| 套用後雜訊暴增 | 增益圖沒平滑 → blur 增益圖 |
| 中心出現亮斑 | 亮場過曝 saturate → 降曝光重拍 |
| 換鏡頭後失效 | LSC 是鏡頭+感測器組合特有 → 重新校正 |
場景:Orange Pi 5 Plus(RK3588)接 OV5640,用 17×13 網格(ISP3)與逐像素增益圖(Python)分別做 LSC,比較角落亮度提升與色差改善。
# 假設已有 tuning 工具設定 17×13 網格
rkisp_tuning -d /dev/media0 --set-lsc grid_17x13
v4l2-ctl -d /dev/video0 --stream-mmap=3 --stream-count=1 --stream-to=isp_lsc.yuvpython3 - <<'EOF'
import numpy as np, cv2
raw = np.fromfile('flat.raw', dtype=np.uint16).reshape(720, 1280)
center = raw[300:420, 500:780].mean()
gain = center / (raw.astype(float) + 1e-6)
gain_smooth = cv2.GaussianBlur(gain, (21, 21), 0) # 平滑避免雜訊放大
np.save('gain_smooth.npy', gain_smooth)
EOFAllwinner H618 的 CIF 完全沒有 LSC 硬體模組,所有 lens shading 校正只能靠後處理:
| 項目 | Allwinner H618 | RK3588 |
|---|---|---|
| LSC 硬體 | ❌ 無 | ✅ ISP3 網格 |
| 網格大小 | —(後處理任意) | 17×13(固定) |
| 分通道 | 後處理可分 R/Gr/Gb/B | ISP3 內建分通道 |
| 即時性 | ⚠️ CPU 敏感 | ✅ 硬體管線 |
| 色差 shading | 後處理可處理 | ISP3 內建 |
容易忽略的邊界案例:H618 的 OV5640 雖然有內建 shading 校正(0x4600 帶),但公開的 register 極少、網格極小——不能依賴它做深度校正。在 H618 上,LSC 後處理是唯一的務實路線。
| 症狀 | 可能原因 | 解決方案 |
|---|---|---|
| LSC 後角落仍暗 | 亮場不均勻或增益圖沒涵蓋角落 | 用更 diffuse 光源(陰天窗邊)重拍亮場 |
| LSC 後角落偏色 | 只用單通道增益做 LSC | 分 R/Gr/Gb/B 各自算增益圖 |
| 增益圖套用後雜訊暴增 | 增益圖沒平滑 | 用 GaussianBlur 平滑增益圖 |
| 換解析度後 LSC 失效 | LSC 是鏡頭+感測器+解析度 組合特有 | 每次換組合都要重新校正 |
| H618 無法用 ISP LSC | CIF 無 LSC 硬體 | 接受後處理;或升級到 RK3588 |
把單元 12 的 LSC 知識做成多鏡頭校正資料庫專案:為每支鏡頭(模組)建立專屬的 LSC 增益圖,並產出「鏡頭 → 增益圖」的對照管理。這是量產一致性的關鍵——每支鏡頭的光學特性都不完全一樣。
# lens_A(廣角)、lens_B(標準)、lens_C(窄角)
mkdir -p lsc_db && cd lsc_db#!/bin/bash
for lens in A B C; do
# 換上 lens_$lens,固定曝光拍均勻亮場
v4l2-ctl -d /dev/video0 --set-fmt-video=pixelformat=SRGGB10 \
--stream-mmap=3 --stream-count=1 --stream-to=flat_$lens.raw
donepython3 - <<'EOF'
import numpy as np, cv2
for lens in 'ABC':
raw = np.fromfile(f'flat_{lens}.raw', dtype=np.uint16).reshape(720,1280)
for name, sl in {'R':(slice(0,None,2),slice(0,None,2)),
'Gr':(slice(0,None,2),slice(1,None,2)),
'Gb':(slice(1,None,2),slice(0,None,2)),
'B':(slice(1,None,2),slice(1,None,2))}.items():
c = raw[sl].astype(float)
center = c[100:260, 200:440].mean()
gain = cv2.GaussianBlur(center/(c+1e-6), (21,21), 0)
np.save(f'lsc_db/{lens}_{name}.npy', gain)
print('已產生 12 個增益圖(3 鏡頭 × 4 通道)')
EOFpython3 - <<'EOF'
import numpy as np
raw = np.fromfile('shot_B.raw', dtype=np.uint16).reshape(720,1280)
out = raw.copy().astype(float)
for name, sl in {'R':(slice(0,None,2),slice(0,None,2)),
'Gr':(slice(0,None,2),slice(1,None,2)),
'Gb':(slice(1,None,2),slice(0,None,2)),
'B':(slice(1,None,2),slice(1,None,2))}.items():
out[sl] = out[sl] * np.load(f'lsc_db/B_{name}.npy')
out = np.clip(out,0,1023).astype(np.uint16)
out.tofile('shot_B_lsc.raw')
c0 = raw[300:420,500:780].mean(); c1 = raw[10:80,10:80].mean()
o0 = out[300:420,500:780].mean(); o1 = out[10:80,10:80].mean()
print(f'校正前 corner/center = {c1/c0:.3f} → 校正後 = {o1/o0:.3f}')
EOFlsc_db/ 資料庫(每鏡頭每通道的增益圖)+ 套用腳本。換鏡頭、換解析度都能快速切換對應增益圖——這是多樣化產品的標準做法。| 步驟 | 作法 | 通過判據 |
|---|---|---|
| 1. 拍均勻亮場 | diffuse 光源 + 灰/白卡 | 峰值壓在 ~80% 滿量程 |
| 2. 量校正前 shading | corner/center 比值 | 記錄基準(如 0.56) |
| 3. 算增益圖 | 分通道 + 平滑 | 增益圖平滑無爆點 |
| 4. 套用後重測 | corner/center 比值 | 接近 1.0(±5%) |
| 5. 色差檢查 | 角落 R/B 比值 | 無明顯色偏(色差 shading 已處理) |
| 6. 換模式回歸 | 換解析度/binning 重測 | 必要時重新校正 |
| 面向 | Orange Pi | RPi5 | Orin Nano | Thor |
|---|---|---|---|---|
| 硬體 LSC | RK3588 有 / H618 無 | 硬體 ISP 內建 | NVIDIA ISP 內建 | NVIDIA ISP 內建 |
| 網格 | RK 17×13 / 後處理任意 | libcamera tuning | NVIDIA 網格 | NVIDIA 網格 |
| 分通道 | 後處理可做 | ISP 內建 | ISP 內建 | ISP 內建 |
| 校正工具 | 自寫 Python | libcamera tuning 工具 | NVIDIA 工具 | NVIDIA 工具 |
| 即時性 | ⚠️ 後處理 CPU | ✅ 硬體 | ✅ 硬體 | ✅ 硬體 |
- [ ] 我能完成多鏡頭 LSC 資料庫專案並產出增益圖。 - [ ] 我能量測校正前後的 corner/center 比值。 - [ ] 我能分通道做 LSC 並避免色差殘留。 - [ ] 我理解為什麼 LSC 必須在 RAW 上做而非 YUV。 - [ ] 我已平滑增益圖避免放大雜訊。 - [ ] 我知道 H618 沒有硬體 LSC,後處理是唯一路線。
鏡頭陰影校正(LSC)的「讀→改→寫→驗證」是影像均勻性的基礎:
| 步驟 | 暫存器/位址 | 位元欄位 | 操作 | 預期值 |
|---|---|---|---|---|
| 1. 開啟 LSC | 0x5800 | [0] LSC enable | i2cset -y 3 0x3c 0x5800 0x01 | LSC 開啟 |
| 2. 設定中心增益 | 0x5801 | [7:0] center gain | i2cset ... 0x5801 0x40 | 1.0×(64=1.0×) |
| 3. 設定角落增益 | 0x5802~0x5806 | [7:0] corner gains | i2cset ... 0x5802 0x80 | 2.0×(補償暗角) |
| 4. 回讀驗證 | 0x5800~0x5806 | [7:0] | i2cget ... 0x5800 | 所有值正確 |
| 5. 關閉 LSC | 0x5800 | [0] | i2cset ... 0x5800 0x00 | LSC off(對比用) |
| 6. 取幀對比 | v4l2-ctl | — | 拍兩幀(LSC on/off) | 角落亮度改善 |
決策樹 A:LSC 效果不明顯
LSC on/off 差異不大? ├─ 鏡頭品質好(暗角小) │ └─ 正常!高品質鏡頭不需要 LSC ├─ LSC 增益設定太小 │ └─ 增加角落增益(0x5802~0x5806) └─ LSC 未生效 └─ 檢查 0x5800[0] 是否為 1
決策樹 B:LSC 後角落雜訊放大
LSC 後角落雜訊增加? ├─ 增益太高 → 減少角落增益 ├─ 雜訊基底高 → 先降噪再 LSC └─ 正常(物理限制) └─ 記錄「LSC 增益 vs 雜訊」的取捨
| 步驟 | 指令 | 預期輸出 | 判讀標準 |
|---|---|---|---|
| 1. 關閉 LSC | i2cset ... 0x5800 0x00 | RAW 無校正 | 角落偏暗 |
| 2. 取 RAW | v4l2-ctl --stream-mmap=1 --stream-count=1 | RAW 檔 | 有暗角 |
| 3. 開啟 LSC | i2cset ... 0x5800 0x01 | LSC on | 角落亮度改善 |
| 4. 取校正後 RAW | v4l2-ctl --stream-mmap=1 --stream-count=1 | 校正後 RAW | 均勻性改善 |
| 5. 量測 corner/center | Python script | 亮度比值 | 校正後比值接近1 |
| 6. 分通道驗證 | R/G/B 分別量測 | 各通道比值 | 避免色差殘留 |
| 面向 | Orange Pi | RPi5 | Orin Nano | Thor | 推薦 |
|---|---|---|---|---|---|
| LSC 硬體 | OV5640 內建(有限) | ISP 內建 | ISP 內建 | ISP 內建 | 學習→OV5640 |
| 後處理 LSC | 自寫 Python | libcamera 後期 | 可選 | 可選 | 底層→Python |
| 即時性 | ⚠️ 後處理 CPU | ✅ 硬體 | ✅ 硬體 | ✅ 硬體 | 效能→硬體 |
| 分通道 | 後處理可做 | ISP 內建 | ISP 內建 | ISP 內建 | 學習→分通道 |
| 校正工具 | 自寫 Python | libcamera tuning | NVIDIA 工具 | NVIDIA 工具 | 學習→Python |
- [ ] 完成「多鏡頭 LSC 資料庫」專案並產出增益圖 - [ ] 量測校正前後的 corner/center 比值 - [ ] 分通道做 LSC 並避免色差殘留 - [ ] 驗證「LSC 必須在 RAW 上做」(YUV 上做會失敗) - [ ] 平滑增益圖避免放大雜訊 - [ ] 確認 H618 沒有硬體 LSC(後處理是唯一路線) - [ ] 產出「LSC 校正報告」(含增益圖/corner-center 比值) - [ ] 測試不同鏡頭的 LSC 差異 - [ ] 驗證 LSC 隨組合(感測器+鏡頭)需重新校正 - [ ] 產出「lens shading database」