10  幾何変換

生産ライン上のワークが毎回同じ位置・同じ角度で視野内に現れることはない:振動パーツフィーダによる供給では姿勢にばらつきが生じ、パレットの位置決めには機械的な隙間があり、部品実装機の吸ノズルも回転ずれを引き起こす。位置補正では画像を「正立させて」から計測を行い、複数カメラの画像つなぎ合わせでは複数の画像を同一座標系に位置合わせし、テンプレートマッチングの前には画像を適切な解像度に縮小することが多い——これらのタスクの共通の核が幾何変換(geometric transformation)であり、画像をある座標系から別の座標系に移す処理である。これは一見「少し移動させて、少し回転させる」だけのように見えるが、実は2つの個別に答えるべき問題が隠れている。座標をどう写像するか——これは行列の役割であり、新しい画素値をどこから取得するか——これは補間の役割である。どちらか一つでも間違えると、画像に穴やギザギザ、あるいは知らず知らずのうちにぼやけが生じる。ぼやけは計測精度の天敵である。

本章の実験では、図 10.1 に示す 480×360 の合成シーンを使用する。このシーンの各構造は、意図的に幾何変換を「難題にする」よう設計されている。左上のテキスト状の小さなブロックは微細部の保持性能を試し、1 px の明るい横線3本と 1 px の暗い縦線3本は細線の維持性能を試し、1 px の対角線は斜め構造の保持性能を試し、中央の 80×80 で 2 px 四方の市松模様はリサンプリングに最も敏感な高周波パターンであり、右上の円環はエッジの滑らかさを観察するためのものである。

図 10.1: 本章のテストシーン(480×360):テキスト状ブロック、1 px の細線3本組(横/縦)、1 px の対角線、中央の 2 px 四方市松模様と円環。いずれも補間とリサンプリングに敏感な微細構造である。

10.1 変換行列

2次元の点 \((x, y)\)同次座標(homogeneous coordinates)\((x, y, 1)^\mathsf{T}\) と表される(チャプター 2 の線形代数の節を参照)。これにより、平行移動、回転、拡大縮小がいずれも単一の \(3\times 3\) 行列の乗算に統合される:

\[ T(t_x,t_y)=\begin{bmatrix}1&0&t_x\\0&1&t_y\\0&0&1\end{bmatrix},\qquad R(\theta)=\begin{bmatrix}\cos\theta&-\sin\theta&0\\ \sin\theta&\cos\theta&0\\0&0&1\end{bmatrix},\qquad S(s_x,s_y)=\begin{bmatrix}s_x&0&0\\0&s_y&0\\0&0&1\end{bmatrix}. \]

これらを任意に合成(行列乗算)することで得られる最も一般的な形式がアフィン変換(affine transformation)である:

\[ A=\begin{bmatrix}a_{11}&a_{12}&t_x\\ a_{21}&a_{22}&t_y\\ 0&0&1\end{bmatrix}, \]

自由度は計6で、直線は直線のまま、平行線は平行線のまま保持される。位置補正における「ワークを中央に平行移動してから、中央を中心に正立させる」という一連の動作は、数学的にはこのような行列がいくつか乗算されて1つの行列になったものに他ならない。

同次座標の真の利点がここに現れる。平行移動は本来加算であるが、1次元分次元を上げることで乗算になり、任意の長さの変換列を事前に乗算して1つの行列にまとめることができる。この一見平凡な代数的事実が、セクション 10.3 で画像品質を左右する工学的規律となる。

行列が得られたら、次の問題はそれを使って新しい画像をどう生成するかである。直感的な方法は順写像(forward mapping)であり、原画像の各画素を走査して目標画像での落下位置を計算し、グレースケール値を移すというものである。この方法には致命的な欠点がある——落下位置の座標は一般に整数ではないため、丸めた結果、複数の原画像素が目標画像の同一画素を奪い合う一方で、一度も割り当てられない目標画像素も生じ、特に回転や拡大時に顕著な穴(holes)が残る。正しい方法はちょうど逆で、逆写像(inverse mapping)と呼ばれる:目標画像の各画素 \(\mathbf p'\) を走査し、逆行列を用いて原画像での元の座標

\[ \mathbf p = A^{-1}\,\mathbf p', \]

を求め、原画像のこの(一般に非整数の)位置からグレースケール値を「取得」する。各出力画素はちょうど一度ずつ値を割り当てられるため、穴は根本的に解消される。本章の付属コードにおける手書きの回転処理は、まさにこの原理に基づいて実装されている。「非整数位置のグレースケール値をどう取得するか」については、次節で詳しく述べる。

10.2 補間

逆写像によって計算された原画像の座標 \((s_x, s_y)\) は画素格子の間に位置するため、近傍の画素からグレースケール値を推定する必要がある。これが補間(interpolation)である。2つの基本的な手法はすでに チャプター 2 で紹介されているため、ここでは復習のみ行う。最近傍補間(nearest-neighbor interpolation)は、四捨五入して最も近い画素の値を直接使用する。新しいグレースケール値を生成しないが、0.5 px 程度の位置量子化誤差が生じる代償がある。双一次補間(bilinear interpolation)は周囲4画素の距離による重み付き平均をとる:\(x_0=\lfloor s_x\rfloor\)\(y_0=\lfloor s_y\rfloor\)、小数部分 \(a=s_x-x_0\)\(b=s_y-y_0\) とおくと、

\[ f(s_x,s_y)\approx(1-a)(1-b)\,f_{00}+a(1-b)\,f_{10}+(1-a)\,b\,f_{01}+ab\,f_{11}, \]

となる。ここで \(f_{00},f_{10},f_{01},f_{11}\) はそれぞれ左上、右上、左下、右下の近傍画素の値である。この手法は連続的に遷移するグレースケール値を出力し、本質的に軽度の局所平滑化である。

両者にはどれほどの差があるだろうか?テストシーンを、手書きの逆写像による最近傍補間と双一次補間のそれぞれで17°回転させ(サンプリング手法以外のコードは全く同一)、結果を 図 10.2 に示す。さらに中央の市松模様の領域を切り出して4倍に拡大し、画素単位で観察した結果を 図 10.3 に示す。

(a) 最近傍補間
(b) 双一次補間
図 10.2: 手書きの逆写像による17°回転:最近傍補間 vs 双一次補間。全体としては両者は似ているが、差異は微細構造に集中している——拡大した比較は 図 10.3 を参照。
(a) 最近傍補間 ×4
(b) 双一次補間 ×4
図 10.3: 中央の市松模様領域を切り出して4倍に拡大。最近傍補間の結果はギザギザが明らかで、2 px 四方の市松模様の白黒のマスが位置量子化によって乱雑に崩れている。双一次補間の結果はエッジが滑らかで、市松模様は規則的なグレースケールに遷移している。

差は一目瞭然である。最近傍補間による回転結果はギザギザだらけで、各出力画素が最も近い原画像素に「ジャンプ」するため、2 px 四方の市松模様の白黒のマスが不規則な断片に崩れている。双一次補間の結果はエッジが滑らかで幾何形状が整っているが、代償として元の画像には存在しない中間のグレースケール値が導入され、微細構造がわずかに柔らかくなっている。

補間手法の選択基準:計測タスク(エッジ位置検出、ノギス計測、位置合わせ)では双一次補間またはより高次の補間を使用し、最近傍補間の位置量子化がサブ画素精度を損なうことを防ぐ。二値マスク、ラベル画像は必ず最近傍補間を使用する——双一次補間では0と255の間に大量の中間グレースケール値が補間され、マスクがマスクでなくなってしまう。

10.3 リサンプリングの代償

双一次補間の「軽度の平滑化」という副作用は1回だけではほとんど見えないが、幾何変換を実行するたびに画像がリサンプリング(resampling)され、平滑化が1層ずつ重ねられる。この代償を定量化するため、「元の位置に戻る」実験を行う。テストシーンを双一次補間で10°回転させ、さらに10°回転させ……を合計36回行い、総角度がちょうど360°となって画像が元の位置にぴったり戻るようにする。結果を 図 10.4 に示す。

(a) 10°×36回の累積回転
(b) 元画像との絶対差
図 10.4: 累積リサンプリング実験。(a) 双一次補間による10°回転を36回行って「元の位置に戻った」画像:2 px 四方の市松模様は一様な灰色の斑点にぼやけ、1 px の細線はほとんど消え、テキストブロックは丸みを帯びた影になり、四隅は回転による切り取りで黒くなっている。(b) 差分画像:中央の構造部で差異が明確に見え、四隅の大きな明るい領域は画面の切り取りによるものである。

位置は確かに元に戻ったが、画像は見るも無残な姿になっている。2 px 四方の市松模様は完全に一様な灰色にぼやけ、1 px の細線はかすかな痕跡しか残っておらず、テキストブロックの角は全て丸くなっている。定量的には、(角の切り取り領域を除いた)中央の半径170 px の円内において、36回の回転後の画像と元画像の平均絶対差(MAD)は10.78グレースケール値に達する。対照として、1ステップで360°回転(数学的には恒等変換)した場合のMADは0.0000であり、円内の1バイトも変化していない。SDKの回転インターフェースを36回連続で呼び出した結果は10.77となり、手書き実装と一致した。これはこの現象が特定の実装の欠陥ではなく、リサンプリングそのものの性質であることを証明している:リサンプリングを行うたびに不可逆な情報損失が生じる

画面全体のMADは78.57であり、円内の10.78よりはるかに大きい——これは角の切り取りに支配されている。isChangeSize=false の場合、画面サイズは変わらず、回転のたびに四隅が画面外に出て塗りつぶし色で補われるため、36回後には中央に内接円だけが残る。誤差を統計する際にはこの部分を必ず除外すること。さもなければ、補間ではなく切り取りの影響を計測することになる。

ここから本章の最も重要な工学的規律が得られる:変換行列は数学的には自由に合成できるが、画像上では段階的に実行してはならない。「平行移動してから回転し、その後拡大縮小する」必要がある場合は、3つの行列をコード内で乗算して1つにまとめ、唯一の1回だけ逆写像によるリサンプリングを行う——結果は数学的には3段階で行った場合と等価であるが、画像品質は天と地ほどの差がある。

角の切り取り自体も注意すべき意味論的な問題である。回転インターフェースの isChangeSize パラメータは、出力画面を拡大して回転後の外接矩形全体を収めるかどうかを決定する。false の場合、画面サイズは元の画像と同じで、四隅の内容は切り取られ、空いた領域は指定された色で塗りつぶされる。true の場合、内容は損なわれないが画面サイズが大きくなり、後続のROI座標は全て無効になる。位置補正系のアプリケーションでは通常 false を選択し、計測対象領域が安全な円内に収まるようにする。

10.4 拡大縮小とエイリアシング

拡大縮小も幾何変換であるが、ダウンサンプリング(downsampling)には回転にはない落とし穴がある。画像を4分の1に縮小することはサンプリングレートが4分の1になることを意味する。チャプター 1 で議論した標本化定理によれば、元の画像で周期が8 px 未満の構造は新しいサンプリングレートでは表現できず、処理しなければエイリアシング(aliasing)として再現されてしまう(周波数領域での説明は チャプター 11 を参照)。正しい手順は事前にフィルタをかけてから間引くことであり、新しいナイキスト周波数を超える成分を先に除去してから解像度を下げる。

SDKの Sampling インターフェースを使用して比較を行う。NEAREST モードは直接4つおきに1つを間引く(事前フィルタなし)のに相当し、AREA モードは4×4の原画像領域ごとに平均をとる(箱型事前フィルタ)してから間引くのに相当する。結果を 図 10.5 に示す(見やすくするため、両方の縮小画像を最近傍補間で元のサイズに拡大しているが、新たな情報は導入されていない)。

(a) NEAREST:直接間引き
(b) AREA:領域平均後に間引き
図 10.5: ×4ダウンサンプリングの比較。(a) 直接間引き:2 px 四方の市松模様がエイリアシングによって一様な暗い斑点になり、1 px の明るい線3本のうちランダムに1本だけが「生き残り」、円環と対角線は破線に途切れている。(b) 箱型事前フィルタ後に間引き:市松模様は正しく平均されて中灰色になり、3本の細線は全て保存されている——ただし薄くなっているが、これは細線のエネルギーが拡散された後の真の姿である。

直接間引きの結果は目を覆うばかりである。2 px 四方の市松模様が一様な暗い斑点にエイリアシングしている——サンプルが毎回暗いマスに位置したため、高周波パターンが元々存在しない低周波構造に偽装されている。3本の全く同じ1 px の明るい線のうち1本だけが「生き残り」、残りの2本はたまたまサンプリングで飛ばされており、その存亡はサンプリング格子との相対位置に完全に依存している。円環と対角線は破線に途切れている。事前フィルタをかけた結果は全ての構造を保存している。市松模様は中灰色に平均され(これが低解像度での唯一の誠実な表現である)、3本の細線は全て見えるが、エネルギーが拡散されたために薄くなっている。ダウンサンプリングには事前フィルタが必須である——細線状の欠陥の見逃しは、多くの場合サムネイル生成フローにおける「手間を省いた」直接間引きに起因する。

Sampling は6種類の補間モードを提供しており、適用場面は以下の表の通りである。

モード 意味 適用場面
LINEAR 双一次 汎用デフォルト:拡大、軽度の縮小
NEAREST 最近傍(直接間引き/複製) 二値マスクとラベル画像。ダウンサンプリング時にはエイリアシングが生じる
CUBIC 双三次(4×4近傍) 高品質な拡大、エッジは双一次より鮮鋭
AREA 領域平均(箱型事前フィルタ内蔵) ダウンサンプリングの推奨
LANCZOS4 8×8 Lanczos窓付きsinc 最高品質の拡大縮小。強いエッジ付近でリンギングが生じる可能性あり
WEIGHT SDK独自の重み付きモード 動作はLINEARに近い。使用前に実測で検証することを推奨

10.5 ミラー、切り取りとつなぎ合わせ

他にもよく使われる「整数レベル」の幾何操作がいくつかあり、補間を伴わず、画素をバイト単位で移動するため情報損失がない。ミラー(mirror)は水平(左右)・垂直(上下)反転を行い、対称なステーションからの画像やプリズムで反射した画像の向きを統一するためによく使われる。切り取り(cropping)はROI領域を独立した小さな画像に切り出し、後続の処理のデータ量を削減するために使用する。ここで必ず記憶しておくべき工学的事実がある:SciVisionの GenRect1 矩形ROIの右下隅は排他端点である——(305,35)–(434,164)を渡すと、出力は130×130ではなく129×129となる(図 10.6)。「両端を含む」という直感でコードを書くと、体系的に最後の行と列が欠落する。つなぎ合わせ(stitching)は複数カメラまたは複数視野の画像を合成して大きな画像にする処理であり、SDKユーザーズマニュアルの5.12節に対応するインターフェースが記載されているため、本書では詳しく述べない。

(a) 水平ミラー
(b) 切り取り結果(129×129)
図 10.6: 整数レベルの幾何操作。(a) 水平ミラー:左右反転、バイト単位で移動し補間はなし。(b) 排他端点(305,35)–(434,164)で切り取られた円環領域。実際の出力は129×129である。

10.6 SciVision実装

回転と平行移動は SciSvRotationShift によって提供され、1つのインターフェースで両方の処理を同時に行う:

SCIMV::SciSvRotationShift rot;
SciColor fill(0, 0, 0);          // 画面外に出た領域の塗りつぶし色
SciImage dst;
// 画像中心を中心に17°回転、画面サイズ不変、平行移動量(0,0)
long rc = rot.RotateShiftImage(src, fill, 17.0f,
                               /*isChangeSize=*/false, 0, 0, &dst);

パラメータは順に、入力画像、塗りつぶし色、回転角(度)、isChangeSize(画面を拡大して回転結果全体を収めるかどうか。意味論は セクション 10.3 を参照)、x/y方向の平行移動量、出力画像である。2つの実測上の事実を知っておく必要がある。1つ目はこのインターフェースには補間手法のパラメータが公開されていないことである。SDKの+17°回転結果を、手書き実装の−17°回転結果と比較した(SDKの正の角度の向きは教科書の慣例と逆であるため、2つ目の事実を参照。負の角度にすることで同一の幾何変換となり、統計範囲は中央の半径170の円内とした)。手書き双一次補間の−17°結果とのMADは1.05、手書き最近傍補間の−17°結果とのMADは3.90となった——これは内部が双一次クラスの平滑補間であり、計測パイプラインで安心して使用できることを証明している。また同じ理由から、本章の最近傍補間/双一次補間の比較実験は手書きの逆写像で実装されている(SDKには最近傍モードが公開されていないため)。2つ目は正の角度の回転方向が一般的な反時計回りの慣例と逆であることである。同じ+17°をSDKと教科書の慣例に従って実装されたコードに渡すと、回転方向が逆になる。OpenCVなどのライブラリからコードを移植する際は、必ず非対称な画像で方向を確認してから角度の符号を決定すること。

拡大縮小、ミラー、切り取りの呼び出しは以下の通り:

SCIMV::SciSvSampling smp;
SciImage down;
smp.Sampling(src, src.Width() / 4, src.Height() / 4,
             SCI_SV_RESAMPLE_AREA, &down);   // ダウンサンプリングには事前フィルタ内蔵のAREAを使用

SCIMV::SciSvMirror mir;
SciImage flipped;
mir.Mirror(src, SCI_MIRROR_HOR, &flipped);   // 水平ミラー(左右反転)

SCIMV::SciSvImageCropping cropper;
SciROI roi;
SciPoint tl(305, 35), br(434, 164);          // 右下隅は排他端点
roi.GenRect1(tl, br);
SciImage patch;
cropper.ImageCrop(src, roi, 0, 0, &patch);   // 出力は129×129

Sampling のパラメータは入力画像、目標幅、目標高さ、補間モード(6種類のモードの選択については セクション 10.4 の表を参照)、出力画像である。Mirror の2番目のパラメータはミラーの種類である。ImageCrop の3番目と4番目のパラメータは切り取りの種類と範囲外の塗りつぶしグレースケール値である。全てのリターンコードを確認するべきである。本章の全ての実験画像を生成する完全なプロジェクトは code/geometric_transforms/ に配置されている。

産業事例:位置補正パイプラインに潜む不可視のぼやけ

ある部品実装検査プロジェクトのパイプラインは「テンプレート位置決め → 回転補正 → ノギス計測」であった。デバッグ段階で、エンジニアは平行移動量と角度を個別に確認しやすくするため、補正を2段階に分けて実行した:まず平行移動を1回行い、次に回転を1回行う——計2回のリサンプリングである。ライン稼働後、ノギスのエッジの遷移帯が約2 px から3 px にぼやけ、エッジ位置検出の再現性が約3割低下した。光源、レンズ、振動を調査したが原因は見つからなかった。最終的に画像パイプラインで原因が発見された:2回の双一次リサンプリングによって2層の平滑化が重ねられていたのである。平行移動と回転の行列をコード内で乗算して1つのアフィン行列にまとめ、リサンプリングを1回だけ行ったところ、エッジ幅と再現性は回復した。さらに深い経験則は補正はできるだけ省略することである——後続のツールが姿勢付きROIをサポートしている場合は、ROIをワークに追従して回転させれば(チャプター 19 を参照)、画像を全くリサンプリングする必要がなくなる。

10.7 まとめ

  • 幾何変換 = 行列 + 補間:同次座標により、平行移動、回転、拡大縮小、アフィン変換が \(3\times 3\) 行列に統一される。画像を生成するには逆写像が必須であり——各出力画素が原画像にサンプリングしに戻る。順写像では穴が生じる。
  • 補間手法はタスクごとに選択する:計測タスクでは双一次補間またはより高次の補間を使用する。二値マスクとラベル画像は必ず最近傍補間を使用する(新しいグレースケール値を導入しない)。
  • リサンプリングは毎回情報損失となる:10°回転を36回行って元の位置に戻した場合のMADは10.78に達するのに対し、等価な1ステップ変換では0.0000となる。変換列は先に1つの行列に乗算してまとめ、リサンプリングは1回だけ行う。姿勢付きROIで対応できる場合は画像には手をつけない。
  • ダウンサンプリングには事前フィルタが必須である:直接間引きでは高周波構造がエイリアシングし(市松模様が一様な斑点になる、細線がランダムに消える)、AREAなどの事前フィルタ内蔵モードを使用すべきである。
  • 工学的意味論を確認するRotateShiftImage には補間パラメータが公開されておらず(実測で双一次クラス)、正の角度の方向が一般的な慣例と逆である。GenRect1 の右下隅は排他端点であり、(305,35)–(434,164)で切り取られるのは129×129の領域である。

幾何変換とリサンプリング補間の標準的な解説はGonzalezとWoodsの教科書 (Gonzalez と Woods 2018) にある。双一次補間とスプライン補間の中間の精度を持ち広く使用されている三次たたみ込み補間の元々の構成はKeysの論文 (Keys 1981) による。計測システムにおける幾何変換と補間のより体系的な解説(投影変換とサブ画素精度の関係を含む)については、Stegerらの著作 (Steger, Ulrich, と Wiedemann 2018) をさらに参照できる。