5.25. ブロブの検出¶
しきい値処理によって、キャプチャしたフレームはバイナリマスクに変換されます。各ピクセルはしきい値テストを通過するか、通過しないかのいずれかです。これにより アプリケーションが対象とする色がシーンのどこに現れるか のうち、どの色が現れるかという問いには答えられますが、どこに 現れるかには答えられません。マスクは単なる1と0の海にすぎないのです。次のステップがブロブ検出です。マスクをたどって、通過したピクセルが連続している領域を見つけ、それぞれを位置・サイズ・向き、そしてアプリケーションが利用できるその他のプロパティを持つオブジェクトとして返します。
find_blobs() はこのステップを担う主力メソッドであり、image モジュールの結果オブジェクトの世界に入る最も一般的な入口です。色付きのボールを追跡する、床に描かれたラインをたどる、サーマルセンサーが捉える明るいスポットの数を数える、青色LEDが点灯しているか消灯しているかを判定する、これらはすべて同じ呼び出しでカバーできます。入力(しきい値、検索する領域、結果に適用するフィルター)は変わりますが、呼び出しのパターンは同じです。
5.25.1. 基本的な呼び出し¶
find_blobs はしきい値のリストを受け取り、ブロブ結果オブジェクトのリストを返します。
thresholds = [(30, 100, 15, 127, 15, 127)] # LAB threshold for red
blobs = img.find_blobs(thresholds)
for b in blobs:
img.draw_rectangle(b.rect, color=(255, 0, 0))
img.draw_cross(b.cx, b.cy, color=(255, 0, 0))
各しきい値タプルは binary() に渡すしきい値と同じ形式を持ちます。RGB565 画像の場合は (l_lo, l_hi, a_lo, a_hi, b_lo, b_hi) という6つのエントリ(境界値は LAB で指定)、グレースケール画像の場合は (lo, hi) という2つのエントリです。1回の呼び出しで最大32個のしきい値を指定でき、これが find_blobs() を非常に柔軟にしています。赤・緑・青のビーコンを同時に追跡でき、それぞれが返されるリストに自分のブロブを寄与し、各ブロブの code プロパティがどのしきい値に一致したかを示します。
上記の draw_rectangle() と draw_cross() の呼び出しは、IDE のプレビュー用にキャプチャしたフレームへ注釈を描き込みます。ブロブ結果はすでに b.rect(4要素タプルとしてのバウンディングボックス)と b.cx / b.cy(整数の重心)を保持しているため、検出結果をフレームに描き戻すのは2つのメソッド呼び出しで済みます。
5.25.2. 結果に含まれる内容¶
各 Blob は、検出器が領域について測定したすべての情報をまとめた属性タプルです。プロパティは4つのグループに分かれます。
バウンディングボックスと重心 のグループ(x、y、w、h、rect、cx、cy、cxf、cyf)はブロブの位置を表します。rect は描画メソッドが期待する (x, y, w, h) の4要素タプルです。cx と cy は整数のピクセル座標での重心、cxf と cyf はサブピクセル精度の浮動小数点座標での重心で、上流のキャリブレーションが小数位置を扱う場合に役立ちます。
形状ディスクリプタ(pixels、area、density、perimeter、roundness、elongation、compactness、rotation)はブロブの見た目を表します。pixels は通過したピクセルの数です。area は軸に沿ったバウンディングボックスの面積(w * h)です。density はこの2つの比で、塗りつぶされた長方形では 1.0 に近づき、細い斜めのストロークでは 0.0 に向かって下がります。roundness と compactness はどちらもブロブの丸さを、異なる幾何学的観点から評価します(roundness は二次モーメントから、compactness は周囲長と面積の比から)。elongation は利便性のために 1.0 - roundness で表されます。rotation は長軸の向きをラジアンで表したもので、細長いブロブで最も正確になり、ほぼ円形のブロブではノイズが大きくなります(軸が曖昧な場合、向きが明確に定まらないためです)。
しきい値とマージのメタデータ(code、count)は、どのしきい値に一致したか、また返されるブロブにいくつのソースブロブがマージされたかを示します。code は一致した各しきい値ごとに1ビットが立つ32ビットのビットマップです(単一のしきい値では code == 1 になり、マージされた複数色のブロブでは複数のビットが立つことがあります)。count は merge=True が複数の検出結果を1つにまとめた場合を除き 1 です。
コーナー のグループ(corners、min_corners)はブロブの回転を考慮した形状を与えます。corners はブロブの輪郭から抽出した (x, y) の極値の4要素タプルで、左上から時計回りにソートされています。min_corners はブロブを囲む最小面積の回転長方形のコーナーの4要素タプルです。最小面積の長方形はタイトにフィットし、軸に沿った rect はピクセルグリッドに合わせたゆるいフィットです。下流の処理が向きを持つボックスを必要とするか、単純なボックスを必要とするかに応じて、どちらも役立ちます。
ブロブは、軸に沿ったバウンディングボックス(rect、x、y、w、h)、重心(cx、cy またはサブピクセルの cxf、cyf)、最小面積の回転長方形(min_corners と rotation)、および以下のモジュールレベルのヘルパーによって計算される任意の長軸 / 短軸の線を保持します。¶
5.25.3. 検索のフィルタリング¶
キャプチャしたフレームには通常、アプリケーションが対象とするオブジェクト以外の理由でしきい値に一致するピクセルが含まれます。鏡面反射のハイライト、遠くの背景オブジェクト、たまたま LAB の範囲に入った画像ノイズのピクセルなどです。find_blobs() のキーワード引数は、その最初の防御線です。
roi は、他のすべての image モジュールメソッドと同様に、検索をフレームの一部の領域に限定します。オブジェクトが視野の下半分にしか現れないと分かっているアプリケーションは roi=(0, h//2, w, h//2) を渡し、それより上はすべて無視します。節約された時間はフレームレートに還元されます。
area_threshold と pixels_threshold はどちらも、気にするには小さすぎるブロブをフィルタリングします。area_threshold は、バウンディングボックスの面積がそのピクセル数に満たないブロブを除外します(散在するノイズのフィルタリングに適しています)。pixels_threshold は、通過した ピクセルがその数に満たないブロブを除外します(大きいが疎なブロブ、たとえばあちこちで1つや2つのピクセルが一致するしきい値処理された点描パターンのフィルタリングに適しています)。どちらもデフォルトは 10 です。数センチほどの前景ターゲットに対してこれらを数百まで引き上げると、小さなノイズの粒をすべて捨てられます。
x_stride と y_stride は、トレースを開始するブロブを 探している 間にスキャナが取るピクセルのステップを設定します。ストライドはトレースの解像度ではありません。トレースは常に実際のブロブの境界を1ピクセル単位で追います。しかしストライドは、スキャンがどれだけ素早く開始ピクセルを見つけるかを制御します。ブロブが大きいと分かっている場合(カメラから30センチほどの拳大の色付きターゲットなら、容易に100ピクセル以上の幅になります)、x_stride=4, y_stride=4 はスキャン時間を16分の1に短縮し、検出における実用上の損失はありません。ブロブが小さい場合(遠くのLEDビーコンで数ピクセル幅)には、完全に飛び越えてしまわないよう、ストライドは 1 のままにする必要があります。invert はしきい値テストを反転します。一致が不一致になり、ルーチンは 不一致の ピクセルからなるブロブを代わりに返します。
threshold_cb は、しきい値処理の後で最終的な結果リストが構築される前に、各ブロブに対して呼び出される Python のコールバックです。コールバックはブロブを受け取り、それを保持する場合は True を、除外する場合は False を返します。これは、キーワード引数が直接公開していないプロパティに対して任意の Python レベルのフィルターを適用する場所です。最小密度、特定の回転範囲、マージ後の独自のコードビットの組み合わせなどです。キーワード引数はネイティブコードのフィルターで高速に動作します。コールバックは Python で実行されるため低速ですが、表現できる内容は無制限です。
5.25.4. 重なり合うブロブのマージ¶
merge=True は、バウンディング長方形が重なり合うブロブを結合するために結果リストを後処理します。自然な用途は、鏡面反射のハイライト、影の線、オブジェクト全体での照明のばらつきなどによって、カメラが対象の色を複数のしきい値処理された領域として捉えてしまうターゲットを検出する場合です。1つの赤いボールが3つか4つの小さな赤いブロブとして返ってきて、それらをまとめるとボールの輪郭をなす、というようなケースです。merge=True を使うと、3つのブロブは1つの大きなブロブになり、rect はその和集合をカバーし、code はマージされたブロブのコードのビット単位の OR になり(複数色のマージではどの色が寄与したかが分かります)、count はいくつのソースブロブが結合されたかを報告します。
margin は、重なりのテストを行う前にバウンディング長方形を拡大または縮小します。margin=2 では、バウンディング長方形が互いに2ピクセル以内にあるブロブもマージされます。margin=-2 では、バウンディング長方形が少なくとも2ピクセル重なっているブロブだけがマージされます。自然な調整方法としては、しきい値が隣接した断片に分割してしまったブロブを処理するには正のマージン、密集した別々のオブジェクトを分離したままにするには負のマージンを使います。
merge_cb は、マージが行われる前に各候補ペアに対して実行されます。コールバックは2つのブロブを受け取り、マージを許可する場合は True を、防ぐ場合は False を返します。これは、幾何学的なルールでは見逃すマージを相互チェックするのに最適なツールです。たとえば、rotation の角度がしきい値以上に食い違う2つのブロブのマージを拒否したり、小さなブロブが単なる斑点である場合にそれをはるかに大きなブロブへマージするのを拒否したりできます。
5.25.5. 投影ヒストグラム¶
x_hist_bins_max と y_hist_bins_max は、各ブロブに任意の 投影ヒストグラム を付加します。投影ヒストグラムは、1つの軸に沿って通過したピクセルの数です。X 軸のヒストグラムはブロブのバウンディングボックス内の列ごとに通過したピクセルを合計し、Y 軸のヒストグラムは行ごとに合計します。どちらもデフォルトはゼロです。ゼロ以外の max が指定されない限りヒストグラムは計算されません。さもなければ、すべての検出に処理が追加されてしまうためです。
計算された場合、ヒストグラムはアプリケーションがさらに分析を実行できる安価な1次元信号を提供します。ブロブ内の垂直なストライプの位置を検出する、2色のターゲットの境目を見つける、長軸に沿って現れるギャップの数を数える、といった用途です。これらは各 Blob の x_hist_bins および y_hist_bins プロパティとして格納されます。
5.25.6. 追加の幾何学的ヘルパー¶
さらにいくつかの幾何学的な測定値は、ブロブを受け取って要求された測定値を返すモジュールレベルの関数として用意されています。
image.get_solidity()はブロブの 充実度(solidity)を返します。これはピクセル数を凸包の面積で割ったものです。塗りつぶされた充実した領域は1.0に近くなり、凹みのあるブロブ(馬蹄形や指を広げた手など)はそれをかなり下回ります。image.get_convexity()は 凸性(convexity)を返します。これは凸包の周囲長をブロブの周囲長で割ったものです。完全に凸なブロブは1.0で、ギザギザしたブロブや切れ込みのあるブロブはそれより低くなります。image.get_major_axis_line()とimage.get_minor_axis_line()は、回転した最小面積長方形から導かれる、ブロブの長軸および短軸に沿ったLineオブジェクトを返します。image.get_enclosing_circle()は、ブロブを囲むCircleを返します。下流の処理が描画やテストに使う円を必要とする場合に役立ちます。image.get_enclosed_ellipse()は、ブロブの最小面積長方形に内接する楕円の5要素タプル(cx, cy, rx, ry, rotation)を返します。これらの値はdraw_ellipse()に直接渡せます。
5.25.7. しきい値の自動学習¶
ブロブ検出器は、それが使用するしきい値の精度以上のものにはなりません。そして、ターゲットの色に対して適切なしきい値を 見つける 作業は、それ自体が1つの課題です。この作業を軽減する一般的なパターンが2つあります。
1つ目は IDE での対話的な選択 です。フレームをキャプチャし、ターゲット色の例の周りに長方形をドラッグして、IDE の しきい値エディタ に検出した LAB の境界値を報告させます。これらの境界値をスクリプトの find_blobs() のしきい値として落とし込めば、検出器の準備は完了です。
2つ目はプログラムによる自動学習です。カメラ上で動作するキャリブレーションルーチンがフレームをキャプチャし、ターゲットがある既知のパッチのヒストグラムを取得し(roi= を指定した get_histogram())、get_percentile() を使ってヒストグラムからパッチの値の範囲を読み取ります。5パーセンタイルが各チャンネルの下限を、95パーセンタイルが上限を設定し、両端の散発的な外れ値ピクセルは無視します。RGB565 画像では1回のパーセンタイル呼び出しで3つの LAB チャンネルすべてが一度に報告されるため、2回の呼び出しで find_blobs() が期待する6つの数値が得られます。
h = img.get_histogram(roi=patch)
lo = h.get_percentile(0.05)
hi = h.get_percentile(0.95)
threshold = (lo.l_value, hi.l_value,
lo.a_value, hi.a_value,
lo.b_value, hi.b_value)