ハフ変換そのものは座標の変換処理なのですが、画像処理では、ハフ変換を用いて画像中の直線部分を抽出するのに用いられます。
ハフ変換と言うだけで、あんに直線検出を指している事が多くあります。
また、ハフ変換を拡張して、円の検出に用いられる場合もあります。
ハフ変換で出来ること
画像の中から、直線が途切れていても、直線らしき部分を抽出したり、前処理でエッジ抽出を行い、輪郭部分を検出する事もできます。
(元画像)
(直線検出)
(元画像)
(輪郭検出)
直線検出のしくみ
まず初めに直線として検出したい場所を二値化して抽出します。
被写体の輪郭を検出したい場合は、SobelやCannyなどの前処理を行います。
二値化された座標(XY座標)に関して、X座標をいくつかに分割し、Y軸方向に二値化された座標をカウントします。
このカウントを点を原点に基点に回転させながら、繰り返しカウントを行います。
すると、点が直線的に並んだ時にカウント数が最大となります。
直線なので、逆向き(回転角度が180度反対)の時もカウント数が最大となります。
このカウント数を横軸を回転角度、縦軸をX座標にして、二次元的に表示すると、下図のようになり、カウント値がピークとなるXとθの値から、直線を検出することができます。
ハフ変換のアルゴリズム
点を回転して特定方向に積算することで、直線を見つける事もできるのですが、ハフ変換では、各点を通る直線を回転させ、直線が重なりあう場所をみつける事で、直線を検出します。
点(x, y)を通る線を、下図のようにρとθで表すと
$$\rho=x cos\theta+y sin\theta$$
となります。
これは、(x, y)座標が決まっていて、θを与えると、ρが計算できる事になります。
点(x,y)を通る直線をθを0~360°の範囲で刻むと下図のようになります。
この直線をρとθで表すと、下図のようにsinカーブとなります。
この処理を各点に関して行うと、点が直線的に並んでいる部分では特定のρとθに集中します。
このρとθの値が集中している点を抽出し、ρとθの値から直線を検出します。
実際のハフ変換
ハフ変換の角度は0~360°で計算すると、必ず180°離れた2か所でρとθが集中するので、0~180°までを計算します。
実際のハフ変換では、エッジ上のxy座標から、θの値を例えば1°おきに0~180°までに対応したρの値を計算し、ρの値の分解能を例えば1などに落として、ρーθの座標系へ投票し、投票した値のカウント数が高い部分が直線となる部分のρ、θとなります。
OpenCV(Python)のサンプルプログラム
import cv2
import math
import numpy as np
# 画像読込
src = cv2.imread('keyboard.jpg', cv2.IMREAD_GRAYSCALE);
# 二値化
_, src = cv2.threshold(src, 200, 255, cv2.THRESH_BINARY)
cv2.imshow("edge image", src)
# ハフ変換
lines = cv2.HoughLines(src, 3, np.pi / 180, 400)
# 結果表示用の画像を作成
dst = cv2.cvtColor(src, cv2.COLOR_GRAY2BGR);
# 直線を描画
line_length = 1000
for line in lines:
rho = line[0][0]
theta = line[0][1]
a = math.cos(theta)
b = math.sin(theta)
x0 = a * rho
y0 = b * rho
cv2.line(
dst,
(int(x0 - line_length * b), int(y0 + line_length * a)),
(int(x0 + line_length * b), int(y0 - line_length * a)),
(0, 0, 255), thickness=2, lineType=cv2.LINE_4 )
cv2.imshow("result", dst)
cv2.waitKey()
(処理前の画像)
(処理後画像)
注意事項
ハフ変換を行うと、直線らしい部分が抽出できますが、この直線の位置の精度を高めようと、ハフ変換時のθとρの分解能を高め過ぎると、直線検出の安定性が悪くなります。
そのため、直線の位置精度を高めるには、ハフ変換で直線付近の座標を抽出し、その座標から回帰直線などを求めるようにします。
コメント
[…] ハフ変換 | イメージングソリューション […]
[…] ハフ変換 | イメージングソリューション […]