ハフ変換の基礎と射影変換を用いた応用

はじめに

今回はハフ変換、中でもハフ線変換について紹介します。 ハフ変換(Hough Transform)とは、画像内に線や円などを検出する手法です。その中でも基本となるのはハフ線変換であり、2値画像から直線を探索する方法となります。

ハフ線変換のイメージ

ハフ線変換の基本的な考えは、2値画像内にある点はどの点でも何かしらの線の一部となっている可能性があるというものです。 その際、 y=ax+bという式で直線を表すことができますが、取り得る傾きの区間が -\inftyから +\inftyとなってしまうため最良とは言えないでしょう。そこで、ハフ線変換では \rho=x\cos \theta +y\sin \thetaという方程式で表現します。 最終的には、多数決ように \left( \rho ,\theta \right)のペアで投票が多いものが直線として検出されます。 OpenCVのハフ変換アルゴリズムは、こういった計算を我々ユーザには見せる訳ではなく、単に \left( \rho ,\theta \right)平面内の極大値を返すだけとなっているため、どんな流れで直線が検出されているのかを理解しておくのは大切です。

OpenCVのハフ線変換

早速ハフ線変換をPythonで実装する前にOpenCVでサポートされているハフ線変換シリーズを紹介します。 OpenCVには、

  1. 標準ハフ変換(SHT : Standard Hough Transform)
  2. マルチスケールハフ変換(MHT : Multiscale Hough Transform)
  3. 確率的ハフ変換(PPHT : Progressive Probabilistic Hough Transform)

があります。 それぞれ、SHTはこれまで説明してきたアルゴリズムであり、MHTはそれの改良版、PPHTはさらにその変形版です。PPHTは推測要素が入るため確率的とされており、計算時間を大幅に削減できる利点があります。 今回はPPHTを使って事前に用意した横断歩道の画像(図1)にある白線周りを検出することを目指します。

図1 オリジナル画像

ハフ線変換の実装

早速PythonでPPTHを実装していきます。コードは以下の通りで、グレースケールへの変換とぼかし処理を経てエッジ検出を用いた2値化を行いハフ線変換の準備をします。 その後、HoughLinesPというPPTHを実行できる関数を用いてハフ線変換を行っていきます。得られた極大値を使って直線を元画像に描画していき完了です。

# 画像の読み込み
img_src = cv2.imread('image_name.jpg')

# グレースケールに変換
img_src = cv2.cvtColor(img_src, cv2.COLOR_BGR2GRAY)
# ぼかし処理
img_src = cv2.blur(img_src, (5,5))
# エッジ検出を用いて二値化
img_src = cv2.Canny(img_src, 150, 200, apertureSize=3)

# 確率的ハフ変換で直線を検出
lines = cv2.HoughLinesP(img_src, 1, np.pi/180, 100, minLineLength=150, maxLineGap=30)

# 直線の描画
for line in lines:
    x1, y1, x2, y2 = line[0]
    cv2.line(img_src,  (x1, y1), (x2, y2), (0,0,255),2)

ハフ線変換後の図2にある通り、横断歩道の白線周りの線を検出できていることが分かります。しかし、他の線も検出してしまっているみたいです。 例えば、横断歩道の白線だけを検出したい場合はどうしたらよいのでしょうか?そんな時、射影変換という手法を用いて横断歩道のみに注目したハフ線変換を行うことが可能です。

図2 ハフ線変換後の画像

射影変換とハフ線変換の組み合わせ

先ほどは横断歩道以外の白線周りも一緒に検出されてしまいました。そこで、まずは射影変換を行い横断歩道のみに注目していきます。 射影変換の説明はまた今度詳しくしようと思いますが、①合同変換、②相似変換、③アフィン変換という画像形状の基本的な変換に対してどんな四角形にも変換できる方法です。 文章だけだとイメージしずらいかなと思うので、図3のように考えて貰えれば良いかと思います。

図3 射影変換のイメージ図

では、横断歩道の画像に射影変換を施し、その後改めてハフ線変換を行っていきます。

# 画像サイズの取得
size = tuple(np.array([img_src.shape[1], img_src.shape[0]]))

# 射影変換を行いたい座標の読み込み
pts1 = np.float32([[1241,357],[3,369],[1199,765],[1911,506]])
pts2 = np.float32([[0,0],[0,1080],[1920,1080],[1920,0]])

# 射影変換の実行
psp_mat = cv2.getPerspectiveTransform(pts1, pts2)
img_dst = cv2.warpPerspective(img_src, psp_mat, size, flags=cv2.INTER_LINEAR)

図4 射影変換後の横断歩道

# グレースケールに変換
img_gray = cv2.cvtColor(img_dst, cv2.COLOR_BGR2GRAY)
# ぼかし処理
img_gray = cv2.blur(img_gray, (5,5))
# エッジ検出を用いて二値化
img_edges = cv2.Canny(img_gray, 30, 40, apertureSize=3)

# 確率的ハフ変換で直線を検出
lines = cv2.HoughLinesP(img_edges, 1, np.pi/180, 100, minLineLength=100, maxLineGap=50)

# 直線の描画
for line in lines:
    x1, y1, x2, y2 = line[0]
    cv2.line(img_dst,  (x1, y1), (x2, y2), (0,0,255),2)

図5 ハフ線変換後の画像

おわり

前半にハフ線変換とは何かであったりの説明や確率的ハフ線変換の実装を行い、後半に射影変換を用いた注目したい部分に絞ったハフ線変換をおこないました。 少し画像解析らしいことができて楽しかったです。次回は輪郭抽出や物体検出に少し入っていこうかと思います。