fullseye

モルフォロジー(形態学) — 使い方ガイド

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

モルフォロジー(数理形態学)は、画像の明るい塊・暗い塊の「形」を 構造要素(SE, structuring element = 近傍窓) で削る・盛る演算の一族です。土台は二つだけ — erosion(収縮 = SE 内の局所 min)dilation(膨張 = SE 内の局所 max)。この二つを合成すると opening(SE より小さい明点を消す)closing(SE より小さい暗穴を埋める) になり、元画像との差分をとると top-hat(SE より小さい明ディテール抽出)/ black-hat(暗ディテール抽出)/ morphological gradient(境界 = エッジ応答) になります。

入力はすべて image(グレースケール [0,1] 配列)、出力もすべて image。つまり「画像を入れると画像が返る」前処理・特徴抽出の一族で、特定サイズの構造だけ残す/消す のが仕事です。典型用途は照明ムラ補正、粒径解析、欠陥検出、文字・血管など細い構造の強調。SE のサイズはつまみ a で決まり(基本 op は 3→5→7→9 px の正方 SE)、どのつまみでも必ず効果が出る(恒等ではない)よう最小 3 px から始まります。この族には基本 4 演算に加え、SE 形状(正方/円盤)を変えた変種、OpenCV/skimage/scipy 由来の実装、そして SE ではなく 面積・直径・再構成 で成分を選ぶ属性フィルタ(粒径解析の本命)、骨格・穴埋めまで含みます。

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

明るい欠陥を背景ムラごと拾って数える定番の流れ。モルフォロジーが前処理を担い、下流の segmentation・features 族へデータ種が image → region → feature と繋がります。

flowchart LR
    A[image 元画像] -->|gopen 小明ノイズ除去| B[image]
    B -->|tophat 背景ムラ除去 / 明欠陥抽出| C[image]
    C -->|otsu 2値化| D[region 欠陥領域]
    D -->|blob_count 計数| E[feature 個数]

この族の内部代数(すべてコード上の定義そのもの)。erosion / dilation という二演算から opening / closing / top-hat / black-hat / gradient が導かれます。

flowchart TB
    IN["image f"] --> E["gerode ε(収縮)"]
    IN --> D["gdilate δ(膨張)"]
    E -->|"δ∘ε"| O["gopen γ(開)"]
    D -->|"ε∘δ"| C["gclose φ(閉)"]
    O -->|"f − γf"| TH["tophat 明ディテール"]
    C -->|"φf − f"| BH["bothat 暗ディテール"]
    E --> G["morph_grad δf − εf 境界応答"]
    D --> G

使い方(op グループ別)

呼び出しは 1 画像 + 2 スカラつまみ a,b∈[0,1]: fullseye.apply(img, "<name>", a, b)。基本 op では a が SE サイズ((3,5,7,9)[min(3,int(a*4))] px の正方 SE)、b は基本 4 演算では未使用。HALCON 別名は括弧で併記。

基本4演算(erosion / dilation / opening / closing)

同じ挙動を「scipy 系(正方 SE)/ HALCON 名 / OpenCV(楕円 SE)」の 3 実装で提供します。順序律 erosion ≤ opening ≤ 入力 ≤ closing ≤ dilation が全画素で成立(examples で数値検証済み)。

SE 形状バリアント(円盤 disk / 矩形 rect)

基本 4 演算と同じ演算を、SE の形を明示的に選んで実行します。円盤 SE は半径 1+int(3a)(1〜4 px, skimage.disk)、矩形 SE は (3,5,7,9) px の正方。円盤は方向依存の少ない等方な削り/盛りに向きます。

差分演算(ディテール抽出・境界応答)

元画像と opening/closing の差、あるいは dilation と erosion の差。いずれも出力は絶対値の最大で正規化され [0,1] に収まります。

属性フィルタ(面積・直径による成分選択)

SE ではなく 接続成分の属性(面積/直径) で残す・消すを決めます。SE 演算が形を丸めるのに対し、こちらは 残す成分の形を保ったまま 小さいものだけ消せる(粒径解析・小欠陥除去の本命)。しきい値はつまみ a に連動。

再構成・骨格・穴埋め

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

repo 直下で py -3.11 で実行可。基本 4 演算の順序律、top-hat の明点抽出、面積オープニングの「形保存」を数値で確認して PASS を出します。

import numpy as np
import fullseye

# GT 画像: 平坦 + 縦エッジ + SE より小さい明点/暗点(決定的・ノイズ無し)
g = np.full((48, 48), 0.3)
g[:, 24:] = 0.7            # 列24に縦エッジ(0.3 -> 0.7)
g[8:11, 8:11] = 0.95      # 左平坦域の小さな明点(3x3 < SE7)
g[38:41, 38:41] = 0.05    # 右平坦域の小さな暗点(3x3)
EPS = 1e-9

# --- 基本4演算(同一 SE=3x3: a=0.0 -> _k=3)---
er = np.asarray(fullseye.apply(g, "gerode",  0.0, 0.0))
di = np.asarray(fullseye.apply(g, "gdilate", 0.0, 0.0))
op = np.asarray(fullseye.apply(g, "gopen",   0.0, 0.0))
cl = np.asarray(fullseye.apply(g, "gclose",  0.0, 0.0))

# 1) erosion は anti-extensive(<=入力)/ dilation は extensive(>=入力)
assert (er <= g + EPS).all() and er.mean() < g.mean() - 1e-4
assert (di >= g - EPS).all() and di.mean() > g.mean() + 1e-4

# 2) 順序律: erosion <= opening <= 入力 <= closing <= dilation(全画素)
assert (er <= op + EPS).all() and (op <= g + EPS).all()
assert (g <= cl + EPS).all() and (cl <= di + EPS).all()

# 3) white top-hat(SE 7x7 > 3x3 明点)は SE より小さい明点を強く拾う
th = np.asarray(fullseye.apply(g, "tophat", 0.5, 0.0))
assert th[8:11, 8:11].mean() > th[18:30, 30:40].mean() + 0.1

# 4) 面積オープニング: SE と違い「形」を保ったまま小面積の明成分だけ消す
ao = np.asarray(fullseye.apply(g, "sk_area_opening", 0.9, 0.0))
assert ao[8:11, 8:11].mean() < g[8:11, 8:11].mean() - 0.1   # 明点(面積9)は除去
assert abs(ao[:, 24:].mean() - g[:, 24:].mean()) < 0.05      # 大面積の明領域は保持

print("PASS")

数式(必要な op のみ)

構造要素 $B$ とグレースケール画像 $f$ に対し、この族の中核は次の通り(コード上は erosion/dilation が SE 内の min/max、他はその合成・差分)。

\[(\varepsilon_B f)(x) = \min_{s\in B} f(x+s), \qquad (\delta_B f)(x) = \max_{s\in B} f(x+s)\] \[\gamma_B f = \delta_B(\varepsilon_B f)\ \text{(opening)}, \qquad \phi_B f = \varepsilon_B(\delta_B f)\ \text{(closing)}\] \[\mathrm{WTH}(f) = f - \gamma_B f, \qquad \mathrm{BTH}(f) = \phi_B f - f, \qquad g_B f = \delta_B f - \varepsilon_B f\]

これらから、同一 SE のもとで常に成り立つ 順序律(examples の GT でも検証):

\[\varepsilon_B f \ \le\ \gamma_B f \ \le\ f \ \le\ \phi_B f \ \le\ \delta_B f\]
なお tophat/bothat/morph_grad の出力は実装上 $\max \cdot $ で割って [0,1] に正規化されます。

サンプルデータ

デバッグには合成画像が扱いやすい: blobs(大小の塊 → opening/面積フィルタで粒径選別)、shapes(境界が明快 → morph_grad で確認)。文字・線構造なら page(skimage.data、細線 → f2_gray_skeleton / bothat 向き)。取得は import sample_images; sample_images.load("blobs")。一覧・ライセンスは ../../SAMPLES.md

参考文献(正典)

台帳は ../../../REFERENCES.md。この族のアルゴリズムの古典:


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