量測與 NR 調校
| 雜訊 | 來源 | 模型 | 抑制方式 |
|---|---|---|---|
| shot noise | 光子隨機性 | σ = √(訊號) | 加曝光(真實訊號) |
| read noise | 讀出電路 | 固定底噪(e-) | 硬體限制 |
| dark current | 高溫/長曝光 | 隨溫度指數上升 | 散熱、減曝光 |
| hot pixel | 缺陷像素 | 固定亮點 | DPC 校正 |
python3 - <<'EOF'
import rawpy, numpy as np, subprocess
for g in [1, 2, 4, 8]:
subprocess.run(['rpicam-still','--raw','--gain',str(g),
'--shutter','20000','-o',f'g{g}.dng'])
raw = rawpy.imread(f'g{g}.dng').raw_image
roi = raw[500:1500,500:1500].astype(float)
print(f'gain={g} mean={roi.mean():.1f} std={roi.std():.1f}')
EOF{ "Denoise": { "strength": 0.3 } }| 降噪類型 | 效果 | 調校建議 |
|---|---|---|
| 亮度降噪(luma) | 影響整體細節 | 弱,避免塑膠感 |
| 彩色降噪(chroma) | 抑制色斑 | 可稍強 |
| 時間降噪(temporal) | 多幀平均 | 需多幀,換幀率 |
OV5647 內建 DPC,可標定並替換缺陷像素(hot pixel)。
python3 - <<'EOF'
import rawpy, numpy as np
raw = rawpy.imread('black.dng').raw_image
hot = raw > raw.mean() + 10 * raw.std()
print('hot pixel 數量:', hot.sum())
print('最亮:', raw.max())
EOF# 假設:目標 SNR >= 30dB(σ/S 比值 < 3.2%) # 量測:gain=8 時 σ=20, 訊號=400 SNR = 20 * log10(400/20) = 26 dB # 低於目標 # 選項 A:曝光加倍(真實訊號)→ 訊號 800, σ≈28 → SNR 29 dB # 選項 B:改回 gain=4(σ 較小)但補光 → 訊號 800, σ≈18 → SNR 33 dB ✓ # 結論:補光 + 降增益 優於 硬拉增益
python3 - <<'EOF'
import rawpy, numpy as np
for name in ['before.dng','after.dng']:
raw = rawpy.imread(name).raw_image
hot = (raw > raw.mean() + 10*raw.std()).sum()
print(name, 'hot pixels:', hot, 'max:', raw.max())
EOFpython3 - <<'EOF'
import rawpy, numpy as np
for ms in [1000, 5000, 20000]:
raw = rawpy.imread(f'black_{ms}.dng').raw_image
print(f'曝光{ms}µs: 黑位均值={raw.mean():.1f}')
# 均值隨曝光線性增 → slope 即 dark current
EOF場景:你要確定 OV5647 的 read noise 底線,以便設定增益上限和 NR 強度。
#!/bin/bash
# 完全遮光 + 短曝光(最小化 dark current)
# 短曝光讓 dark current 影響最小,量到的是純 read noise
for GAIN in 1 2 4 8; do
rpicam-still --raw --gain $GAIN --shutter 1000 \
--awb off --awbgains 1,1 -o rn_g${GAIN}.dng 2>/dev/null
done
python3 - <<'EOF'
import rawpy, numpy as np
print("gain | read_noise(e-) | dark_current_offset")
print("-----|---------------|-------------------")
for g in [1,2,4,8]:
raw = rawpy.imread(f'rn_g{g}.dng').raw_image.astype(float)
roi = raw[600:1400, 600:1400]
std = roi.std()
mean = roi.mean()
# 假設 10-bit ADC, 1 LSB ≈ 1 e- (簡化)
print(f" {g} | {std:5.1f} | {mean:.1f}")
# 量測不同區域的雜訊一致性
zones = {
'top-left': raw[100:500, 100:500].std(),
'center': roi.std(),
'bottom-right': raw[1500:1900, 2100:2500].std()
}
for zone, s in zones.items():
print(f" {zone}: σ={s:.1f}")
EOFOV5647 的雜訊不是常數——它隨溫度、增益、曝光時間變化:
| 症狀 | 可能原因 | 解決方案 |
|---|---|---|
| 遮光 RAW 中有固定亮點(hot pixel) | 感測器缺陷像素,OV5647 的正常現象 | 啟用 DPC(Defect Pixel Correction);標定 hot pixel 位置 |
| 不同增益下 σ 的增加不成比例 | 類比放大器的非線性 + read noise 隨增益增加 | 量測完整 σ vs gain 曲線,找出「σ 暴增」的拐點作為增益上限 |
| 長曝光(>5s)暗部充滿雜訊 | dark current 累積,尤其高溫環境 | 散熱(加散熱片);限制長曝光時間;用 dark frame subtraction |
| NR 開到最強仍無法消除低光雜訊 | 源頭雜訊過高(增益太高),NR 無法「無中生有」 | 回到源頭:降增益、加曝光、補光;NR 只能收尾 |
| 平坦區 σ 低但紋理區 σ 高 | 紋理區的 shot noise 隨訊號增加(正常的 Poisson 特性) | 這是物理限制,NR 強度需在「平坦區乾淨」與「紋理區保留」間取捨 |
場景:要建立每顆感測器型號的「雜訊特性曲線庫」(σ vs gain、dark current vs 曝光、hot pixel 地圖),供演算法與調校決策使用。整合單元 13(雜訊)、2(物理)、7(黑位)、5(拍攝)知識。
#!/bin/bash # 1. read noise:遮光 + 短曝光(1ms)不同增益 for G in 1 2 4 8 16; do rpicam-still --raw --gain $G --shutter 1000 \ --awb off --awbgains 1,1 -o rn_g$G.dng 2>/dev/null done # 2. dark current:遮光 + 長曝光不同時長 for T in 1000 10000 50000 100000; do rpicam-still --raw --gain 1 --shutter $T \ --awb off --awbgains 1,1 -o dc_t$T.dng 2>/dev/null done python3 - <<'EOF' import rawpy, numpy as np print("== read noise(短曝 1ms,σ 即 read noise)==") for g in [1,2,4,8,16]: roi = rawpy.imread(f'rn_g{g}.dng').raw_image[600:1400,600:1400] print(f" gain={g:2d} σ={roi.std():5.2f}") print("== dark current(黑位均值 vs 曝光,slope=暗電流)==") means = [] for t in [1000,10000,50000,100000]: m = rawpy.imread(f'dc_t{t}.dng').raw_image[600:1400,600:1400].mean() means.append((t,m)) print(f" {t:6d}µs mean={m:6.1f}") slope = (means[-1][1]-means[0][1])/(means[-1][0]-means[0][0]) print(f" 暗電流斜率 ≈ {slope*1e6:.2f} e-/s(10-bit 單位換算)") EOF
| 面向 | RPi5 | Orange Pi | Orin Nano | Thor |
|---|---|---|---|---|
| NR 控制 | tuning Denoise(luma/chroma) | 後處理 | NVIDIA 自動(kernel 可調) | AI 驅動 |
| DPC | 感測器端(OV5647 內建) | 感測器端 | NVIDIA 處理 | NVIDIA 處理 |
| 雜訊量測 | rawpy 全開源 | 手動解 RAW | NVIDIA 工具 | Holoscan |
| 坑 | NR 過強塑膠感 | 無 NR 介面 | 黑箱難深調 | 生態新 |
| 步驟 | 暫存器 | 值 | 位元級說明 |
|---|---|---|---|
| 1. 啟用 DPC | 0x5080 | 0x01 | bit[0]=1 DPC on |
| 2. 設定 DPC threshold | 0x5081 | 0x0A | bit[7:0] = 雜訊閾值(越大越保守) |
| 3. 啟用 NR (luma) | 0x5290 | 0x04 | bit[3:2]=01 Luma NR strength medium |
| 4. 啟用 NR (chroma) | 0x5291 | 0x08 | bit[3:2]=10 Chroma NR strength high |
| 5. 驗證 | read-back | — | 確認所有值正確 |
決策樹 A:NR 過強導致細節模糊
NR 過強 ├─ 檢查 A:NR strength 設定值 │ ├─ 過高 → 降低 strength level │ └─ 適中 → 繼續 ├─ 檢查 B:NR threshold 是否針對當前 gain │ ├─ 不是 → 不同 gain 需要不同 threshold │ └─ 是 → 繼續 ├─ 檢查 C:是否同時有 spatial + temporal NR │ ├─ 是 → 可能疊加過強 → 分別調整 │ └─ 否 → 繼續 └─ 檢查 D:後處理 pipeline 是否有額外 NR ├─ 有 → 去除多餘 NR 階段 └─ 無 → 調整 NR 演算法參數
| 面向 | RPi5 | Orange Pi | Orin Nano | Thor | 推薦 |
|---|---|---|---|---|---|
| NR 類型 | Spatial NR | 基本 NR | Spatial + Temporal | Spatial + Temporal + AI | Thor 最先進 |
| DPC | 感測器端 | 感測器端 | ISP 端 | ISP + AI | Thor 最強 |
| NR 強度可調 | ✓ tuning | 有限 | ✓ 完整 | ✓ AI 自適應 | Orin/Thor 最靈活 |
| 低光 SNR | 中等 | 較差 | 優 | 優異 | Orin/Thor 最佳 |
| 功耗 | 低 | 低 | 中 | 高 | RPi5 功耗最低 |