ソースに絡まるエスカルゴ

貧弱プログラマの外部記憶装置です。

【python/OpenCV】読み込んだ画像から直線を抽出する方法

 今回はタイトルにある通りpythonOpenCVを使って読み込んだ画像から直線を抽出する方法の備忘録です。
 基本的には参考資料に挙げたページ様からの引用ですので、詳しく知りたい方はそちらを参照してください。

 直線を抽出する大まかな流れとしては以下の通りです。

  1. 画像読み込み
  2. グレースケール化
  3. 輪郭抽出
  4. ハフ変換による直線抽出
  5. 直線描画

 では説明していきます。


1:画像読み込み〜輪郭抽出
 画像の読み込みから輪郭抽出までは以下の方法でできます。

import cv2

img = cv2.imread("./test.jpg") # 画像読み込み
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) # グレースケール化
outLineImage = cv2.Canny(gray, 220, 250, apertureSize = 3)   # 輪郭線抽出

 Canny関数で線の部分を白色、他の部分を黒色として抽出した画像を取得しています。各引数の値は読み込む画像によってうまく調節する必要があります。


2:ハフ変換による直線抽出
 直線を抽出するには「ハフ変換」を使います。OpenCVではハフ変換を使って直線を抽出する関数が2種類あるので、両方説明していきます。

・ハフ変換(HoughLines関数)の場合
 HoughLines関数を使って直線を得る場合は以下の記述でできます。

import numpy as np
import cv2

# ハフ変換で直線抽出
lines = cv2.HoughLines(outLineImage, rho=1, theta=np.pi/180, threshold=200)

for line in lines:
    rho, theta = line[0]
    a = np.cos(theta)
    b = np.sin(theta)
    x0 = a*rho
    y0 = b*rho
    x1 = int(x0 + 1000*(-b))
    y1 = int(y0 + 1000*(a))
    x2 = int(x0 - 1000*(-b))
    y2 = int(y0 - 1000*(a))

    cv2.line(result,(x1,y1),(x2,y2),(0,0,255),2) # 赤色で直線を引く

 第一引数は輪郭線の画像、第二引数と第三引数は参考資料を見ていただくとして、とりあえず定数として上記の値を設定しています。
 第四引数は直線と認定する最低限の値を設定します。つまり値を小さくすると直線として判定されやすくなり、値を大きくすると直線として判定されにくくなります。


・確率的ハフ変換(HoughLinesP関数)の場合
 HoughLinesP関数を使って直線を得る場合は以下の記述でできます。ちなみに確率的ハフ変換の方が計算量が少なくて済みます。

import numpy as np
import cv2

# 確率的ハフ変換で直線を抽出
lines = cv2.HoughLinesP(outLineImage, rho=1, theta=np.pi/180, threshold=200, minLineLength=100, maxLineGap=100)

for line in lines:
    x1, y1, x2, y2 = line[0]
    cv2.line(resultP,(x1,y1),(x2,y2),(0,255,0),2) # 緑色で直線を引く

 第四引数までは先ほどと同じですが、第五引数と第六引数が追加されています。
 第五引数は線とみなす最低の長さを設定します。つまり値が大きいほど長い直線でないと認識されなくなります。
 第六引数は同一線上にある点の間隔の最大値を設定します。つまり値が大きいほど離れた点の直線を認識できるようになります。


3:サンプルコード
 画像から直線を抽出する方法がわかったので、2のやり方の両方を実装してみたサンプルコードが以下になります。

・houghLines.py

import numpy as np
import cv2

IMAGE_PATH = "./test.jpg" # 読み込む画像

def main():
    image  = cv2.imread(IMAGE_PATH) # 画像読み込み
    image2 = cv2.imread(IMAGE_PATH) # 画像読み込み
    gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY) # グレースケール化
    outLineImage = cv2.Canny(gray, 220, 250, apertureSize = 3)   # 輪郭線抽出

    cv2.imwrite("./outLine.png", outLineImage)    # ファイル保存

    hough  = hough_lines(image, outLineImage)     # ハフ変換による直線抽出
    cv2.imwrite("./result_hough.png", image)    # ファイル保存

    houghP = hough_lines_p(image2, outLineImage)   # 確率的ハフ変換による直線抽出
    cv2.imwrite("./result_houghP.png", image2)  # ファイル保存


# ハフ変換で直線を抽出する関数
def hough_lines(image, outLineImage):
    result = image
    lines = cv2.HoughLines(outLineImage, rho=1, theta=np.pi/180, threshold=200) # ハフ変換で直線抽出
    print("hough_lines: ", len(lines))

    for line in lines:
        rho, theta = line[0]
        a = np.cos(theta)
        b = np.sin(theta)
        x0 = a*rho
        y0 = b*rho
        x1 = int(x0 + 1000*(-b))
        y1 = int(y0 + 1000*(a))
        x2 = int(x0 - 1000*(-b))
        y2 = int(y0 - 1000*(a))

        cv2.line(result,(x1,y1),(x2,y2),(0,0,255),2) # 赤色で直線を引く

    return result


# 確率的ハフ変換で直線を抽出する関数
def hough_lines_p(image, outLineImage):
    resultP = image
    # 確率的ハフ変換で直線を抽出
    lines = cv2.HoughLinesP(outLineImage, rho=1, theta=np.pi/180, threshold=200, minLineLength=100, maxLineGap=100)
    print("hough_lines_p: ", len(lines))

    for line in lines:
        x1, y1, x2, y2 = line[0]
        cv2.line(resultP,(x1,y1),(x2,y2),(0,255,0),2) # 緑色で直線を引く

    return resultP


if __name__ == '__main__':
    main()

 上記のサンプルコードを実行すると、以下のようになります。

・元画像
f:id:rikoubou:20190327201608j:plain

・左:ハフ変換 右:確率的ハフ変換

f:id:rikoubou:20190327201637p:plainf:id:rikoubou:20190327201647p:plain


 以上がpythonOpenCVを使って画像から直線を抽出する方法です。

 カードなどを識別する時にはこの方法を応用することになるのかなと思います。
 ただ写真でやってみると余計なところに線が引かれてしまうので、線が引かれたくないところは黒塗りにしておく必要がありそうです。

 今回作成したサンプルコードも一応公開しておきます。


・参考資料