社会人研究者が色々頑張るブログ

pythonで画像処理やパターン認識をやっていきます

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)

実行例は以下になります。

入力画像 出力画像
f:id:nsr_9:20210811002941p:plain f:id:nsr_9:20210811003023p:plain
f:id:nsr_9:20210811002952p:plain f:id:nsr_9:20210811003034p:plain

いい感じに直線が検出されていますね。

[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
f:id:nsr_9:20210811003348g:plain f:id:nsr_9:20210811003402g:plain

length_thresholdを短くすると、画像上の小さな線分も検出できるので、線分情報からシーンの情報をなんとなく理解する事ができますね。
逆にlength_thresholdを長くすると、ガードレールや白線など比較的長い直線しか検出されないので、車線検出や消失点推定なんかに利用しやすそうな感じがします。
また推論時間も17ms±170μsと、非常に高速に動作しました。(解像度: 1280x720)

消失点検出

せっかくなので、左右の線分を使って消失点を検出してみました。 f:id:nsr_9:20210811003457g:plain

左右の線分を直線とみなし、その交点をピンク色の点で表示しました。
この動画は下り坂から上り坂に差し掛かる様な構造をしているのでちょっと分かりづらいですが、道路の無限遠の位置(上り坂に差し掛かる所)にピンク色の点が来ています。
今回は消失点を求めて満足しちゃったのですが、この消失点から道路構造を推定したり、物体検出の範囲を制限し、検出精度や検出速度をあげる等々、様々な実応用の可能性があります。

まとめ

高速に線分を検出できるFast Line Detectorを使って遊んでみました。
線分検出は画像処理の中でも特に実応用につながる重要な技術です。
OpenCVを使うことで簡単に、高速・高精度な線分検出を試すことができました。
次回は、この線分検出器を用いて色々なものを作ってみたいですね。