OpenCVで直線の検出

# はじめに

OCRで読み込ませる時に、画像の枠線や罫線がノイズとなり、邪魔をして正しく読み込めないときがあります。
ここでは、そのような線を検出し、削除してみたいと思います。
ハフ変換という関数を利用するのですが、ハフ変換ってなんぞや？という感じで、ここでは説明しませんので、ググってください…。

では、やることについて１つずつ説明し、最後にすべてのソースをくっつけてみます。

# 直線の検出方法

**HoughLinesP** 関数を使い、白と黒だけの2値画像から検出
下記のステップでやってみます     
 0.画像の読み込み
 1. グレースケールに変換
 2. ネガポジ変換で反転
 3. ハフ変換でラインの検出
 4. 線の色付け

## 0. 画像の読み込み

* 対象となる画像です

``` python
img = cv2.imread("calendar.png")
```
![calendar.png](https://qiita-image-store.s3.amazonaws.com/0/92328/3e6edb74-6f82-2512-0e63-526b7ca25d46.png)


## 1. グレースケールに変換

グレースケールに変換します。

``` python
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imwrite("calendar_mod.png", gray)
```

![calendar_mod.png](https://qiita-image-store.s3.amazonaws.com/0/92328/b7283f19-8fbd-18d7-73f8-daae2959f970.png)


## 2. ネガポジ変換で反転

この変換で白と黒を逆転させ、白黒はっきりさせます。
こうすることで、線が白く（明るく）なり、特定がしやすくなります。

```python
gray2 = cv2.bitwise_not(gray)
cv2.imwrite("calendar_mod2.png", gray)
```
![calendar_mod2.png](https://qiita-image-store.s3.amazonaws.com/0/92328/99e2cd31-09e4-e48e-b833-f77dfe6abb45.png)


## 3. ハフ変換でラインの検出

ハフ変換自体難しく、ここでハマりました。
ここでは対象の画像に対して、**パラメータ値の調整**が必要になります。

* rho, theta はデフォルトを利用
* threshold は、直線を動かして、その直線状に乗ってきた点の数がこの値を超えたら線とみなす
* minLineLength は、ここに指定された値以上の長さを持つ線の候補が見つかったら、それを線として検出する
* maxLineGapは、2つの点が1つ線上にある場合に、点と点の間の間隔がここに指定した数より小さければ、同一の線とみなす

### minLineLengthの値を大きい場合

```python
lines = cv2.HoughLinesP(gray2, rho=1, theta=np.pi/360, threshold=80, minLineLength=400, maxLineGap=5)
print(lines)
```

* lineの座標 x1, y1, x2, y2 が7セットだけ返ってきました。 

```
[[[ 11 391 515 391]]
 [[ 11 139 515 139]]
 [[ 11  13 515  13]]
 [[ 11 328 515 328]]
 [[ 11 265 515 265]]
 [[ 11  76 515  76]]
 [[ 11 202 515 202]]]
```

* 下図の赤線7本になります。(線の色付けは次項参照）
* minLineLengthの値が縦線の長さを超えてしまってるため、縦線が検出されていません。

![calendar_mod3.png](https://qiita-image-store.s3.amazonaws.com/0/92328/95342f68-7b66-517f-45b1-8215eafadc25.png)


### minLineLengthの値を小さい場合

```python
lines = cv2.HoughLinesP(gray2, rho=1, theta=np.pi/360, threshold=80, minLineLength=30, maxLineGap=5)
```

* 今度は文字部分までが線と認識されてしまいました。

![calendar_mod3.png](https://qiita-image-store.s3.amazonaws.com/0/92328/7a42de44-ed87-31e4-c417-e491f9c60fbf.jpeg)

### 調整の上、今回の画像はこのくらい

```python
lines = cv2.HoughLinesP(gray2, rho=1, theta=np.pi/360, threshold=80, minLineLength=80, maxLineGap=5)
```
![calendar_mod3.png](https://qiita-image-store.s3.amazonaws.com/0/92328/c19f48ce-8962-3612-838d-c82e274ea32f.jpeg)

## 4. 線の色付け

### 赤線で線を引く
上記にもあるとおり、線の座標が返ってくるので、色を付けるのがこちらになります。
複数の線座標が取れるので、ループで回して、**line**関数に設定していきます。
その結果が上記の赤線の図です。

```python
for line in lines:
    x1, y1, x2, y2 = line[0]

    # 赤線を引く
    red_line_img = cv2.line(img, (x1,y1), (x2,y2), (0,0,255), 3)
    cv2.imwrite("calendar_mod3.png", red_line_img)
```

### 線を消す

逆に線を消すには、白で線を塗りつぶせば良いのです。

```python
for line in lines:
    x1, y1, x2, y2 = line[0]
    # 線を消す(白で線を引く)
    no_lines_img = cv2.line(img, (x1,y1), (x2,y2), (255,255,255), 3)
    cv2.imwrite("calendar_mod4.png", no_lines_img)
```

![calendar_mod4.png](https://qiita-image-store.s3.amazonaws.com/0/92328/cd240d5c-2e2c-e516-a7e3-a9ad49b5ade8.png)


# まとめ

上記のソースをまとめるとこのようになります。

```python
import cv2
import numpy as np

# カレンダー
img = cv2.imread("calendar.png")
img2 = img.copy()
img3 = img.copy()

# グレースケール
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imwrite("calendar_mod.png", gray)

## 反転 ネガポジ変換
gray2 = cv2.bitwise_not(gray)
cv2.imwrite("calendar_mod2.png", gray2)
lines = cv2.HoughLinesP(gray2, rho=1, theta=np.pi/360, threshold=80, minLineLength=80, maxLineGap=5)

for line in lines:
    x1, y1, x2, y2 = line[0]

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

    # 線を消す(白で線を引く)
    no_lines_img = cv2.line(img3, (x1,y1), (x2,y2), (255,255,255), 3)
    cv2.imwrite("calendar_mod4.png", no_lines_img)
```