成因、網格、校正實作
鏡頭邊緣光量低於中心,主要因 cos⁴ law:亮度 ∝ cos⁴(入射角)。廣角鏡頭還伴隨角落偏色(R/G/B 各自衰減不同,因濾鏡與入射角)。
# 固定曝光、AWB off,多拍幾張不同亮度 rpicam-still --shutter 20000 --gain 1 --awb off -o lsc1.png rpicam-still --shutter 40000 --gain 1 --awb off -o lsc2.png # 用 libcamera / 社群 calibration 工具產生網格 → tuning
社群工具:rpicam-apps 或 libcamera tuning 範例的 LSC 產生腳本。
python3 - <<'EOF'
import cv2, numpy as np
img = cv2.imread('lsc_check.png', 0) # 均勻灰拍出的圖
h, w = img.shape
c = img[h//2, w//2]
corners = [img[50,50], img[50,w-50], img[h-50,50], img[h-50,w-50]]
print('center:', c)
print('corners:', corners)
print('暗角比:', min(corners)/c) # 目標 >0.9(均勻)
EOF| 症狀 | 檢查 |
|---|---|
| 四周暗角 | LSC 增益偏低、網格未涵蓋全畫面 |
| 角落偏色 | R/G/B 分通道 LSC 是否獨立設定 |
| 換鏡頭又暗 | LSC 是組合特有 → 重做 |
| 網格邊界不連續 | 網格節點過疏、需加密 |
{ "LensShading": {
"meshes": [
{ "size": [17, 13], # 網格維度
"type": "sector", # 資料型態
"reference": [...], # 參考網格
"r": [...], "gr": [...], "gb": [...], "b": [...] }
] } }reference 是理想均勻網格;實際增益為測量值。python3 - <<'EOF'
import rawpy, numpy as np
raw = rawpy.imread('lsc.dng').raw_image.astype(float)
h, w = raw.shape
# 以中心為基準,算每一格對中心的比例 → 倒數為增益
center = raw[h//2 - 50:h//2+50, w//2-50:w//2+50].mean()
grid = 13
gh, gw = h//grid, w//grid
print('增益圖(中心=1,四周>1):')
for gy in range(grid):
row = []
for gx in range(grid):
cell = raw[gy*gh:(gy+1)*gh, gx*gw:(gx+1)*gw].mean()
row.append(f'{center/cell:.2f}')
print(' '.join(row))
EOF鏡頭不只亮度衰減,R/G/B 因微透鏡與濾鏡的入射角響應不同,角落偏色是獨立於亮度的問題——這就是為什麼 LSC 要 R/Gr/Gb/B 分通道。
# 均勻灰拍圖 center = 中央區平均 corner = 四角平均 暗角比 = corner / center # 目標:校正後 > 0.9(原可能 0.7 甚至更低)
場景:你沒有積分球,但有一張用 OV5647 拍攝的均勻白牆照片。需要從這張照片推算 LSC 網格增益。
python3 - <<'EOF'
import rawpy, numpy as np
raw = rawpy.imread('white_wall.dng').raw_image.astype(float)
h, w = raw.shape
grid_h, grid_w = 13, 17 # libcamera 標準網格
cell_h, cell_w = h // grid_h, w // grid_w
center_roi = raw[h//2-50:h//2+50, w//2-50:w//2+50]
center_val = center_roi.mean()
print(f"{'':>8}", end="")
for gx in range(grid_w):
print(f"col{gx:2d}", end=" ")
print()
for gy in range(grid_h):
print(f"row{gy:2d}: ", end="")
for gx in range(grid_w):
cell = raw[gy*cell_h:(gy+1)*cell_h, gx*cell_w:(gx+1)*cell_w].mean()
gain = center_val / cell if cell > 0 else 99
print(f"{gain:5.2f}", end=" ")
print()
print(f"\n中心值: {center_val:.1f}")
print(f"最暗角落增益: {center_val / raw[20:80, 20:80].mean():.2f}")
EOFcos⁴ law 是 LSC 的物理基礎,但實務中鏡頭的 shading 比純 cos⁴ 更複雜:
| 症狀 | 可能原因 | 解決方案 |
|---|---|---|
| LSC 校正後暗角改善但四角偏紫 | LSC 只校正了亮度,未分 R/Gr/Gb/B 通道 | 確認 LSC 網格的四通道各自獨立;偏紫是 R/B 衰減比例不同 |
| 換鏡頭後暗角比從 0.7 變成 0.5 | 新鏡頭的 shading 特性不同(LSC 是鏡頭+感測器組合特有) | 必須重新校正 LSC;不能沿用舊鏡頭的參數 |
| LSC 網格邊界處出現微弱網格狀紋路 | 網格節點過疏,雙線性插值無法平滑過渡 | 加密網格節點(如 17×13→33×25);或用更高階插值 |
| 均勻灰拍攝的四角亮度不一致(已校正 LSC) | LSC 校正用的拍攝環境不夠均勻(有陰影或反射) | 用更均勻的光源重做校正;或增加曝光範圍取平均 |
| 低光下 LSC 效果比高光下差 | 低光下暗角區域的雜訊被 LSC 增益放大 | 在低光下降低 LSC 增益上限;或接受暗角(不校正) |
場景:鏡頭/模組批次更換頻繁,每次手動算 LSC 太慢。本專案建立「LSC 校正流水線」:拍均勻灰 → 分通道算網格增益 → 驗證暗角比 → 輸出 tuning 網格。整合單元 12(LSC)、7(黑位)、8(RAW)、5(拍攝)知識。
#!/bin/bash # 1. 拍均勻灰(白牆/積分球)固定參數 rpicam-still --raw --shutter 20000 --gain 1 \ --awb off --awbgains 1,1 -o lsc_src.dng # 2. 分通道算網格增益(以中央為 1.0) python3 - <<'EOF' import rawpy, numpy as np raw = rawpy.imread('lsc_src.dng').raw_image.astype(float) h, w = raw.shape gh, gw = 13, 17 # libcamera 標準網格 ch, cw = h//gh, w//gw center = raw[h//2-50:h//2+50, w//2-50:w//2+50].mean() print("角落/中心 暗角比 =", round(raw[20:80,20:80].mean()/center, 3)) # 對每格輸出增益(此處以整體亮度示意,實作需分 RGGB) for gy in range(gh): row = [] for gx in range(gw): cell = raw[gy*ch:(gy+1)*ch, gx*cw:(gx+1)*cw].mean() row.append(f"{center/cell:.3f}") print(" ".join(row)) EOF # 3. 將網格填入 tuning JSON 的 LensShading 區塊 → v2_lsc.json
# 套用新 tuning 重拍均勻灰
rpicam-still --raw --tuning-file v2_lsc.json --shutter 20000 \
--gain 1 --awb off --awbgains 1,1 -o lsc_after.dng
python3 - <<'EOF'
import rawpy, numpy as np
raw = rawpy.imread('lsc_after.dng').raw_image.astype(float)
c = raw[800:1200, 1100:1500].mean()
corn = [raw[50,50],raw[50,-51],raw[-51,50],raw[-51,-51]]
print("中央:", round(c,1), " 四角:", [round(x,1) for x in corn])
print("暗角比:", round(min(corn)/c, 3), "(目標 >0.9)")
EOF| 面向 | RPi5 | Orange Pi | Orin Nano | Thor |
|---|---|---|---|---|
| LSC 機制 | tuning JSON 網格(17×13 等) | 有限/後處理 | NVIDIA 工具自動 | NVIDIA 工具 |
| 網格可調性 | 節點數/分通道全可設 | 少 | 可自訂(工具) | 可自訂 |
| 校正輸入 | 均勻灰 RAW(自拍) | 手動 | NVIDIA 校正流程 | NVIDIA 校正流程 |
| 坑 | 白牆不均勻會污染網格 | 無標準工具 | 流程封閉 | 生態新 |
| 步驟 | 操作 | 位元級說明 |
|---|---|---|
| 1. 啟用 LSC 模組 | ISP control register bit[3]=1 | 0x7e80200C |= 0x00000008 |
| 2. 設定表格地址 | 寫入 LSC RAM base address | 通常在 ISP SRAM 內固定地址 |
| 3. 寫入 gain 表 | 依序寫入 R/Gr/Gb/B 四通道的 gain 值 | 每通道 17×13 = 221 個 1.8 fixed-point gain 值 |
| 4. 驗證 | read-back LSC table | 與計算值比對 |
決策樹 A:LSC 後暗角仍然存在
暗角未消除 ├─ 檢查 A:LSC 是否已啟用 │ ├─ 未啟用 → 啟用 LSC register │ └─ 已啟用 → 繼續 ├─ 檢查 B:LSC table 是否針對當前 lens │ ├─ 不是 → 重新生成 LSC table(用 flat field 拍攝) │ └─ 是 → 繼續 ├─ 檢查 C:lens mount 是否正確 │ ├─ 偏心 → 重新對準 lens │ └─ 正確 → 繼續 └─ 檢查 D:zoom level 是否改變 ├─ 是 → 不同 zoom 需要不同的 LSC table └─ 否 → LSC 補償範圍不足
| 面向 | RPi5 | Orange Pi | Orin Nano | Thor | 推薦 |
|---|---|---|---|---|---|
| LSC 控制 | libcamera tuning | 手動為主 | NV 自動 + 手動 | NV + AI | 各有方式 |
| LSC table 生成 | flat field 拍攝 | 手動計算 | NV camera tools | Holoscan 自動 | RPi5 最透明 |
| 多 lens 支援 | ✓(不同 tuning file) | 有限 | ✓(多 profile) | ✓(自動偵測) | Thor 最智慧 |
| 精準度 | 中等 | 較差 | 優 | 優異 | Orin/Thor 最佳 |