fullseye

カメラのデータシートの数を EMVA 1288 の手順で測り直し、カメラの中の前処理がその数をどれだけ動かすかを見る(Linear と General)

できること

画像センサ・カメラのデータシートの量子効率 η・システムゲイン K・暗雑音・飽和容量・最大 SNR・ダイナミックレンジ・DSNU・PRNU は、 EMVA 1288 Release 4.0 の手順で測った数です。ここではその手順を op にし、値を仕込んだ合成センサで手順そのものを確かめます。 式は Release 4.0 Linear / General(2021-06-16、無償で公開されている本文)から写し、docstring に節と式の番号を書いてあります。

PoC の主図は 640 × 480 の暗画像・平坦画像・飽和像を等倍で並べます。行 1 DN・列 1.5 DN・画素 4 DN の固定模様と、仕込んだ 9 個の欠陥画素を、集計値だけでなく画像上でも確認できます。

What it does

The PoC’s lead figure places 640 x 480 dark, flat and saturated frames at native scale, making the 1 DN row, 1.5 DN column and 4 DN pixel fixed pattern—and nine planted defective pixels—visible before reporting aggregate metrics.

The EMVA 1288 Release 4.0 procedure behind camera datasheet numbers, as operators: the Linear model (photon transfer, responsivity, dark noise, dark current, column/row/pixel nonuniformity, defect-pixel histograms, the section 8.1 highpass filter, the section 8.6 spectrograms, total SNR, the inverse SNR, photon counts and the equation 51 linearity check) and the General model for non-linear cameras (a cubic B-spline of the characteristic curve whose slope refers output noise back to the input, giving input SNR, sensitivity threshold, dynamic range, dark noise in photons, DSNU / PRNU and relative quantum efficiency, while reporting η and K as not measurable). Closed forms predict how linear in-camera processing (digital gain, denoising, sharpening, recursive temporal filtering) moves the Linear numbers, and a whiteness check tests assumption 3 directly; the check is this library’s, not a number defined by the standard.

向くところ / 向かないところ

最初の 1 本

import numpy as np
import fullseye as fs

rng = np.random.default_rng(0)


def shot(mu_p, blend=0.0):                     # η 0.45・K 0.25 DN/e⁻・暗雑音 6 e⁻・12 bit。blend > 0 で 3×3 の平均を混ぜる
    y = np.clip(np.round(0.25 * (rng.poisson(0.45 * mu_p, (256, 256)) + rng.normal(0, 6, (256, 256))) + 64), 0, 4095)
    box = sum(np.roll(np.roll(y, i, 0), j, 1) for i in (-1, 0, 1) for j in (-1, 0, 1)) / 9.0
    return np.round((1 - blend) * y + blend * box)


for blend in (0.0, 0.3):
    mp = np.linspace(0.0, 30000.0, 50)
    st = [fs.ledger.emva_pair_statistics(shot(m, blend), shot(m, blend)) for m in mp]
    mu = np.array([s["mu"] for s in st])
    var = np.array([s["var_temporal"] for s in st])
    ptc = fs.ledger.emva_photon_transfer(mu[1:], var[1:], mu[0], var[0], float(mu[-1]))
    qe = fs.ledger.emva_quantum_efficiency(mp[1:], mu[1:], mu[0], float(mu[-1]), ptc["K"])
    white = fs.ledger.emva_noise_whiteness(shot(8000, blend), shot(8000, blend))
    print("blend %.1f: K %.3f η %.3f 白い %s" % (blend, ptc["K"], qe["eta"], white["white"]))
# blend 0.0: K 0.249 η 0.451 白い True
# blend 0.3: K 0.136 η 0.826 白い False

w = np.full((3, 3), 0.3 / 9)
w[1, 1] += 0.7
print(round(fs.ledger.emva_filter_distortion(w)["eta_factor"], 3))   # 1.829(η が何倍に見えるかの閉形式)

裏づけ