画像の台形補正をしてみた

# 動機

- 近所にあるショッピングモールには、外通路に多くの植物が植えられていて、一緒に植物の説明が記載されているプレートがあります。どんな種類（100種類以上はあると思われる）の植物があるか気になった為、プレートの情報を集計する事にしました。
- しかしプレートの情報をスマホに打ち込んで情報収集するのは面倒くさい。少しても情報収集を簡略化する為に写真を撮り、台形補正してからOCRする事にしました。


## 補正前画像
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203291/bd7a722c-e6dd-d286-40f0-5f7d7c5f43c3.jpeg" width="300">


# 実施事項
1. プレート領域の取得
    1. プレート領域のマスク処理
    2. ノイズ除去
    3. 輪郭の取得・近似
2. プレート領域の台形補正
    1. 台形補正


## 1. プレート領域の取得
### 1. プレート領域のマスク処理
画像をHSV形式に変換し、Hue（色相）がプレートであろうの範囲に含まれているものを白、それ以外を黒と出力する事にし、マスクと2値化を行います。

```preprocess.py
cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
```

良い感じにマスクできています。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203291/be9fda9c-5b66-ce2c-251d-08de928f69bf.png" width="300">

#### 補足
HSVとは色を3要素で表現する方式

- 色相(Hue)
    - 色の種類（例えば赤、青、黄色）
- 彩度(Saturation)
    - 色の鮮やかさ
- 明度(Value・Brightness)
    - 色の明るさ


### 2. ノイズ除去
クロージングしてから、オープニング
プレートのピクセルが欠損する事がある為、先にクロージング

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203291/17b34704-e348-081b-075e-3ff8f2dc45e9.png" width="300">


#### 補足
- 膨張(Dilation)
    - 注目画素の周辺に白色の画素が1つでも存在すれば、注目画素を白色に置き換える
- 縮小(Erosion)
    - 注目画素の周辺に黒色の画素が1つでも存在すれば、注目画素を黒色に置き換える
- クロージング
    - 同じ回数分だけ膨張して収縮する処理
- オープニング
    - 同じ回数分だけ収縮して膨張する処理

### 3. 輪郭の取得・プレートの輪郭を近似

### 輪郭の取得
ノイズ除去した画像に含まれる物体の輪郭を検出し、描画します。

```preprocess.py
img, contours, hierarchy = cv2.findContours(img, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPL)
img = cv2.drawContours(img, contour, -1, (0, 0, 255), 30)
cv2.imwrite(output_path, img)
```
この用に輪郭が描写されました。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203291/4a8f71f1-643e-4126-6d33-27e6de51113a.png" width="300">

#### プレートの輪郭を取得
今回の画像ではプレート以外の輪郭が存在しないのですが、
プレート以外の輪郭が存在する時のため、最大の面積をもつ輪郭（プレート）を取得します。


```preprocess.py
contour_areas = {}
for i, contour in enumerate(contours):
    area = cv2.contourArea(contour)
    contour_areas[i] = area

max_area = max(contour_areas.values())
max_area_idx = [i for i, v in contour_areas.items() if v == max_area][0]
max_contour = contours[max_area_idx]
```

### プレート四隅の座標を取得
少数の点で領域の形を近似し、プレート四隅の座標を取得します。

```preprocess.py
arc_len = cv2.arcLength(max_contour, True)
approx_contour = cv2.approxPolyDP(max_contour, epsilon=0.1 * arc_len, closed=True)
img = cv2.drawContours(img, approx_contour, -1, (0, 0, 255), 30)
cv2.imwrite(output_path, img)
```
良い感じに四隅の座標を取得する事ができました。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203291/970b0e82-0f74-1b50-6506-c34e05e464d1.png" width="300">


## 2. プレート領域の台形補正

### 1. 台形補正
4隅の座標を左上、左下、右上、右下に分け、台形補正し画像を描画します。

```preprocess.py
approx = approx_contour.tolist()

left = sorted(approx, key=lambda x: x[0])[:2]
right = sorted(approx, key=lambda x: x[0])[2:]
left_down = sorted(left, key=lambda x: x[0][1])[0]
left_up = sorted(left, key=lambda x: x[0][1])[1]
right_down = sorted(right, key=lambda x: x[0][1])[0]
right_up = sorted(right, key=lambda x: x[0][1])[1]

perspective_base = np.float32([left_down, right_down, right_up, left_up])
perspective = np.float32([[0, 0], [700, 0], [700, 500], [0, 500]])

psp_matrix = cv2.getPerspectiveTransform(perspective_base, perspective)
plate_img = cv2.warpPerspective(org_img, psp_matrix, (700, 500))
cv2.imwrite(output_path, img)
```

#### 台形補正の結果
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203291/e4c20142-cc22-b082-164a-4d7a7c9ab18d.png" width="300">


# まとめ
## つまずいた事
- 最初は補正前の画像に対し直線検出を行っていましたが、植物の線を取得したり、プレートの直線を取得しても切れてしまっていたりと、意図する結果にならなかったです。
- その後に、RGBの値を元にマスクを試みたのですが、プレートの大部分が欠けたり、プレートと関係ない箇所がマスクできなかったりしたため、プレートの輪郭を上手く検出できなかったです。


## 良かった事
- 完全に手探りで始めた為、どの手法を使えば良いのかわからず、結果的に色々な画像処理の手法を試せた事。（理論は追いついていませんが。）
    - 二値化（固定値、大津、適応的閾値処理)
    - 直線検出（ハフ変換、確率的ハフ変換）
    - エッジ検出（Canny法、LSD）
    - 平滑化（移動平均、ガウシアン）

## 課題
以下の画像の様にプレートに植物が重なり、プレートの領域が分断されると、台形補正が上手くいかない。（写真とる時に、植物がプレートに被らないようにするかな。。。）
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203291/4bae66ee-6344-1cad-143c-f4c57f7ed49c.jpeg" width="300">



## オチ
ちなみに、Google Cloud Visionでは補正前と補正後ではOCR結果はほぼ変わりません、補正前でもほぼ完璧にOCRできています。Googleさん流石！！！

# その他
ソースコードはこちら
https://github.com/ChihiroHozono/Plate-Text-Detector