Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
95
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

@tifa2chan

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)
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
95
Help us understand the problem. What are the problem?