KURORO BLOGのロゴ

このエントリーをはてなブックマークに追加
OpenCVで使われるHoughLinesPとは?定義から実用例を徹底解説!?

OpenCVで使われるHoughLinesPとは?定義から実用例を徹底解説!?

今回はOpenCVで使われるHoughLinesPに関して、定義から実用例をわかりやすく解説致します。なんとなくHoughLinesPを使っていた、OpenCVを使い始めた方へおすすめです。ぜひ最後までご覧ください。

目次
  1. OpenCVで使われるHoughLinesPとは?
  2. ハフ変換の理論を徹底解説
    1. 直線(線分)の方程式を導き出す
    2. ハフ変換が動く仕組みを解説
  3. OpenCVで使われるHoughLinesP関数の定義
    1. HoughLinesP関数の値入力に困った場合
  4. まとめ
  5. 参考文献

執筆者 - おすすめの記事1選

OpenCVで使われるHoughLinesPとは?

OpenCVで使われるHoughLinesPとは、ハフ変換を用いて、画像内の線分を検出するために利用する関数です。

具体的なイメージとしては、以下の「HoughLinesP関数のイメージ画像」の青色で描画される箇所を、検出するイメージを持ってもらうと良いでしょう。

元画像

HoughLinesP関数のイメージ画像

ハフ変換の理論を徹底解説

早急にHoughLinesP関数の定義から利用法を知りたい方は、次章で紹介する「OpenCVで使われるHoughLinesP関数の定義」からご確認ください。

先ほどはOpenCVで使われるHoughLinesPとは、ハフ変換を用いて、画像内の線分を検出するために利用する関数であると説明しました。

「ハフ変換の理論を徹底解説」では、HoughLinesP関数が動く原理となる、ハフ変換に関して解説いたします。

まずハフ変換に関して説明する前に、直線(線分)はどのような方程式で成り立つのか、理解しておく必要があります。

直線(線分)の方程式を導き出す

【図1】

図1のような直線(線分)Lについて考えます。L上で原点Oに最も近い点(垂線との交点)をr、 その距離をρ、 ベクトルrがx軸となす角をθとすると、点rの座標は次のように表せます。

r = ρ(cosθ, sinθ) ・・・①(参考)

またrと同じ向きの単位ベクトルは、次のように表せます。

→n = (cosθ, sinθ) ・・・②(単位ベクトル = nの長さが1になるような座標 = 三平方の定理sin^2θ + cos^2θ = 1であることを利用して、(cosθ, sinθ)となる。)

さらに、図1の赤色のベクトル(→n)と、→rpのベクトルは直交しているので、次のことが成り立ちます。

(→n・→rp) = 0・・・③(参考)

図1のpの座標を(x, y)と仮定して、③式を展開していくと、

→rp = (→op-→or) = (x, y)-ρ(cosθ, sinθ) = (x-ρcosθ, y-ρsinθ)・・・④(参考)

(→n・→rp) = ((cosθ, sinθ)・(x-ρcosθ, y-ρsinθ)) = cosθ * (x-ρcosθ) + sinθ * (y-ρsinθ) = xcosθ-ρcos^2θ + ysinθ-ρsin^2θ = ysinθ + xcosθ -ρ(sin^2θ + cos^2θ) = ysinθ + xcosθ -ρ = 0・・・⑤(参考)

よって直線(線分)は、以下の方程式が成り立ちます。

xcosθ + ysinθ = ρ・・・⑥

ハフ変換が動く仕組みを解説

ハフ変換では、先ほど求めた直線(線分)の方程式(xcosθ + ysinθ = ρ)を元に、線分の検出を行います。

実際に以下の手順を繰り返し行います。

  1. 画像を2値化(画像の色を白か黒かに)する。
  2. xcosθ + ysinθ = ρのρ, θの値(HoughLinesP関数の第二引数, 第三引数)を固定する。
  3. ある白色の画素(点)に注目する。(画素に関しては、以下の画素に関する説明を行う画像を参照)
  4. xcosθ + ysinθ = ρに当てはまる直線(線分)を探す。(以下のxcosθ + ysinθ = ρに当てはまる直線(線分)を探す例の画像1, 2を参照)
  5. 手順3で得られた線分情報(線分開始座標, 線分終了座標, 線分の出現した数(過去に調べた線分との重複数))をメモしておく。
  6. 3~5を繰り返す。
  7. しきい値(HoughLinesP関数の第四引数)よりも線分の出現した数が多い線分情報を直線(線分)とする。

【画素に関する説明を行う画像】

1枚の画像は、複数の画素の集まりで成り立っています。

【xcosθ + ysinθ = ρに当てはまる直線(線分)を探す例の画像1】

【xcosθ + ysinθ = ρに当てはまる直線(線分)を探す例の画像2】

OpenCVで使われるHoughLinesP関数の定義

HoughLinesP関数は

1# cv2(OpenCV)を利用する宣言を行う。
2import cv2
3
4# HoughLinesP関数 : ハフ変換を用いて、画像内の線分を検出するために利用する関数。
5
6# 第一引数(必須) : 多次元配列(画像情報)
7# 第二引数(必須) : xcosθ + ysinθ = ρのρの値。float型。1以上の値を指定する。xcosθ + ysinθ = ρに関しては、ハフ変換の理論を徹底解説の「直線(線分)の方程式を導き出す」箇所をご確認ください。
8# 第三引数(必須) : xcosθ + ysinθ = ρのθの値。float型。xcosθ + ysinθ = ρに関しては、ハフ変換の理論を徹底解説の「直線(線分)の方程式を導き出す」箇所をご確認ください。
9# 第四引数(必須) : しきい値。int型。
10
11###########################
12# 第五引数以降(任意)
13# minLineLength : float型。デフォルト0。指定する数字以上の長さを持つ線の候補のみを、線分として検出する。
14# maxLineGap : float型。2つの点が1つ線分上にある場合に、点と点の間の間隔が指定する数より小さければ、同一の線とみなす。
15# maxLineGapの説明例) 小さい線分が大きい線分と重なっています。小さい線分の長さがmaxLineGapよりも小さい場合に、小さい線分を大きい線分と同じ線とみなします。
16###########################
17
18# 戻り値
19# 検出された線分一覧。[(線分開始x1, 線分開始y1, 線分終了x1, 線分終了y2), ...]で返される。1つも線分が見つからない場合、Noneを返す。
20lines = cv2.HoughLinesP(img, 数字, 数字, 数字, ...)

で定義されます。

例えば以下のようなコードを作成すると、

1import cv2
2import numpy as np
3import sys
4
5# imread : 画像ファイルを読み込んで、多次元配列(numpy.ndarray)にする。
6# imreadについて : https://kuroro.blog/python/wqh9VIEmRXS4ZAA7C4wd/
7# 第一引数 : 画像のファイルパス
8# 戻り値 : 行 x 列 x 色の三次元配列(numpy.ndarray)が返される。
9img = cv2.imread("xxx.xxx")
10
11# 画像ファイルが正常に読み込めなかった場合、プログラムを終了する。
12if img is None:
13    sys.exit("Could not read the image.")
14
15# cvtColor : 画像の色空間(色)の変更を行う関数。
16# cvtColorについて : https://kuroro.blog/python/7IFCPLA4DzV8nUTchKsb/
17# 第一引数 : 多次元配列(numpy.ndarray)
18# 第二引数 : 変更前の画像の色空間(色)と、変更後の画像の色空間(色)を示す定数を設定。
19# cv2.COLOR_BGR2GRAY : BGR(Blue, Green, Red)形式の色空間(色)を持つ画像をグレースケール画像へ変更する。
20# グレースケールとは? : https://www.shinkohsha.co.jp/blog/monochrome-shirokuro-grayscale/
21# 戻り値 : 多次元配列(numpy.ndarray)
22gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
23
24# threshold関数 : しきい値を用いて画素の色を示す値を2値化するための関数。
25# thresholdについて : https://kuroro.blog/python/jofbNumJ9HtfTxnM8QHJ/
26
27# 第一引数 : 多次元配列(numpy.ndarray)
28# 第二引数 : しきい値。float型。150とする。
29
30# 第三引数 : しきい値を超えた画素に対して、色を示す値を指定。float型。255とする。
31
32# 第四引数 : 2値化するための条件のタイプを指定する。
33# cv2.THRESH_BINARY : (画素の色を示す値 <= 第二引数)の場合、画素に対して、0の値を与える。(画素の色を示す値 > 第二引数)の場合、画素に対して、第三引数の値を与える。
34
35# 戻り値 #################
36# _ : しきい値を返す。150を返す。
37# gray : 多次元配列(numpy.ndarray)を返す。
38#########################
39_, gray = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)
40
41# HoughLinesP関数 : ハフ変換を用いて、画像内の線分を検出するために利用する関数。
42
43# 第一引数(必須) : 多次元配列(numpy.ndarray)
44# 第二引数(必須) : xcosθ + ysinθ = ρのρの値。float型。1以上の値を指定する。
45# 第三引数(必須) : xcosθ + ysinθ = ρのθの値。float型。
46# radiansについて : https://note.nkmk.me/python-numpy-sin-con-tan/
47# 第四引数(必須) : しきい値。int型。
48# maxLineGap : float型。2つの点が1つ線分上にある場合に、点と点の間の間隔が指定する数より小さければ、同一の線とみなす。
49# maxLineGapの説明例) 小さい線分が大きい線分と重なっています。小さい線分の長さがmaxLineGapよりも小さい場合に、小さい線分を大きい線分と同じ線とみなします。
50
51# 戻り値
52# lines : 検出された線分一覧。[(線分開始x1, 線分開始y1, 線分終了x1, 線分終了y2), ...]で返される。1つも線分が見つからない場合、Noneを返す。
53lines = cv2.HoughLinesP(gray, 1, np.radians(1), 240, maxLineGap=50)
54
55if lines is not None:
56    # squeeze関数について : https://jellyware.jp/kurage/openvino/c06_numpy.html
57    for x1, y1, x2, y2 in lines.squeeze():
58        # line関数 : 画像内へ線分を描画する関数
59        # line関数について : https://kuroro.blog/python/08XlKyvdgaRJCCqlEoNT/
60        # 第一引数 : 多次元配列(numpy.ndarray)
61        # 第二引数 : 線分の始点の座標。
62        # 第三引数 : 線分の終点の座標。
63        #######################
64        # 第四引数 : 線分の色を指定する。B(Blue)G(Green)R(Red)形式で指定する。
65        # ※ 検出される線分を消したい場合は、第四引数へ線分の周りと同系の色を指定ください。Chrome拡張機能の「ColorZilla」などを用いて、同系の色を調べると良いでしょう。(https://chrome.google.com/webstore/detail/colorzilla/bhlhnicpbhignbdhedgjhgdocnmhomnp/reviews?hl=ja)
66        ####################### 
67        cv2.line(img, (x1, y1), (x2, y2), (255, 0, 0))
68
69    # imwrite : 画像の保存を行う関数
70    # 第一引数 : 保存先の画像ファイル名
71    # 第二引数 : 多次元配列(numpy.ndarray)
72    # <第二引数の例>
73    # [
74    # [
75    # [234 237 228]
76    # ...
77    # [202 209 194]
78    # ]
79    # [
80    # [10 27 16]
81    # ...
82    # [36 67 46]
83    # ]
84    # [
85    # [34 51 40]
86    # ...
87    # [50 81 60]
88    # ]
89    # ]
90    # imwriteについて : https://kuroro.blog/python/i0tNE1Mp8aEz8Z7n6Ggg/
91    cv2.imwrite('output.png', img)

以下の画像のように、画像が描画されます。

元画像

HoughLinesP関数を利用して保存される画像

imreadに関しては、OpenCVで使われるimreadとは?使い方から配列が画像になる仕組みを解説でまとめていますので、詳しく知りたい方は是非ご確認ください。

cvtColorに関しては、OpenCVで使われるcvtcolorとは?cvtcolorの活用例を徹底紹介でまとめていますので、詳しく知りたい方は是非ご確認ください。

thresholdに関しては、OpenCVで使われるthresholdとは?threshold活用例を徹底解説でまとめていますので、詳しく知りたい方は是非ご確認ください。

lineに関しては、OpenCVで使われるlineとは?lineの各引数の意味や使用法を徹底解説!?でまとめていますので、詳しく知りたい方は是非ご確認ください。

imwriteに関しては、OpenCVで使われるimwriteとは?imwriteの定義から使用例をご紹介でまとめていますので、詳しく知りたい方は是非ご確認ください。

HoughLinesP関数の値入力に困った場合

HoughLinesP関数の第二引数~第四引数へ具体的にどのような値を入れるべきか、悩んでいる方も多いかと思います。

上記の問題を解決するために、筆者の場合ipywidgetsというJupyter Notebookで使える拡張機能を使って、第二引数~第四引数の値を調整しています。

【ipywidgetsを利用するために準備したこと】

上記で作成した空のJupyter Notebookへ、以下のコードを貼り付けて、実行(▶️を選択)してみてください。

1import cv2
2from IPython.display import Image, display
3from ipywidgets import widgets
4import numpy as np
5
6def imshow(img):
7    """画像をJupyter Notebook上に表示する。"""
8    _, encoded = cv2.imencode(".png", img)
9    display(Image(encoded))
10
11def houghline(img, rho, theta, threshold, min_line_len, max_line_gap):
12    """ハフ変換で直線(線分)検出を行い、結果を表示する。"""
13
14    # cvtColor : 画像の色空間(色)の変更を行う関数。
15    # cvtColorについて : https://kuroro.blog/python/7IFCPLA4DzV8nUTchKsb/
16    # 第一引数 : 多次元配列(numpy.ndarray)
17    # 第二引数 : 変更前の画像の色空間(色)と、変更後の画像の色空間(色)を示す定数を設定。
18    # cv2.COLOR_BGR2GRAY : BGR(Blue, Green, Red)形式の色空間(色)を持つ画像をグレースケール画像へ変更する。
19    # グレースケールとは? : https://www.shinkohsha.co.jp/blog/monochrome-shirokuro-grayscale/
20    # 戻り値 : 多次元配列(numpy.ndarray)
21    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
22
23    # threshold関数 : しきい値を用いて画素の色を示す値を2値化するための関数。
24    # thresholdについて : https://kuroro.blog/python/jofbNumJ9HtfTxnM8QHJ/
25
26    # 第一引数 : 多次元配列(numpy.ndarray)
27    # 第二引数 : しきい値。float型。150とする。
28
29    # 第三引数 : しきい値を超えた画素に対して、色を示す値を指定。float型。255とする。
30
31    # 第四引数 : 2値化するための条件のタイプを指定する。
32    # cv2.THRESH_BINARY : (画素の色を示す値 <= 第二引数)の場合、画素に対して、0の値を与える。(画素の色を示す値 > 第二引数)の場合、画素に対して、第三引数の値を与える。
33
34    # 戻り値 #################
35    # _ : しきい値を返す。150を返す。
36    # gray : 多次元配列(numpy.ndarray)を返す。
37    #########################
38    _, gray = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)
39
40    # HoughLinesP関数 : ハフ変換を用いて、画像内の線分を検出するために利用する関数。
41
42    # 第一引数(必須) : 多次元配列(numpy.ndarray)
43    # 第二引数(必須) : xcosθ + ysinθ = ρのρの値。float型。1以上の値を指定する。
44    # 第三引数(必須) : xcosθ + ysinθ = ρのθの値。float型。
45    # radiansについて : https://note.nkmk.me/python-numpy-sin-con-tan/
46    # 第四引数(必須) : しきい値。int型。
47    # minLineLength : float型。デフォルト0。指定する数字以上の長さを持つ線の候補のみを、線分として検出する。
48    # maxLineGap : float型。2つの点が1つ線分上にある場合に、点と点の間の間隔が指定する数より小さければ、同一の線とみなす。
49    # maxLineGapの説明例) 小さい線分が大きい線分と重なっています。小さい線分の長さがmaxLineGapよりも小さい場合に、小さい線分を大きい線分と同じ線とみなします。
50
51    # 戻り値
52    # lines : 検出された線分一覧。[(線分開始x1, 線分開始y1, 線分終了x1, 線分終了y2), ...]で返される。1つも線分が見つからない場合、Noneを返す。
53    lines = cv2.HoughLinesP(gray, rho, np.radians(theta), threshold, minLineLength=min_line_len, maxLineGap=max_line_gap)
54
55    # 検出した直線(線分)を描画する。
56    dst = img.copy()
57
58    if lines is not None:
59        # squeeze関数について : https://jellyware.jp/kurage/openvino/c06_numpy.html
60        for x1, y1, x2, y2 in lines.squeeze():
61            # line関数 : 画像内へ線分を描画する関数
62            # line関数について : https://kuroro.blog/python/08XlKyvdgaRJCCqlEoNT/
63            # 第一引数 : 多次元配列(numpy.ndarray)
64            # 第二引数 : 線分の始点の座標。
65            # 第三引数 : 線分の終点の座標。
66            #######################
67            # 第四引数 : 線分の色を指定する。B(Blue)G(Green)R(Red)形式で指定する。
68            # ※ 検出される線分を消したい場合は、第四引数へ線分の周りと同系の色を指定ください。Chrome拡張機能の「ColorZilla」などを用いて、同系の色を調べると良いでしょう。(https://chrome.google.com/webstore/detail/colorzilla/bhlhnicpbhignbdhedgjhgdocnmhomnp/reviews?hl=ja)
69            ####################### 
70            cv2.line(dst, (x1, y1), (x2, y2), (255, 0, 0))
71
72        imshow(dst)
73
74# imread : 画像ファイルを読み込んで、多次元配列(numpy.ndarray)にする。
75# imreadについて : https://kuroro.blog/python/wqh9VIEmRXS4ZAA7C4wd/
76# 第一引数 : 画像のファイルパス。
77# 戻り値 : 多次元配列(numpy.ndarray)が返される。
78# ※ 線分を検出したい画像を設定ください。
79img = cv2.imread("xxx.xxx")
80
81# HoughLinesP関数の第二引数を設定するスライダー
82# 参考 : https://ipywidgets.readthedocs.io/en/latest/examples/Widget%20List.html?highlight=IntSlider#IntSlider
83# min : 第二引数の最小値
84# mix : 第二引数の最大値
85# step : 第二引数の増減値
86# value : 第二引数の初期値
87# description : スライダーの説明文
88rho_slider = widgets.IntSlider(min=1, max=10, step=1, value=1, description="rho: ")
89# HoughLinesP関数の第二引数を設定するスライダーの横幅を設定。
90rho_slider.layout.width = "400px"
91
92# HoughLinesP関数の第三引数を設定するスライダー
93# 参考 : https://ipywidgets.readthedocs.io/en/latest/examples/Widget%20List.html?highlight=IntSlider#IntSlider
94# min : 第三引数の最小値
95# mix : 第三引数の最大値
96# step : 第三引数の増減値
97# value : 第三引数の初期値
98# description : スライダーの説明文
99theta_slider = widgets.IntSlider(min=1, max=360, step=1, value=1, description="theta: ")
100# HoughLinesP関数の第三引数を設定するスライダーの横幅を設定。
101theta_slider.layout.width = "400px"
102
103# HoughLinesP関数の第四引数を設定するスライダー
104# 参考 : https://ipywidgets.readthedocs.io/en/latest/examples/Widget%20List.html?highlight=IntSlider#IntSlider
105# min : 第四引数の最小値
106# mix : 第四引数の最大値
107# step : 第四引数の増減値
108# value : 第四引数の初期値
109# description : スライダーの説明文
110threshold_slider = widgets.IntSlider(min=0, max=500, step=1, value=100, description="threshold: ")
111# HoughLinesP関数の第四引数を設定するスライダーの横幅を設定。
112threshold_slider.layout.width = "400px"
113
114# HoughLinesP関数のminLineLengthを設定するスライダー
115# 参考 : https://ipywidgets.readthedocs.io/en/latest/examples/Widget%20List.html?highlight=IntSlider#IntSlider
116# min : minLineLengthの最小値
117# mix : minLineLengthの最大値
118# step : minLineLengthの増減値
119# value : minLineLengthの初期値
120# description : スライダーの説明文
121min_line_len_slider = widgets.IntSlider(min=0, max=500, step=1, value=0, description="minLineLength: ")
122# HoughLinesP関数のminLineLengthを設定するスライダーの横幅を設定。
123min_line_len_slider.layout.width = "400px"
124
125# HoughLinesP関数のmaxLineGapを設定するスライダー
126# 参考 : https://ipywidgets.readthedocs.io/en/latest/examples/Widget%20List.html?highlight=IntSlider#IntSlider
127# min : maxLineGapの最小値
128# mix : maxLineGapの最大値
129# step : maxLineGapの増減値
130# value : maxLineGapの初期値
131# description : スライダーの説明文
132max_line_gap_slider = widgets.IntSlider(min=0, max=500, step=1, value=50, description="maxLineGap: ")
133# HoughLinesP関数のmaxLineGapを設定するスライダーの横幅を設定。
134max_line_gap_slider.layout.width = "400px"
135
136widgets.interactive(houghline, img=widgets.fixed(img), rho=rho_slider, theta=theta_slider, threshold=threshold_slider, min_line_len=min_line_len_slider, max_line_gap=max_line_gap_slider)

すると以下の画像のような画面が現れます。

実際に部品(スライダー)を動かしてみると、それぞれの値に応じて画像内の直線(線分)を検出して、画像が変更されます。

まとめ

  • OpenCVで使われるHoughLinesPとは、ハフ変換を用いて、画像内の線分を検出するために利用する関数です。
  • HoughLinesP関数の値入力に困った場合、ipywidgetsを利用する。

参考文献

記事に関するお問い合わせ