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

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

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

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

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

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

# LSDとは
詳しくは[こちら](https://rest-term.com/archives/3393/)のページなどを参照してください

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

# LSDを使う準備

```
pip install pylsd
```

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

```
pip install "ocrd-fork-pylsd == 0.0.3"
```

を利用してください。

準備は終了です。

# 検証

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

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404462/c097f9a6-e1fa-4664-004e-f814ab7fdffd.jpeg" width = "500" >


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

# コード

```
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)

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

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

# 結果
ハフ変換

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404462/a5c4b8d3-98ac-3556-5794-fafb500e0241.jpeg" width="500">

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

LSD
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404462/9686f6e0-6bc9-41e0-b3d8-7c37f3b9059d.jpeg" width="500">

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

LSD(長いやつだけ)
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404462/c10ed495-6661-c3e6-e9fb-39d612223c37.jpeg" width="500">

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

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

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

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