fullseye

2-D 幾何変換 — 使い方ガイド

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

gallery2d_geometry は「画素・点をどこへ動かすか(写像)」を担う族で、視覚パイプラインの 前処理・姿勢正規化・データ拡張・計測 の土台になる。入力は 3 種類 — 濃淡画像 image([0,1] の 2-D float64)、二値領域 region(同じ形の 0/1 マスク)、 XLD 輪郭 contour({"shape": (H,W), "cs": [Nx2 の (row,col) 点列]})— で、出力は 入力を写した image/region、抽出した点集合 contour、または計測スカラ feature。 中身は 5 カテゴリに分かれる: geometry(回転・拡大縮小・鏡映・アフィン/射影/極座標変換)、 transform(ラドン変換サイノグラム・Haar/Daubechies ウェーブレット)、 subpix(濃淡曲面の臨界点をサブピクセルで抽出)、 xldgeom(輪郭点集合の幾何量と整形)、deformation(制御点ベースの自由変形)。

呼び出しモデルは族全体で統一されている: 1 入力 + 2 スカラつまみ a,b∈[0,1]fullseye.apply(x, "op_name", a, b)。慣習として a が変形の主強度(回転角・倍率・ 変形振幅・検出しきい値)、b が副パラメータ(せん断量・空間周波数・角度オフセット等)を担う。 a=0 を恒等写像に寄せた op(deform_*)もあり、変形量をゼロから連続的に上げられる。 つまみの具体的な割り当ては各 op で異なるので、下の「使い方」節と検証済みサンプルを見てほしい。

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

幾何正規化で姿勢を整え、サブピクセル臨界点を輪郭として取り出し、XLD 幾何量で計測する 「正規化 → 抽出 → 計測」の縦串(image → image → contour → feature):

flowchart LR
    A[image<br/>生画像] -->|rotate_image / affine_trans_image / zoom_image_factor| B[image<br/>姿勢正規化済み]
    B -->|sp_local_max_sub_pix / sp_saddle_points_sub_pix| C[contour<br/>サブピクセル臨界点]
    C -->|xg_gen_polygons / xg_crop_contours| D[contour<br/>整形済み点列]
    D -->|xg_area_center / xg_orientation / xg_height_width_ratio| E[feature<br/>計測スカラ]

座標系変換・自由変形の系統(すべて image → image。極座標は往復でき、変形は拡張に使う):

flowchart LR
    I[image] -->|polar_trans_image| P[image<br/>極座標]
    P -->|polar_trans_image_inv| I2[image<br/>直交座標に復元]
    I -->|tf_log_polar / xcv2_warp_logpolar| LP[image<br/>対数極座標]
    I -->|tf_radon_sinogram| S[image<br/>サイノグラム]
    I -->|deform_tps / deform_ffd / deform_mls| W[image<br/>自由変形・データ拡張]

使い方(op グループ別)

A. 剛体・相似・アフィン変換(image → image)

★2026-09-02 まで zoom_image_factor / zoom_image_size / rescale_img3 つとも同一実装で(実測: 相互の最大絶対差 0.0 と 4.9e-14)、しかも 3 つとも b を使っていなかった。上記のとおり役割を分け、rescale_img の HALCON 名も実態に合わせて zoom_image_sizezoom_image_factor に付け替えてある。

B. 射影・極座標・非線形ワープ(image → image)

C. 領域の幾何変換(region → region)

同じ幾何写像を二値マスクへ適用する。fullseye.apply はグレー配列を 0.5 で二値化してから渡す。

D. キャンバス整形(image → image)

E. 座標系変換 / ウェーブレット(image → image)

F. 制御点ベース自由変形(image → image)

a=変形振幅、b=空間周波数/格子解像度/局所性。いずれも a=0 はサブピクセル補間誤差を除いて恒等。

G. サブピクセル臨界点抽出(image → contour)

濃淡曲面の臨界点を、3×3 近傍への 2 次多項式フィット grad z = 0 でサブピクセル精度に絞る。 出力は各点を 1 点のサブ輪郭にした XLD dict(count_contours で個数が取れる)。a=顕著度/しきい値、b=未使用。

H. XLD 輪郭の幾何量・整形(contour → feature / contour → contour)

点集合の共分散・中心モーメント(標準的な 2-D 定義)から不変量を測り、あるいは点列を整形する。

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

repo 直下で py -3.11 <file>.py(または PYTHONPATH=. py -3.11 …)として実行できる自己完結例。 examples/gallery2d_geometry.py の GT を土台に、族の 5 カテゴリを 1 本で叩いて既知効果を assert する。

import numpy as np
import fullseye

# --- 合成 [0,1] 濃淡画像: 勾配 + 明るい円板 + 市松 + 微小ノイズ ---
n = 48
yy, xx = np.mgrid[0:n, 0:n].astype(float)
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
noise = 0.03 * np.random.default_rng(20260812).standard_normal((n, n))
img = np.clip(0.35 * grad + 0.45 * disk + checker + noise, 0.0, 1.0)

# (1) mirror_image (a<0.34 -> 行反転): 画素の置換なので値の多重集合は不変、かつ恒等ではない
mir = fullseye.apply(img, "mirror_image", 0.2, 0.5)
assert np.array_equal(np.sort(mir.ravel()), np.sort(img.ravel()))
assert not np.array_equal(mir, img)

# (2) transpose_region は対合(2 回適用で元へ戻る / 1 回では入れ替わる)
reg = (((yy - 24) ** 2 + (xx - 19) ** 2) < 10 ** 2).astype(float)   # 偏心した円板
t1 = fullseye.apply(reg, "transpose_region", 0.5, 0.5)
t2 = fullseye.apply(t1,  "transpose_region", 0.5, 0.5)
assert np.array_equal(t1, reg.T) and np.array_equal(t2, reg)

# (3) deform_tps: a=0 は恒等写像、a=0.8 では明確に変形する
o0 = fullseye.apply(img, "deform_tps", 0.0, 0.5)
o8 = fullseye.apply(img, "deform_tps", 0.8, 0.5)
assert np.abs(o0 - img).max() < 1e-6
assert np.abs(o8 - img).mean() > 1e-2

# (4) sp_local_max_sub_pix: 単峰ガウスの峰をサブピクセルで検出(出力は XLD dict)
bump = np.exp(-(((yy - 20) ** 2 + (xx - 28) ** 2) / (2 * 3.0 ** 2)))
peaks = fullseye.apply(bump, "sp_local_max_sub_pix", 0.2, 0.0)
pts = np.array([c[0] for c in peaks["cs"]])
assert len(pts) >= 1 and float(np.min(np.hypot(pts[:, 0] - 20, pts[:, 1] - 28))) < 1.0

# (5) xg_area_center: 一辺 20 の正方形輪郭のシューレース面積 = 400(出力は float)
square = {"shape": (50, 50),
          "cs": [np.array([[10., 10.], [10., 30.], [30., 30.], [30., 10.], [10., 10.]])]}
area = fullseye.apply(square, "xg_area_center", 0.5, 0.5)
assert abs(area - 400.0) < 1e-6

print("PASS")

族の 全 op を契約(有限・型・決定性)+ 既知効果(GT + beat-the-null)で検証する完全版は examples/gallery2d_geometry.py(py -3.11 examples/gallery2d_geometry.py)。

数式(必要な op のみ)

回転・アフィン写像(rotate_image / affine_trans_image / affine_warp)— 出力画素 p を 逆写像 M^{-1} で入力座標へ引き戻して補間する。回転角は θ = -45° + 90°a(アフィンは -20°+40°a):

\[M(\theta)=\begin{pmatrix}\cos\theta & -\sin\theta\\ \sin\theta & \cos\theta\end{pmatrix},\qquad M_{\text{affine}}=\begin{pmatrix}\cos\theta & -\sin\theta+(b-0.5)\cdot0.4\\ \sin\theta & \cos\theta\end{pmatrix}\]

シューレース面積(xg_area_center)— 閉多角形 ${(x_i,y_i)}$ について $A=\tfrac12\left|\sum_i \left(x_i y_{i+1}-x_{i+1}y_i\right)\right|$。

主軸方位・中心モーメント(xg_orientation / xg_moments)— 点集合の 2 次中心モーメント $\mu_{20},\mu_{02},\mu_{11}$ から共分散行列を作り、その主固有ベクトルの角度が方位: $\theta=\operatorname{atan2}(v_y,v_x)\bmod 180^\circ$。xg_moments はトレース $\mu_{20}+\mu_{02}$(回転不変)を返す。

薄板スプライン(deform_tps)— 放射基底関数 $U(r)=r^2\log r$ を用い、制御点で $\sum_i w_i=0,\ \sum_i w_i \mathbf{p}_i=\mathbf 0$ の側条件つき線形系を解いて滑らかな変位場を作る。

ラドン変換(tf_radon_sinogram)— 角度 $\theta$・検出器位置 $s$ に対する線積分: $R(\theta,s)=\iint f(x,y)\,\delta(x\cos\theta+y\sin\theta-s)\,dx\,dy$。サイノグラムは行が $\theta$、列が $s$。

対数極座標(tf_log_polar)— 中心からの半径 $r$ と角度 $\phi$ を $\rho=\log r,\ \phi=\operatorname{atan2}(y,x)$ に写す。スケール変化は $\rho$(行)の平行移動、 回転は $\phi$(列)の平行移動になり、スケール・回転が加法的シフトに線形化される。

サブピクセル臨界点(sp_*)— 3×3 近傍に 2 次曲面 $z=a_0+a_1x+a_2y+a_3x^2+a_4y^2+a_5xy$ を最小二乗で当て、$\nabla z=0$ を解いて オフセット $\Delta=-H^{-1}g$(勾配 $g$、Hessian $H$)を得る。$\det H<0$ は鞍点。

サンプルデータ

デバッグ用の 2-D 画像源は サンプルデータ カタログ を参照。 import sample_images; sample_images.load("checker_noisy") の合成市松や "coins"/"camera" (skimage.data、BSD/public)は回転・極座標・サブピクセル臨界点の効果確認に向く。回転対称な "blobs"tf_radon_sinogram の行一定性、偏心した円板は正弦軌跡の確認に使える。

参考文献(正典)

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


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