OpenCV计算机视觉实战(15)——霍夫变换详解
0. 前言
在图像处理与计算机视觉领域,霍夫变换以其对抗噪声、鲁棒性强的特点,广泛应用于直线、圆形等几何模型的检测。从建筑立面分析到工业零件检测,再到自动驾驶中的车道线识别,掌握霍夫变换的各种变体与优化思路至关重要。本文将围绕三大核心任务——直线检测、圆形检测、以及车道线检测实现进行介绍。
1. 直线检测
使用标准霍夫变换和概率霍夫变换 (HoughLinesP
) 检测图像中的直线,并根据长度、角度等条件过滤出我们关心的线段(例如水平方向或近垂直方向的长线段)。
1.1 应用场景
- 建筑立面分析:提取窗框、梁柱等垂直与水平结构
- 文本行分割:在扫描文档中检测基线,辅助
OCR
排版校正 - 创意艺术:用直线检测结果生成抽象画,或做形状映射
1.2 实现过程
- 读取图像与预处理
- 转灰度,使用高斯模糊去噪
- 利用
Canny
边缘检测获得边缘图
- 直线检测
cv2.HoughLinesP
:检测所有可能的线段
- 线段过滤
- 根据线段长度阈值剔除过短的伪线
- 根据线段角度(例如与水平或垂直的夹角)只保留感兴趣方向的线段。
- 可视化
- 在彩色图上绘制保留的线段
import cv2
import numpy as np
import math
# 1. 读取与预处理
img = cv2.imread('2.jpeg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (5,5), 1.5)
edges = cv2.Canny(blur, 50, 150)
# 2. 概率霍夫线段检测
lines = cv2.HoughLinesP(
edges,
rho=1,
theta=np.pi/180,
threshold=80,
minLineLength=50,
maxLineGap=10
)
# 3. 线段过滤:只保留与水平夹角 < 10° 或 > 30°
filtered = []
for x1,y1,x2,y2 in lines[:,0]:
dx, dy = x2-x1, y2-y1
angle = abs(math.degrees(math.atan2(dy, dx)))
length = math.hypot(dx, dy)
if length > 60 and (angle < 10 or angle > 30):
filtered.append((x1,y1,x2,y2))
# 4. 绘制结果
output = img.copy()
for x1,y1,x2,y2 in filtered:
cv2.line(output, (x1,y1), (x2,y2), (0,0,255), 2)
cv2.imshow('Filtered Lines', output)
cv2.waitKey(0)
cv2.destroyAllWindows()
关键函数解析:
cv2.Canny(src, lowThresh, highThresh)
:提取图像边缘,为霍夫变换提供二值输入cv2.HoughLinesP(image, rho, theta, threshold, minLineLength, maxLineGap)
:概率霍夫直线检测,返回线段端点坐标minLineLength
:线段最小长度,小于该值将被忽略maxLineGap
:同一条直线的两段最大间隔,小于该值可被连接
- 使用
atan2(dy,dx)
和hypot(dx,dy)
分别计算线段的角度和长度,用以过滤不符合需求的线段
2. 圆形检测
利用霍夫圆变换 (HoughCircles
) 在图像中检测不同半径范围的圆形,并通过调节参数 (dp
、minDist
、param1
、param2
、minRadius
、maxRadius
) 来优化检测效果,尽量减少误检与漏检。
2.1 应用场景
- 工业检测:识别轴承滚珠、齿轮孔位置
- 医学影像:定位细胞、胶状囊泡等圆形结构
- 天文图像:在星空照片中检测月亮、行星轮廓
2.2 实现过程
- 读取图像与预处理
- 转灰度,做高斯模糊减少噪声
- 霍夫圆检测
cv2.HoughCircles
:在灰度图中检测圆心和半径
- 参数调优策略
dp
:累加器分辨率与图像分辨率比例minDist
:检测到的圆心之间最小距离,防止多次检测同一圆param1/param2
:Canny
边缘高阈值与累加阈值,影响边缘强度和圆心确认minRadius/maxRadius
:限制检测半径范围
- 可视化
- 在原图上绘制检测到的圆心和圆环
import cv2
import numpy as np
# 1. 读取与预处理
img = cv2.imread('9.jpeg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.medianBlur(gray, 5)
# 2. 霍夫圆变换
circles = cv2.HoughCircles(
blur,
method=cv2.HOUGH_GRADIENT,
dp=1.5,
minDist=50,
param1=100,
param2=30,
minRadius=10,
maxRadius=50
)
# 3. 绘制结果
output = img.copy()
if circles is not None:
circles = np.uint16(np.around(circles[0]))
for x,y,r in circles:
cv2.circle(output, (x,y), r, (0,255,0), 2)
cv2.circle(output, (x,y), 2, (0,0,255), 3)
cv2.imshow('Detected Circles', output)
cv2.waitKey(0)
cv2.destroyAllWindows()
关键函数解析:
cv2.medianBlur(src, ksize)
:中值滤波对脉冲噪声效果好,常用于圆检测前的预处理cv2.HoughCircles(image, method, dp, minDist, param1, param2, minRadius, maxRadius)
:dp
:累加器分辨率与图像分辨率的反比,dp>1
可减少误检minDist
:同心圆心之间的最小距离,防止多次检测param1
:Canny
边缘检测的高阈值param2
:累加器阈值,越大圆检测越严格minRadius/maxRadius
:限制半径搜索范围,加快速度并减少误检
3. 车道线检测
结合透视变换、感兴趣区域 (Region of Interest
, ROI
) 与概率霍夫线段检测,实现简单的车道线检测。
import cv2, numpy as np
def region_of_interest(img):
mask = np.zeros_like(img)
h, w = img.shape[:2]
pts = np.array([[(w*0.1,h), (w*0.4,h*0.6), (w*0.6,h*0.6), (w*0.9,h)]], dtype=np.int32)
cv2.fillPoly(mask, pts, 255)
return cv2.bitwise_and(img, mask)
# 1. 读取与颜色过滤
img = cv2.imread('11.jpeg')
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# 白色掩码
white = cv2.inRange(hsv, (0,0,200), (180,30,255))
# 黄色掩码
yellow = cv2.inRange(hsv, (15,100,100), (35,255,255))
color_mask = cv2.bitwise_or(white, yellow)
filtered = cv2.bitwise_and(img, img, mask=color_mask)
# 2. ROI + 灰度+Canny
roi = region_of_interest(filtered)
gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 50, 150)
# 3. 透视变换
h, w = img.shape[:2]
src = np.float32([[w*0.45, h*0.6], [w*0.55, h*0.6], [w*0.1, h], [w*0.9, h]])
dst = np.float32([[w*0.2, 0], [w*0.8, 0], [w*0.2, h], [w*0.8, h]])
M = cv2.getPerspectiveTransform(src, dst)
warped = cv2.warpPerspective(edges, M, (w,h))
# 4. 滑动窗口+二次拟合
hist = np.sum(warped[h//2:,:], axis=0)
mid = w//2
left_base = np.argmax(hist[:mid])
right_base = np.argmax(hist[mid:]) + mid
nwindows = 9; margin=100; minpix=50
window_height = h // nwindows
nonzero = warped.nonzero()
nonx, nony = nonzero[1], nonzero[0]
left_current, right_current = left_base, right_base
left_inds, right_inds = [], []
for i in range(nwindows):
win_y_low = h - (i+1)*window_height
win_y_high = h - i*window_height
win_xl_low = left_current - margin; win_xl_high = left_current + margin
win_xr_low = right_current - margin; win_xr_high = right_current + margin
good_left = ((nony>=win_y_low)&(nony<win_y_high)&
(nonx>=win_xl_low)&(nonx<win_xl_high)).nonzero()[0]
good_right = ((nony>=win_y_low)&(nony<win_y_high)&
(nonx>=win_xr_low)&(nonx<win_xr_high)).nonzero()[0]
left_inds.append(good_left); right_inds.append(good_right)
if len(good_left)>minpix: left_current = int(np.mean(nonx[good_left]))
if len(good_right)>minpix: right_current = int(np.mean(nonx[good_right]))
left_inds = np.concatenate(left_inds); right_inds = np.concatenate(right_inds)
leftx, lefty = nonx[left_inds], nony[left_inds]
rightx, righty = nonx[right_inds], nony[right_inds]
# 二次多项式拟合
left_fit = np.polyfit(lefty, leftx, 2)
right_fit = np.polyfit(righty, rightx, 2)
# 5. 绘制车道区域
ploty = np.linspace(0, h-1, h)
left_fitx = left_fit[0]*ploty**2 + left_fit[1]*ploty + left_fit[2]
right_fitx = right_fit[0]*ploty**2 + right_fit[1]*ploty + right_fit[2]
pts_left = np.array([np.transpose(np.vstack([left_fitx, ploty]))])
pts_right = np.array([np.flipud(np.transpose(np.vstack([right_fitx, ploty])))])
pts = np.hstack((pts_left, pts_right))
warped_color = cv2.cvtColor(warped, cv2.COLOR_GRAY2BGR)
cv2.fillPoly(warped_color, np.int32([pts]), (0,255, 0))
invM = np.linalg.inv(M)
lane = cv2.warpPerspective(warped_color, invM, (w,h))
result = cv2.addWeighted(img, 0.8, lane, 0.2, 0)
cv2.imshow('Advanced Lane Detection', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
关键函数解析:
cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
:将BGR
图像转换为HSV
色彩空间,更适合分离颜色信息cv2.inRange()
:用于颜色阈值分割,提取特定颜色cv2.bitwise_or()
:将两种颜色掩码合并cv2.bitwise_and()
:保留感兴趣颜色区域,背景设为黑色cv2.getPerspectiveTransform(src, dst)
:计算透视变换矩阵cv2.warpPerspective(img, M, size)
:执行图像透视变换cv2.fillPoly()
:将拟合的车道区域填充为绿色cv2.warpPerspective(..., invM)
:将鸟瞰图逆变换回原图视角cv2.addWeighted()
:将车道绘制结果与原图进行加权叠加
小结
本文系统介绍了霍夫变换在三大经典任务中的应用与实践要点:
- 直线检测:结合
Canny
边缘、概率霍夫变换与线段长度/角度过滤,快速提取图像中水平与垂直的主干直线,适用于建筑立面、文档基线或艺术化分割 - 圆形检测:通过中值滤波降噪后使用
HoughCircles
,并调节累加器分辨率、最小圆心距离与阈值参数,能精准定位多种尺寸的圆形目标,如工业零件、细胞结构或天文图像中的天体 - 车道线检测:依次进行颜色空间筛选、
ROI
裁剪、Canny
边缘检测、鸟瞰透视变换,再利用滑动窗口搜索与二次多项式拟合,最终将检测到的车道区域逆变换叠加回原图
系列链接
OpenCV计算机视觉实战(1)——计算机视觉简介
OpenCV计算机视觉实战(2)——环境搭建与OpenCV简介
OpenCV计算机视觉实战(3)——计算机图像处理基础
OpenCV计算机视觉实战(4)——计算机视觉核心技术全解析
OpenCV计算机视觉实战(5)——图像基础操作全解析
OpenCV计算机视觉实战(6)——经典计算机视觉算法
OpenCV计算机视觉实战(7)——色彩空间详解
OpenCV计算机视觉实战(8)——图像滤波详解
OpenCV计算机视觉实战(9)——阈值化技术详解
OpenCV计算机视觉实战(10)——形态学操作详解
OpenCV计算机视觉实战(11)——边缘检测详解
OpenCV计算机视觉实战(12)——图像金字塔与特征缩放
OpenCV计算机视觉实战(13)——轮廓检测详解
OpenCV计算机视觉实战(14)——直方图均衡化