Posted at

OpenCVで直線の検出


はじめに

OCRで読み込ませる時に、画像の枠線や罫線がノイズとなり、邪魔をして正しく読み込めないときがあります。

ここでは、そのような線を検出し、削除してみたいと思います。

ハフ変換という関数を利用するのですが、ハフ変換ってなんぞや?という感じで、ここでは説明しませんので、ググってください…。

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


直線の検出方法

HoughLinesP 関数を使い、白と黒だけの2値画像から検出

下記のステップでやってみます


0.画像の読み込み

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

2. ネガポジ変換で反転

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

4. 線の色付け


0. 画像の読み込み


  • 対象となる画像です

img = cv2.imread("calendar.png")

calendar.png


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

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

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

cv2.imwrite("calendar_mod.png", gray)

calendar_mod.png


2. ネガポジ変換で反転

この変換で白と黒を逆転させ、白黒はっきりさせます。

こうすることで、線が白く(明るく)なり、特定がしやすくなります。

gray2 = cv2.bitwise_not(gray)

cv2.imwrite("calendar_mod2.png", gray)

calendar_mod2.png


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

ハフ変換自体難しく、ここでハマりました。

ここでは対象の画像に対して、パラメータ値の調整が必要になります。


  • rho, theta はデフォルトを利用

  • threshold は、直線を動かして、その直線状に乗ってきた点の数がこの値を超えたら線とみなす

  • minLineLength は、ここに指定された値以上の長さを持つ線の候補が見つかったら、それを線として検出する

  • maxLineGapは、2つの点が1つ線上にある場合に、点と点の間の間隔がここに指定した数より小さければ、同一の線とみなす


minLineLengthの値を大きい場合

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


minLineLengthの値を小さい場合

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


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

calendar_mod3.png


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

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

calendar_mod3.png


4. 線の色付け


赤線で線を引く

上記にもあるとおり、線の座標が返ってくるので、色を付けるのがこちらになります。

複数の線座標が取れるので、ループで回して、line関数に設定していきます。

その結果が上記の赤線の図です。

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)


線を消す

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

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


まとめ

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

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)