2014.12.13.
2016. 7.21. hueの範囲を、0~180に修正し、hueの上限にも言及
石立 喬

OpenCVとVisual C++による画像処理と認識(4)

----- HSV表色系への変換と道路標識の検出 -----

 画像認識では、色情報が有用である。ディスプレイに用いられるRGB系は、認識には有効ではなく、HSV系などの表色系が適している。ここでは、RGB系とHSV系を相互に変換できる便利なOpenCVの関数を用いて色の検出を行い、道路標識の検出の第一歩とする。

HSV表色系とは
 Hはhue(色相)、Sはsaturation(彩度)、Vはvalue(明度)の意味で、より人間の感覚に近い。
HSVに似たものに、HSBがあるが、呼び方をValueからBrightnessに変えただけで、同一である。
  H ---- どんな色? 色を変えるには、これを変える
  S ---- 鮮やかで、はっきりしてる? もっと鮮やかにするには、これを大きくする
  V ---- 明るい? 明るくするにはこれを大きくする
 画像認識で使うのは、主としてHで、Vは、むしろ無視する方が、光源の影響を避けることができる。道路標識などは、色が原色に近く、はっきりしているので、Sが一定値よりも大きいことを条件とすると選別できる。肌色検出のように、淡い色を探す場合には、Sが大きすぎないことが条件となる。

RGB系からHSV系に変換して、擬似画像として表示する
 OpenCVの場合は、画像の各ピクセルchannnelsはBGRの順に並んでいるので、BGR系と言うべきかもしれない。少なくとも、プログラム上では、常に意識しておく必要がある。
 OpenCVには、BGRをHSVに変更する、
  cvtColor(src_image, hsv_image, CV_BGR2HSV);
の便利な関数がある。
 hsv_imageは、Matクラスとして、あらかじめ宣言しておく。hsv_imageはMatクラスなので、そのまま画像として表示できる。しかし、B、G、Rの並びに代わってH、S、Vの順になっているだけに過ぎず、画像としての意味は特にない。擬似画像と言うべきである。
  hsv_image擬似画像を、H、S、V個別の擬似画像に分離するには、
  Mat channels[3];
と宣言しておいてから、
  split(hsv_image, channels);
  hue = channels[0];
  saturation = channels[1];
  value = channels[2];
を使う。channels[0]~[2]もMatクラスなので、擬似画像として、表示できる。
 hsv_image擬似画像の特定ピクセルにおけるhue、saturation、valueを取り出す場合には、
  hsv_image.at<Vec3b>(y, x)[0]
のように、ピクセルを選択してから、添え字で分けると良い。
 
OpenCVのhue, saturation, valueの値と、規定範囲外の値を入れた場合の扱われ方
 hue、saturation、valueは、すべてuchar型であり、ucharの範囲に収めるために、hueは0~180、saturationは0~255、valueは0~255となっている。これは、hueを0~360で表し、saturationとvalueを0~1で表す一般的な方法と異なるので、注意を要する。
 プログラム中で、これらを使用する場合、誤ってucharの範囲外の値を入れてしまった場合のチェックをしてみた。その結果、0以下の値に対しては0が、256以上の値に対しては255が代わりに入るようで、ビルド時も実行時もエラーにはならない。hueについては、180以上の値を入れると、180が引かれた値になり、hueが環状になっているのに対応している。ただし、ucharの範囲内に限られ、hueの最大値255で頭打ちになり(255 - 180 = 75)、マイナスの値は0になってしまって、環状を満足しない。

HSV系に変換して表示するプログラム
 図1は、原画像を読み込み、HSV画像に変換し、H、S、V各要素に分離して個別に擬似画像として表示するプログラムである。hueの範囲は0~180なので、それをucharの範囲いっぱいに広げるため、1.42倍してある。


図1 HSV系に変換して、H,S,V個別に擬似画像として表示するプログラム


プログラムで得られたHSV擬似画像
 図2は、図1プログラムで表示した原画像である。


図2 画面に表示された原画像


 図3で、左上は、HSVをそのままカラー画像として表示したもので、value(明度)がR成分で表されているため、明るい部分は赤く、暗い部分は赤の反対色になっている。HSV擬似画像以外はすべてグレイスケール画像にしてあり、右上のH擬似画像では、赤色付近が0に近い値か255に近い値かに分かれるので、白と黒になっている。左下のS擬似画像では、色彩が薄い(白、黒、灰色)部分が黒く、鮮やかな色が白くなっている。右下のV擬似画像は、原画像をそのままグレイスケール化した画像に近い。


図3 図1のプログラムの実行結果


HSV系で変更を加えてからRGB系に戻す
 原画像をHSV系に変換した後に、HSVの値を変え、元のRGB系に戻して描画するとどうなるかを試みた。図4はそのプログラムで、原画像src_imageをHSV系に変換したものをhsv_imageとし、それをH、S、Vに分離したものをchannels[0]~[2]とする。
 それらを意図的に変更したものをchannels1[0]~[2]とし、merge(channels1, 3, hsv_image1)で新しいHSV系擬似画像を生成する。さらに、それをRGB系に変換し、dst_imageとして描画する。


図4 HSV系で変更してRGB系に戻すプログラム

 図5は得られた結果である。右上は、hueを60(一般に用いられる通常のhueでは、120°に相当)だけ増加させた画像で、赤や黄が緑や青に変化している。左下はsaturationを最大にしたため、色が原色化され、右下はvalueを最大にしたため、明るくなっている。


図5 HSV系で変更した結果


HSV系へ変換して、道路標識を検出する
 道路標識の認識にOpenCVが実用化されている例は多い。最初に色情報で道路標識候補を取り出してから、詳しく認識の作業をするのが一般的なので、まずは道路標識の特定の色を検出する(プログラムを図6に示す)。
 道路標識は、赤、緑、青、黄のいずれかで、それぞれ詳細に色が規定されている。「日本の道路標識 - Wikipedia」によると、それらがRGBでも表されていて、HSVに換算すると(OpenCVで求めた、プログラムを付録に示す)、赤色と青色の標識は下記のようになる。
 赤 --- hue = 178, sat = 227, val = 251
 青 --- hue = 107, sat = 237, val = 157
 図6のプログラムは、以下の内容から成る。
1)原画像を読み込みsrc_imageとする。
2)cvtColor()で、原画像src_imageからHSV擬似画像hsv_imageを得る。
3)赤の検出結果を入れるred_imageと、青の検出結果を入れるblue_imageを用意する。
4)hsv_imageの全ピクセルをスキャンし、各ピクセル位置のhue、sat、valを取得する。
5)赤を検出する。条件は、hue < 8 またはhue > 168 である。これは、hue = 178 を中心に±10の範囲である(179を超えた分は0に戻って増えて行く)。
6)青を検出する。条件は、hue > 97 で、かつhue < 117 である。これは、hue = 107 を中心に±10 の範囲である。
7)上記いずれの場合も、道路標識は純粋な色なので、sat > 100によって、他と区別し易くしている。


図6 交通標識を検出するプログラム


道路標識を検出した結果
 図6のプログラムで原画像として使用し、表示させたのが図7で、検出した結果が図8である。道路標識としての確実な認識には、ノイズを除去した後、標識の形状、描かれている文字、図形の識別に進む必要がある。


図7 原画像、「止まれ」には樹の陰が写っている



図8 道路標識が検出された


結 論
 画像の中から特定の色を持ったものを探し出すには、HSV系を用いると良い。H(hue、色相)は、光線の当たり具合、明暗によって変わらないので、屋外の物体を検出するのにも適している。道路標識のように、比較的純粋な色に対しては、S(saturation、彩度)を高めてスクリーニングすると良い。V(value、明度)を無視することは、明暗の差を問わないことを意味する。
 最終的な判断には、まだ色々な処理を引き続き行うことが必要である。

付 録
 道路標識に規定されたRGB値からHSV値を求めるのに用いたプログラムを、図Aに示す。図で、16行目のRGB値は、コードとして直接記述した。
 図Bは、その結果である。色を見本で確認しながら、HSV値を知ることができる。


図A RGB値をHSV値に変換して表示するプログラム



図B 色見本とHSV値が表示された

「Visual C++の勉強部屋」(目次)へ