動機
- 近所にあるショッピングモールには、外通路に多くの植物が植えられていて、一緒に植物の説明が記載されているプレートがあります。どんな種類(100種類以上はあると思われる)の植物があるか気になった為、プレートの情報を集計する事にしました。
- しかしプレートの情報をスマホに打ち込んで情報収集するのは面倒くさい。少しても情報収集を簡略化する為に写真を撮り、台形補正してからOCRする事にしました。
補正前画像
実施事項
- プレート領域の取得
- プレート領域のマスク処理
- ノイズ除去
- 輪郭の取得・近似
- プレート領域の台形補正
- 台形補正
1. プレート領域の取得
1. プレート領域のマスク処理
画像をHSV形式に変換し、Hue(色相)がプレートであろうの範囲に含まれているものを白、それ以外を黒と出力する事にし、マスクと2値化を行います。
preprocess.py
cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
補足
HSVとは色を3要素で表現する方式
- 色相(Hue)
- 色の種類(例えば赤、青、黄色)
- 彩度(Saturation)
- 色の鮮やかさ
- 明度(Value・Brightness)
- 色の明るさ
2. ノイズ除去
クロージングしてから、オープニング
プレートのピクセルが欠損する事がある為、先にクロージング
補足
- 膨張(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)
プレートの輪郭を取得
今回の画像ではプレート以外の輪郭が存在しないのですが、
プレート以外の輪郭が存在する時のため、最大の面積をもつ輪郭(プレート)を取得します。
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)
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)
台形補正の結果
まとめ
つまずいた事
- 最初は補正前の画像に対し直線検出を行っていましたが、植物の線を取得したり、プレートの直線を取得しても切れてしまっていたりと、意図する結果にならなかったです。
- その後に、RGBの値を元にマスクを試みたのですが、プレートの大部分が欠けたり、プレートと関係ない箇所がマスクできなかったりしたため、プレートの輪郭を上手く検出できなかったです。
良かった事
- 完全に手探りで始めた為、どの手法を使えば良いのかわからず、結果的に色々な画像処理の手法を試せた事。(理論は追いついていませんが。)
- 二値化(固定値、大津、適応的閾値処理)
- 直線検出(ハフ変換、確率的ハフ変換)
- エッジ検出(Canny法、LSD)
- 平滑化(移動平均、ガウシアン)
課題
以下の画像の様にプレートに植物が重なり、プレートの領域が分断されると、台形補正が上手くいかない。(写真とる時に、植物がプレートに被らないようにするかな。。。)
オチ
ちなみに、Google Cloud Visionでは補正前と補正後ではOCR結果はほぼ変わりません、補正前でもほぼ完璧にOCRできています。Googleさん流石!!!
その他
ソースコードはこちら
https://github.com/ChihiroHozono/Plate-Text-Detector