fullseye

HALCON 拡充 tier(hx_) — 使い方ガイド

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

hx_ 族は、Fullseye の既存 registry では未カバーだった 実在の HALCON operatordata/halcon_operators.json に載るもの)を、外部 HALCON に依存せず自作 numpy で genuine に再実装した「拡充 tier」です。1 つのファイルに複数分野が同居する寄せ集めですが、全 op が Fullseye 共通の進化契約 fn(v, a, b) を守ります — 入力 v(1 枚の 2-D 画像 or 二値 region、あるいは XLD 輪郭 dict)と 2 つのスカラつまみ a, b ∈ [0,1] を受け取り、1 つの「種(sort)」を別の種へ写します。呼び出しは全 op 共通で fullseye.apply(v, "<op名>", a, b) です。

扱う入出力の「種」は 4 つ — image[0,1] の 2-D float 配列)、region{0,1} の 2-D マスク)、contour(XLD 輪郭 {"shape": (H,W), "cs": [Nx2 の (row,col) 配列, ...]})、feature(有限スカラ)。この族は「幾何 region を作る(gen_)」「region を形態変換する(erosion1/dilation1/…)」「グレー画像を照明面近似・陰影・周波数マスクで加工する(fit_surface/shade/gen_lowpass/…)」「画像を region へ切り出す(char_threshold/lowlands/…)」「XLD 輪郭を整形しフィットして 1 つのスカラ計測値を出す(sort/split/fit_circle/…)」「陰影から光源方向・アルベドを推定する(estimate_)」といった、産業ビジョンの前処理〜計測〜復元の断片を横断的に供給します。

契約の根拠: 検証済みサンプル examples/gallery2d_halcon_ext.py が全 81 op を叩き、(1) 出力に NaN/Inf が無い (2) 宣言 out_sort と型が一致 (3) 同じ入力・ノブなら 2 回の呼び出しがビット一致(乱数 op も固定 seed)、を機械検証します。以下の説明はすべて実装 backends_halcon_ext.py を読んだ挙動に基づきます。

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

hx_ 族は「種を写す」ので、ある op の出力の種が次の op の入力の種と一致すれば繋がります。産業計測で頻出する 2 本の鎖:

flowchart LR
    IMG[image<br/>入力画像]
    R1[region<br/>合成マスク]
    R2[region<br/>膨張後]
    F1[feature<br/>点→region 距離]

    IMG -->|hx_gen_circle a=半径| R1
    R1 -->|hx_dilation1 a=SE半径| R2
    R2 -->|hx_expand_region 隙間結合| R2
    R2 -->|hx_distance_pr a,b=クエリ点| F1

region を「作って(gen)→ 形態で整えて(dilation/expand)→ 計測する(distance)」流れ。全ステージが region を受け渡し、最後に 1 スカラへ落ちます。

flowchart LR
    C0[contour<br/>XLD 入力]
    C1[contour<br/>ソート後]
    C2[contour<br/>ドメイン内]
    C3[contour<br/>線分分割]
    FC[feature<br/>円フィット残差]

    C0 -->|hx_sort_contours 重心順| C1
    C1 -->|hx_clip_contours a,b=margin| C2
    C2 -->|hx_split_contours a=RDP eps| C3
    C2 -->|hx_fit_circle_contour Kåsa| FC

XLD 輪郭を「整列(sort)→ 定義域でクリップ(clip)→ 支配点で分割(split)」と整形し、別経路で形状フィット(fit_circle)に掛けて丸さの残差を得る流れ。整形系(→contour)と計測系(→feature)が同じ contour を共有します。

使い方(op グループ別)

呼び出しは全て fullseye.apply(v, "<name>", a, b)v の種は各グループの見出しに記載。feature 出力は Python floatregion/image 出力は float64 の 2-D 配列、contour 出力は {"shape","cs"} dict が返ります。

A. 幾何 region の生成(image → region、v は画布サイズの参照にのみ使う)

B. 画像の定義域を region 化(image → region)

C. Region の形態・移動(region → region、v は 0.5 で二値化)

D. Region の点包含・距離(region → feature)

E. グレー画像の空間フィルタ・照明/形状加工(image → image)

F. 周波数マスク画像の生成(image → image、v は形状の参照にのみ使う)

G. 画像 → region のセグメンテーション(image → region)

H. テクスチャ特徴(image → feature)

I. 陰影からの光源推定・1D 計測(image → feature)

J. XLD 輪郭の整形(contour → contour、v{"shape","cs"} dict)

K. XLD 輪郭の計測・形状フィット(contour → feature)

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

repo 直下で py -3.11 <file>.py として実行可。examples/gallery2d_halcon_ext.py の GT チェックを fullseye.apply 経由に落とした自己完結版で、3 種(生成・形態・輪郭フィット)を横断します。

# repo 直下で: py -3.11 this_file.py  → 最後に PASS を印字
import numpy as np
import fullseye


def image_normal(n=48):
    """勾配 + 円板 + 市松 + 微小ノイズ(gallery の 'normal' 入力と同構成)。"""
    yy, xx = np.mgrid[0:n, 0:n].astype(np.float64)
    grad = xx / (n - 1)
    disk = ((yy - n * 0.35) ** 2 + (xx - n * 0.4) ** 2) < (n * 0.18) ** 2
    checker = ((xx.astype(int) // 6 + yy.astype(int) // 6) % 2) * 0.15
    rng = np.random.default_rng(20260812)
    return np.clip(0.35 * grad + 0.45 * disk + checker + 0.03 * rng.standard_normal((n, n)), 0, 1)


def region_disk(n=48):
    yy, xx = np.mgrid[0:n, 0:n]
    return (((yy - n // 2) ** 2 + (xx - n // 2) ** 2) < (n * 0.25) ** 2).astype(np.float64)


img = image_normal()
reg = region_disk()
n = img.shape[0]

# 1) hx_gen_circle は二値 region を作り、半径ノブ a が面積を単調に増やす(beat-the-null)。
small = fullseye.apply(img, "hx_gen_circle", 0.0, 0.0)   # image -> region (ndarray)
big = fullseye.apply(img, "hx_gen_circle", 1.0, 0.0)
assert set(np.unique(big)) <= {0.0, 1.0}, "生成 region が二値でない"
assert big[n // 2, n // 2] == 1.0 and big[0, 0] == 0.0, "中心内/隅外が不成立"
assert big.sum() > small.sum() * 3, "半径ノブが面積を増やさない"

# 2) region 形態の単調性: 侵食 < 原面積 < 膨張。
ar_orig = float(reg.sum())
ar_er = float(fullseye.apply(reg, "hx_erosion1", 0.5, 0.0).sum())   # region -> region
ar_di = float(fullseye.apply(reg, "hx_dilation1", 0.5, 0.0).sum())
assert ar_er < ar_orig < ar_di, f"morphology 単調性が破れた {ar_er}<{ar_orig}<{ar_di}"

# 3) hx_fit_circle_contour: 円周点への残差≈0、四角の点には大きい残差(beat-the-null)。
t = np.linspace(0.0, 2 * np.pi, 60, endpoint=False)
circ = {"shape": (n, n), "cs": [np.column_stack([24 + 15 * np.sin(t), 24 + 15 * np.cos(t)])]}
lo, hi = n * 0.2, n * 0.8
ts = np.linspace(0.0, 1.0, 12, endpoint=False)
top = np.column_stack([np.full_like(ts, lo), lo + (hi - lo) * ts])
right = np.column_stack([lo + (hi - lo) * ts, np.full_like(ts, hi)])
bot = np.column_stack([np.full_like(ts, hi), hi - (hi - lo) * ts])
left = np.column_stack([hi - (hi - lo) * ts, np.full_like(ts, lo)])
square = {"shape": (n, n), "cs": [np.vstack([top, right, bot, left, top[:1]])]}
res_circ = fullseye.apply(circ, "hx_fit_circle_contour", 0.5, 0.5)     # contour -> feature (float)
res_sq = fullseye.apply(square, "hx_fit_circle_contour", 0.5, 0.5)
assert res_circ < 1e-3, f"円周点への円フィット残差が大きすぎ {res_circ}"
assert res_sq > res_circ * 20 + 1e-3, f"円モデルが四角にも当たっている {res_circ} vs {res_sq}"

print("PASS")

数式(必要な op のみ)

Kåsa 代数円フィットhx_fit_circle_contour)— 点 $(x_i,y_i)$ に対し $x^2+y^2 = 2c_x x + 2c_y y + (r^2 - c_x^2 - c_y^2)$ を線形最小二乗で解き、中心 $(c_x,c_y)$・半径 $r=\sqrt{c_z + c_x^2 + c_y^2}$ を得て、残差を返す:

\[\mathrm{RMS} = \sqrt{\frac{1}{N}\sum_{i=1}^{N}\bigl(\sqrt{(x_i-c_x)^2+(y_i-c_y)^2}\; - \; r\bigr)^2}\]
Gabor カーネルhx_gabor)— 方位 $\theta=a\pi$、周波数 $f=0.08+0.35b$、包絡 $\sigma=2.2$。回転座標 $x_\theta = x\cos\theta + y\sin\theta$ に対し DC 除去した実 Gabor(応答の大きさをカーネルの L1 ノルム $\sum g $ で割って返す = 画像に依らない固定スケール):
\[g(x,y) = \exp\!\Bigl(-\tfrac{x_\theta^2 + y_\theta^2}{2\sigma^2}\Bigr)\,\cos\!\bigl(2\pi f\, x_\theta\bigr)\]

Lambertian 光源方位hx_estimate_tilt_lr)— 画像勾配 $E_x,E_y$ の平均から光源 tilt を推定($[0,1]$ 正規化):

\[\mathrm{tilt} = \operatorname{atan2}\bigl(\langle E_y\rangle,\ \langle E_x\rangle\bigr)\]

視差→深度hx_disparity_to_xyz)— 焦点距離 $f$・基線 $B$ の平行ステレオで、視差 $d$(画素)から深度 $Z$:

\[Z = \frac{f\,B}{d}\]

サンプルデータ

デバッグ用の 2-D 画像源は ../../SAMPLES.md(Fullseye サンプルカタログ)を参照。外部 DL 不要で使える合成画像 gradient/blobs/shapes/checker_noisy(Fullseye 自作)と、skimage.data 由来の coins/camera/page/cell(BSD / public domain)が import sample_images; sample_images.load("<name>") で取得できます。この族なら文字抽出(hx_char_threshold)に page、形態・生成 region の重畳確認に blobs/shapes が向きます。

参考文献(正典)

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


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