8  モルフォロジー

閾値処理(チャプター 7)の出力は、きれいな二値画像になることはない。どれほど適切な閾値を選んでも、前景にはコショウのような小さな穴が数個残り、エッジにはバリがつき、本来分離すべき2つの対象が細いブリッジで結合され、本来一体であるべき対象が細い隙間で複数に分割され、背景には孤立したノイズ点が散在する。これらの瑕疵は人間の目には些細な問題だが、プログラムにとっては災難である。後続のBlob解析(チャプター 23)は、ノイズ点をすべて対象としてカウントし、結合した2つの対象を1つとしてカウントし、分割された1つの対象を3つとしてカウントしてしまう。数学的モルフォロジー(mathematical morphology)は、二値画像を補修するためのレンチである。小さなテンプレートを画像上で走査し、集合演算の規則に従って画素を削除または補完することで、セグメンテーション結果を後続のアルゴリズムが直接処理できる形状に整える。

本章では、480×360の合成二値シーンをすべての実験で一貫して使用する(図 8.1)。シーン内のすべての瑕疵は意図的に配置されている。内部に1~2pxのコショウ穴、エッジに1~2pxのバリを持つ中実の矩形;2pxの細いブリッジで結合した2つのブロブ;1pxと2pxの細い隙間で3つに分割された矩形;3pxの厚さの細いストリップ;さらに10個の孤立ノイズ点(6個の1×1、4個の2×2)である。すべての座標はハードコードされており、乱数は含まれないため、実行ごとの統計結果は完全に一致する。前景は合計47426画素、8連結で16個のblobとなる。

図 8.1: 本章で一貫して使用する合成二値シーン:コショウ穴とバリを持つ矩形、2pxの細いブリッジで結合した二重ブロブ、1px/2pxの細い隙間で分割された矩形、3pxの細いストリップ、10個の孤立ノイズ点。前景は47426px、8連結で16個のblob。

8.1 構造要素と収縮・膨張

モルフォロジーのすべての演算は、1つの主役を中心に展開する。構造要素(structuring element)である。これはアンカー点(anchor)を持つ小さな形状テンプレートであり、最も一般的なのは3×3、5×5の矩形で、アンカー点は中心に配置される。演算時にはアンカー点が画像を1画素ずつ走査し、構造要素が覆う近傍がその画素の運命を決定する。構造要素はモルフォロジーにとって、線形フィルタリング(チャプター 6)における畳み込みカーネルに相当する。テンプレートの形状とサイズが、演算の性質全体を決定するのである。

最も基本的な2つの演算は、「反対語」のペアを形成する。収縮(erosion)の集合論的定義は次のとおりである。

\[ A \ominus B = \{\, z \mid B_z \subseteq A \,\}, \]

ここで\(A\)は前景画素の集合であり、\(B_z\)は構造要素\(B\)のアンカー点を位置\(z\)に平行移動したものを表す。一言で言えば、構造要素が前景に完全に収まる場合に限り、アンカー点の位置を前景として残すのである。構造要素が収まらない箇所——細いブリッジ、バリ、孤立した小点、対象の最外縁——はすべて削り取られる。膨張(dilation)はこれと逆である。

\[ A \oplus B = \{\, z \mid B_z \cap A \neq \emptyset \,\}, \]

構造要素が前景と1画素でも接していれば、アンカー点の位置を前景に設定する。前景は外側に1周分成長し、細い隙間や小さな穴は反対側から成長した前景によって埋められる。

収縮と膨張は互いに双対(duality)である:\((A \ominus B)^{c} = A^{c} \oplus \check{B}\)、すなわち前景の収縮は背景の膨張と等価である。これは数学的な装飾ではない——「前景の小さな構造を削除する」ことと「背景の小さな隙間を埋める」ことは、本質的には同じ演算を両側から見た結果であり、次節で説明する開・閉演算の双対性はまさにこれに由来する。

二値画像については、さらに直感的な等価な見方が存在する。収縮は近傍の最小値フィルタ(min filter)——ウィンドウ内に1つでも背景(0)があれば出力は0になる——であり、膨張は近傍の最大値フィルタ(max filter)——ウィンドウ内に1つでも前景(255)があれば出力は255になる——である。この見方は、セクション 8.4 でグレースケールモルフォロジーに一般化する際に直接役立つ。

3×3の矩形構造要素を用いてテストシーンに対してそれぞれ1回適用した結果を@fig-mo-erode-dilate に示す。収縮後、前景は47426pxから44233pxに減少し、blob数は16個から7個に急減した。10個のノイズ点はすべて消滅し、バリは削り取られ、2pxの細いブリッジは切断され——結合していた二重ブロブは2つに分離した。代償も肉眼で明らかである。すべての対象が1周分細くなり、コショウ穴は拡大され、細い隙間はさらに広くなった。膨張は逆の結果をもたらした。前景は50603pxに増加し、コショウ穴は埋められ、1pxと2pxの細い隙間は両方とも閉じたが、すべての対象が1周分太くなり、ノイズ点は消滅どころか成長してしまった。

(a) 3×3収縮
(b) 3×3膨張
図 8.2: 収縮と膨張(3×3矩形構造要素)。(a) 収縮:ノイズ点とバリが完全に除去され、細いブリッジが切断された(16→7個のblob)が、前景は44233pxに縮小し、穴と隙間は拡大された;(b) 膨張:コショウ穴が埋められ、細い隙間が閉じられたが、前景は50603pxに膨張し、ノイズ点も成長した。

いずれの演算も問題の半分しか解決せず、それぞれ新たな問題——サイズが変化する——を引き起こす。計測用途ではこれは許容できない。1回収縮すれば対象の面積と輪郭の位置は系統的に小さく偏り、1回膨張すれば大きく偏る。「ノイズ除去/隙間充填」の利点を享受しつつ、サイズを元に戻すにはどうすればよいか?答えは、両者を連鎖させて使用することである。

8.2 開演算と閉演算

開演算(opening)は、収縮の後に膨張を行うものとして定義される。

\[ A \circ B = (A \ominus B) \oplus B, \]

閉演算(closing)は、膨張の後に収縮を行うものとして定義される。

\[ A \bullet B = (A \oplus B) \ominus B. \]

両者の設計思想は同じである。最初のステップで対象構造を「削除するか残すか」を決定し、次のステップで生存した構造のサイズを近似的に元に戻すのである。開演算では、まず収縮によって構造要素が収まらないすべての小さな構造が削除される——一度消滅すると、後続の膨張では復元できない。一方、大きな対象は単に1周分細くなるだけであり、膨張によってその1周分が補われる。閉演算は背景に対して双対的に作用する。まず膨張によって構造要素が収まらないすべての小さな隙間や穴が埋められ、次に収縮によって膨張した対象が元の形状に戻される。

開演算と閉演算はいずれもべき等(idempotent)である:\((A \circ B) \circ B = A \circ B\)であり、閉演算についても同様である。1回行っても100回行っても結果は完全に同じ——削除すべきものは最初の1回できれいに削除され、生存した構造はすでに「構造要素が収まる」状態になっているため、それ以上適用しても恒等変換になるだけである。これは、平滑化フィルタの「フィルタリングするほどぼやける」という累積的な振る舞いとは全く異なる。

実験結果を@fig-mo-open-close に示す。数値は「サイズが近似的に保持される」ことを非常に明確に示している。3×3開演算後の前景は47225pxであり、元の47426pxに対してわずか0.4%の減少に過ぎない。3×3閉演算後は47801pxであり、わずか0.8%の増加に過ぎない。単独の収縮/膨張による約±6.7%の変化と比較すると、開・閉演算はサイズの偏差を1桁小さく抑えている。しかもこの減少と増加は1画素単位で正確である。開演算で削除された201pxは、10個のノイズ点(22px)、3本のバリ(59px)、細いブリッジ(120px)の面積の合計と正確に一致する——矩形本体は1画素も減少していない。閉演算で補われた375pxは、2本の細い隙間(360px)とすべてのコショウ穴(15px)の面積の合計と正確に一致する。

(a) 3×3開演算
(b) 3×3閉演算
図 8.3: 開演算と閉演算(3×3矩形構造要素)。(a) 開演算:ノイズ点、バリ、細いブリッジが完全に除去され(16→7個のblob)、対象のサイズはほぼ変化しない(−0.4%)が、コショウ穴はそのまま残る;(b) 閉演算:1pxと2pxの細い隙間がすべて閉じられ(3つの断片が再び一体となり、16→14個のblob)、コショウ穴が埋められる(+0.8%)が、ノイズ点とバリはそのまま残る。

blob数のカウントは、両者の役割分担をさらに明らかにする。開演算後に残ったblobは7個である。10個のノイズ点は消滅し、細いブリッジは切断され(結合したペアが2つに分離し)、バリもきれいに除去された——しかし矩形内部のコショウ穴は残ったままである。開演算は穴を埋めない:前景内の小さな突出構造を削除するだけで、背景内の小さな穴には無力である。閉演算後に残ったblobは14個である。1pxと2pxの隙間は両方とも閉じられ、分割されていた3つの断片は再び一体となり、コショウ穴も埋められた——しかし10個のノイズ点は1つも消滅していない。閉演算はバリもノイズ点も除去しない。これは双対性が目に見える形で現れたものである。開演算は前景の「余分な部分」を補修し、閉演算は背景の「余分な部分」を補修する。どちらも他方の代わりにはならない。実際のエンジニアリングでは、両者を連鎖させて使用することが多い——まず開演算を行い、次に閉演算を行う。ノイズをきれいに除去してから、切れた隙間を閉じるのである。本章のすべての演算のカウント結果を@tbl-mo-counts にまとめる。

表 8.1: 各モルフォロジー演算がテストシーンの前景画素とblob数に与える影響
演算 前景画素 元画像に対する比 blob数(8連結)
元のシーン 47426 16
収縮 3×3 44233 −6.7% 7
膨張 3×3 50603 +6.7%
開演算 3×3 47225 −0.4% 7
閉演算 3×3 47801 +0.8% 14
開演算 5×5 46025 −3.0% 6

8.3 構造要素サイズの選択

開演算と閉演算は、「何を削除し、何を残すか」の決定権を完全に構造要素のサイズに委ねている——構造要素が収まらないものは消滅し、収まるものは生存する。では、構造要素は大きいほど安全だろうか?開演算の構造要素を3×3から5×5に変更して再実行する(図 8.4)と、前景は46025pxに減少し、blobは6個だけになった。消滅した1個はノイズではない——3pxの厚さの細いストリップである。それは3×3の構造要素は収まるが、5×5の構造要素は収まらないため、根こそぎ消去されてしまったのである。もしこの「細いストリップ」が製品上のリード線や本物のキズであった場合、検査システムは前処理段階で自らの手で証拠を破棄したことになる。

図 8.4: 5×5開演算:ノイズは同様にきれいに除去されるが、3pxの細いストリップも全体が消去される(blob数16→6)——構造要素が残すべき構造より大きくなると、削除はノイズと対象を区別しなくなる。

これから本章で最も重要なエンジニアリングの法則が得られる。構造要素は、削除すべきノイズより大きく、残すべき最小の構造より小さくなければならない。本例では最大のノイズは2×2であり、最小の真の構造は3pxの厚さであるため、3×3はちょうどその中間に位置するが、5×5は境界を越えてしまう。もし2つの境界が衝突する——ノイズの粒子が最小の対象より大きい——場合、サイズという1つの次元ではそれらを分離できなくなる。構造要素を無理に調整するのではなく、形状の異なる構造要素(細長い対象には細長い構造要素)を使用するか、残留ノイズを後続の面積による選別に委ねるべきである。

この法則は、チャプター 6 のメディアンフィルタのカーネルサイズの法則と同型である。メディアンカーネルは、残すべき最小の構造の幅の2倍より小さくなければならない。さもないと、細い線がノイズとともに消去されてしまう。これは偶然ではない——開演算とメディアンフィルタはいずれも「サイズによる順位付けで取舍する」非線形フィルタのファミリーに属しており、いずれも「欠陥」と「ノイズ」を認識せず、サイズだけを認識するのである。

8.4 グレースケールモルフォロジーとトップハット

セクション 8.1 で述べたmin/maxの見方により、モルフォロジーは自然に二値の世界から外に出ることができる。グレースケール画像に対して、グレースケール収縮(grayscale erosion)は構造要素の近傍内のグレースケール値の最小値をとり、グレースケール膨張(grayscale dilation)最大値をとる。グレースケール開演算(minの後にmax)は、「構造要素が収まらない幅の」明るいピークをすべて削り取り、大面積の背景の起伏にはほとんど影響を与えない——言い換えれば、グレースケール開演算は背景だけを残す演算である

これから直ちに産業用の強力なツールが派生する。白トップハット変換(white top-hat)は、元の画像からそのグレースケール開演算を減じたものとして定義される。

\[ T_b(f) = f - (f \circ b). \]

\(f \circ b\)は「小さな明るい構造が消去された背景の推定値」である。元の画像からこれを減じると、残るのはまさにそれらの小さな明るい構造自体である——背景が平らであろうと、傾斜していようと、湾曲していようと、きれいに減じられる。トップハットは背景の明るさレベルに対して本質的に免疫がある。これはまさに@sec-illumination のシェーディング補正が達成しようとしている目標である。トップハットは「背景推定を内蔵した小対象抽出」と理解でき、背景モデルを別途撮影したり、フィッティングしたりする必要はなく、開演算の中に隠されているのである。

(a) グレースケールシーン
(b) 11×11白トップハット
(c) トップハット後の閾値35
図 8.5: 不均一な照明下での小さな明るいスポットの抽出。(a) 30から160までの水平グラデーション背景上に、「背景+70」の16個の小さな明るいスポットが重畳されている。最も暗いスポットのピークは約116であり、右側の背景の160よりも暗い——いかなる大域閾値も失敗する;(b) 11×11白トップハットはグラデーション背景を全体的に減じ、16個のスポットだけが残る;(c) 閾値35で二値化:正確に16個のblob、216pxであり、スポットの面積の真値と1画素単位で一致する。

実験シーン(図 8.5)は、チャプター 7 の大域閾値が必ず失敗するように意図的に設計されている。背景は30から160までの水平グラデーションであり、その上に局所的な背景より70グレースケール値明るい16個の小さな明るいスポット(2×2から5×5まで)が重畳されている。画像の左側に位置する最も暗いスポットのピークは約116に過ぎないのに、右側の最も明るい背景はすでに160である——スポットが背景より暗いため、両者を分離できる大域閾値は存在しない。11×11の構造要素(最大の5×5のスポットより大きい)を用いて白トップハットを行うと、グラデーション背景が全体的に減じられる。次に閾値35で二値化すると、結果は正確に16個のblob、合計216pxが抽出される——16個のスポットの面積の真値(\(4\times(4+9+16+25)=216\))と完全に一致し、1画素も多くも少なくもない。トップハットの構造要素サイズの法則は前節と同じだが、逆になる。抽出すべき対象より大きくなければならない。そうすることで、開演算が背景推定から対象を消去し、差分に対象が現れるようになるのである。

8.5 SciVision実装

SciVision SDKのモルフォロジー機能は、SciMorphStructure(構造要素)とSCIMV::SciSvMorphology(演算)の2つのクラスによって提供される。本章の実験と一致する呼び出し方法は次のとおりである。

// 構造要素:CreateElement(rows, cols, anchorX, anchorY, shape, data)
SciMorphStructure se3, se11;
long rc = se3.CreateElement(3, 3, 1, 1, SCI_MS_RECT);    // 3×3矩形、アンカー(1,1)中心
if (rc) { /* リターンコードは必ずチェックする */ }
rc = se11.CreateElement(11, 11, 5, 5, SCI_MS_RECT);      // トップハット用11×11

SciROI roi;
SciPoint tl(0, 0), br(W, H);   // GenRect1の右下は排他端点:全画像には(W,H)を渡す
roi.GenRect1(tl, br);          // non-const参照を受け取るため、名前付き変数が必要

SCIMV::SciSvMorphology m;
SciImage dErode, dOpen, dTop;
rc = m.Erode(src, roi, se3, 1, &dErode);     // 収縮、iteration = 1
rc = m.Open (src, roi, se3, 1, &dOpen);      // 開演算
rc = m.TopHat(gsrc, roi, se11, 1, &dTop);    // 白トップハット

CreateElementの最初の2つのパラメータは構造要素の行数と列数であり、anchorX/anchorYはアンカー点の位置を指定する(通常は中心を使用し、例えば3×3では(1,1)、11×11では(5,5)とする)。shapeSCI_MS_RECTに設定すると中実の矩形を意味し、最後のint* dataパラメータはカスタム形状の場合にのみ使用される——矩形カーネルの場合は省略できる。Erode/Dilate/Open/Close/TopHatの5つのインターフェースは同じシグネチャを共有する:入力画像、ROI、構造要素、反復回数、出力ポインタである。iterationパラメータは演算を繰り返す回数である——矩形構造要素の場合、3×3の収縮を\(n\)回反復することは、1回の\((2n+1)\times(2n+1)\)の収縮と等価であるため、小さなカーネルの反復で大きなカーネルの代わりにできる。ただし開演算と閉演算については、べき等性のためiterationを1より大きくしても1の場合と結果が同じになる可能性があるため、通常は1に固定する。

正直に報告しなければならない落とし穴が1つある。SciROI::GenRect1の右下隅は排他端点である(チャプター 7 のSciVisionの節で同じセマンティクスが既に記録されている)。もし「最後の画素の座標」という直感に従って\((W-1,H-1)\)を渡すと、最後の行と最後の列がROIから除外されて処理されなくなる——モルフォロジーの出力はその境界帯で恒等的に0となり、トップハットの差分\(f - 0\)は元のグレースケール値に縮退し、グラデーション背景の明るい側に閾値を突破するほど強い高応答の帯が残る。このアーティファクトは「SDKが最外の行/列を処理しない」というSDKの欠陥と誤読されやすい。実際には、\((W,H)\)を渡す(または単に未定義のROIを使用する)と、SDKは完全な画像を処理するようになり、アーティファクトは消滅する。本章の実験はすべて\((W,H)\)の全画像ROIで実行した。

産業事例:はんだ接合検査におけるオープニングの活用

あるPCBのはんだ接合検査工程で、はんだペーストの飛び散りが基板表面に1~3pxの微少な輝点を多数残し、閾値処理後にそれらがすべて前景となり、ブロブの個数が真のはんだ接合数の数倍に水増しされた。セグメンテーションと計数の間に3×3のオープニングを1段挿入したところ、飛び散りの粒子はきれいに除去され、計数は正常に戻った——教科書通りのオープニングの適用例である。3か月後、ある技術者が「より安全」という理由で構造要素を7×7に変更した:飛び散りは確かにより徹底的に除去されたが、直後にラインで見逃しが発生した。この製品の最小仕様のはんだ接合の直径はわずか6pxしかなく、7×7の構造要素に対応できず、飛び散りとともに消去されてしまったのである。事後検証の結論は部門規範に書き込まれた:構造要素のサイズの上限は、ノイズの除去具合から順方向に導くのではなく、工程文書に記載された最小のはんだ接合サイズから逆方向に導かなければならない。残留した飛び散りは後段の面積による選別に任せるほうが、構造要素を拡大するよりもはるかに安全である。

8.6 まとめ

  • モルフォロジーは閾値セグメンテーションとブロブ解析の間の補修工程である:収縮は「構造要素が前景内に完全に収まる」という規則で画素を削除し、膨張は「構造要素が前景に接触したら画素をセットする」という規則で画素を補充する。両者は互いに双対であり、近傍の最小値/最大値フィルタと等価であるが、単独で使用するといずれも系統的に対象のサイズを変化させる(本章の実験では約±6.7%)。
  • オープニング=収縮後に膨張:ノイズの点、バリ、細い橋を除去するが穴は埋めない;クロージング=膨張後に収縮:細い隙間を塞ぎ小さな穴を埋めるがノイズの点は除去しない。両者ともサイズの偏差を±1%以内に抑え(47426 → 47225 / 47801)、かつべき等である——1回実行すれば十分である。
  • 構造要素のサイズの法則:削除したいノイズより大きく、保持したい構造より小さい(3×3は3pxの細い線を残し、5×5はそれを消去する)。これはメディアンフィルタのカーネルサイズの法則と同型である——非線形フィルタはサイズしか認識せず、意味論的な意味は認識しない。
  • ホワイトトップハット\(f-(f\circ b)\)は、背景推定を内蔵した微小な輝点目標抽出であり、照明勾配に対して不変である:グローバル閾値では必ず失敗する勾配のある場面でも、トップハット後に1つの固定閾値を用いるだけで16個の輝点すべてを正確に抽出でき、216pxが真値と一致する。構造要素は抽出したい目標より大きくなければならない。
  • エンジニアリング上の注意GenRect1の右下隅は排他的な端点である——\((W-1,H-1)\)を渡すと最終行/列が処理から除外され、その境界帯でトップハットが元の濃淡値に縮退する。全画像ROIでは\((W,H)\)を渡さなければならない。

数学的モルフォロジーの理論的な起源はSerraの基礎的な専門書(Serra 1982)であり、Soilleの著書は濃淡モルフォロジーとその応用を体系的に扱っている(Soille 2004);二値および濃淡モルフォロジー作用素の統一的な概説については、Haralick、Sternberg、Zhuangによる古典的な論文(Haralick, Sternberg, と Zhuang 1987)を参照されたい。モルフォロジーの完全な理論体系(ヒットアンドミス変換、測地的再構成、ウォーターシェッドなどの発展的なトピック)については、Stegerらの著書(Steger, Ulrich, と Wiedemann 2018)をさらに参照されたい。