fullseye

良品だけから学んで外れたパッチを指す(古典特徴の「位置ごとの正規分布」と「記憶バンク」、再現率を保証する運用点)

できること

良品の画像だけを何枚か渡すと、パッチ(格子の小窓)ごとの古典特徴を覚え、新しい 1 枚のどのパッチが良品から外れているかを異常地図で返します。覚え方は 2 つあります。

特徴 patch_features は多重解像度の勾配(構造テンソル)・Gabor 応答・回転不変の一様 LBP のヒストグラム・パッチの周波数帯・色(反対色)で、深層特徴は使いません(決定的で、チャネルの意味を読めます)。採点は方法に依存しない anomeval の族で: 画素 AUROC・AUPRO・画像 AUROC・F1 最大(anomaly_pixel_f1_max / anomaly_image_f1_max)。そして ★再現率を保証する運用点 anomaly_operating_point: 持ち出した欠陥 k 枚のうち何枚を拾うかを Clopper–Pearson の下限(信頼水準つき)で決め、保証できないほど欠陥が少ないときは閾値を返さず拒否します。

What it does

Anomaly detection from good samples only, with classical, deterministic patch features instead of a pretrained network. patchdist_* fits a multivariate normal per grid position and scores by Mahalanobis distance; since n good images give a singular covariance when n <= D, the shrinkage is declared (Ledoit–Wolf analytic intensity, or a number) and shrinkage="none" is refused for a singular covariance. pool="global" fits one distribution to all patches for stationary textures. patchbank_* keeps a memory bank thinned by greedy k-centre covering, reports the covering radius, and optionally appends the grid position to find a correct pattern in the wrong place. Evaluation uses the method-independent anomeval family (pixel / image AUROC, AUPRO, F1 max) and anomaly_operating_point, which picks the highest threshold whose defect recall is guaranteed at a stated confidence with an exact Clopper–Pearson limit and refuses when there are too few defective images. On a stochastic texture the per-position normal distribution loses to the pooled distribution and the memory bank at 5, 20 and 80 good images; a memory bank at 10 % keeps its accuracy at about a tenth of the scoring time; the guaranteed operating point keeps its declared confidence in 95.7 % of 300 repetitions against 50.3 % for the unguaranteed quantile.

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

推奨パイプライン

patchbank_fit → patchbank_score(必要なら anomaly_pro で採点、anomaly_operating_point で閾値)

良品を学び → 試験の 1 枚ごとに異常地図 → 画像ごとの最大値を持ち出した良品・欠陥で anomaly_operating_point にかけて運用の閾値を決める。位置が決まった品物は position_weight、模様が毎回違う面は patchdist_fit(pool="global") も試す。

代替

位置を合わせた良品なら varmodel_fit(画素ごとの帯、誤検出率を宣言)。基準 1 枚との差なら compare_to_golden。1 枚だけで周期的な背景の上の孤立した欠陥なら dc_rpca_sparse。

限界

古典の手作り特徴で、深層特徴の方法とは同じ数字にならない(実データのテクスチャで画素 AUROC は最良でも 0.77、画像ごとの AUROC は 0.5〜0.6)。良品に欠陥が混ざると両方式とも鈍る(記憶バンクのほうが緩やか)。位置の重みは位置の違いを言うだけで、意味(2 つの部品の組合せの正しさ)は言えない。

実寸校正

異常地図は特徴空間の距離で、画素値の単位ではありません。欠陥の大きさを実寸で言うなら、既知寸法の的を同じ光学系で測り mm_per_px_from_reference → pixel_to_world(地図の分解能は格子の刻み stride px)。

最初の 1 本

import numpy as np
from scipy import ndimage as ndi
import fullseye as fs

rng = np.random.default_rng(0)
tex = lambda: ndi.gaussian_filter(rng.standard_normal((64, 64)), 2.0) * 4.0   # 1 枚ごとに模様が違う確率場
goods = np.stack([tex() for _ in range(20)])                                    # 良品 20 枚
model = fs.ledger.patchbank_fit(goods, coreset=0.1)                             # 記憶 10 %
print("記憶 %d / %d 個、被覆半径 %.2f" % (model["n_memory"], model["n_total"], model["covering_radius"]))

test = tex()
test[30:36, 20:40] += 3.0                                                       # 硬い縁の傷
r = fs.ledger.patchbank_score(test, model)
print("画像の異常度 %.2f、最大の場所 %s" % (r["image_score"], tuple(int(i) for i in np.unravel_index(r["map"].argmax(), r["map"].shape))))

裏づけ