Python+OpenCVによる線分検出
線分検出とは
その名の通り、画像の中に含まれる線分(直線)を検出します。
似たような技術としてエッジ検出がありますが、ピクセル単位で輝度勾配を求めるエッジ検出と異なり、線分の端点のペアを求めます。
線分検出は画像処理技術の中でも非常に応用性の高い技術となっています。
物体の形状認識、スケールの測量、消失点探索など直感的なユースケースに始まり、カメラの運動推定やステレオマッチングなど、びっくりするような応用例もあります。
ステレオカメラを利用した高速3次元線分抽出によるリアルタイム空間認識 - YouTube
今回は、OpenCVに実装されている線分検出器で遊んでみたいと思います。
Fast Line Detector
Fast Line Detector[1]はOpenCV4より実装された線分検出アルゴリズムです。
線分検出と聞くとHough変換が思い浮かびますが、Fast Line Detectorはそれよりも高速・高精度に動作するようです。(すごい!)
Hough変換は別に直線検出に特化したアルゴリズムという訳でも無いので、やはり特化型の検出器に軍配が上がるようですね。
Fast Line Detectorはcv2.ximgproc packageに実装されています。
以下に、fastLineDetectorのHelpファイルを記載します。
createFastLineDetector(...) createFastLineDetector([, _length_threshold[, _distance_threshold[, _canny_th1[, _canny_th2[, _canny_aperture_size[, _do_merge]]]]]]) -> retval . @brief Creates a smart pointer to a FastLineDetector object and initializes it . . @param _length_threshold 10 - Segment shorter than this will be discarded . @param _distance_threshold 1.41421356 - A point placed from a hypothesis line . segment farther than this will be . regarded as an outlier . @param _canny_th1 50 - First threshold for . hysteresis procedure in Canny() . @param _canny_th2 50 - Second threshold for . hysteresis procedure in Canny() . @param _canny_aperture_size 3 - Aperturesize for the sobel operator in Canny(). . If zero, Canny() is not applied and the input . image is taken as an edge image. . @param _do_merge false - If true, incremental merging of segments . will be performed
パラメータは6種類のようですね。
パラメータ | 意味 |
---|---|
length_threshold | 検出する最小の線分のピクセル数 |
distance_threshold | マージする線分の距離 |
canny_th1 | Canny Edge Detectorの引数1(詳細は[2]参照) |
canny_th2 | Canny Edge Detectorの引数2(詳細は[2]参照) |
canny_aperture_size | Canny Edge Detectorに使うSobelのサイズ(0ならCannyは適用しない) |
do_merge | Trueなら線分をマージして出力する |
Fast Line Detectorのインスタンスを作成したら、detect()メソッドにimg arrayを渡すだけで線分リストが帰ってきます。
import cv2 if __name__ == "__main__": length_threshold = 10 distance_threshold = 1.41421356 canny_th1 = 50 canny_th2 = 50 canny_aperture_size = 3 do_merge = False fld = cv2.ximgproc.createFastLineDetector( length_threshold, distance_threshold, canny_th1, canny_th2, canny_aperture_size, do_merge ) img = cv2.imread("image.png", 0) lines = fld.detect(img) out = fld.drawSegments(img, lines) cv2.imwrite("out.png", out)
実行例は以下になります。
入力画像 | 出力画像 |
---|---|
いい感じに直線が検出されていますね。
[1]Lee, Jin Han, et al. "Outdoor place recognition in urban environments using straight lines." 2014 IEEE International Conference on Robotics and Automation (ICRA). IEEE, 2014.
[2]http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_imgproc/py_canny/py_canny.html
出力データの構造
線分検出結果をprintで出力すると次のようになります。
print(lines) array([[[ 258.0611 , 0. , 258.79755 , 20.044174]], [[ 261.1108 , 20.922274, 288.82095 , 60.42411 ]], [[ 287.15384 , 62. , 287.15384 , 74. ]], ..., [[ 402.39984 , 875.4129 , 396.41507 , 866.7144 ]], [[ 696.99585 , 869.9479 , 709.99286 , 868.91016 ]], [[1078.2802 , 876.6829 , 1094.082 , 870.1998 ]]], dtype=float32)
また、shapeは次のようになっています。
print(lines.shape) (3610, 1, 4)
3610は検出した全線分の数です。
一つ一つの要素は、端点1と端点2のX, Y座標が順番に格納されています。
したがって以下のようにcv2.lineで前線分を描画する事ができます。
def drawLines(img, lines): out = img.copy() for line in lines: x1, y1, x2, y2 = map(int, line[0]) cv2.line(out, (x1, y1), (x2, y2), (0, 0, 255), 1) return out
ドライビング動画への適用
有志の方がアップしてくれたLane Detection Test Videoに線分検出を適用してみました。
length_threshold=10 | length_threshold=100 |
---|---|
length_thresholdを短くすると、画像上の小さな線分も検出できるので、線分情報からシーンの情報をなんとなく理解する事ができますね。
逆にlength_thresholdを長くすると、ガードレールや白線など比較的長い直線しか検出されないので、車線検出や消失点推定なんかに利用しやすそうな感じがします。
また推論時間も17ms±170μsと、非常に高速に動作しました。(解像度: 1280x720)
消失点検出
せっかくなので、左右の線分を使って消失点を検出してみました。
左右の線分を直線とみなし、その交点をピンク色の点で表示しました。
この動画は下り坂から上り坂に差し掛かる様な構造をしているのでちょっと分かりづらいですが、道路の無限遠の位置(上り坂に差し掛かる所)にピンク色の点が来ています。
今回は消失点を求めて満足しちゃったのですが、この消失点から道路構造を推定したり、物体検出の範囲を制限し、検出精度や検出速度をあげる等々、様々な実応用の可能性があります。
まとめ
高速に線分を検出できるFast Line Detectorを使って遊んでみました。
線分検出は画像処理の中でも特に実応用につながる重要な技術です。
OpenCVを使うことで簡単に、高速・高精度な線分検出を試すことができました。
次回は、この線分検出器を用いて色々なものを作ってみたいですね。