OpenCVで特定の色だけを検出・抽出!inRange関数の使い方と応用徹底解説
画像処理の世界では、特定の対象物を識別したり、背景から分離したりするタスクが頻繁に発生します。その中でも、色情報を利用した対象物の検出は、比較的シンプルでありながら非常に強力な手法の一つです。たとえば、工場で不良品の色をチェックする、交通標識の特定の色の部分だけを抽出する、ロボットが特定の色のボールを追いかける、といった多様な場面で色の検出技術が活用されています。
OpenCVは、これらの色検出・抽出タスクを効率的に行うための強力なツールを提供しています。その中でも、cv2.inRange関数は、指定した色の範囲内にあるピクセルを抽出するための、最も基本的ながら非常に重要な関数です。
この記事では、cv2.inRange関数を徹底的に掘り下げ、その基本的な使い方から、なぜ特定のカラー空間を使うべきなのか、色の範囲をどう設定するのか、さらに検出精度を向上させるための応用テクニック(ノイズ除去や輪郭検出)まで、詳細かつ実践的に解説します。約5000語というボリュームで、初心者の方でも理解できるよう丁寧に進め、さらに一歩進んだ活用方法まで網羅することを目指します。
さあ、OpenCVとcv2.inRangeを使って、画像の中から特定の色を見つけ出す旅に出かけましょう!
1. 特定の色を検出・抽出するとは? なぜそれが重要なのか?
画像はピクセルの集まりであり、各ピクセルは色情報を持っています。色検出・抽出とは、画像中のすべてのピクセルに対して、「このピクセルの色は、私が見つけたい特定の色(または色の範囲)に属するか?」という判断を行い、その条件を満たすピクセルだけを選び出すプロセスです。
このプロセスは、様々な応用分野で中心的な役割を果たします。
- 物体認識・追跡: 特定の色を持つ物体(例: 赤いボール、青いマーカー)を画像から見つけ出し、その位置や動きを追跡する際に使われます。
- 画像セグメンテーション: 画像を意味のある領域(例: 空、地面、人)に分割する基本的な手法として、色の違いを利用することがあります。
- 品質管理: 製造ラインで、製品の色が仕様を満たしているか、異物混入がないかなどを自動的にチェックします。
- ロボティクス: ロボットが環境中の特定の色のオブジェクト(例: 搬送すべき緑の箱、避けるべき赤い障害物)を識別するために使われます。
- Augmented Reality (AR): 特定の色のマーカーを認識し、その上にデジタル情報を重ね合わせるようなアプリケーションで利用されます。
色情報は、形状やテクスチャといった他の特徴に比べて計算量が少なく、リアルタイム処理に適している場合が多いという利点があります。しかし、照明条件の変化や、似たような色が背景に存在する場合に精度が低下するという課題もあります。これらの課題に対処するためにも、cv2.inRange関数を正しく理解し、適切な前処理や後処理と組み合わせることが重要です。
2. cv2.inRange関数とは?
cv2.inRange関数は、OpenCVにおいて特定の色範囲内にあるピクセルを効率的に抽出するために設計された関数です。その基本的な機能は、入力画像の各ピクセルの値が、指定された下限値と上限値の範囲内にあるかどうかを判定することです。
cv2.inRange関数の基本的な動作
cv2.inRange関数は、入力画像と同じサイズのバイナリ(2値)画像を返します。
* 入力画像のピクセル値が、指定された色の下限値以上かつ上限値以下である場合、出力画像の対応するピクセルは255(白)になります。
* それ以外の場合、出力画像の対応するピクセルは0(黒)になります。
この出力されるバイナリ画像を「マスク画像」と呼びます。マスク画像は、入力画像の中で条件を満たす領域(今回の場合、指定した色の範囲内のピクセル)が白く、それ以外の領域が黒く表示されたものです。このマスク画像を使えば、元の画像から特定の色を持つ部分だけを抽出したり、その領域に対してさらに処理を加えたりすることができます。
cv2.inRange関数の書式
Python版OpenCVにおけるcv2.inRange関数の書式は以下のようになります。
python
mask = cv2.inRange(src, lowerb, upperb)
src: 入力画像(Numpy配列)。通常、BGRまたはHSVなどのカラー画像です。単チャンネルのグレースケール画像や他の形式でも使用可能ですが、色の範囲指定には通常カラー画像が使われます。重要な点として、この関数のパフォーマンスを最大限に引き出すためには、入力画像の型(データタイプ)が適切である必要があります。通常はuint8型です。lowerb: 下限値。タプルまたはNumpy配列で指定します。入力画像が3チャンネル(BGRやHSV)なら3つの値、単チャンネルなら1つの値を指定します。たとえば、HSV画像の場合、(Hueの下限, Saturationの下限, Valueの下限)のように指定します。upperb: 上限値。lowerbと同様に、タプルまたはNumpy配列で指定します。(Hueの上限, Saturationの上限, Valueの上限)のように指定します。mask: 出力されるマスク画像(Numpy配列)。入力画像と同じサイズで、型はuint8(値は0または255)になります。
3. どのカラー空間を使うべきか? BGR vs HSV
特定の色検出を行う上で、どのカラー空間(色の表現方法)を使うかは非常に重要です。OpenCVが画像を読み込む際のデフォルトはBGR(Blue, Green, Red)ですが、cv2.inRangeで色の範囲を指定する際には、通常HSV(Hue, Saturation, Value)カラー空間が推奨されます。
BGRカラー空間の課題
BGRカラー空間は、人間の目の錐体細胞が感知する3つの主要な色(赤、緑、青)の組み合わせで色を表現します。各ピクセルは(B値, G値, R値)の3つの数値で表され、それぞれの値は通常0から255の範囲をとります。
BGR空間で特定の色(例えば、鮮やかな赤)の範囲を指定しようとすると、問題が発生します。なぜなら、同じ「赤」でも、明るさや鮮やかさが変わると、B, G, Rの各値が大きく変化してしまうからです。例えば、明るい赤と暗い赤、鮮やかな赤とくすんだ赤は、人間の目には同じ「赤」の系統に見えても、BGR値としては全く異なる値を持つことがあります。したがって、BGR空間で特定の色を包括的に捉えるための範囲(B_min, G_min, R_min)から(B_max, G_max, R_max)を設定するのは非常に困難で、多くの色のバリエーションをカバーしようとすると、目的の色以外の類似した色まで拾ってしまう可能性が高くなります。
HSVカラー空間の利点
一方、HSVカラー空間は、色を人間の感覚に近い3つの要素で表現します。
* Hue (色相): 色の種類そのもの(赤、緑、青など)を表します。通常、0から180の範囲で表現されます(OpenCVの場合)。0は赤、30は黄、60は緑、120は青、150は紫、180は再び赤というように、色相環のように変化します。
* Saturation (彩度): 色の鮮やかさを表します。0は彩度が最も低い(グレー)、180や255に近づくにつれて鮮やかになります(OpenCVの場合、通常0-255)。
* Value (明度): 色の明るさを表します。0は最も暗い(黒)、180や255に近づくにつれて明るくなります(OpenCVの場合、通常0-255)。
HSVカラー空間の最大の利点は、色相(Hue)が、彩度(Saturation)や明度(Value)から比較的独立していることです。つまり、同じ「赤」であれば、それが明るかろうが暗かろうが、鮮やかだろうがくすんでいようが、Hueの値は比較的近い範囲に収まる傾向があります。
これにより、特定の色(例: 赤)を検出したい場合、HSV空間であればHueの値で大まかな色相を指定し、SaturationとValueでその色の鮮やかさや明るさの許容範囲を調整するというアプローチが可能になります。これは、BGR空間で3つの値を同時に調整するよりも直感的で、頑健な色の検出範囲を設定しやすくなります。
したがって、cv2.inRangeを使って特定の色を検出する際には、まず画像をBGRからHSVに変換するのが一般的なベストプラクティスです。OpenCVではcv2.cvtColor(image, cv2.COLOR_BGR2HSV)という関数を使って簡単に変換できます。
OpenCVのHSV値の範囲に注意!
OpenCVのHSVは、他の標準的な定義(Hueが0-360など)と異なり、デフォルトでは以下の範囲で表現されます。
* Hue (H): 0 – 179 (合計180段階)
* Saturation (S): 0 – 255
* Value (V): 0 – 255
Hueが0-179である点に特に注意が必要です。これは、メモリ効率のために値を半分にしているためです。
4. 実践!cv2.inRangeを使った特定色検出・抽出の基本ステップ
それでは、実際にcv2.inRangeを使って特定の色(例: 青)を検出・抽出する手順を見ていきましょう。
ステップ1: 必要なライブラリをインポートする
まず、OpenCVとNumpyをインポートします。
python
import cv2
import numpy as np
import matplotlib.pyplot as plt # 結果表示に便利
ステップ2: 画像を読み込む
処理したい画像をファイルから読み込みます。
“`python
画像ファイルのパスを指定してください
image_path = ‘your_image.jpg’
image_bgr = cv2.imread(image_path)
if image_bgr is None:
print(f”エラー: 画像ファイル ‘{image_path}’ を読み込めません。”)
exit()
Matplotlibで表示するためにBGRからRGBに変換(OpenCVはBGR順なので)
image_rgb = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2RGB)
元画像を表示
plt.figure(figsize=(10, 8))
plt.subplot(1, 3, 1)
plt.imshow(image_rgb)
plt.title(“元画像 (BGR)”)
plt.axis(‘off’)
“`
ステップ3: 画像をBGRからHSVに変換する
cv2.inRangeを使うために、画像をHSVカラー空間に変換します。
python
image_hsv = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2HSV)
ステップ4: 検出したい色のHSV範囲を定義する
ここが最も重要なステップです。検出したい特定の色(例: 青)のHSV値の許容範囲(下限と上限)を定義します。この範囲の設定が、検出精度に大きく影響します。
青色の一般的なHSV範囲(OpenCV形式 H:0-179, S:0-255, V:0-255)は以下のようになります。
- Hue (H): 青はHue値が比較的高く、一般的に100~140あたりに位置します。
- Saturation (S): 彩度が高いほど鮮やかな青です。0に近いほどくすんだ色や灰色になります。検出したい青の鮮やかさに応じて調整します。
- Value (V): 明るさです。0は真っ黒、255は最も明るい色になります。検出したい青の明るさに応じて調整します。
ここでは例として、鮮やかな青色の典型的な範囲を設定してみます。
“`python
青色のHSV範囲を定義 (OpenCV形式 H:0-179, S:0-255, V:0-255)
これらの値は画像や検出したい青の色によって調整が必要です
lower_blue = np.array([100, 50, 50])
upper_blue = np.array([140, 255, 255])
print(f”検出したい青色のHSV範囲:”)
print(f” 下限: {lower_blue}”)
print(f” 上限: {upper_blue}”)
``(0, S_min, V_min)
**注意点:** 赤色のように、Hueが0(または180)付近にある色は、範囲指定が少し複雑になります。なぜなら、Hueは色相環として閉じているため、0付近の赤と179付近の赤は同じ「赤」だからです。この場合、二つの範囲(例:-(10, S_max, V_max)と(170, S_min, V_min)-(179, S_max, V_max))を定義し、それぞれの範囲でcv2.inRange`を実行したマスク画像を論理和(OR)で結合する必要があります。
ステップ5: cv2.inRange関数を適用してマスク画像を生成する
定義したHSV範囲を使って、cv2.inRange関数をHSV画像に適用します。
“`python
mask = cv2.inRange(image_hsv, lower_blue, upper_blue)
生成されたマスク画像を表示
plt.subplot(1, 3, 2)
plt.imshow(mask, cmap=’gray’) # マスク画像はグレースケールで表示
plt.title(“マスク画像”)
plt.axis(‘off’)
“`
このマスク画像は、青色のピクセルが白(255)、それ以外のピクセルが黒(0)になります。
ステップ6: マスク画像を使って元の画像から特定の色を抽出する
生成されたマスク画像を使って、元の画像から指定した色のピクセルだけを抽出します。これには、cv2.bitwise_and関数がよく使われます。
cv2.bitwise_and(src1, src2, mask=mask) は、src1とsrc2の各ピクセルに対してビットごとの論理積を計算しますが、mask引数が指定されている場合、マスク画像の対応するピクセルが255(非ゼロ)である場合にのみ論理積の結果を出力し、マスク画像のピクセルが0の場合は出力画像のピクセルを0とします。
ここでは、src1とsrc2の両方に元のBGR画像を指定し、マスク画像としてcv2.inRangeの出力を使います。これにより、マスク画像で白になっている領域(つまり、inRangeで検出された青色の領域)のピクセルだけが元の画像から抽出され、マスク画像で黒になっている領域は全て黒になります。
“`python
マスクを使って元の画像から青色部分を抽出
result = cv2.bitwise_and(image_bgr, image_bgr, mask=mask)
抽出結果をMatplotlibで表示するためにBGRからRGBに変換
result_rgb = cv2.cvtColor(result, cv2.COLOR_BGR2RGB)
抽出結果を表示
plt.subplot(1, 3, 3)
plt.imshow(result_rgb)
plt.title(“青色抽出結果”)
plt.axis(‘off’)
plt.tight_layout()
plt.show()
必要であれば、結果をファイルに保存
cv2.imwrite(‘blue_extracted.jpg’, result)
cv2.imwrite(‘blue_mask.png’, mask)
“`
これで、画像の中から特定の色(この例では青)の部分だけを検出し、抽出することができました。この抽出された画像(result)やマスク画像(mask)は、後続の処理(例: オブジェクトの面積計算、位置特定、追跡など)に利用できます。
5. 検出したい色のHSV範囲をどう見つけるか?
上記のステップで最も難しいのは、「ステップ4: 検出したい色のHSV範囲を定義する」です。画像の照明条件、カメラのホワイトバランス、検出したいオブジェクトの具体的な色合いによって、最適なHSV範囲は大きく異なります。決まった普遍的な値があるわけではありません。
色のHSV範囲を見つけるための一般的なアプローチをいくつか紹介します。
アプローチ1: オンラインHSVカラーピッカーツールを使う
インターネット上には、HSVカラーピッカーツールが多数存在します。これらのツールを使えば、カラーパレットや画像上の任意の場所をクリックして、その色のHSV値(そしてRGB値なども)を確認できます。
- オンラインツールを開く。
- 検出したい色に近い色をカラーピッカーで探すか、画像をアップロードして目的の色をクリックする。
- 表示されたHSV値(通常はH:0-360, S:0-100%, V:0-100% の形式)をメモする。
- メモした値をOpenCVのHSV範囲(H:0-179, S:0-255, V:0-255)に変換する。
- H: 元のH値 ÷ 2
- S: 元のS値 × 2.55 (または 255 ÷ 100)
- V: 元のV値 × 2.55 (または 255 ÷ 100)
- 変換した値を基に、検出したい色の「範囲」を推測し、下限値と上限値を設定する。例えば、ある青色がH=120, S=200, V=150だとわかったら、範囲を H:110-130, S:100-255, V:100-255 のように、ある程度の幅を持たせて設定してみる。
この方法は手軽ですが、実際に使用する画像での色の見え方とオンラインツールの表示には差がある可能性がある点に注意が必要です。
アプローチ2: OpenCVを使って画像上のピクセル値を調べる
最も確実なのは、対象の画像自体を使って、検出したい色の領域のピクセル値を直接調べることです。
- 画像を読み込み、HSVに変換する。
- 画像表示ウィンドウを作成し、マウスイベントをトラッキングできるように設定する。
- 画像表示ウィンドウ上で、検出したい色の領域をマウスでクリックまたはドラッグする。
- マウスイベントコールバック関数内で、クリックされたピクセルのHSV値を読み取り、表示する。
- 目的の色の複数の箇所でHSV値を調べ、その値のばらつきを確認する。
- 確認した値の最小値と最大値のあたりを参考に、
lowerbとupperbを設定する。
以下は、マウスでクリックした位置のHSV値を表示する簡単なコード例です。
“`python
import cv2
import numpy as np
グローバル変数として画像とウィンドウ名を用意
image_hsv_global = None
window_name = ‘Image with HSV values’
マウスイベントコールバック関数
def mouse_callback(event, x, y, flags, param):
if event == cv2.EVENT_LBUTTONDOWN: # 左クリック時
# x, y はクリックされたピクセルの座標
# 画像のサイズチェック
if image_hsv_global is not None and 0 <= y < image_hsv_global.shape[0] and 0 <= x < image_hsv_global.shape[1]:
hsv_pixel = image_hsv_global[y, x]
# BGRに戻して元の色も表示してみる
color_bgr = cv2.cvtColor(np.uint8([[hsv_pixel]]), cv2.COLOR_HSV2BGR)[0][0]
print(f”座標 ({x}, {y}):”)
print(f” HSV: {hsv_pixel} (H: {hsv_pixel[0]}, S: {hsv_pixel[1]}, V: {hsv_pixel[2]})”)
print(f” BGR: {color_bgr} (B: {color_bgr[0]}, G: {color_bgr[1]}, R: {color_bgr[2]})”)
print(“-” * 20)
— メイン処理 —
画像ファイルのパス
image_path = ‘your_image.jpg’
image_bgr = cv2.imread(image_path)
if image_bgr is None:
print(f”エラー: 画像ファイル ‘{image_path}’ を読み込めません。”)
exit()
画像をHSVに変換し、グローバル変数に格納
image_hsv_global = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2HSV)
ウィンドウを作成し、マウスイベントコールバックを設定
cv2.namedWindow(window_name)
cv2.setMouseCallback(window_name, mouse_callback)
print(“画像ウィンドウが表示されました。目的の色をマウスでクリックしてください。”)
print(“ESCキーを押すと終了します。”)
while True:
# HSV画像ではなく、元のBGR画像を表示した方が見やすいかもしれません
# ただし、コールバック関数内ではHSV画像を使います
cv2.imshow(window_name, image_bgr)
# 'Esc'キーで終了
key = cv2.waitKey(1) & 0xFF
if key == 27:
break
cv2.destroyAllWindows()
“`
このコードを実行し、表示された画像上で目的の色をいくつかクリックしてみてください。出力されるHSV値を参考に、下限と上限の範囲を決定します。
アプローチ3: トラックバーを使ってリアルタイムに範囲を調整する
最も対話的で効果的な方法は、OpenCVのトラックバー(スライダー)を使って、HSV範囲の下限値と上限値をリアルタイムに調整しながらマスク画像の生成結果を確認する方法です。
- HSV範囲の各成分(H_min, S_min, V_min, H_max, S_max, V_max)に対応する6つのトラックバーを作成する。
- トラックバーの値が変更されるたびに、新しい範囲で
cv2.inRangeを実行し、生成されたマスク画像を表示ウィンドウに更新する。 - マスク画像を見ながらトラックバーを調整し、目的の色が白く、それ以外の色が黒くなるように最適な範囲を見つける。
この方法を使うと、微妙な色の違いや照明の変化による影響を確認しながら、視覚的に最適な範囲を設定できます。コードが少し長くなるためここでは割愛しますが、「OpenCV trackbar inRange HSV color detection」などで検索すると多くのサンプルコードが見つかります。この方法は、開発段階で最適なパラメーターを見つけるのに非常に強力です。
赤色の範囲指定の特別なケース
前述しましたが、赤色はHue値が0(または180)付近に位置するため、範囲指定に注意が必要です。例えば、Hueが0-10と170-179の範囲が赤色に対応する場合、単純なinRangeでは一つの範囲として指定できません。
この場合は、2つの範囲でそれぞれマスクを作成し、それらを論理和(OR)で結合します。
“`python
赤色のHSV範囲例 (Hueが0付近と179付近)
鮮やかな赤を想定
lower_red1 = np.array([0, 100, 100])
upper_red1 = np.array([10, 255, 255])
lower_red2 = np.array([170, 100, 100])
upper_red2 = np.array([179, 255, 255])
最初の範囲でマスクを作成
mask1 = cv2.inRange(image_hsv, lower_red1, upper_red1)
二番目の範囲でマスクを作成
mask2 = cv2.inRange(image_hsv, lower_red2, upper_red2)
二つのマスクを結合(論理和)
mask_red = cv2.bitwise_or(mask1, mask2)
この mask_red を使って赤色を抽出する
result_red = cv2.bitwise_and(image_bgr, image_bgr, mask=mask_red)
“`
このように、色相環の端にある色を検出する際には、複数の範囲を扱うテクニックが必要になります。
6. inRangeの応用と検出精度向上
cv2.inRangeで生成されたマスク画像は、ノイズ(目的の色ではないのに白になっているピクセルや、目的の色なのに黒になっているピクセル)を含んでいることがよくあります。また、マスクはあくまでピクセル単位の判定結果であり、個々の検出されたピクセルがまとまって一つのオブジェクトを形成しているかを判断するには、追加の処理が必要です。
マスク画像のノイズ除去(モルフォロジー変換)
マスク画像に含まれる小さな白い点(誤検出)や、目的のオブジェクト内の小さな穴(未検出)を修正するために、モルフォロジー変換(Morphological Transformations)がよく使われます。
モルフォロジー変換は、カーネル(構造要素と呼ばれる小さな形状)を使って画像の形状を変化させる操作です。cv2.erode(収縮)やcv2.dilate(膨張)が基本操作です。
- 収縮 (
cv2.erode): 白い領域を収縮させ、ノイズとなる小さな白い点や境界のギザギザを取り除くのに役立ちます。ただし、オブジェクト自体も小さくなります。 - 膨張 (
cv2.dilate): 白い領域を膨張させ、オブジェクト内の小さな穴を埋めたり、離れた白い領域を結合させたりするのに役立ちます。ただし、オブジェクト自体も大きくなります。
これらの基本操作を組み合わせたオープニングとクロージングが、ノイズ除去によく使われます。
- オープニング (Opening): 収縮 → 膨張
- 小さな白いノイズを除去してから膨張させることで、ノイズの影響を抑えつつ元のオブジェクトのサイズをある程度戻します。小さな白い点の除去に効果的です。
opening = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
- クロージング (Closing): 膨張 → 収縮
- 小さな黒い穴を埋めてから収縮させることで、穴を埋めつつオブジェクトの形状を保ちます。オブジェクト内の穴埋めや、近いオブジェクトの結合に効果的です。
closing = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
kernelは通常、cv2.getStructuringElementで作成するnumpy.onesのような小さな行列です。カーネルのサイズが大きいほど、効果は強くなります。
“`python
ノイズ除去の例 (オープニング)
kernel = np.ones((5, 5), np.uint8) # 5×5のカーネルを作成
オープニング処理
mask_opened = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
マスク画像とオープニング後のマスク画像を比較表示してみる
plt.figure(figsize=(10, 5))
plt.subplot(1, 2, 1)
plt.imshow(mask, cmap=’gray’)
plt.title(“元のマスク画像”)
plt.axis(‘off’)
plt.subplot(1, 2, 2)
plt.imshow(mask_opened, cmap=’gray’)
plt.title(“オープニング後のマスク画像”)
plt.axis(‘off’)
plt.tight_layout()
plt.show()
クロージング処理の例
mask_closed = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
必要に応じてmask_opened または mask_closed を後続処理に使う
“`
マスク画像にモルフォロジー変換を施すことで、より滑らかでクリーンな領域を得ることができ、後続のオブジェクト検出精度を向上させることができます。
オブジェクトの輪郭検出 (cv2.findContours)
クリーンになったマスク画像(白黒画像)は、検出された色領域がはっきりしています。この白い領域が、私たちが検出したい「オブジェクト」の候補です。これらのオブジェクトの形状や位置を知るためには、輪郭検出を行います。OpenCVのcv2.findContours関数は、バイナリ画像中の白い領域の境界(輪郭)を見つけ出す関数です。
“`python
オープニング後のマスク画像を使用 (またはモルフォロジー処理を適用しない元のマスク画像)
processed_mask = mask_opened
輪郭を検出
cv2.findContoursはOpenCVのバージョンによって戻り値が異なります
OpenCV 3.x, 4.x の場合
contours, hierarchy = cv2.findContours(processed_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
OpenCV 2.x の場合 (現在では少ないかもしれませんが念のため)
contours, hierarchy = cv2.findContours(processed_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
バージョンに依存しない書き方 (findContoursの最初の戻り値がcontours)
関数は処理中に元のマスク画像を書き換える可能性があるため、コピーを渡すのが安全です
contours, _ = cv2.findContours(processed_mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
検出された輪郭を元の画像に描画してみる
BGR画像に描画する必要があるため、元画像またはそのコピーを使います
image_with_contours = image_bgr.copy()
すべての輪郭を描画 (輪郭リスト, -1で全て描画, 色, 太さ)
cv2.drawContours(image_with_contours, contours, -1, (0, 255, 0), 2) # 緑色で太さ2の線
輪郭を描画した画像を表示
plt.figure(figsize=(8, 6))
plt.imshow(cv2.cvtColor(image_with_contours, cv2.COLOR_BGR2RGB))
plt.title(“検出された輪郭”)
plt.axis(‘off’)
plt.show()
``cv2.findContoursの引数について補足します。processed_mask.copy()
*: 入力画像。通常はバイナリ画像(マスク画像)を渡します。関数の内部で画像が変更される可能性があるため、.copy()で複製を渡すのが安全です。cv2.RETR_EXTERNAL
*: 輪郭の取得モード。RETR_EXTERNALは最も外側の輪郭のみを取得します。オブジェクトの穴の輪郭などは無視されます。他にもRETR_LIST,RETR_CCOMP,RETR_TREEなどがあります。cv2.CHAIN_APPROX_SIMPLE
*: 輪郭の近似手法。CHAIN_APPROX_SIMPLEは輪郭の冗長な点を削除し、端点だけを記録します。例えば、水平線や垂直線、斜め線は2点で表現されます。CHAIN_APPROX_NONE`はすべての点を記録します。
cv2.findContoursの戻り値は、検出された輪郭のリスト(contours)と輪郭の階層情報(hierarchy、RETR_EXTERNALの場合は通常不要なので_で受け取る)です。contoursリストの各要素が、一つの検出された輪郭に対応する点のNumpy配列です。
検出されたオブジェクトのフィルタリング
cv2.findContoursで多くの輪郭が検出されることがありますが、中には非常に小さな領域や、目的のオブジェクトではない形状のものも含まれる場合があります。これらの不要な輪郭を除去するために、輪郭のプロパティ(面積、周囲長、縦横比など)を使ってフィルタリングを行うのが一般的です。
- 面積でフィルタリング:
cv2.contourArea(contour)関数を使って各輪郭の面積を計算し、閾値より小さい輪郭を無視します。 - 周囲長でフィルタリング:
cv2.arcLength(contour, closed)関数を使って各輪郭の周囲長を計算します。 - 外接矩形や最小外接円:
cv2.boundingRect(contour)で輪郭の外接矩形を取得したり、cv2.minEnclosingCircle(contour)で最小外接円を取得したりして、オブジェクトの位置やサイズ情報を得ます。
“`python
面積でフィルタリングする例
min_area = 100 # 最小面積の閾値 (ピクセル数)
フィルタリング後の輪郭を格納するリスト
filtered_contours = []
輪郭を一つずつ処理
for contour in contours:
area = cv2.contourArea(contour) # 輪郭の面積を計算
# 面積が閾値より大きい場合のみ採用
if area > min_area:
filtered_contours.append(contour)
# 外接矩形を取得して描画(オプション)
x, y, w, h = cv2.boundingRect(contour)
cv2.rectangle(image_with_contours, (x, y), (x+w, y+h), (255, 0, 0), 2) # 青色で描画
フィルタリングされた輪郭の数
print(f”検出された輪郭の数 (フィルタリング前): {len(contours)}”)
print(f”検出された輪郭の数 (面積フィルタリング後, 面積 > {min_area}): {len(filtered_contours)}”)
フィルタリング後の輪郭を描画した画像を表示 (外接矩形も含まれている可能性あり)
plt.figure(figsize=(8, 6))
plt.imshow(cv2.cvtColor(image_with_contours, cv2.COLOR_BGR2RGB)) # 元画像に描画したので元画像を表示
plt.title(“面積フィルタリング後の輪郭”)
plt.axis(‘off’)
plt.show()
これで、filtered_contours リストには目的のオブジェクトに対応する可能性が高い輪郭だけが残ります。
これらの輪郭の情報(位置、サイズ、形状など)を使って、さらに高度な処理に進むことができます。
“`
これらの後処理を組み合わせることで、単に色範囲でピクセルを抽出するだけでなく、画像中の具体的なオブジェクトを特定し、そのプロパティを取得することが可能になります。
7. cv2.inRangeの限界と代替手段
cv2.inRangeはシンプルで高速な色検出手法ですが、万能ではありません。いくつかの限界と、それに対する代替手段や補完手段を理解しておくことが重要です。
限界
- 照明条件への弱さ: 画像の明るさや影、異なる光源(自然光、蛍光灯、LEDなど)によって、同じオブジェクトでもHSV値が大きく変化します。これは、特にValue(明度)やSaturation(彩度)に影響します。設定した固定のHSV範囲では、異なる照明下での同じ色を頑健に検出するのが難しい場合があります。
- 色の均一性への依存:
inRangeはピクセル単位の判定です。検出したい色が画像中で均一でない(グラデーションがかかっている、テクスチャがあるなど)場合、一部が範囲から外れて検出漏れが発生することがあります。 - 背景との区別: 検出したい色と同じような色が背景や他のオブジェクトに存在する場合、それらも一緒に検出されてしまいます。マスク画像だけでは、目的のオブジェクトか背景ノイズかを区別できません。
- 透過性や鏡面反射: 半透明の物体や光沢のある表面を持つ物体は、背景の色や反射光の影響を受けて色が変化するため、単純な
inRangeでは安定した検出が困難です。
代替手段・補完手段
これらの限界に対処するために、以下のような手法が考えられます。
- 適応的な閾値処理(Adaptive Thresholding): 明るさが場所によって異なる画像に対して、局所的な領域ごとに閾値を計算して2値化する手法です。ただし、これはグレースケール画像に対する処理であり、カラー画像の
inRangeに直接適用するものではありませんが、画像の前処理として明るさの不均一性をある程度補正するのに役立つ場合があります。 - カラー正規化: 画像全体の明るさやコントラストを調整することで、照明の影響を軽減しようとする手法です。
- 機械学習ベースの手法:
- カラー分類器: 学習データを用いて、特定の色のピクセルとそうでないピクセルを分類するモデル(例: SVM, K-Nearest Neighbors, ニューラルネットワークなど)を訓練します。照明や背景の色に対してより頑健な分類器を構築できる可能性があります。
- セマンティックセグメンテーション: 画像中の各ピクセルが何(例: 青いボール、人、空)に属するかを高精度に分類する手法です。FCN, U-Netなどのディープラーニングモデルが使われます。
inRangeよりもはるかに計算コストがかかりますが、複雑なシーンや多様な照明条件下での物体検出に強力です。 - オブジェクト検出モデル: YOLO, SSD, Faster R-CNNなどのモデルは、訓練データに基づいて特定のカテゴリのオブジェクト(例: 車、人、特定の製品)を直接検出します。色情報だけでなく、形状やテクスチャなどの特徴も総合的に判断するため、
inRangeよりもはるかに高精度ですが、大量のラベル付きデータと高い計算能力が必要です。
- 形状やテクスチャとの組み合わせ: 色情報だけでなく、検出したいオブジェクトの形状(例: 円形、四角形)やテクスチャパターンも同時に考慮することで、背景との区別をつけたり、誤検出を減らしたりできます。
cv2.findContoursで得られた形状情報をフィルタリングに使うのはその一例です。 - 複数の特徴の融合: 色、形状、テクスチャ、さらには時間的な情報(動画の場合)など、複数の特徴を組み合わせてオブジェクトを検出・追跡する手法です。
cv2.inRangeはシンプルであるという最大の利点があり、特定のタスク(例: 固定された照明下での単色の物体検出、簡単な画像セグメンテーション)においては非常に効果的で十分な性能を発揮します。しかし、より複雑な状況や高い頑健性が求められる場合には、上記の他の手法や、inRangeをこれらの手法と組み合わせることを検討する必要があります。
8. まとめと次のステップ
この記事では、OpenCVのcv2.inRange関数を使った特定色の検出・抽出について、基本的な使い方から応用までを詳しく解説しました。
主なポイント:
cv2.inRangeは、指定された色のHSV範囲内にあるピクセルを抽出してマスク画像を生成する関数です。- 色検出には、BGRよりもHSVカラー空間を使うのが一般的で効果的です。
- 検出したい色のHSV範囲(下限値と上限値)の設定が最も重要であり、画像や照明条件に合わせて調整が必要です。オンラインツール、ピクセル値の直接参照、トラックバーによる対話的調整などの方法があります。
- 赤色のようにHueが色相環の端に位置する場合は、複数の範囲でマスクを作成し、論理和で結合するテクニックが必要です。
inRangeで得られたマスク画像は、モルフォロジー変換(オープニング、クロージングなど)でノイズを除去することで、精度を向上させることができます。- クリーンになったマスク画像に対して
cv2.findContoursを適用することで、検出された色領域に対応するオブジェクトの輪郭を取得できます。 - 輪郭の面積などでフィルタリングすることで、目的のオブジェクト候補を絞り込むことができます。
cv2.inRangeはシンプルで高速ですが、照明の変化などに弱いという限界があります。より頑健な検出には、機械学習や他の特徴(形状、テクスチャ)との組み合わせを検討する必要があります。
cv2.inRange関数は、OpenCVを使った画像処理における色の利用の第一歩です。この関数をマスターすることは、より高度な画像解析やコンピュータービジョンアプリケーションを開発する上での重要な基礎となります。
次のステップとして:
- 実際に様々な画像を読み込み、異なる色の検出を試してみてください。
- トラックバーを使ったHSV範囲調整ツールを自作し、最適な範囲を見つける練習をしてみてください。
- 検出した輪郭を使って、オブジェクトの中心座標や面積、円形度などのプロパティを計算し、表示するコードを書いてみてください。
- 動画ファイルやWebカメラの映像に対してリアルタイムに色検出・追跡を実装してみてください。
これらの実践を通して、cv2.inRange関数とその応用技術に対する理解がさらに深まるはずです。特定の色を検出・抽出する能力は、あなたの画像処理プロジェクトの可能性を大きく広げてくれるでしょう。
Happy coding!