2016. 7.25.
石立 喬

OpenCV3.1.0とVisual C++ 2015による画像処理と認識(4)

----- HSV表色系へ変換しての処理と道路標識の検出への応用 -----

 画像認識では、色情報が有用である。ディスプレイに用いられるRGB系は、認識には有効ではなく、HSV系などの表色系が適している。ここでは、RGB系とHSV系を相互に変換できる便利なOpenCVの関数を用いて画像の変換してHSV表色系の理解を深め、あわせて、道路標識の検出を試みる。旧バージョンによる「OpenCVとVisual C++ による画像処理と認識(4)」と類似しているが、色相(hue)の変化の効果を色相円で調べた結果を追加して、OpenCV3.1.0で確認した。cvtColor関数で指定するカラー変換コードが、OpenCV3.1.0ではCV_BGR2HSVなどからCOLOR_BGR2HSVなどに変わっているが、従来のままでも、支障なく動作する。
 表色系一般については、「Visual C++ 2010 Expressを用いた易しい画像処理(3)」に詳しく述べてあるので、そちらも参照されたい。

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

RGB系からHSV系に変換して、擬似画像として表示する
 OpenCVの場合は、画像の各ピクセルに含まれるchannelsはBGRの順に並んでいるので、BGR系と言うべきである。少なくとも、プログラム上では、常に意識しておく必要がある。
◎cvtColor関数で表色系を変更する
 ある表色系の画像(配列)を他の表色系の配列に変換するカラー変換には、cvtColor関数が用いられる。この関数は、
  cvtColor( src, dst, code, dstCn = 0);
の形で使用する。srcはMatクラスの入力配列(この場合は画像)、dstはMatクラスの出力配列(画像と同じ形ではあるが、この場合はHSVなどのチャンネルから成る配列)、codeはCOLOR_BGR2HSVなどのカラー変換コードである(OpenCV3.0以降、CV_BGR2HSV等から、変更になった)。dstCnは、出力配列のチャンネル数で、デフォルトの0にして置くと、入力配列と同じになる。
 良く使われるカラー変換コードには、以下がある。カッコ内は対応する整数値である。
   COLOR_BGR2RGB ( = 4) ---- BGR → RGB
   COLOR_BGR2GRAY ( = 7) --- BGR → グレイスケール
   COLOR_BGR2HSV ( = 40) --- BGR → HSV
   COLOR_HSV2BGR ( = 54) --- HSV → BGR
 具体的な例として、BGR画像をHSVに変更するには、
  cvtColor(image_src, image_dst, COLOR_BGR2HSV);
を用いる。image_srcは、Matクラスの原画像で、image_dstも、あらかじめ宣言してあるMatクラスなので、そのまま画像として表示できる。しかし、B、G、Rの並びに代わってH、S、Vの順になっているに過ぎず、画像としての意味は特にない。擬似画像と言うべきである。
◎split関数で表色系配列を分解してチャンネル別に取り出す
 split関数は、
  split( m, mv);
の形で使用する。mは入力する多チャンネル配列、mvは分解されたチャンネル配列のベクトルである。
  cvtColor関数で得たimage_hsv擬似画像を、H、S、V個別のチャンネルに分離するには、
  Mat channels[3];
と宣言しておいてから、
  split(image_hsv, channels);
  hue = channels[0];
  saturation = channels[1];
  value = channels[2];
を使う。channels[0]〜[2]もMatクラスなので、擬似画像として、表示できる。
 各チャンネルの特定ピクセルの値を取得するには、
  channels[0].at<uchar>(y, x);
などを使用する。
 あらかじめチャンネルに分解しなくても、
  image_hsv.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の範囲外の値を入れてしまった場合のチェックをしてみた。その結果、saturationとvalueに関しては、0以下の値に対しては0が、255以上の値に対しては255が入るようで、ビルド時も実行時もエラーにはならない。hueについては、180以上の値を入れると、180が引かれた値になり、hueが環状になっているのに対応している。ただし、ucharの範囲内に限られ(255を超えると255でクリップされ、結果的に255 - 180 = 75で頭打ちになる)、マイナスの値は0になってしまって、環状性を満足しない。

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


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


プログラムで得られたHSV擬似画像
 図2は、図1のプログラムで表示した原画像である。 図3で、左上はHSV擬似画像で、HSVの3個のチャンネルをそのままBGRカラー画像として擬似的に表示している。画像としての意味は特にない。value(明度)がR成分で表されているため、明るい部分は赤く、暗い部分は赤の反対色になっている。H、S、Vに分解した後はチャンネルが1個のため、すべてグレイスケール画像になり、右上のH擬似画像では、赤色付近が0に近い値か255に近い値かに分かれるので、白と黒になっている。左下のS擬似画像では、色彩が薄い(白、黒、灰色)部分が黒く、鮮やかな色が白くなっている。右下のV擬似画像は、原画像をグレイスケール化した画像に近い。


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



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


 図4は、同じ処理を色相円(hue circle)について実施した場合を示す。HSV擬似画像は省略してある。右上のH擬似画像では、真上から時計方向に向かって、hueが0から180(濃度では0〜255になるようにしてある)に変化していく様子を表している。原画像での白色の背景は、hue = 不定(undefined)であるが、0と見なされている。左下のS擬似画像では、色相円がsaturation = 255で描かれていることを示している。原画像での白色の背景は、定義上、saturation = 0となる。右下のV擬似画像では、色相円がvalue = 255で描かれて、白色の背景もvalue = 255であるので、全体が真っ白になっている。


図4 原画像に色相円を用いた結果


HSV系で変更を加えてからBGR系に戻す
 原画像をHSV系に変換した後に、HSVの値を変え、元のBGR系に戻して描画するとどうなるかを試みた。図5はそのプログラムで、cvtColor関数とsplit関数で得たHSVのチャンネル値をchannel[0]〜[2]とし、それらの一部をを意図的に変更したものをchannels1[0]〜[2]とし、merge(channels1, 3, image_hsv1)で新しいHSV系擬似画像を生成する。さらに、それをcvtColorでBGR系に変換し、image_dstとして描画する。
 merge関数は、splitの逆を行う関数で、
  merge(mv, count, dst);
の形で使用する。mvは一体化(merge)させたい入力チャンネル配列のベクトル(配列の配列)で、すべて同一サイズ、同一深度(depth)である。countは、入力チャンネル数で、何個の配列を一体化させたいかの値を入れる。dstは、指定されたチャンネル数を持つ出力配列である。



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


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


図6 HSV系で変更した結果


色相円(hue circle)を使って、hueの増減の効果を調べる
 すでに述べたように、OpenCVのhueの範囲は0〜180であり、一般的な0〜360とは異なる。OpenCV上で、hueを30、60、90(一般的表現では、60°、120°、180°)と増やしたり、逆に30、60だけ減らしたりして、色相円がどのように変化するかを確認する。
 OpenCVでは、hueの入力時に、uchar型の範囲に収まるように、
   hue > 255 の場合は、 hue = 255にクリップされ、
   hue < 0 の場合は、 hue = 0にクリップされる。
 その後、
   hue > 180は、hue = hue - 180
に変換される。
 つまり、hueにマイナスの値を与えると、hue = 0に固定され、hueに180以上の値を与えると、再び0からスタートするが、その上限は255 - 180 = 75である。
 実際にプログラム上でhueを増減させた結果が図7と図9で、図7では、hueを30、60、90だけ増やすと、色相円が、反時計方向に60°、120°、180°回転していることが分かる。図8を用いての説明の通り、hue + 90では若干、hue + 120ではかなりの部分がhue = 75に固定化されている。
 図9は、hueを30と60だけ減じた場合で、色相円が時計方向に60°、120°回転している。図10に示す通り、それぞれ、60°以下、120°以下は、hue = 0に固定化されている。図10の説明を参照されたい。


図7 色相円で、hueを正方向に変化させた場合



図8 hueを正方向に変化させた時の色相円の説明(hue = 75に固定される部分が生じる)



図9 色相円で、hueを負方向に変化させた場合



図10 hueを負方向に変化させた時の色相円の説明(hue = 0に固定化される部分が生じる)


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



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


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


図12 原画像、「止まれ」には樹の陰が写っているが、影響は受けない



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


結 論
 画像の表色系をRGB系(OpenCVではBGR系)からHSV系に変換し、HSV系で変更を加えてから、元のRGB系に戻す処理を試みた。Opencv3.1.0では、カラー変換コードが、CV_******からCOLOR_******に変わっている(従来のままでも使用可)。
 実際にH(hue、色相)を大きく変化させる用途はないが、Hを減らしたり大きく増やしたりすると、hueが一定値に固定される現象が生じ、色相円の環状性が失われる。
 画像の中から特定の色を持ったものを探し出すには、HSV系を用いると良い。H(hue、色相)は、光線の当たり具合、明暗によって変わらないので、屋外の物体を検出するのにも適している。道路標識のように、比較的純粋な色に対しては、S(saturation、彩度)を高めてスクリーニングすると良い。V(value、明度)を無視することは、明暗の差を問わないことを意味する。







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