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つのグループに分かれます。

バウンディングボックスと重心 のグループ(xywhrectcxcycxfcyf)はブロブの位置を表します。rect は描画メソッドが期待する (x, y, w, h) の4要素タプルです。cxcy は整数のピクセル座標での重心、cxfcyf はサブピクセル精度の浮動小数点座標での重心で、上流のキャリブレーションが小数位置を扱う場合に役立ちます。

形状ディスクリプタpixelsareadensityperimeterroundnesselongationcompactnessrotation)はブロブの見た目を表します。pixels は通過したピクセルの数です。area は軸に沿ったバウンディングボックスの面積(w * h)です。density はこの2つの比で、塗りつぶされた長方形では 1.0 に近づき、細い斜めのストロークでは 0.0 に向かって下がります。roundnesscompactness はどちらもブロブの丸さを、異なる幾何学的観点から評価します(roundness は二次モーメントから、compactness は周囲長と面積の比から)。elongation は利便性のために 1.0 - roundness で表されます。rotation は長軸の向きをラジアンで表したもので、細長いブロブで最も正確になり、ほぼ円形のブロブではノイズが大きくなります(軸が曖昧な場合、向きが明確に定まらないためです)。

しきい値とマージのメタデータcodecount)は、どのしきい値に一致したか、また返されるブロブにいくつのソースブロブがマージされたかを示します。code は一致した各しきい値ごとに1ビットが立つ32ビットのビットマップです(単一のしきい値では code == 1 になり、マージされた複数色のブロブでは複数のビットが立つことがあります)。countmerge=True が複数の検出結果を1つにまとめた場合を除き 1 です。

コーナー のグループ(cornersmin_corners)はブロブの回転を考慮した形状を与えます。corners はブロブの輪郭から抽出した (x, y) の極値の4要素タプルで、左上から時計回りにソートされています。min_corners はブロブを囲む最小面積の回転長方形のコーナーの4要素タプルです。最小面積の長方形はタイトにフィットし、軸に沿った rect はピクセルグリッドに合わせたゆるいフィットです。下流の処理が向きを持つボックスを必要とするか、単純なボックスを必要とするかに応じて、どちらも役立ちます。

バイナリのしきい値マスクに対して示したブロブ 検出の図解。左のパネルは通過したピクセルから なる傾いた楕円のマスクを示しています。右のパネル は同じマスクに、その周りに描かれた軸に沿った バウンディングボックス、中央に十字で示された重心、 楕円の実際の角度に沿って密着する破線の最小面積 回転長方形、そして楕円の長い方向に沿って重心を 通る長軸の線を注釈として加えたものを示しています。

ブロブは、軸に沿ったバウンディングボックス(rectxywh)、重心(cxcy またはサブピクセルの cxfcyf)、最小面積の回転長方形(min_cornersrotation)、および以下のモジュールレベルのヘルパーによって計算される任意の長軸 / 短軸の線を保持します。

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_maxy_hist_bins_max は、各ブロブに任意の 投影ヒストグラム を付加します。投影ヒストグラムは、1つの軸に沿って通過したピクセルの数です。X 軸のヒストグラムはブロブのバウンディングボックス内の列ごとに通過したピクセルを合計し、Y 軸のヒストグラムは行ごとに合計します。どちらもデフォルトはゼロです。ゼロ以外の max が指定されない限りヒストグラムは計算されません。さもなければ、すべての検出に処理が追加されてしまうためです。

計算された場合、ヒストグラムはアプリケーションがさらに分析を実行できる安価な1次元信号を提供します。ブロブ内の垂直なストライプの位置を検出する、2色のターゲットの境目を見つける、長軸に沿って現れるギャップの数を数える、といった用途です。これらは各 Blobx_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)