ハフ変換とLSDによる直線検出の比較


関連記事: 画像解析

前回の記事は「OpenCV + Pythonでの直線検出」を解説しました。今回はPythonでハフ変換(Hough)とLSDによる直線検出を比較します。

目次

1. ハフ変換(Hough Transform)
2. LSD (Line Segment Detector)
3. ハフとLSDの比較
4. 実験・コード
__4.1 データロード
__4.2 ライブラリのインストール
__4.3. 直線検出
__4.4. 結果比較

1. ハフ変換とは

ハフ変換 (Hough変換) は、画像処理で画像の特徴抽出法の一つです。現在広く用いられている変換法はRichard Duda及びPeter Hartが1972年に発明しました。ハフ変換の基本原理は点を通る直線は無限個存在し、それぞれが様々な方向を向きます。ハフ変換の目的は、それらの直線の中で、画像の「特徴点」を最も多く通るものを決定します。
直線の式は次のようになる:

ハフ変換では画像空間からρ-θパラメータ空間への変換を行います。ある画像空間上に孤立点があり、点の座標が(x, y)である場合、パラメータ空間への変換を行うとどのような結果が得られるのか、考えることにします。
ρ : 座標(x, y)を通る直線に対し、原点から垂線を下ろしたときの長さ
θ : 座標(x, y)を通る直線に対し、原点から垂線を下ろしたときにx軸となす角度

論文:Use of the Hough Transformation To. Detect Lines and Curves in Pictures

2. LSD (Line Segment Detector)

LSDは、画像上の局所的に直線の輪郭を検出することを目的です。輪郭とは、グレーレベルが暗から明、またはその逆に十分に速く変化している画像のゾーンです。 したがって、画像の勾配とレベルラインは重要な概念です。

LSDでは以下のような画像中の場所をLevel-Line、小さな領域内での輝度勾配とLevel Lineの角度を画像全体で計算したものをLevel Line Fieldと定義しています。そして生成したLevel Line Field内から直線領域候補(Line Support Regions)を計算します。

LSDのアルゴリズムは以下のようになっています。

論文:LSD: a Line Segment Detector

3. ハフとLSDの比較

ハフ
– 黒白バイナリ画像の入力データ
– 固定の形式であればパラメータにより最適化できます。
– パラメータ探索をグリッドサーチしないと行けないので時間がかかります。(パラメータの設定より)
– ランダム化された性質により、複数の実行で異なる結果になります。

LSD
– グレースケールの入力データ
– パラメータの設定が不要です。
– サブピクセルの正確な結果を提供します。

4. 実験・コード

概要:
入力データ: ウィキペディアからの画像
環境:Google Colab GPU
ライブラリ: OpenCV, Pylsd
実験:直線検出 (3枚の画像)

4.1 データロード

# 画像をロード

# 画像をロード
import urllib
img_src = "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0d/Peer_Vs_Chakvetadze.JPG/634px-Peer_Vs_Chakvetadze.JPG"
img_path = '/tennis_court.jpg'
urllib.request.urlretrieve(img_src, img_path)

(‘/tennis_court.jpg’, <http.client.HTTPMessage at 0x7f309a86cef0>)

# 画像を表示
from IPython.display import Image,display_jpeg
img_path = '/tennis_court.jpg'
display_jpeg(Image(img_path))

4.2 ライブラリのインストール

# pip install pylsd 
# python3に対応していないので、下記のバージョンを利用してください。
pip install 'ocrd-fork-pylsd == 0.0.3'

Collecting ocrd-fork-pylsd==0.0.3
Downloading https://files.pythonhosted.org/packages/6a/df/12fba60b9b3e141f515d69edd539bd066294d6b3be79b12450888819986d/ocrd_fork_pylsd-0.0.3-py3-none-any.whl (47kB)
|████████████████████████████████| 51kB 2.9MB/s
Installing collected packages: ocrd-fork-pylsd
Successfully installed ocrd-fork-pylsd-0.0.3

4.3. 直線検出の関数を作成

import cv2
import time
from pylsd.lsd import lsd

import numpy as np

def line_detect_model(img_path):
    img = cv2.imread(img_path)
    img = cv2.resize(img,(int(img.shape[1]/1.1),int(img.shape[0]/1.1)))
    gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    gray = cv2.GaussianBlur(gray,(5,5),5)

    t1 = time.time()
    edges = cv2.Canny(gray,50,150,apertureSize = 3)
    linesH = cv2.HoughLinesP(edges, rho=1, theta=np.pi/360, threshold=50, minLineLength=50, maxLineGap=10)
    t2 = time.time()

    linesL = lsd(gray)
    t3 = time.time()

    img2 = img.copy()
    for line in linesH:
        x1, y1, x2, y2 = line[0]

    # 赤線を引く
    img2 = cv2.line(img2, (x1,y1), (x2,y2), (0,0,255), 3)

    cv2.imwrite('output_hough.jpg',img2)

    img3 = img.copy()
    img4 = img.copy()
    for line in linesL:
        x1, y1, x2, y2 = map(int,line[:4])
        img3 = cv2.line(img3, (x1,y1), (x2,y2), (0,0,255), 3)
        if (x2-x1)**2 + (y2-y1)**2 > 1000:
        # 赤線を引く
        img4 = cv2.line(img4, (x1,y1), (x2,y2), (0,0,255), 3)
    print("Hough")
    print(len(linesH),"lines")
    print(t2-t1,"sec")
    print("time per a line :{:.4f}".format((t2-t1)/len(linesH)))
    print(" ")
    print("LSD")
    print(len(linesL),"lines")
    print(t3-t2,"sec")
    print("time per a line {:.4f}".format((t3-t2)/len(linesL)))
    cv2.imwrite('output_pylsd.jpg',img3)

4.4. 結果比較 実験1

# モデル作成
img_path = '/tennis_court.jpg'
line_detect_model(img_path)

Hough
30 lines
0.03397536277770996 sec
time per a line :0.0011

LSD
134 lines
0.10620927810668945 sec
time per a line 0.0008

# ハフ変換の結果
from IPython.display import Image,display_jpeg
img_output = 'output_hough.jpg'
display_jpeg(Image(img_output))

# LSDの結果
from IPython.display import Image,display_jpeg
img_output = 'output_pylsd.jpg'
display_jpeg(Image(img_output))

実験2

# 画像をロード
import urllib
img_src = "https://vignette.wikia.nocookie.net/rubiks-cube/images/5/50/Wiki-background/revision/latest?cb=20110127110929"
img_path = '/rubik.jpg'
urllib.request.urlretrieve(img_src, img_path)

# 画像を表示
from IPython.display import Image,display_jpeg
img_path2 = '/rubik.jpg'
display_jpeg(Image(img_path2))

# モデル作成
img_path = '/rubik.jpg'
line_detect_model(img_path)

Hough
22 lines
0.01903223991394043 sec
time per a line :0.0009

LSD
108 lines
0.03812599182128906 sec
time per a line 0.0004

# ハフ変換の結果
from IPython.display import Image,display_jpeg
img_output = 'output_hough.jpg'
display_jpeg(Image(img_output))

# LSDの結果
from IPython.display import Image,display_jpeg
img_output = 'output_pylsd.jpg'
display_jpeg(Image(img_output))

実験3

# 画像をロード
import urllib
img_src = "https://upload.wikimedia.org/wikipedia/commons/thumb/e/eb/SkHwy11ShoulderBumps.jpg/640px-SkHwy11ShoulderBumps.jpg"
img_path = '/road.jpg'
urllib.request.urlretrieve(img_src, img_path)

# 画像を表示
from IPython.display import Image,display_jpeg
img_path3 = '/road.jpg'
display_jpeg(Image(img_path3))

# モデル作成
img_path = '/road.jpg'
line_detect_model(img_path)

Hough
20 lines
0.010786294937133789 sec
time per a line :0.0005

LSD
229 lines
0.0957939624786377 sec
time per a line 0.0004

# ハフ変換の結果
from IPython.display import Image,display_jpeg
img_output = 'output_hough.jpg'
display_jpeg(Image(img_output))

# LSDの結果
from IPython.display import Image,display_jpeg
img_output = 'output_pylsd.jpg'
display_jpeg(Image(img_output))

まとめ

実験1
Houghは直線検出がよくできました。実行時間をみると、Houghは実行時間が長くなりました。ただし、LSDの各線の実行時間が早いです。

実験2
LSDは直線検出がよくできました。実行時間をみると、Houghは実行時間が長くなりましたが、LSDの各線の実行時間が早いです。

実験3
LSDは直線検出がよくできました。Houghは曇の直線を検出しまいました。Houghは実行時間が長くなりましたが、LSDの各線の実行時間が早いです。