導入
ハフ変換 (Hough Transform) は、1962 年に Paul Hough によって最初に提案され、1972 年に Richard Duda と Peter Hart によって推進されました。画像処理において画像から幾何学的形状を検出するための基本的な方法の 1 つです。古典的なハフ変換は画像内の直線を検出するために使用されますが、後にハフ変換を拡張して円や楕円などの任意の形状のオブジェクトを認識できるようになります。
ハフ変換は、2 つの座標空間間の変換を使用して、一方の空間の同じ形状の曲線または線を別の座標空間の点にマッピングしてピークを形成します。これにより、任意の形状を検出する問題が統計的なピークの問題に変換されます。
直線を検出するハフ変換の原理
デカルト座標系の 2 点によって決まる直線は y = kx+q です。既知の点 A と B を考慮すると、一意の k と q を決定できます。k と q を独立変数および従属変数として使用すると
、 Huo デカルト座標系が使用されている場合、デカルト座標系の直線はハフ座標系の点に対応します。
逆に、デカルト座標系の点 (x1, y1) と、次の方程式を考えてみましょう。この点を通る直線は q = -x1k+y1
このとき、方程式はハフ空間上の直線を表します。
共線点が 3 点ある場合、デカルト座標上の共線点は 1 点で交わることがわかります。デカルト座標のため、ハフ空間 システムの下の線はハフ空間の点に対応します。
元画像の直交座標空間における特殊な直線x=c(x軸に垂直、直線の傾きは無限大)は、直交座標系に基づくパラメータ空間では表現できないためです。したがって、実際のアプリケーションでは、ハフ空間は極座標系で表されます。
極座標では、ハフ空間の直線は曲線になります。デカルト座標系では直線は無数の点で構成されており、ハフ空間に相当する無数の直線や曲線が一点で交差するため、ハフ空間の最大値を求めることで直線のパラメータを求めることができます。空間。
前述したように、ハフ線検出は画像空間の線をパラメータ空間の点に変換し、統計的特性によって検出問題を解決します。具体的には、画像内のピクセルが直線を形成している場合、パラメーター空間内のこれらのピクセル座標値 (x, y) に対応する曲線は点で交差する必要があるため、すべてのピクセルを画像(座標値)をパラメータ空間上の曲線に変換し、パラメータ空間上の曲線の交点を検出することで直線を求めることができます。
理論的には、点は無限の直線、または任意の方向の直線に対応しますが、実際のアプリケーションでは、計算できる直線の数 (つまり、限られた数の方向) を制限する必要があります。
したがって、直線の方向 θ を有限数の等間隔の離散値に離散化し、それに対応してパラメータ ρ も有限数の値に離散化するため、パラメータ空間は連続ではなくなりますが、離散化および量子化されます。等しいサイズのグリッドユニット。画像空間(直交座標系)の各ピクセルの座標値をパラメータ空間(極座標系)に変換した後、得られた値が特定のグリッドに収まり、グリッド単位の累積カウンタを1増加させます。 。画像空間内のすべてのピクセルがハフ変換を受けた後、グリッド単位を調べ、累積カウント値が最も大きいグリッドの座標値 (ρ0、θ0) が画像空間内で求めた直線に対応します。
上記は、ハフ ライン検出アルゴリズムの動作です。パラメータ空間内の対応する曲線間の画像内の各ピクセルの交点を検出します。ある点で交差する曲線の数がしきい値を超える場合、この交点は考慮されます ( ρ、θ) は画像空間内の直線に対応します。
opencvをベースにした実装
opencv のハフ変換を使用して、直線関数 HoughLines および HoughLinesP を検出します。関連する関数については次のとおりです。
cv2.HoughLines(
image, # 原图像。
rho, # 累加器的距离分辨率(以像素为单位)。
theta, # 累加器的弧度角分辨率。
threshold[, # 累加器阈值参数。只返回那些获得足够投票的直线(> threshold)。
lines[, # 返回的线,格式为 [[rho_1,theta_1], [rho_2,theta_2], ...]
srn[, # 对于多尺度 Hough 变换,它是距离分辨率 ρ 的一个除数。
粗的累加器距离分辨率为 ρ,精确的累加器分辨率为 ρ/srn。
如果同时使用 srn = 0 和 stn = 0,则使用经典的 Hough 变换。
否则,这两个参数都应该是正数。
stn[, # 对于多尺度 Hough 变换,它是距离分辨率 θ 的除数。
min_theta[, # 对于标准和多尺度霍夫变换,最小角度检查线。必须在0和max_theta之间。
max_theta # 对于标准和多尺度霍夫变换,检查线路的最大角度。必须在 min_theta 和 cv2.CV_pi 之间。
]]]]]) -> lines
cv2.HoughLinesP(
image, # 源图像。
rho, # 累加器的距离分辨率(以像素为单位)。
theta, # 累加器的弧度角分辨率。
threshold[, # 累加器阈值参数。只返回那些获得足够投票的直线(> threshold)。
lines[, # 返回线结果,格式为 [[xmin, ymin, xmax, ymax], ...]。
minLineLength[, # 最短线段长度。
maxLineGap]]] # 点连接成线的最大距离。
) -> lines
コードは次のように実装されます。
edge = cv2.Canny(np.array(img, dtype='uint8'), 1743, 3400, apertureSize=7, L2gradient=True)
plt.imshow(edge)
plt.show()
# ============================使用opencv霍夫变换=============================
lines = cv2.HoughLines(edge, rho=1, theta=np.pi/180, threshold=round(800*scale_factor))
if lines is not None:
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(img, (x1, y1), (x2, y2), (250, 20, 250), 2)
cv2.imshow("img", img)
cv2.imshow("edge", edge)
cv2.waitKey(0)
cv2.destroyAllWindows()
効果は次のとおりです。
上記の例では、OpenCV に付属している機能で直線を非常にうまく検出できることがわかりますが、画像がより複雑になると、しきい値を制御する必要があり、そうしないと、多くの予期せぬ結果が得られます。
ハフ変換に基づくROI検出原理
以上より、ハフ変換により直線を検出することで対象画像の長方形のROIを検出できることがわかりましたが、OpenCV付属のハフ変換検出直線機能はあまり便利ではありません。私たちは知っています:
- ハフ領域の長い直線により高レベルのブロブが発生する
- 平行線は同じ角度を持っているため、それらに対応する点はハフ領域上で同じ値の θ で表示されます。
- 縦線間の距離は θ=π/2
- ほぼ平行で垂直な視準線が 2 組あるため、ハフ領域解析では θ の値がほぼ同じ 2 組の輝点が検索され、それらの間の距離は約 θ=π/2 になります。
- したがって、長方形ROIの4本の直線が得られ、長方形ROIの検出が実現される。
Python に基づくハフ変換検出長方形 ROI
アルゴリズムのステップ:
(1) アキュムレータ H をすべて 0 に初期化する;
(2) 画像内のすべてのエッジ点をトラバースする;
(3) H の極大値の点と π/2 によって分離された点を見つける;
(4) 設定する点 ( θ 、 ρ ) を画像内の直線に変換します。
# 测试,笛卡尔坐标系到霍夫域的转换
rho = []
rad = []
for theta in range(max_theta):
# print(theta)
theta = np.pi*theta/180
rad.append(theta)
rho1 = 100 * np.cos(theta) + 100 * np.sin(theta)
rho.append(rho1)
plt.plot([0, x_data[-1]], [0, 0])
plt.plot(x_data, rho), plt.xlabel('theta'), plt.ylabel('rho')
plt.xlim([0, 180])
plt.show()
デカルト座標系 (画像座標) をハフ領域 (パラメーター座標) に変換します。
H = np.zeros([round(edge.shape[0]*3.2), max_theta])
points = [] # 边缘点集
for y in range(edge.shape[0]):
for x in range(edge.shape[1]):
if edge[y, x] > 0:
p1 = [x, y]
points.append(p1)
for p2 in points:
# print(p2)
for theta in range(max_theta):
theta_rad = np.pi * theta / 180
rho = p2[0] * np.cos(theta_rad) + p2[1] * np.sin(theta_rad)
H[round(rho+H.shape[0]/2), round(theta)] += 1 # x,y,height的中点为坐标原点
# H[:, 0] = H[:, 0] + H[:, -1]
H = np.flipud(H)
hough_max = np.max(H, 0)
# plt.subplot(211), plt.imshow(H)
# plt.subplot(212), plt.plot(x_data, hough_max)
plt.imshow(H)
plt.show()
ハフ ドメイン内の平行線と垂直線、合計 4 つの点を見つけて、それらをハフ画像に表示します。
# 在霍夫域寻找平行和垂直线,四个点
theta_rho_max1 = np.argmax(hough_max)
theta_rho_max2 = theta_rho_max1 + 90
theta_rho_max2 = theta_rho_max2 % 180
print(theta_rho_max1)
print(theta_rho_max2)
rho_max1 = round(hough_max0[theta_rho_max1]) # 得到theta最大值的第一个rho
temp = np.where(H[:, theta_rho_max1] == rho_max1)
rho_max1_index = temp[0][0]
rho_max11_index = round(hough_max2(H[:, theta_rho_max1], rho_max1_index))
rho_max2 = round(hough_max0[theta_rho_max2])
temp = np.where(H[:, theta_rho_max2] == rho_max2)
rho_max2_index = temp[0][0]
rho_max22_index = round(hough_max2(H[:, theta_rho_max2], rho_max2_index))
# 在霍夫图像中标记处检测到的四个点
H[rho_max1_index, theta_rho_max1] = 100
H[rho_max11_index, theta_rho_max1] = 100
H[rho_max2_index, theta_rho_max2] = 100
H[rho_max22_index, theta_rho_max2] = 100
plt.imshow(H), plt.xlim([0, 180])
plt.show()
完全なコードは次のとおりです。
# -*- coding: UTF-8 -*-
"""
霍夫变换实现并将其用于检测ROI
"""
import numpy as np
import matplotlib.pyplot as plt
import cv2
img = cv2.imread('../images/hough_test2.jpg', -1)
scale_factor = 1.0/2.0
img = cv2.resize(img, None, fx=scale_factor, fy=scale_factor)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
print(np.shape(gray))
# ret, bw = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU)
edge = cv2.Canny(gray, 17430*scale_factor, 34000*scale_factor, apertureSize=7, L2gradient=True)
# cv2.imshow("", bw)
# cv2.waitKey()
# edge = cv2.Canny(gray, 17, 34, apertureSize=7, L2gradient=True)
# ============================使用opencv霍夫变换=============================
# lines = cv2.HoughLines(edge, rho=1, theta=np.pi/180, threshold=round(170*scale_factor))
# if lines is not None:
# 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(img, (x1, y1), (x2, y2), (0, 20, 250), 2)
# cv2.imshow("img", img)
# cv2.imshow("edge", edge)
# cv2.waitKey()
# cv2.destroyAllWindows()
# ============================自己实现霍夫变换=============================
def mean_filter1(data, fsize):
l = np.shape(data)[0]
dst = np.zeros([l, 1])
hsize = round(fsize / 2)
for i in range(l):
left = i - hsize
right = i + hsize
if left < 0:
left = 0
if right > l-1:
right = l-1
dst[i] = np.mean(data[left:right], 0)
return dst
def hough_max2(arr, m1=0):
l = len(arr)
m = 0
max_index = 0
for i in range(l):
if arr[i] > m and i != m1:
m = arr[i]
max_index = i
return max_index
max_theta = 181
x_data = np.arange(max_theta)
# 测试
rho = []
rad = []
for theta in range(max_theta):
# print(theta)
theta = np.pi*theta/180
rad.append(theta)
rho1 = 100 * np.cos(theta) + 100 * np.sin(theta)
rho.append(rho1)
plt.plot([0, x_data[-1]], [0, 0])
plt.plot(x_data, rho), plt.xlabel('theta'), plt.ylabel('rho')
plt.xlim([0, 180])
plt.show()
H = np.zeros([round(edge.shape[0]*3.2), max_theta])
points = [] # 边缘点集
for y in range(edge.shape[0]):
for x in range(edge.shape[1]):
if edge[y, x] > 0:
p1 = [x, y]
points.append(p1)
for p2 in points:
# print(p2)
for theta in range(max_theta):
theta_rad = np.pi * theta / 180
rho = p2[0] * np.cos(theta_rad) + p2[1] * np.sin(theta_rad)
H[round(rho+H.shape[0]/2), round(theta)] += 1 # x,y,height的中点为坐标原点
# H[:, 0] = H[:, 0] + H[:, -1]
H = np.flipud(H)
hough_max = np.max(H, 0)
# plt.subplot(211), plt.imshow(H)
# plt.subplot(212), plt.plot(x_data, hough_max)
plt.imshow(H)
plt.show()
hough_max0 = hough_max
hough_max = mean_filter1(hough_max, 10)
kernelSize = 10
hough_max_nms = np.zeros([hough_max.shape[0]])
for i in range(hough_max.shape[0]):
temp = hough_max[i:i+kernelSize]
max1 = np.max(temp)
hough_max_nms[i] = max1
for i in range(hough_max.shape[0]):
if hough_max[i] == hough_max_nms[i]:
hough_max_nms[i] = hough_max[i]
else:
hough_max_nms[i] = 0
plt.plot(x_data, hough_max)
for i in range(hough_max_nms.shape[0]):
if hough_max_nms[i] > 0:
plt.scatter(x_data[i], hough_max_nms[i], c='r')
plt.xlabel('theta'), plt.ylabel('rho')
plt.show()
# 在霍夫域寻找平行和垂直线,四个点
theta_rho_max1 = np.argmax(hough_max)
theta_rho_max2 = theta_rho_max1 + 90
theta_rho_max2 = theta_rho_max2 % 180
print(theta_rho_max1)
print(theta_rho_max2)
rho_max1 = round(hough_max0[theta_rho_max1]) # 得到theta最大值的第一个rho
temp = np.where(H[:, theta_rho_max1] == rho_max1)
rho_max1_index = temp[0][0]
rho_max11_index = round(hough_max2(H[:, theta_rho_max1], rho_max1_index))
rho_max2 = round(hough_max0[theta_rho_max2])
temp = np.where(H[:, theta_rho_max2] == rho_max2)
rho_max2_index = temp[0][0]
rho_max22_index = round(hough_max2(H[:, theta_rho_max2], rho_max2_index))
# 在霍夫图像中标记处检测到的四个点
H[rho_max1_index, theta_rho_max1] = 100
H[rho_max11_index, theta_rho_max1] = 100
H[rho_max2_index, theta_rho_max2] = 100
H[rho_max22_index, theta_rho_max2] = 100
plt.imshow(H), plt.xlim([0, 180])
plt.show()
hp1 = [rho_max1_index, theta_rho_max1]
hp2 = [rho_max11_index, theta_rho_max1]
hp3 = [rho_max2_index, theta_rho_max2]
hp4 = [rho_max22_index, theta_rho_max2]
hough_points = [hp1, hp2, hp3, hp4]
print(hough_points)
for line in hough_points:
rho = -(line[0] - H.shape[0]/2 + 1)
theta = line[1]
theta = np.pi * theta / 180
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(img, (x1, y1), (x2, y2), (250, 0, 0), 2)
cv2.imshow("edge", edge)
cv2.imshow("", img)
cv2.waitKey()
cv2.destroyAllWindows()