良品の画像だけを何枚か渡すと、パッチ(格子の小窓)ごとの古典特徴を覚え、新しい 1 枚のどのパッチが良品から外れているかを異常地図で返します。覚え方は 2 つあります。
patchdist_fit / patchdist_score): 格子の位置ごとにパッチ特徴の多変量正規分布を当てはめ、マハラノビス距離で採点します。良品が n 枚で特徴が D 次元なら共分散は n ≦ D で特異になるので、★縮小推定を宣言します(shrinkage="auto" は Ledoit–Wolf の解析的な強さ、数を渡せば固定)。縮小を宣言せずに特異なら ValueError で拒否します(良品 5 枚・51 次元では階数 4)。pool="global" は全位置・全画像のパッチから分布を 1 つ当てはめ、位置が決まらない確率的なテクスチャに向きます。patchbank_fit / patchbank_score): 良品のパッチ特徴を記憶し、k 近傍の距離で採点します。記憶は貪欲な k 中心の被覆で間引き(coreset)、★被覆半径(どの良品パッチも記憶のこの距離以内)を返します。position_weight を足すと格子の位置を特徴に加え、「正しい模様が違う場所にある」論理的な異常を指します。特徴 patch_features は多重解像度の勾配(構造テンソル)・Gabor 応答・回転不変の一様 LBP のヒストグラム・パッチの周波数帯・色(反対色)で、深層特徴は使いません(決定的で、チャネルの意味を読めます)。採点は方法に依存しない anomeval の族で: 画素 AUROC・AUPRO・画像 AUROC・F1 最大(anomaly_pixel_f1_max / anomaly_image_f1_max)。そして ★再現率を保証する運用点 anomaly_operating_point: 持ち出した欠陥 k 枚のうち何枚を拾うかを Clopper–Pearson の下限(信頼水準つき)で決め、保証できないほど欠陥が少ないときは閾値を返さず拒否します。
varmodel_*)は同じ確率場で画素 AUROC 0.785、記憶バンクは 0.881。織り(位相が毎回ランダム)では位置合わせが成り立たず変動モデルは拒否し、パッチ特徴は 0.97 の AUPRO で当てます。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.
pool="global" か記憶バンクに替える。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)。
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))))
patch_features / patchdist_fit / patchdist_score / patchbank_fit / patchbank_score、評価 anomaly_pixel_auroc / anomaly_pro / anomaly_image_auroc / anomaly_region_overlap / anomaly_pixel_f1_max / anomaly_image_f1_max / anomaly_operating_pointpoc_patch_anomaly —— 合成の 3 つの世界で 8 変種 + 変動モデル + 無作為、良品 5・20・80 枚、論理的な異常、記憶の間引き、縮小の宣言、運用点の保証の 300 回、公開実データ。anomaly_eval_metrics —— 画素 AUROC と PRO が違うものを数えること。