fullseye

平滑化・ランク・復元フィルタ — 使い方ガイド

呼び出しモデル: 2-D op は「1 画像 + 2 つのつまみ a,b∈[0,1]」。 out = fullseye.apply(img, "<op名>", a, b)。すべて image → image(2-D float、入力と同形状)。 検証済みサンプル: examples/gallery2d_smoothing_rank.py(この族 86 op を全数、有限性・型・決定性・効果 GT で assert)。

この族は何をする道具箱か

画像を 「均す / 直す / 汚す」 ための道具箱です。入力も出力も 2-D の濃淡画像(grayscale)で、撮像・計測の前処理の中核を担います。用途は 4 つに分かれます。

つまみ a,b の割り当ては op ごとに違いますが、多くの近傍フィルタで a = 窓の大きさや平滑化強度b = 二次パラメータ(分位・レンジ幅・ノイズ強度など)です。出力は設計上 [0,1] を超えることを許容します(先鋭化・逆畳み込みはレンジを越えうる。クリップはパイプライン境界側の責任)。

代表的なパイプライン(op の繋がり)

前処理の王道は「均す → 先鋭化 → 次段(閾値・エッジ)」。データ種は image のまま繋がります。

flowchart LR
  raw["生画像<br/>image(2-D)"] --> den["除去/平滑化<br/>gaussian · median · bilateral"]
  den --> shp["先鋭化<br/>unsharp · cv_sharpen"]
  shp --> out["整えた画像<br/>image(2-D)"]
  out -.->|次段へ| seg["閾値 / エッジ<br/>(別ファミリ)"]

もう一つの典型は「劣化を人工付加 → 復元」。学習・ロバスト性評価の骨格で、examples の GT もこの往復(ぼかし/ノイズ → 中央値・先鋭化で戻す)を検証しています。

flowchart LR
  clean["clean image"] --> deg["劣化を付加<br/>add_noise_white · simulate_motion · simulate_defocus"]
  deg --> rest["復元<br/>median · sk_tv · iv_richardson_lucy"]
  rest --> ev["復元画像<br/>→ 品質評価"]

使い方(op グループ別)

各 op は fullseye.apply(img, "<名前>", a, b) で呼びます。HALCON 相当名がある op は末尾に添えます。

線形平滑化(ぼかす)— smoothing

エッジ保存平滑化(均すが輪郭は残す)

拡散平滑化(PDE)

非局所 / 変分 / 変換ドメイン除去(強い denoiser)

先鋭化(sharpen)

背景 / 劣化シミュレーション

ランク(順位統計)— rank

中央値系:

最小 / 最大 / レンジ:

分位 / ランク:

トリム / salt-pepper / 幾何平均 / 最頻値:

復元(逆問題)— restoration

逆畳み込み:

欠損補間(inpaint):

超解像 / 位相:

ノイズ注入(学習 / 評価用)— noise

一般フィルタ — filtering

動く最小例(検証済み gallery2d_smoothing_rank から)

repo 直下に保存して py -3.11 <file> で実行できます(import fullseye が通る位置 = repo 直下)。汚した段差エッジを作り、この族の効果 GT(中央値の salt-pepper 除去 / Gaussian の分散低減 / ランクの順序保存 / unsharp の勾配増大 / 型契約)を assert します。

# -*- coding: utf-8 -*-
"""平滑化・ランク・復元フィルタ族の効果 GT(examples/gallery2d_smoothing_rank.py 準拠)。"""
import numpy as np
import fullseye

# --- 汚した段差エッジ(左 0.2 / 右 0.8 + salt-pepper)と Gaussian ノイズ場 --------
n = 64
rng = np.random.default_rng(7)
_, xx = np.mgrid[0:n, 0:n]
clean = np.where(xx >= n // 2, 0.8, 0.2).astype(np.float64)
sp = clean.copy()
m = rng.random((n, n))
sp[m < 0.05] = 0.0            # salt
sp[m > 0.95] = 1.0           # pepper
noisy = np.clip(0.5 + 0.15 * rng.standard_normal((n, n)), 0.0, 1.0)

mae = lambda a, c: float(np.mean(np.abs(a - c)))
grad_energy = lambda a: float(np.sum(np.diff(a, axis=1) ** 2) + np.sum(np.diff(a, axis=0) ** 2))

# (1) median(rank): salt-pepper を除去 → clean への誤差が半減以下(null=何もしない を圧倒)
med = fullseye.apply(sp, "median", 0.5, 0.0)
assert mae(med, clean) < 0.5 * mae(sp, clean), "median が salt-pepper を除去していない"

# (2) gaussian(smoothing): ノイズ場の分散を下げる
assert float(np.var(fullseye.apply(noisy, "gaussian", 0.5, 0.0))) < float(np.var(noisy))

# (3) rank の順序保存: min_filter <= 入力 <= max_filter(各画素)
mn = fullseye.apply(noisy, "min_filter", 0.5, 0.0)
mx = fullseye.apply(noisy, "max_filter", 0.5, 0.0)
assert np.all(mn <= noisy + 1e-9) and np.all(mx >= noisy - 1e-9), "min<=in<=max が壊れている"

# (4) unsharp(smoothing): ぼけを先鋭化 → 勾配エネルギー増
blur = fullseye.apply(clean, "gaussian", 0.6, 0.0)
sharp = fullseye.apply(blur, "unsharp", 0.6, 0.5)
assert grad_energy(sharp) > grad_energy(blur), "unsharp が先鋭化していない"

# (5) 型契約(image -> image): 2-D float、同形状、有限。復元/エッジ保存系も含めて確認
for name in ("bilateral", "sigma_image", "anisotropic_diffusion", "iv_richardson_lucy"):
    out = np.asarray(fullseye.apply(noisy, name, 0.5, 0.5))
    assert out.ndim == 2 and out.shape == noisy.shape
    assert np.issubdtype(out.dtype, np.floating) and np.all(np.isfinite(out))

print("PASS")

数式(必要な op のみ)

Gaussian(gaussian, gauss_filter) — 分離 Gaussian カーネルとの畳み込み。σ はつまみ a で決まる($\sigma = 0.3 + 2.7a$)。

\[G_\sigma(x,y)=\frac{1}{2\pi\sigma^2}\exp\!\left(-\frac{x^2+y^2}{2\sigma^2}\right),\qquad I'=G_\sigma * I\]

Bilateral(bilateral) — 空間の近さと輝度の近さの積を重みにする(エッジを跨いだ平均を避ける)。

\[I'(p)=\frac{1}{W_p}\sum_{q\in\Omega} \exp\!\left(-\frac{\lVert p-q\rVert^2}{2\sigma_s^2}\right)\exp\!\left(-\frac{(I(p)-I(q))^2}{2\sigma_r^2}\right) I(q)\]

ここで $\sigma_s$ は空間つまみ a、$\sigma_r$ は値域つまみ b、$W_p$ は重みの総和。

Unsharp mask(unsharp) — 元画像に「元 − ぼかし」(高域)を足し戻す。$\text{amount}=1.5a$、マスクの $\sigma=0.5+1.5b$。

\[I' = I + \text{amount}\,\bigl(I - G_\sigma * I\bigr)\]

σ フィルタ(sigma_image, eliminate_sp) — 局所平均 $\mu$ から $\pm\sigma$ 以内の近傍だけで平均を取る(外れ値を混ぜない、Lee 1983)。

\[I'(p)=\frac{\sum_{q:\,|I(q)-\mu(p)|<\sigma} I(q)}{\#\{q:\,|I(q)-\mu(p)|<\sigma\}}\]

Perona-Malik 異方性拡散(anisotropic_diffusion) — 伝導率 $c$ を勾配で抑え、エッジを保ちながら平坦部だけ拡散させる反復 PDE($K$ はつまみ b)。

\[\frac{\partial u}{\partial t}=\operatorname{div}\!\bigl(c(\lVert\nabla u\rVert)\,\nabla u\bigr),\qquad c(s)=\exp\!\left(-\left(\frac{s}{K}\right)^2\right)\]

Richardson-Lucy 逆畳み込み(iv_richardson_lucy) — 観測 $d=\text{psf}\otimes u$ を満たすよう推定 $u$ を乗算的に更新(反復数はつまみ a)。

\[u^{(t+1)} = u^{(t)}\cdot\left(\text{psf}^{\!*} \otimes \frac{d}{\text{psf}\otimes u^{(t)}}\right)\]

サンプルデータ

デバッグ・動作確認には ../../SAMPLES.md の 2-D 画像源が使えます(外部 DL 不要)。合成の checker_noisy / blobs(import sample_images; sample_images.load("checker_noisy"))は平滑化・ランク除去の効果を見るのに向き、skimage.datacoins / camera(BSD/public domain)はエッジ保存や先鋭化・復元(逆畳み込み・inpaint)の題材になります。

参考文献(正典)

台帳 ../../../REFERENCES.md に op ごとの出典を収録。この族の中核アルゴリズムの古典は以下。


© 2026 Kazufumi Furuse — Fullseye operator documentation. Licensed under Apache-2.0.