2015. 1. 8.
2017.10. 2. OpenCV3.3.0とVisual C++ 2017で動作確認済み、ただし、文末の付記参照
石立 喬

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

----- Hough変換を用いて、直線を検出する -----

 画像の中から、四角いもの丸いものを探したい場合がある。色の検出に次いで有効な処理が、直線や円の検出をするハフ(Hough)変換である。ここでは、ハフ変換による直線の検出を取り上げ、OpenCVに備わっている二つの関数を比較し、道路標識検出への応用も探る。

Hough変換による直線検出
 ハフ変換については、すでに「Visual C++ 2010 Expressを用いた易しい画像処理(2) --- ハフ(Hough)変換を用いて直線と円弧を検出する ---」に詳しく述べてある。
 画像上のXY座標における点P(x, y)を、ρ = x・cosθ + y・sinθなる式を用いて、ρ=F(θ)の関数に変換する。点が5個あると、この関数が5個できる。θ- ρ平面に、これらの関数を描くと、重なりが生じる場所が生じる。重なりは、それらの元になる点P(x, y)が一直線上に並んでいることを意味する。
 θ- ρ平面上のある点で重なりが多いと、それだけ多数の点が同じ直線状に並んでいることになり、重なりの多い順に直線と判定して行く。θ- ρ平面の特定のaccumulator(頻度を蓄えておく入れ物)に入れることはvote(投票)とも言われる。投票数の多いところが求める直線である。
 得票の多いθ- ρの組み合わせが決まると、それを元のXY座標に戻して直線を描けばよい。

準備段階としてCanny画像(エッジ画像)を得るプログラム
 図1は、Hough変換を行う前に必要なCanny画像を得る準備プログラムで、下記から成っている。
1)原画像を、画像src_imageとして読み込む。
2)ノイズ除去のために、原画像src_imageをGaussianBlurで平滑化し、同名のsrc_imageとする。
3)RGBに分解してCanny画像(エッジ画像)を得て、描画する。
 これらの結果は、図4の上半分にしめしてある。


図1 原画像からRGB分解してCanny画像を用意するプログラム


HoughLines関数とHoughLinesP関数を実行し、結果を描画するプログラム
 図3にプログラムを示し、下記から成っている。
1)Canny画像に対して、HoughLines関数を実施する。
2)得られた直線を、原画像のコピーであるsrc_image1上に描画する。
 HoughLinesでは、結果として得られるlinesベクトルは、二つの要素から成り、
     lines[0] ----- 原点から直線への垂線の長さρ(rho)
     lines[1] ----- 基準線と直線の成す角度θ(theta)
のようになっているので、図2で説明する計算式により、点P1と点P2とをline関数で結ぶ。zは、画像の範囲より長ければよく、画像の幅canny_image.colsまたは高さcanny_image.rowsのいずれか大きい方に選んでおけば良い(OpenCVの資料に、zを1000に設定する例が示され、多くの資料がそれに倣っているが、これは特に根拠がない)。


図2 rhoとthetaから直線を描く式の原理


3)Canny画像に対して、HoughLinesP関数を実施する。
4)得られた直線を、原画像のコピーであるsrc_image2上に描画する。
 HoughLinesPでは、結果として得られるlines_pベクトルは、[0]~[3]の四つの要素から成り、それぞれが座標上の値を表しているので、それらをそのまま使って線を引けばよい。

使用したOpenCV関数の説明
◎HoughLines関数
 標準のアルゴリズムによるHough変換を行う関数で、

  HoughLines(二値原画像、結果直線ベクトル、ρの分解能、θの分解能、直線を構成する点の数のしきい値);

のように引数を与える。ρの分解能は、ρ-θ平面におけるρの最小刻みで、一般には1で良い。θの分解能は、CV_PI/180が用いられることが多い。検出したい角度は、30°、60°、90°など、°(度)で表して区切りの良い数字が多いので、その理由は理解できるが、計算はradianで行われ、°(度)ではないので、180にこだわる必要はない。
◎HoughLinesP関数
 すべてのエッジ出力に対してHough変換を行うのではなく、確率的にランダムに選んで実行するので、計算時間が短い。変換した結果がしきい値のレベル(voteの数)を超えると、直線と見なす。そのとき、新しいピクセルがすでにあるピクセルと直線上に連続しているか、または決められたギャップ以内の同一直線上にあるかを確認し、線分(segment)として登録する。その線分の長さが、決められた長さ以上であれば、直線と見なす。
 関数は

  HoughLinesP(二値原画像、結果直線ベクトル、ρの分解能、θの分解能、、直線を構成する点の数のしきい値、最小の線長、同一直線内の許容される最大ギャップ);

となっている。ρの分解能、θの分解能、、直線を構成する点の数のしきい値はHoughLinesと変わらないが、最小の線長(連続したピクセルの数)、同一直線内の許容される最大ギャップ(連続したピクセルと同一直線上にあるが、切れ目があり、その切れ目のピクセル数)の指定が必要である。


図3 Canny画像からHoughで直線検出をするプログラム


HoughLines関数とHoughLinesP関数で直線を検出した結果
 図4で、左上は平滑化した原画像、右上はRGB分解して作成したCannyエッジ画像、左下は単純なHoughLinesを使った結果、右下はHoughLinesPを用いた結果である。しきい値を下げると、より多くの直線がされて不要なノイズが増え、しきい値を上げすぎると、必要な直線が得られない。結局、試行錯誤の産物である。
 HoughLinesPを用いると、直線部分のみ(延長部分を含まない)が示され、見やすい。


図4 Hough変換した結果


道路標識「止まれ」に注目して、逆三角形を探す
◎HoughLinesを用いる
 HoughLinesでは、結果として得られるlinesベクトルは、二つの要素から成り、
     [0] ----- 原点から直線への垂線の長さρ(rho)
     [1] ----- 基準線と直線の成す角度θ(theta)
のように対応する。
 rhoは原点からの距離なので、直線のなす角度には関係なく、検出された直線を絞り込むのにはthetaのみを用いる。図5に示すように、画面上の垂直線はtheta = 0 radianで、画面に水平な直線はtheta = π/2 radianである。その他の角度は図示の通りである。


図5 直線の傾斜とthetaの関係


 図6は、プログラムの要部を示したもので、Hough変換の結果であるlines配列をひとつづつ取り出し、rhoとthetaから直線を描く。ただし、図5を参照して、「止まれ」の上辺はtheta = π/2、右辺はtheta = π/6、左辺はtheta = π*5/6付近なので、この条件を満たす直線は緑色で描画し、それ以外は黄色で描画する。


図6 角度thetaから三角形を構成する直線を探すプログラム


 図7は、得られた結果である。


図7 「止まれ」関連の角度の直線は緑色になっている


◎HoughLinesPを用いる
 HoughLinesPでは、結果として得られるlines_pベクトルは、四つの要素から成り、
     lines_p[0] ----- 始点のX座標
     lines_p[1] ----- 始点のY座標
     lines_p[2] ----- 終点のX座標
     lines_p[3] ----- 終点のY座標
となっているので、直線が画面上で水平であることは[1]と[3]が等しいことを、垂直であることは[0]と[2]が等しいことを意味する。その他の角度のついては 図8で説明する通りである。


 図8 直線の傾斜とX座標、Y座標の関係


 図9はプログラムの要部を示したもので、HoughLinesP関数で得られたlines_p[0]~[3]から、図8を参照して直線の角度angleを求め、条件を満足する直線を緑色で描く。距離は絶対値を取っているので、angle = π/3で、「止まれ」の右辺と左辺の条件を兼ねている。「止まれ」の上辺は、直線の端部のY方向の差が2ピクセル以内と定義している。


図9 二点の位置関係から三角形を構成する直線を探すプログラム


 図10は、得られた結果である。


図10 垂直線や45°の線などは黄色のままになっている


結 論
 Hough変換の基本的な原理にもとづくHoughLines関数は、理解しやすく、直線の成す角度thetaが直接得られるので、角度を認識の手がかりとする応用には便利である。一方、処理に時間がかかり、実在する直線部分以外にも直線が引かれる欠点がある。
 確率を導入したHough変換のHoughLinesPは、原理的にはやや難解であるが、処理時間が短く、実在する線分だけを検出する優れた機能を有している。さすが、OpenCVの関数との気がする。
 Hough変換による直線の検出も、OpenCVの関数を使う限り、完全な自動ではなく、万能でない。各種のしきい値を外部から、試行錯誤で与える必要がある。たとえ、Hough変換で直線が検出されたとしても、最終的な判断にはまだ遠く、ここから先が本当に重要な部分である。

付 記
 OpenCV3.3.0とVisual C++ 2017を使用するにあたり、下記を参照されたい。それ以外は、古いプログラムをそのまま使用できる。
 1)従来のインクルードファイルをそのまま使用しても良いが、#include <opencv2/opencv.hpp>と変更しても問題はない。
 2)従来のint _tmain(int argc, _TCHAR* argv[])をそのまま使用しても良いが、int main()にしても差支えない。
 3)vectorを使用するのに、using namespace std;の追加が必要である(従来は無くても良かったような気がする)。







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