47
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

pythonの直線検出は間違いなくOpenCVではなくPylsdが優れている

背景

直線検出器といえばOpenCVのハフ変換による直線検出器が有名(?)かと思います。
少なくとも検索上位にくるのはOpencvです(執筆当時)
こちらの記事など
https://qiita.com/tifa2chan/items/d2b6c476d9f527785414

しかし使ったことある人はわかるでしょう

・ハフ変換のとてつもないノイズはすごいヤバイ(日本語)
・なぜそこに線を引く!
・なぜこの明らかな直線を認識しない!
等々(筆者談)

そして四苦八苦するわけです。
ハフ変換のパラメータ調整に。
直線が少ないな、LineGapあげよう→ノイズが大きいな、ガウシアンフィルタを強くかけよう→欲しい線が消えたな、閾値をさげよう・・・
おそらくうまく欲しい線だけが検出できる設定なんてないのです(絶望)

と言うわけで代わりになるものを探していたわけですが、
ありました。
Line Segment Detector(LSD)というアルゴリズムです。

LSDとは

詳しくはこちらのページなどを参照してください

一言で言えば、明暗の勾配を利用して直線を検出するわけです!(適当)
使えればよいですよね。

LSDを使う準備

pip install pylsd

これでインストール出来ます。しかし、これはpython3に対応していないので、
python3の方は対応させた別バージョン

pip install 'ocrd-fork-pylsd == 0.0.3'

を利用してください。

準備は終了です。

 検証

使用する画像はなんでもよかったのですが、自分のアルバムにあった名著たちを利用したいと思います。

比較対象はハフ変換です。ある程度パラメータは調整したつもりですが、正直その辺はなげやりです笑
なお、時間も計測していますが、ハフ変換はエッジ画像などの2値画像に対し、LSDはgray画像を入力とするため、2値化も含めてハフ変換の時間としています。

コード

import cv2
import time
from pylsd.lsd import lsd

import numpy as np

img = cv2.imread('samp.jpg')
img = cv2.resize(img,(int(img.shape[1]/5),int(img.shape[0]/5)))
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('samp_hagh.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("Hagh")
print(len(linesH),"lines")
print(t2-t1,"sec")
print("time per a line :{:.4f}".format((t2-t1)/len(linesH)))
print("LSD")
print(len(linesL),"lines")
print(t3-t2,"sec")
print("time per a line {:.4f}".format((t3-t2)/len(linesL)))
cv2.imwrite('samp_pylsd.jpg',img3)
cv2.imwrite('samp_pylsd2.jpg',img4)

output

Hagh
113 lines
0.021329164505004883 sec
time per a line :0.000189
LSD
539 lines
0.19267797470092773 sec
time per a line 0.000357

実行するごとに微妙に時間変わるので、同じ結果にはならないかもしれませんが、基本的にHaghの方が早そうです。
検出の数が直線検出の時間と影響がありそうだったので、直線あたりの時間を求めた形です。

 簡単な解説

Pylsdのコードは単純で、

from pylsd.lsd import lsd
linesL = lsd(gray)

この2行だけで動作します。他に引数は無いので調整のしようがありません。

返り値は
(x1,y1,x2,y2,width)の5つを返します。widthがあるのでそこだけ注意してください(用途あるのだろうか)

結果

ハフ変換

文字のところが打ち消し線みたいになってますが、意外とよくとれてます。文字見たいな明暗が激しいところを線と認識してしまいがちです。

LSD

結構ちらちらしちゃってますね。
閾値など設定がないので、出力から短いものを取り除いたものが次の画像になります。

LSD(長いやつだけ)

元の画像をみると、光沢にまで直線が検出されていることがわかります。
lsd上で線を取捨選択はできませんが、出てきた線を長さや角度からこちらで絞り込むことで欲しい線だけを抽出することはできそうです。

おわりに

LSD優秀です!
特に線を見逃すことがハフ変換と比べて圧倒的にすくないのと
よくわからないところに線が出てしまうことも少ないです。

性能が良すぎて光沢などにも直線を引いてしまうのが少ない弱点でしょうか。。。

直線検出を利用する際はぜひ、利用してみてください。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
47
Help us understand the problem. What are the problem?