OpenCV HSVで特定の色を検出する方法【初心者向け】


OpenCV HSVで特定の色を検出する方法【初心者向け】

はじめに:なぜ特定の色を検出したいのか?

コンピュータに画像を見せて、「この画像の中に赤いリンゴはありますか?」「緑色の信号機を見つけてください」「これは青い車ですか?」といった問いに答えさせる――これはコンピュータビジョンという分野の基本的な課題の一つです。そして、これらの課題に取り組む上で非常に重要な技術の一つが「特定の色を検出すること」です。

例えば、

  • 自動運転車:信号機の色(赤、黄、青)や道路標識の色を識別する。
  • 品質検査:製造ラインで不良品(色がおかしい製品)を検出する。
  • 物体追跡:特定の色の物体(例えば、マーカー)をカメラで追いかける。
  • 画像編集:写真の中の特定の色だけを変更したり、強調したりする。
  • ロボット工学:ロボットが特定の色のおもちゃを見つけて掴む。

このように、特定の色を検出する技術は、私たちの身の回りの様々な場所で活用されています。

この検出を行うために、最も一般的で効果的な手法の一つが、OpenCVという強力なコンピュータビジョンライブラリと、HSV色空間という色の表現方法を組み合わせる方法です。

この記事では、PythonとOpenCVを使って、画像の中から特定の色(例えば「青色」)を持つ領域だけを抜き出す方法を、初心者の方でも理解できるように、徹底的に、そしてステップバイステップで解説していきます。なぜRGBではなくHSVを使うのか、HSVとは何か、具体的にどうコードを書けば良いのか、そして検出精度を上げるための工夫まで、約5000語のボリュームで詳細に説明します。

この記事を読み終える頃には、あなたもOpenCVを使って画像の中から好きな色を見つけ出せるようになっているはずです。さあ、一緒にコンピュータに「色」を認識させる世界へ踏み出しましょう。

OpenCVとは?コンピュータビジョンの強力な味方

特定の色を検出するために利用する主要なツールが、OpenCV(Open Source Computer Vision Library)です。OpenCVは、画像処理やコンピュータビジョンのための様々なアルゴリズムや機能が豊富に実装された、まさにコンピュータビジョンのためのオープンソースライブラリです。

  • 様々な機能: 画像の読み込み、保存、表示から始まり、画像フィルタリング、幾何学的な変換、特徴点検出、物体認識、顔検出、動画処理、3次元復元など、コンピュータビジョンの幅広い分野をカバーしています。
  • 多くの言語に対応: C++で開発されていますが、Python、Java、MATLABなど、様々なプログラミング言語から利用できます。特にPythonからの利用が非常に手軽で人気があります。
  • 高性能: C++で実装されているため処理速度が速く、リアルタイムな画像・動画処理にも適しています。
  • 無料かつオープンソース: 誰でも自由に利用でき、商用利用も可能です。

このOpenCVを使うことで、私たちは複雑な画像処理のアルゴリズムをゼロから実装する必要なく、数行のコードで高度な画像処理を行うことができます。特定の色を検出する機能も、OpenCVが提供する機能の一つとして簡単に利用できます。

色空間とは?色を数値で表現する方法

コンピュータが画像を扱うとき、色は数値で表現されます。この色の表現方法を「色空間(Color Space)」と呼びます。私たちが普段目にしているディスプレイやカメラは、通常「RGB色空間」を使っています。しかし、色の検出においては、RGB色空間よりも「HSV色空間」の方が都合が良い場面が多くあります。

いくつか代表的な色空間を見てみましょう。

  1. RGB色空間 (Red, Green, Blue)

    • 赤(R)、緑(G)、青(B)の3つの色の光を混ぜ合わせることで全ての色を表現します。
    • それぞれの色の強さを数値(例えば0から255の整数)で表し、(R, G, B)の組み合わせで一つの色を指定します。
    • ディスプレイやデジタルカメラ、スキャナーなど、ハードウェアの色表現によく使われます。
    • 直感的で分かりやすい色空間ですが、色の「種類」(例えば「赤」)だけを指定したい場合や、照明の変化の影響を受けやすいという欠点があります。
  2. グレースケール (Grayscale)

    • 色の情報を持たず、明るさ(輝度)だけで画像を表現します。
    • 通常、0(黒)から255(白)までの単一の数値で表現されます。
    • 色情報が不要な処理(例えば輪郭検出や特定の特徴点の検出など)で利用されます。RGB画像をグレースケールに変換することで、データ量を減らし処理を高速化できる場合があります。
  3. HSV色空間 (Hue, Saturation, Value)

    • 私たちが色を認識する方法に近いと言われる色空間です。
    • 色相(Hue)、彩度(Saturation)、明度(Value)の3つの要素で色を表現します。
    • 色の検出において非常に強力な色空間であり、本記事の主役です。これについては後ほど詳しく解説します。

他にも様々な色空間(CMYK, YCbCr, Labなど)が存在しますが、特定の色を検出する基本的なタスクにおいては、RGBとHSVを理解していれば十分です。

なぜRGBではなくHSVを使うのか?RGBの限界

私たちが日常的に使っているディスプレイやカメラはRGB色空間で画像を扱います。では、なぜ特定の色を検出する際に、あえてHSV色空間に変換して処理を行うことが多いのでしょうか? その理由は、RGB色空間が「色の検出」というタスクにおいていくつかの課題を持っているからです。

RGB色空間では、全ての色が赤、緑、青の光の三原色の組み合わせで表現されます。例えば、純粋な赤は(255, 0, 0)、純粋な緑は(0, 255, 0)、純粋な青は(0, 0, 255)といったように表現されます(値の範囲はシステムによって異なりますが、ここでは0-255とします)。

しかし、例えば「赤色」を画像から検出したいと考えた場合、RGB値で「赤色」の範囲を定義するのは簡単ではありません。

  • 明るさによる変化: 明るい赤は(255, 0, 0)に近いかもしれませんが、暗い赤(例えば影になっている部分)は(100, 0, 0)のように、Rの値だけでなく、GやBの値も微妙に変化する可能性があります。単にRの値が大きいというだけでは、「赤みがかった茶色」や「ピンク」なども含まれてしまうかもしれません。
  • 鮮やかさによる変化: 鮮やかな赤は(255, 0, 0)ですが、彩度が低い赤(例えば、灰色がかった赤)は(200, 50, 50)のように、R以外の値も大きくなり、全体的に白っぽく、または黒っぽくなります。
  • 照明条件による変化: 白っぽい照明の下での赤と、暖色系の照明の下での赤、寒色系の照明の下での赤では、同じ物体であってもカメラが捉えるRGB値は大きく異なります。例えば、暖色系の照明の下ではRの値が全体的に高めに出る傾向があります。

このように、RGB色空間では、「色の種類」(赤であること)と「明るさ」や「鮮やかさ」、「照明の影響」が密接に絡み合って数値に反映されます。そのため、「赤」という色を定義するために、「Rの値が○から△、かつGの値が□から✕、かつBの値が△から☆」といった複雑な範囲指定が必要になり、しかもその範囲は照明条件や物体自身の特性によって大きく変わってしまいます。

これは、人間が色を認識する方法とは少し異なります。私たちは、明るい赤でも暗い赤でも、鮮やかな赤でも少し白っぽい赤でも、「赤色だ」と認識できます。色の「種類」と「明るさ」や「鮮やかさ」を分けて認識していると言えます。

そこで登場するのが、HSV色空間です。

HSV色空間の詳細:色を検出するのに最適な理由

HSV色空間は、人間の色の認識に近い形で色を表現することを目的として設計されています。HSVは、以下の3つの要素で構成されます。

  1. Hue (H: 色相)

    • 色の「種類」を示します。赤、オレンジ、黄、緑、青、紫といった、いわゆる「色み」です。
    • 通常、円環状に配置され、角度で表現されます。赤を0度とし、時計回りに変化していきます(例: 黄色約60度、緑約120度、青約240度など)。
    • OpenCVでは、Hueの値は0から179の範囲になります。 一般的なHSVモデルでは0-360度で表現されることが多いのですが、OpenCVでは1バイト(0-255)に収めるために、角度を半分にして0-179としています。これは非常に重要な点です。赤色は0付近と179付近の両方に現れます。
    • 色の種類だけを指定したい場合に、Hueの値の範囲だけを見れば良いため、非常に直感的です。照明の変化や明るさが変わっても、色の種類(Hue)自体は比較的安定しているという特性があります。
  2. Saturation (S: 彩度)

    • 色の「鮮やかさ」を示します。どれだけ色が混じりけのない純粋な色に近いかを表します。
    • 0に近いほど灰色に近く、値が大きいほど鮮やかな色になります。
    • OpenCVでは、Saturationの値は0から255の範囲になります。 0が完全に灰色の段階で、255が最も鮮やかな色です。
  3. Value (V: 明度)

    • 色の「明るさ」を示します。どれだけ色が明るいか、暗いかを表します。
    • 0に近いほど黒に近く、値が大きいほど白に近い明るさになります。
    • OpenCVでは、Valueの値は0から255の範囲になります。 0が完全に黒で、255が最も明るい色です。

HSV色空間では、Hue(色の種類)と、Saturation(鮮やかさ)およびValue(明るさ)が分離されています。これが、特定の色を検出する際にHSVが有利な最大の理由です。

  • 色の種類を指定しやすい: 「赤色」を検出したい場合は、Hueの値で「赤」の範囲(OpenCVでは0-10と170-179あたり)を指定し、SaturationとValueの値で「どれくらいの鮮やかさと明るさの赤を検出するか」という条件を加えることができます。
  • 照明の変化に比較的強い: 物体が明るくなったり暗くなったりしても、色の種類(Hue)は比較的変化しにくい傾向があります。これは、主にValueの値が変化することで明るさが表現されるためです。もちろん、極端に暗い場所や明るい場所ではHueも変化しますが、RGBに比べると安定しています。

例えば、明るい赤も暗い赤も、鮮やかな赤も少しくすんだ赤も、「赤色」として検出したい場合、HSV色空間であればHueの範囲を「赤」の範囲に固定し、SaturationとValueの範囲を広めに設定することで対応しやすくなります。

このように、HSV色空間は人間の色認識に近い構造を持ち、色の種類、鮮やかさ、明るさを分離して扱えるため、特定の色の検出というタスクにおいて非常に強力で直感的な色空間と言えます。

特定の色を検出する基本的な流れ

OpenCVとHSV色空間を使って画像から特定の色を検出する基本的な手順は以下の通りです。

  1. 必要なライブラリのインポート: OpenCV (cv2)とNumPy (numpy)をインポートします。NumPyは数値計算ライブラリで、画像データを扱う際に頻繁に利用します。
  2. 画像を読み込む: cv2.imread() 関数を使って、処理したい画像を読み込みます。
  3. 画像をRGBからHSVに変換する: 読み込んだ画像は通常RGB(またはBGR)形式で格納されています。これを cv2.cvtColor() 関数を使ってHSV形式に変換します。OpenCVではBGR順で画像を読み込むため、実際にはBGRからHSVへの変換になりますが、概念としてはRGBからHSVへの変換と考えて問題ありません。
  4. 検出したい色のHSV範囲(上限と下限)を指定する: これが特定の色を検出する上での最も重要なステップの一つです。検出したい色のHSV値を調べ、その色の許容範囲(最低値と最高値)を定義します。これはNumPy配列として定義します。
  5. 指定したHSV範囲内のピクセルを抽出するためのマスク画像を生成する: cv2.inRange() 関数を使います。この関数は、HSV画像と指定したHSV範囲を受け取り、その範囲内のピクセルが「白」(255)、それ以外のピクセルが「黒」(0)となるような二値画像(マスク画像と呼ばれます)を生成します。
  6. マスク画像を使って元の画像から特定の色が含まれる領域を抽出する: cv2.bitwise_and() 関数を使います。この関数は、元の画像とマスク画像を論理積(AND)演算することで、マスク画像が白(255)のピクセルに対応する元の画像の色情報だけを抽出します。結果として、特定の色を持つ領域だけが表示された画像が得られます。
  7. 結果を表示する: cv2.imshow() 関数を使って、生成されたマスク画像や抽出された色領域の画像を表示します。
  8. ウィンドウを閉じる: cv2.waitKey()cv2.destroyAllWindows() 関数を使って、キー入力を待ってから表示ウィンドウを閉じます。

この流れに沿って、実際にPythonコードを書いてみましょう。

実践: 特定の「青」色を検出する

ここでは例として、画像の中から「青色」を検出してみましょう。

まず、必要なライブラリをインポートします。

python
import cv2
import numpy as np

次に、処理したい画像を読み込みます。お手元に青い物体(例えば青いバケツ、青い本、青空などが写った写真)が写っているサンプル画像を用意してください。この記事では blue_example.jpg という名前のサンプル画像を使用すると仮定します。

“`python

画像の読み込み

ファイルパスはあなたの環境に合わせて変更してください

image = cv2.imread(‘blue_example.jpg’)

画像が正しく読み込めたか確認

if image is None:
print(“エラー: 画像ファイルを読み込めませんでした。ファイルパスを確認してください。”)
exit()

元の画像を表示 (確認用)

cv2.imshow(“Original Image”, image)
“`

次に、画像をBGR色空間からHSV色空間に変換します。OpenCVの imread 関数はデフォルトでBGR形式で画像を読み込むことに注意してください。

“`python

BGR画像をHSV画像に変換

hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
“`

これで、画像データがHSV形式になりました。各ピクセルは3つの値 (Hue, Saturation, Value) で表現されています。

さて、いよいよ「特定の色」である「青色」を定義し、そのHSV範囲を指定します。

特定の色のHSV範囲を調べる方法

特定の色(例えば青)のHSV範囲を知るにはどうすれば良いでしょうか? いくつか方法があります。

  1. カラーピッカーツールを利用する:

    • オンラインで「HSVカラーピッカー」と検索すると、多くのツールが見つかります。色を選択すると、その色のRGB値やHSV値が表示されます。
    • 画像編集ソフト(Photoshop, GIMPなど)にもカラーピッカー機能があります。画像上の特定の色をクリックすると、その色のRGB値やHSV値が表示されます。
    • ただし、これらのツールで表示されるHSV値は、Hueが0-360度、SaturationとValueが0-100%や0-255など、ツールによって範囲が異なります。OpenCVのHSV範囲(Hue: 0-179, Saturation: 0-255, Value: 0-255)に変換して考える必要があります(Hueは半分にする、SaturationとValueは必要に応じてスケール変換する)。
  2. OpenCVを使って特定のRGB値のHSV値を調べるスクリプトを書く:

    • 最も確実な方法は、OpenCV自体を使って、調べたい色のRGB値をHSV値に変換してみることです。
    • 例えば、RGBで(255, 0, 0)の純粋な赤がOpenCVでどのようなHSV値になるか調べたい場合、次のようにします。

    “`python

    調べたい色のBGR値(例: 純粋な青 B=255, G=0, R=0)

    OpenCVはBGR順なので注意!青なら (255, 0, 0) ではなく (255, 0, 0) は B=255, G=0, R=0 です

    純粋な青は RGB(0, 0, 255) -> BGR(255, 0, 0)

    blue_bgr = np.uint8([[[255, 0, 0]]]) # NumPy配列として [[[‘BGR’]]] の形にするのがポイント

    BGRからHSVに変換

    blue_hsv = cv2.cvtColor(blue_bgr, cv2.COLOR_BGR2HSV)

    print(f”BGR(255, 0, 0) のHSV値 (OpenCV形式): {blue_hsv}”)

    出力例: [[[120 255 255]]] (Hue=120, Saturation=255, Value=255)

    “`
    この方法で、検出したい「青」が画像中でだいたいどのようなRGB値になっているかを画像編集ソフトなどで調べ、そのRGB値をOpenCVでHSVに変換してみると、目安となるHSV値が得られます。

  3. 検出対象の画像からHSV値を取得する:

    • 検出したい特定の色が実際に含まれている画像を開き、画像編集ソフトなどでその色のRGB値を確認します。
    • 次に、上記のスクリプトを使ってそのRGB値をHSV値に変換します。
    • 画像中の複数の「青色」ピクセルのHSV値を調べてみて、だいたいどのくらいの範囲に収まっているかを確認すると、より正確な範囲を設定できます。

青色の代表的なHSV範囲(OpenCV形式: H=0-179, S=0-255, V=0-255):

一般的な「青色」のHueの範囲は、OpenCVのHSV空間ではおよそ100から140程度になることが多いです。ただし、これはあくまで目安であり、青の種類(水色、紺色など)や照明条件によって変化します。

SaturationとValueの範囲は、「どれくらい鮮やかで、どれくらい明るい青を検出したいか」によって調整します。

  • 鮮やかな青だけ を検出したいなら、Saturationの下限を高めに設定します(例えば 100 や 150)。白っぽい青や灰色がかった青は除外されます。
  • 暗い青(紺色など)も含めたい なら、Valueの下限を低めに設定します(例えば 50 や 30)。明るい青だけでなく、暗い青も含まれるようになります。
  • 明るい青(水色など)も含めたい なら、Valueの上限を高めに設定します(例えば 255)。
  • 白に近い青(水色など)や黒に近い青(紺色など) も含めたいなら、SaturationやValueの範囲を広く設定します。

ここでは例として、ある程度の鮮やかさがあり、極端に暗すぎない「青色」を検出するためのHSV範囲を設定してみましょう。これはあくまで一例であり、実際に検出したい色に合わせて調整が必要です。

  • Hue: 100 – 140 (青の一般的な範囲)
  • Saturation: 50 – 255 (ある程度鮮やかさがある色。0に近い灰色や白っぽい色は除外)
  • Value: 50 – 255 (ある程度の明るさがある色。真っ黒に近い色は除外)

この範囲をNumPy配列として定義します。範囲の最低値と最高値をそれぞれ lower_blueupper_blue とします。

“`python

検出したい色のHSV範囲を設定 (例: 青色)

OpenCVのHSV範囲: H (0-179), S (0-255), V (0-255)

lower_blue = np.array([100, 50, 50])
upper_blue = np.array([140, 255, 255])
“`
重要: これらの値はあくまで例です。検出したい画像の中の「青色」の具体的な色合いや照明条件によって、最適な値は大きく異なります。実際に画像を見ながら、HSV値を調べたり、この範囲の値を少しずつ調整したりして、最適な範囲を見つける作業が重要になります。

次に、このHSV範囲を使ってマスク画像を生成します。cv2.inRange() 関数は、入力HSV画像に対して、指定された下限と上限の範囲内にあるピクセルを255(白)、それ以外のピクセルを0(黒)に設定した二値画像(マスク画像)を返します。

“`python

HSV画像から指定した範囲の色を抽出するためのマスクを作成

cv2.inRange(入力画像, 下限のHSV値, 上限のHSV値)

mask = cv2.inRange(hsv_image, lower_blue, upper_blue)

生成されたマスク画像を表示

cv2.imshow(“Mask (Blue)”, mask)
“`

生成された mask 画像は、元の画像と同じサイズで、青色と判断されたピクセルが白、そうでないピクセルが黒になっています。これは、特定の色が画像中のどこにあるかを示す地図のようなものです。

最後に、このマスク画像を使って、元の画像から青色の領域だけを抽出します。cv2.bitwise_and() 関数は、二つの画像に対してビットごとの論理積(AND)演算を行います。ここでは、元の画像自身と論理積を取り、マスク画像を適用します。マスク画像が白(255)のピクセルに対応する元の画像の色情報はそのまま残り、マスク画像が黒(0)のピクセルに対応する元の画像の色情報は0(黒)になります。

“`python

マスクを使って元の画像から青色領域のみを抽出

cv2.bitwise_and(画像1, 画像2, mask=マスク画像)

画像1と画像2は同じ画像を指定することが多い

result_image = cv2.bitwise_and(image, image, mask=mask)

抽出結果を表示

cv2.imshow(“Result Image (Blue)”, result_image)
“`

これで、画像から青色が含まれる領域だけが抽出された画像 result_image が得られました。

最後に、ウィンドウが表示されたままにならないように、キー入力で閉じる処理を加えます。

“`python

ウィンドウを表示し、何らかのキーが押されるまで待機

cv2.waitKey(0)

表示した全てのウィンドウを閉じる

cv2.destroyAllWindows()
“`

全てのコードをまとめると以下のようになります。

“`python
import cv2
import numpy as np

1. 画像の読み込み

ファイルパスはあなたの環境に合わせて変更してください

image = cv2.imread(‘blue_example.jpg’)

画像が正しく読み込めたか確認

if image is None:
print(“エラー: 画像ファイルを読み込めませんでした。ファイルパスを確認してください。”)
exit()

元の画像を表示 (確認用)

cv2.imshow(“Original Image”, image)

2. BGR画像をHSV画像に変換

hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)

3. 検出したい色のHSV範囲を設定 (例: 青色)

OpenCVのHSV範囲: H (0-179), S (0-255), V (0-255)

検出したい「青」の色合いに合わせてこれらの値を調整する必要があります

lower_blue = np.array([100, 50, 50])
upper_blue = np.array([140, 255, 255])

4. HSV画像から指定した範囲の色を抽出するためのマスクを作成

cv2.inRange(入力画像, 下限のHSV値, 上限のHSV値)

mask = cv2.inRange(hsv_image, lower_blue, upper_blue)

生成されたマスク画像を表示

cv2.imshow(“Mask (Blue)”, mask)

5. マスクを使って元の画像から青色領域のみを抽出

cv2.bitwise_and(画像1, 画像2, mask=マスク画像)

result_image = cv2.bitwise_and(image, image, mask=mask)

6. 抽出結果を表示

cv2.imshow(“Result Image (Blue)”, result_image)

7. ウィンドウを表示し、何らかのキーが押されるまで待機

cv2.waitKey(0)

8. 表示した全てのウィンドウを閉じる

cv2.destroyAllWindows()
“`

このコードを実行すると、元の画像、生成されたマスク画像、そして青色領域だけが抽出された結果画像の3つのウィンドウが表示されます。マスク画像を見れば、どのピクセルが青色と判断されたかが白く表示されていることで確認できます。結果画像では、マスクが白だった部分のみ元の画像の色が表示され、マスクが黒だった部分は真っ黒になっているはずです。

これが、OpenCVとHSV色空間を使った特定の色検出の基本的な流れです。

さまざまな色を検出する際のHSV範囲の設定

青色の検出例を見ましたが、他の色を検出したい場合はどうすれば良いでしょうか? 基本的な手順は同じですが、「検出したい色のHSV範囲を設定する」部分が最も重要になります。

ここでは、他の代表的な色のHSV範囲の目安と、特に注意が必要な「赤色」の検出について説明します。

代表的な色のHSV範囲の目安(OpenCV形式: H=0-179, S=0-255, V=0-255):

あくまで目安です。同じ色でも、照明、影、隣接する色、カメラの種類など様々な要因でHSV値は変化します。また、「黄色」一つをとっても、レモンイエローとオレンジがかった黄色ではHueが異なります。

  • 赤色: Hueが0付近と179付近の二つの範囲に分かれます。
    • 低いHue範囲の赤: lower_red_1 = np.array([0, 50, 50]), upper_red_1 = np.array([10, 255, 255])
    • 高いHue範囲の赤: lower_red_2 = np.array([170, 50, 50]), upper_red_2 = np.array([179, 255, 255])
  • 橙色 (オレンジ): lower_orange = np.array([10, 50, 50]), upper_orange = np.array([25, 255, 255])
  • 黄色: lower_yellow = np.array([25, 50, 50]), upper_yellow = np.array([40, 255, 255])
  • 緑色: lower_green = np.array([40, 50, 50]), upper_green = np.array([80, 255, 255])
  • 水色: lower_cyan = np.array([80, 50, 50]), upper_cyan = np.array([100, 255, 255])
  • 青色: lower_blue = np.array([100, 50, 50]), upper_blue = np.array([140, 255, 255])
  • 紫色: lower_purple = np.array([140, 50, 50]), upper_purple = np.array([170, 255, 255])
  • 茶色: 茶色はHueが橙色〜赤色に近く、SaturationとValueが低い(彩度が低く、暗い)色です。範囲設定が少し難しいですが、lower_brown = np.array([10, 100, 20]), upper_brown = np.array([20, 255, 200]) のように、Hueはオレンジあたり、Saturationはある程度高く(ただし非常に鮮やかではない)、Valueは低い(暗い)といった範囲で試行錯誤します。
  • 白、灰色、黒: これらの色は鮮やかさ(Saturation)が非常に低い色です。
    • 白: lower_white = np.array([0, 0, 200]), upper_white = np.array([179, 30, 255]) (Sが低く、Vが高い)
    • 灰色: lower_gray = np.array([0, 0, 50]), upper_gray = np.array([179, 50, 200]) (Sが低く、Vが中程度)
    • 黒: lower_black = np.array([0, 0, 0]), upper_black = np.array([179, 255, 30]) (Vが非常に低い)

赤色の検出について:

赤色はHueが0°と360°(OpenCVでは0と179)の両端に位置するため、一つの連続した範囲では指定できません。例えば、H=170からH=10という範囲はOpenCVではうまく扱えません。

この場合、二つの範囲でマスクを作成し、それらを結合するという方法をとります。

例えば、OpenCV形式でHueの0-10の範囲と170-179の範囲を赤色と定義する場合:

  1. 低いHue範囲の赤のマスクを作成:
    python
    lower_red_1 = np.array([0, 50, 50])
    upper_red_1 = np.array([10, 255, 255])
    mask1 = cv2.inRange(hsv_image, lower_red_1, upper_red_1)
  2. 高いHue範囲の赤のマスクを作成:
    python
    lower_red_2 = np.array([170, 50, 50])
    upper_red_2 = np.array([179, 255, 255])
    mask2 = cv2.inRange(hsv_image, lower_red_2, upper_red_2)
  3. 二つのマスクを論理和(OR)で結合する:
    python
    # マスク1とマスク2を結合
    # cv2.add は単純な加算ではなく、ビットごとの論理和として使われることが多い
    mask = cv2.add(mask1, mask2)
    # あるいは cv2.bitwise_or(mask1, mask2) と書いても良い
    # mask = cv2.bitwise_or(mask1, mask2)

このようにして得られた mask 画像を使えば、低いHueの赤と高いHueの赤の両方を含む領域を抽出できます。

HSV範囲設定のコツ:

  • 対象画像をよく観察する: 検出したい色が画像中でどのような色合いになっているか(明るいか、暗いか、鮮やかか、くすんでいるか)を確認します。
  • カラーピッカーやHSV変換スクリプトを使う: 画像中の特定の色をクリックしてHSV値を調べ、目安とします。複数の場所で調べて、色のばらつきを確認すると良いでしょう。
  • 最初は広めに、徐々に狭める: 最初はHue, Saturation, Valueの範囲を広めに設定して実行してみます。もし意図しない色も検出されてしまう場合は、検出したい色のHSV値の目安を見ながら、少しずつ範囲を狭めていきます。特にSaturationとValueの下限・上限を調整することで、検出する色の明るさや鮮やかさの範囲をコントロールできます。
  • 環境光を考慮する: 検出する画像が撮影された環境(明るいか、暗いか、自然光か、人工光か)によって、Valueの値は大きく変動します。照明条件が変わる可能性がある場合は、Valueの範囲を広めにとるか、あるいは照明の影響を軽減する前処理(例: ヒストグラム均一化など)を検討する必要があります。
  • 繰り返し試行錯誤する: 最適なHSV範囲は、検出対象、撮影環境、求める検出精度によって異なります。何度かコードを実行し、結果を見ながらHSV範囲を調整するという試行錯誤が不可欠です。

マスク画像の改善(ノイズ除去など)

基本的な色検出は cv2.inRangecv2.bitwise_and で実現できましたが、生成されたマスク画像には、小さなノイズ(検出したい色とは無関係な、ごく小さな白い点や領域)が含まれてしまうことがあります。これは、画像センサーのノイズや、検出したい色の境界付近での微妙な色合いの変化などが原因で起こります。

このようなノイズが含まれたマスク画像を使って色領域を抽出したり、後続の処理(例えば輪郭検出など)を行ったりすると、誤検出や不要な小さな物体を扱ってしまうことにつながります。

そこで、マスク画像を改善するために、モルフォロジー変換(Morphological Transformations)という画像処理手法をよく利用します。モルフォロジー変換は、画像の形状(特に二値画像の白い領域の形)を、特定の形状のカーネル(構造要素とも呼ばれます)を使って変化させる処理です。

色の検出でよく使われるモルフォロジー変換には以下の2つがあります。

  1. 収縮 (Erosion): マスク画像上の白い領域を縮小させます。カーネル内の全てのピクセルが入力画像で白である場合にのみ出力画像の中心ピクセルが白になります。これにより、小さな白いノイズ(孤立点)を除去したり、物体の境界を滑らかにしたりする効果があります。同時に、物体のサイズも小さくなります。
  2. 膨張 (Dilation): マスク画像上の白い領域を拡大させます。カーネル内のピクセルのうち、少なくとも一つでも入力画像で白であれば出力画像の中心ピクセルが白になります。これにより、収縮で小さくなった物体を元に戻したり、物体の欠けた部分を埋めたり、近くにある物体同士を結合させたりする効果があります。

これらの基本的な操作を組み合わせることで、より複雑なマスク画像の改善が可能です。よく使われる組み合わせとして以下の2つがあります。

  • オープニング (Opening): 収縮の後に膨張を行います(Erosion -> Dilation)。これにより、小さな白いノイズ(収縮で消滅する)を除去する効果があります。物体のサイズは収縮で小さくなった後に膨張でほぼ元に戻りますが、孤立したノイズは消えたままになります。
  • クロージング (Closing): 膨張の後に収縮を行います(Dilation -> Erosion)。これにより、白い領域の中にある小さな穴(黒い点)を埋めたり、近くにある白い領域同士を結合させたりする効果があります。

色の検出では、多くの場合、オープニングを使って小さなノイズを除去することが有効です。

モルフォロジー変換を行うには、まずカーネルを定義します。カーネルは通常、cv2.getStructuringElement() 関数を使って作成します。よく使われるのは矩形(cv2.MORPH_RECT)です。

“`python

モルフォロジー変換のためのカーネルを定義 (例: 5×5の矩形カーネル)

kernel = np.ones((5, 5), np.uint8)

または cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)) を使う

kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))

“`

次に、作成したカーネルを使ってオープニング処理を適用します。

“`python

マスク画像にオープニング処理を適用 (ノイズ除去)

cv2.morphologyEx(入力画像, 処理の種類, カーネル)

処理の種類: cv2.MORPH_OPEN (オープニング), cv2.MORPH_CLOSE (クロージング), cv2.MORPH_ERODE (収縮), cv2.MORPH_DILATE (膨張)

opening = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)

改善されたマスク画像を表示

cv2.imshow(“Mask after Opening”, opening)
“`

オープニング処理を施した opening マスク画像を使えば、よりクリーンな結果が得られるはずです。抽出処理も、元の mask の代わりに opening を使います。

“`python

改善されたマスクを使って元の画像から色領域を抽出

result_image_cleaned = cv2.bitwise_and(image, image, mask=opening)

改善された抽出結果を表示

cv2.imshow(“Result Image (Cleaned)”, result_image_cleaned)
“`

これにより、小さなノイズによる誤検出が減り、より正確に特定の色領域を抽出できるようになります。カーネルのサイズ(例: (5, 5))は、除去したいノイズのサイズや物体の大きさに応じて調整が必要です。サイズが大きいほど、より大きなノイズを除去したり、より大きな穴を埋めたりできますが、物体の形状も大きく変化してしまいます。

検出した色領域の輪郭検出

特定の色領域をマスク画像として抽出できた後、その色を持つ物体のアウトライン(輪郭)を検出したい場合があります。例えば、検出した物体を囲む矩形を描画したり、物体の面積や中心座標を計算したりしたいときです。

これは、モルフォロジー変換を施したマスク画像(opening など)に対して、cv2.findContours() 関数を使うことで実現できます。

cv2.findContours() は、二値画像(ここではマスク画像)を受け取り、画像内の白い領域のアウトライン(輪郭)を検出します。

“`python

マスク画像 (オープニング処理後) から輪郭を検出

findContours() は入力を破壊するので、コピーを渡すのが安全

cv2.RETR_EXTERNAL: 一番外側の輪郭のみを検出

cv2.CHAIN_APPROX_SIMPLE: 輪郭の点の数を減らす (直線部分は端点のみ保持)

contours, _ = cv2.findContours(opening.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

検出された輪郭の数を確認

print(f”検出された輪郭の数: {len(contours)}”)
``cv2.findContoursの第二引数cv2.RETR_EXTERNALは、階層構造を無視して一番外側の輪郭だけを検出するためのフラグです。第三引数cv2.CHAIN_APPROX_SIMPLE` は、輪郭を構成する点を圧縮するためのフラグで、ほとんどの場合これで十分です。

findContours() は、検出された輪郭のリストと、輪郭の階層構造を返します。通常、輪郭のリストだけが必要なので、contours, _ = ... のように受け取ります。

検出された輪郭はNumPy配列のリストとして contours に格納されます。各輪郭は、その輪郭を構成する点の座標の配列です。

これらの輪郭を元の画像に描画してみましょう。cv2.drawContours() 関数を使います。

“`python

輪郭を描画するために元の画像をコピー

image_with_contours = image.copy()

検出された輪郭を画像に描画

cv2.drawContours(描画対象画像, 輪郭のリスト, 描きたい輪郭のインデックス(-1は全て), 色(BGR), 線の太さ)

cv2.drawContours(image_with_contours, contours, -1, (0, 255, 0), 2) # 緑色で太さ2の線

輪郭が描画された画像を表示

cv2.imshow(“Image with Contours”, image_with_contours)
“`

これで、検出された青色領域の輪郭が元の画像上に緑色の線で描画されます。

さらに、それぞれの輪郭に対して、例えばそれを囲む最小の矩形(バウンディングボックス)や、物体の中心座標を計算することもできます。これは、検出した物体がどこにあるかを特定したり、そのサイズを測ったりするのに役立ちます。

“`python

検出された各輪郭に対して処理を行う

for contour in contours:
# 輪郭を囲む最小の矩形を取得 (x, y, w, h) -> 左上の座標と幅、高さ
x, y, w, h = cv2.boundingRect(contour)

# 矩形を描画 (例: 赤色で太さ2の線)
cv2.rectangle(image_with_contours, (x, y), (x + w, y + h), (0, 0, 255), 2)

# 物体の中心座標を計算 (モーメントを利用)
M = cv2.moments(contour)
if M["m00"] != 0: # 分母がゼロでないことを確認
    cx = int(M["m10"] / M["m00"])
    cy = int(M["m01"] / M["m00"])
    # 中心に点を描画 (例: マゼンタ色で半径5の塗りつぶし円)
    cv2.circle(image_with_contours, (cx, cy), 5, (255, 0, 255), -1) # -1は塗りつぶし

# 検出された物体の情報を表示 (例: 矩形の座標)
# print(f"  物体検出: x={x}, y={y}, w={w}, h={h}, cx={cx}, cy={cy}")

輪郭と矩形、中心点が描画された画像を再度表示

cv2.imshow(“Image with Bounding Boxes and Centers”, image_with_contours)

ウィンドウ表示継続と終了処理

cv2.waitKey(0)
cv2.destroyAllWindows()
“`

これで、検出された青色領域がそれぞれ矩形で囲まれ、中心に点が描画された画像が表示されます。

このように、色検出で得られたマスク画像は、単に色領域を抽出するだけでなく、輪郭検出と組み合わせることで、画像内の特定の色の物体を識別し、その位置やサイズといった情報を取得するための基盤となります。

動画での色検出

ここまでは静止画像での色検出を見てきましたが、OpenCVを使えば、カメラからのリアルタイム映像や動画ファイルに対しても同じ色検出処理を適用できます。基本的な考え方は、動画を構成する各フレーム(静止画像)に対して、上記と同じ処理を繰り返し行うことです。

動画を処理する基本的な流れは以下のようになります。

  1. cv2.VideoCapture オブジェクトを作成し、カメラ(通常は引数に0を指定)または動画ファイルを開きます。
  2. 無限ループ(またはフレーム数がなくなるまでのループ)を開始します。
  3. ループ内で、read() メソッドを使って1フレームずつ画像を取得します。
  4. フレームが正常に読み込めたか確認します。読み込めなかった場合はループを終了します。
  5. 読み込んだフレーム(静止画像)に対して、上記で説明した静止画像の色検出処理(HSV変換、マスク生成、抽出、必要に応じてモルフォロジー変換や輪郭検出など)を適用します。
  6. 処理結果のフレームを表示します。
  7. cv2.waitKey() を使って、一定時間待機しつつ、特定のキー(例えば ‘q’ キー)が押されたかどうかをチェックします。キーが押されたらループを終了します。
  8. ループ終了後、cv2.VideoCapture オブジェクトを解放し、全てのウィンドウを閉じます。

リアルタイムで青色を検出する動画処理の例を以下に示します。

“`python
import cv2
import numpy as np

カメラを起動 (通常、0はPCに接続されたデフォルトのカメラ)

動画ファイルの場合はファイルパスを指定: cv2.VideoCapture(‘your_video.mp4’)

cap = cv2.VideoCapture(0)

カメラが正しく起動したか確認

if not cap.isOpened():
print(“エラー: カメラを起動できませんでした。カメラが接続されているか、別のカメラ番号を試してください。”)
exit()

検出したい色のHSV範囲を設定 (例: 青色)

これらの値は環境に合わせて調整が必要です

lower_blue = np.array([100, 50, 50])
upper_blue = np.array([140, 255, 255])

マスク画像改善のためのカーネル

kernel = np.ones((5, 5), np.uint8)

while True:
# 1フレームずつ読み込む
ret, frame = cap.read()

# フレームが正しく読み込めなかった場合 (動画終了など)
if not ret:
    print("フレームを読み込めませんでした。終了します。")
    break

# フレームをBGRからHSVに変換
hsv_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)

# 指定したHSV範囲の色を抽出するためのマスクを作成
mask = cv2.inRange(hsv_frame, lower_blue, upper_blue)

# マスク画像にオープニング処理を適用 (ノイズ除去)
mask_cleaned = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)

# 改善されたマスクを使って元のフレームから色領域を抽出
result_frame = cv2.bitwise_and(frame, frame, mask=mask_cleaned)

# 結果を表示
cv2.imshow("Original Frame", frame)
cv2.imshow("Mask (Blue)", mask_cleaned) # 処理後のマスクを表示すると分かりやすい
cv2.imshow("Result Frame (Blue)", result_frame)

# 'q' キーが押されたらループを抜ける
if cv2.waitKey(1) & 0xFF == ord('q'):
    break

キャプチャを解放

cap.release()

全てのウィンドウを閉じる

cv2.destroyAllWindows()
“`

このコードを実行すると、カメラからのリアルタイム映像が表示され、同時に青色領域だけが抽出された映像と、マスク画像が表示されます。青色の物体をカメラにかざすと、それが検出されるのが分かります。

cv2.waitKey(1) は、1ミリ秒待機し、キー入力を受け付けます。これにより、ループが高速に回転し、動画として滑らかに表示されます。引数を大きくすると、コマ送りになります。& 0xFF == ord('q') は、押されたキーが ‘q’ であるかを判定するためのPythonの定石です。

動画処理では、各フレームに対して処理を行うため、処理速度が重要になります。あまりに複雑な処理を行うと、フレームレートが低下し、映像がカクつくことがあります。特定の色検出のような処理は比較的軽量なので、リアルタイムでの実行も十分可能です。

HSV範囲の自動調整や学習(発展的な話題)

これまで説明した特定の色検出は、手動でHSV範囲を設定する静的な方法です。この方法の欠点は、照明条件が変わったり、検出対象の色のバリエーションが多かったりすると、設定した固定のHSV範囲ではうまく検出できなくなる可能性があることです。

より頑健な色検出システムを構築するためには、以下のような発展的なアプローチが考えられます。

  1. 環境適応型のHSV範囲調整:

    • 画像全体、あるいは画像中の特定の領域の色分布を分析し、その結果に基づいて動的にHSV範囲を調整する方法です。
    • 例えば、初期フレームや、ユーザーが指定した領域のピクセルのHSV値をサンプリングし、その平均やばらつきからHSV範囲を自動的に設定・更新するといったアプローチがあります。
    • 色のクラスタリング(例えばK-Means法など)を使って、画像に含まれる主要な色を抽出し、その中から目的の色に最も近いクラスタを選択してHSV範囲とするといった方法も考えられます。
  2. 機械学習を利用した色検出:

    • 特定の色を持つ領域とそうでない領域のサンプル画像を用意し、これらの画像を機械学習モデル(例えばSVM、ランダムフォレスト、ニューラルネットワークなど)に学習させます。
    • 学習済みモデルは、ピクセルの色情報(RGBやHSVなど)や、その周囲のピクセルの情報などを基に、そのピクセルが目的の色である確率を予測できるようになります。
    • このアプローチは、照明や影、背景の複雑さに強く、より複雑な色のパターンやテクスチャも考慮に入れることができるため、単なるHSV閾値処理よりも高い精度が期待できます。ただし、大量のデータ収集とモデルの学習が必要になります。

これらの発展的な方法は、本記事の初心者向けという範囲を超えますが、特定の色検出というテーマの先にどのような技術があるかを知っておくことは、今後の学習の指針となるでしょう。基本的なHSV閾値処理をしっかりと理解した上で、必要に応じてこれらの高度な手法にステップアップしていくのが良いでしょう。

トラブルシューティング:うまくいかない時は?

コードを書いて実行してみたけれど、期待通りに特定の色が検出されない、あるいはエラーが出てしまう、といった状況に遭遇することもあるでしょう。ここでは、よくある問題とその解決策について説明します。

  1. 「画像ファイルを読み込めませんでした」というエラーが出る:

    • 指定した画像ファイルが、Pythonスクリプトと同じフォルダに存在するか確認してください。
    • ファイル名に間違いがないか、拡張子(.jpg, .pngなど)を含めて正確に記述しているか確認してください。
    • 画像ファイルが別のフォルダにある場合は、絶対パスまたはスクリプトからの相対パスを正しく指定してください(例: 'images/blue_example.jpg')。
    • 画像ファイルが壊れていないか、別の画像ビューアで開けるか確認してください。
  2. コードは動くが、特定の色が全く検出されない、または関係ない色も検出される:

    • 最も可能性が高いのは、HSV範囲の設定ミスです。
      • 検出したい色が含まれる実際の画像を開き、画像編集ソフトなどでその色のHSV値を調べ、OpenCVの範囲(H:0-179, S:0-255, V:0-255)に合わせて範囲を再設定してください。
      • HSV変換スクリプトを使って、目安となるRGB値のHSV値を調べてみてください。
      • SaturationやValueの下限が、検出したい色の鮮やかさや明るさと合っているか確認してください。例えば、検出したい色が鮮やかなのにSaturationの下限が低すぎたり、暗いのにValueの下限が高すぎたりしていませんか?
      • 検出したい色のバリエーション(影になっている部分、ハイライトされている部分など)が多い場合は、HSV範囲を少し広めに設定する必要があるかもしれません。
    • 画像がBGRではなくRGB形式で読み込まれている可能性があります。 OpenCVの imread は通常BGRで読み込みますが、PILなどの他のライブラリで画像を読み込んだ場合はRGBになっていることがあります。その場合は cv2.cvtColor(image, cv2.COLOR_RGB2HSV) を使う必要があります。
    • 照明条件が極端に悪い: 極端に暗い画像や明るすぎる画像、あるいは特定の色(例えば赤っぽい照明)が当たっている画像では、色の検出が難しくなります。可能な場合は、照明条件を改善するか、より高度な前処理(ヒストグラム均一化、ホワイトバランス調整など)を検討してください。
  3. 検出したい色の一部が検出されない:

    • HSV範囲が狭すぎる可能性があります。特にSaturationやValueの範囲を少し広げてみてください。
    • 照明や影によって、検出したい色のHSV値が大きく変化している可能性があります。画像中の検出されない部分の色をHSV変換スクリプトで調べて、その範囲も含まれるようにHSV範囲を調整してみてください。
    • 物体の境界部分で色が曖昧になっている可能性があります。
  4. 検出した領域にノイズが多い(小さな点がたくさん検出されるなど):

    • マスク画像の改善(オープニング処理)を試してください。カーネルサイズを大きくすると、より大きなノイズを除去できます。
  5. エラーメッセージが出る:

    • エラーメッセージをよく読んでください。何行目のコードで、どのような種類のエラー(例: NameError, TypeError, cv2.error など)が発生していますか?
    • NameError は変数名や関数名が間違っている(スペルミスなど)場合に起こります。
    • TypeError は関数に渡している引数の型が間違っている場合に起こります(例: 画像データではなくNoneを渡しているなど)。
    • cv2.error はOpenCVの関数内で問題が発生している場合に起こります。エラーメッセージの中に「Size」「Depth」「Channel」といったキーワードがあれば、画像のサイズやデータ型、チャンネル数が関数が期待するものと異なっている可能性があります。特に cv2.inRangecv2.bitwise_and の入力画像が期待通りになっているか確認してください。NumPy配列として正しく定義されているか(次元数など)も確認しましょう。
    • 画像ファイルが正しく読み込めているか(if image is None: のチェック)、HSV変換が成功しているかなどを、print デバッグで確認するのも有効です。

これらのトラブルシューティングのヒントを参考に、問題を一つずつ解決していきましょう。特にHSV範囲の設定は、画像と検出したい色に合わせて粘り強く調整することが、色検出成功の鍵となります。

まとめ:特定の色検出のマスターへ

この記事では、OpenCVとPythonを使って、画像や動画の中から特定の色を持つ領域を検出する方法を、HSV色空間の概念から具体的なコード実装、応用、そしてトラブルシューティングまで、初心者向けに詳細に解説しました。

学んだ主要なポイントを振り返りましょう。

  • RGB色空間の限界: 特定の色を検出する際に、RGB値だけでは照明や明るさの影響を受けやすく、直感的な範囲設定が難しい。
  • HSV色空間の優位性: Hue(色相)、Saturation(彩度)、Value(明度)に色を分解するため、色の種類を指定しやすく、照明の変化にも比較的強い。OpenCVではHueが0-179、SとVが0-255の範囲であること。
  • 基本的な検出手順: 画像の読み込み -> BGRからHSVへの変換 -> 検出したい色のHSV範囲設定 -> cv2.inRange によるマスク画像の生成 -> cv2.bitwise_and による色領域の抽出。
  • HSV範囲の決定: 検出したい色を含む実際の画像を見ながら、カラーピッカーツールやOpenCVでのHSV変換スクリプトを使って目安を調べ、試行錯誤しながら最適な範囲を見つけることが重要。赤色はHueが円環の両端にあるため、二つの範囲でマスクを作り結合する必要がある。
  • マスク画像の改善: cv2.morphologyEx (特にオープニング処理)を使うことで、生成されたマスク画像から小さなノイズを除去し、検出精度を向上させることができる。カーネルサイズを調整して効果をコントロールする。
  • 輪郭検出: 改善されたマスク画像に対して cv2.findContours を使うことで、検出した色領域の輪郭を取得できる。これにより、物体の位置(バウンディングボックス、中心座標)やサイズなどの情報を得られる。
  • 動画での応用: cv2.VideoCapture を使い、フレームごとに上記の処理を適用することで、リアルタイムでの色検出が可能になる。

特定の色検出は、コンピュータビジョンの様々な応用分野における基礎技術です。今回学んだHSV閾値処理はシンプルでありながら非常に強力で、多くの場面で十分な精度を発揮します。

しかし、実際の環境は複雑で、照明の変化や物体の色の多様性など、課題もたくさんあります。より高度な課題に取り組む際には、この記事で紹介した発展的な話題(環境適応型の範囲調整や機械学習)も視野に入れることで、さらに頑健で実用的なシステムを構築できるようになるでしょう。

この一歩を踏み出したあなたは、コンピュータに「見る」ことを教える世界へ、さらに深く進んでいくことができます。 OpenCVには、特定の色検出以外にも、物体の形状認識、動きの検出、顔認識、画像の特徴点抽出など、驚くほど多くの機能が搭載されています。

ぜひ、今回学んだことを活かして、様々な画像や動画で特定の色を検出してみてください。そして、他のOpenCVの機能にも触れて、コンピュータビジョンの面白さをさらに体験してください。あなたの好奇心と探求心が、きっと新たな発見へと繋がるはずです。

この記事が、あなたのOpenCVとコンピュータビジョン学習の素晴らしい一助となれば幸いです。


コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール