fullseye

良品を並べて「いつもと違う」を言う(良品統計の変動モデル、誤検出率の宣言つき)

できること

位置をそろえた良品の画像を何枚か渡すと、画素ごとの「いつもの値」(中心)と「いつものばらつき」を覚え、新しい 1 枚のどの画素が許容帯の外にあるか、何 σ 外れたかを領域ごとに説明します。外観検査の古典(良品統計・バリエーションモデル)ですが、帯の幅 k を手で決めずに、1 画素あたりの誤検出率を宣言して分布から決めます。

What it does

A per-pixel variation model learned from aligned good samples, with the band half-width derived from a declared per-pixel false-alarm rate rather than tuned by hand: a t prediction interval for mean/std, an empirical-Bayes moderated t (Smyth 2004) for few samples, and a Monte-Carlo pivot for the robust median/MAD variant. Measured false-alarm rates on synthetic normal good samples fall inside the binomial 4-sigma band for all four estimators. The stack’s alignment is measured to 1/20 px and a misaligned stack is refused (or registered by an all-pass Fourier shift); forcing a 1-2 px misaligned stack fattens the band on edges 22-fold and misses every edge defect. Held-out calibration re-derives k from good images when real data break the model, and every out-of-band region is explained by area, sign and peak sigma. Method-independent metrics (pixel AUROC, AUPRO to FPR 0.3, image AUROC, per-region overlap) compare it with RPCA and golden comparison.

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

推奨パイプライン

varmodel_alignment → varmodel_fit → varmodel_calibrate → varmodel_score

良品群のずれを測り(許容を超えたら align="translate" で合わせるか治具を直す)→ 宣言した誤検出率で帯を当てはめ → 当てはめに使わなかった良品で帯の幅を測り直し → 試験の 1 枚を当てて、帯の外を領域ごとに説明する。方法の比べ合いは anomaly_pixel_auroc / anomaly_pro。

代替

良品が 1 枚しか無いなら compare_to_golden(基準 1 枚との差、閾値は手で決める)。周期的な背景の上の孤立した欠陥なら dc_rpca_sparse(1 枚の低ランク分解)。

限界

位置合わせは並進だけです。回転・拡大・照明の違いが残る実画像では、閉形式の誤検出率は守られません(VisA pcb1 で宣言 1e-3 に対し 3.8 %、持ち出しの良品で較正しても 2.4 %)。確率的なテクスチャ(DAGM 2007)では偶然並みです。

実寸校正

帯は画素値の単位のままです。欠陥の大きさを実寸で言うなら、既知寸法の的を同じ光学系で測り mm_per_px_from_reference → pixel_to_world。

最初の 1 本

import numpy as np
import fullseye as fs

rng = np.random.default_rng(0)
yy, xx = np.mgrid[0:64, 0:64]
base = 0.5 + 0.3 * np.sin(xx / 4.0) * np.cos(yy / 5.0)         # 良品の模様
goods = base + 0.02 * rng.standard_normal((20, 64, 64))         # 位置のそろった良品 20 枚
model = fs.ledger.varmodel_fit(goods, spread="moderated", far=1e-3)
print("k = %.2f(宣言 1e-3、%s)" % (model["k"], model["k_source"]))

test = base + 0.02 * rng.standard_normal((64, 64))
test[20:24, 30:34] += 0.2                                        # 雑音の 10 倍の傷
r = fs.ledger.varmodel_score(test, model)
for g in r["regions"][:3]:
    print("面積 %d px、ピーク %+.1f σ(%s)、外接矩形 %s" % (g["area"], g["peak_z"], g["polarity"], g["bbox"]))

裏づけ