fullseye

領域(region)処理 — 使い方ガイド

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

gallery2d_region二値マスク(前景=1 / 背景=0 の 2 次元 float 配列、以下「領域(region)」)の 形を整え・選り分け・測る ためのオペレータ族です。しきい値処理(segmentation 族)や検出で得た 生のマスクは、ギザギザ・小さな穴・ノイズ塊・複数成分が混ざった状態になりがちです。この族は、 モルフォロジー(収縮・膨張・開閉)で輪郭をならし、穴を埋め、連結成分を選別し、 スケルトン(骨格)や境界を抜き出し、外接/内接図形や距離変換で形状を定量化します。 用途は前処理後のマスク整形・ブロブ選別・形状計測で、検査(欠陥/寸法)・OCR 前処理・ 顕微鏡・医用・歩行 Physical AI の踏み場領域処理などに向きます。

呼び出しは全オペレータ共通で「1 領域 + 2 スカラつまみ a,b∈[0,1]」です: fullseye.apply(region, "op_name", a, b)。多くは領域→領域(out_sort=region)ですが、 距離変換は領域→画像(out_sort=image、連続値マップ)、ラン長・形状計測は 領域→特徴量(out_sort=feature、有限スカラ)を返します。グレースケール画像を渡した場合は 0.5 で二値化されてから処理されます(apply(..., coerce=True) が既定)。

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

しきい値処理で得た荒いマスクを、整形→成分選別→計測へと繋ぐのが典型です。

flowchart LR
    IMG[画像 image] -->|threshold / otsu| M[領域 region 荒いマスク]
    M -->|reg_close / closing_circle| C[隙間を閉じた領域]
    C -->|fill_holes / fill_up| F[穴埋め済み領域]
    F -->|select_largest / remove_small| S[目的成分だけの領域]
    S -->|region_boundary / get_region_contour| B[境界 region]
    S -->|r3_region_features| FEAT[形状特徴 feature]

骨格化・距離変換は「形の芯」や「厚みマップ」を取り出す別の枝です。

flowchart LR
    S[整形済み領域 region] -->|skeleton / thinning| SK[1画素幅の骨格 region]
    SK -->|pruning| SKP[ヒゲを刈った骨格]
    SKP -->|junctions_skeleton| J[分岐点 region]
    S -->|distance_transform / cv_dist| DT[厚みマップ image]
    S -->|closest_point_transform| CPT[背景までの距離 image]

使い方(op グループ別)

以下は実在の op のみ。呼び出しは fullseye.apply(region, "<op>", a, b)

モルフォロジー(収縮・膨張・開・閉)

前景を構造要素(円 disk / 矩形 rect)で削る・太らせる・開く(erode→dilate)・閉じる(dilate→erode)。 つまみ a が構造要素の大きさ/反復回数を決めます(膨張は面積増、収縮は面積減、 開は突起・小塊を除き、閉は隙間・小穴を埋める)。

穴埋め・成分選別(トポロジ整形)

穴(内部背景)を埋める、連結成分を面積や位置で選り分ける、補集合を取る。

スケルトン・細線化(1 画素幅の骨格)

領域を連結性を保ったまま 1 画素幅へ細らせ、形の「芯」を得る。

HALCON との差(重要): HALCON の skeleton は Eckhardt–Maderlechner 法、 Fullseye の既定 skeleton/sk_skeleton は Zhang–Suen 法(skimage)で、 アルゴリズムが異なります。HALCON からの移植で骨格の枝ぶりを 近づけたい場合は、同系アルゴリズムの em_skeleton を使ってください (独立実装だが、公表されている EM93 の参照出力とビット単位一致を検証済み。 HALCON 実機との直接照合のみ未実施)。 スケルトンは実装依存性が強い代表例で、細い枝(ヒゲ)の生え方・分岐点の 画素位置・斜め 2 画素幅部の残り方は一致しません。したがって junctions_skeleton / r2_split_skeleton_lines の分岐数・線分数も HALCON と同数にはなりません。一致するのは位相的な性質 (連結性の保存・穴の数・おおむね 1 画素幅)です。HALCON からの移植で 分岐数などを閾値に使っている場合は、pruning でヒゲを刈ってから 閾値を取り直してください。sk_medial(距離変換の稜線)はさらに別物で、 ヒゲが多い代わりに局所半径(厚み)が得られます。

境界・輪郭抽出

領域から外周(1 画素の縁)を取り出す。いずれも元領域の部分集合になる薄いリング。

凸包・外接/内接図形(形状近似)

領域を凸包・外接矩形/円・内接矩形/円などの単純図形で包む/満たす。計測やアライメントの土台。

距離変換(領域 → 画像)

領域を連続値マップに変換する。出力は image(0..1 に正規化)。

分割・クリップ・幾何再配置

領域を窓で切り出す、格子や首(くびれ)で分割する、極座標へ写す、細い横ランを消す。

ラン長・形状の計測(領域 → 特徴量)

領域を 1 つの有限スカラに要約する(out_sort=feature)。

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

repo 直下で py -3.11 から実行可能な自己完結スニペット(検証済みギャラリーの GT を写経・短縮)。 モルフォロジー・反転・穴埋め・境界・距離変換の既知性質を機械検証します。

import numpy as np
import fullseye

# --- 合成の二値領域(前景=1 の中実円)を作る ---
n = 64
yy, xx = np.mgrid[0:n, 0:n]
disk = (((yy - n // 2) ** 2 + (xx - n // 2) ** 2) < (n * 0.28) ** 2).astype(np.float64)
area = float(disk.sum())

# 膨張は面積を増やし、収縮は減らす(モルフォロジーの基本性質)
dil = fullseye.apply(disk, "reg_dilate", 0.5, 0.5)
ero = fullseye.apply(disk, "reg_erode", 0.5, 0.5)
assert dil.sum() > area > ero.sum()

# 反転は厳密な二値補集合
inv = fullseye.apply(disk, "invert_region", 0.5, 0.5)
assert np.array_equal(inv > 0.5, disk <= 0.5)

# 穴あき円環を穴埋めすると中実円に戻る(面積が増える)
annulus = disk.copy()
annulus[(((yy - n // 2) ** 2 + (xx - n // 2) ** 2) < (n * 0.12) ** 2)] = 0.0
filled = fullseye.apply(annulus, "fill_holes", 0.5, 0.5)
assert filled.sum() > annulus.sum()
assert abs(filled.sum() - area) < 1e-9

# 境界抽出は薄いリングで、かつ元領域の部分集合
bnd = fullseye.apply(disk, "region_boundary", 0.5, 0.5)
assert 0 < bnd.sum() < area * 0.4
assert np.all((bnd > 0.5) <= (disk > 0.5))

# 距離変換(領域 -> 画像): 中心が縁より高い(距離のピークは内側)
dt = fullseye.apply(disk, "distance_transform", 0.5, 0.5)
assert dt[n // 2, n // 2] > dt[n // 2, n // 2 + int(n * 0.28) - 2] + 0.3

print("PASS")

op を数珠つなぎにする実務パターン(閉→穴埋め→最大成分→境界)も同じ呼び方で書けます。

import numpy as np
import fullseye

n = 64
yy, xx = np.mgrid[0:n, 0:n]
# 大きな塊(内部に穴)+ 離れた小ノイズ塊
mask = (((yy - 34) ** 2 + (xx - 30) ** 2) < 15 ** 2).astype(np.float64)
mask[(((yy - 34) ** 2 + (xx - 30) ** 2) < 4 ** 2)] = 0.0   # 穴
mask[(((yy - 8) ** 2 + (xx - 55) ** 2) < 3 ** 2)] = 1.0    # 小ノイズ

closed  = fullseye.apply(mask,    "reg_close",       0.4, 0.5)
filled  = fullseye.apply(closed,  "fill_holes",      0.5, 0.5)
largest = fullseye.apply(filled,  "select_largest",  0.5, 0.5)
edge    = fullseye.apply(largest, "region_boundary", 0.5, 0.5)

# 最大成分選択は小ノイズを消す / 境界は最大成分の薄い部分集合
assert largest.sum() < filled.sum()
assert np.all((edge > 0.5) <= (largest > 0.5))
assert edge.sum() < largest.sum() * 0.5

# region -> feature: ラン長平均は正、コンパクトネスは円で ~1 近傍
disk = (((yy - 32) ** 2 + (xx - 32) ** 2) < 14 ** 2).astype(np.float64)
assert float(fullseye.apply(disk, "r2_runlength_features", 0.5, 0.5)) > 0
comp = float(fullseye.apply(disk, "r3_region_features", 0.9, 0.5))  # a>=0.5 -> compactness
assert 0.8 < comp < 1.6

print("PASS")

数式(必要な op のみ)

領域 $A \subseteq \mathbb{Z}^2$(前景画素の集合)、構造要素 $B$ とする。

モルフォロジー(Minkowski 演算)。収縮・膨張・開・閉:

\[A \ominus B = \{\,z : B_z \subseteq A\,\}, \qquad A \oplus B = \{\,z : B_z \cap A \neq \varnothing\,\}\] \[A \circ B = (A \ominus B) \oplus B, \qquad A \bullet B = (A \oplus B) \ominus B\]

境界(boundary / region_boundary)は領域から収縮を引いた薄い縁:

\[\partial A = A \setminus (A \ominus B)\]

距離変換。distance_transform は前景各点から背景までのユークリッド距離、 closest_point_transform は各点から最も近い前景までの距離(= 補集合の距離変換):

\[D_A(p) = \min_{q \notin A} \lVert p - q \rVert, \qquad D^{\ast}_A(p) = \min_{q \in A} \lVert p - q \rVert\]

コンパクトネス(r3_region_featuresa≥0.5)。周囲長 $P$、面積 $A$ に対し (理想的な円で 1、細長いほど大):

\[C = \frac{P^2}{4\pi A}\]

サンプルデータ

この族のデバッグには 二値化しやすい形状画像が便利です。詳細は ../../SAMPLES.md を参照。 import sample_images; sample_images.load("blobs")(合成ブロブ、穴埋め/成分選別に好適)や sample_images.load("coins")(skimage.data、BSD/public。しきい値→モルフォロジー整形の題材)を fullseye.apply(img, "otsu") などで二値化してから領域 op を試せます。

参考文献(正典)

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


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