OpenCV计算机视觉实战(15)——霍夫变换详解

发布于:2025-07-15 ⋅ 阅读:(22) ⋅ 点赞:(0)

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) 在图像中检测不同半径范围的圆形,并通过调节参数 (dpminDistparam1param2minRadiusmaxRadius) 来优化检测效果,尽量减少误检与漏检。

2.1 应用场景

  • 工业检测:识别轴承滚珠、齿轮孔位置
  • 医学影像:定位细胞、胶状囊泡等圆形结构
  • 天文图像:在星空照片中检测月亮、行星轮廓

2.2 实现过程

  • 读取图像与预处理
    • 转灰度,做高斯模糊减少噪声
  • 霍夫圆检测
    • cv2.HoughCircles:在灰度图中检测圆心和半径
  • 参数调优策略
    • dp:累加器分辨率与图像分辨率比例
    • minDist:检测到的圆心之间最小距离,防止多次检测同一圆
    • param1/param2Canny 边缘高阈值与累加阈值,影响边缘强度和圆心确认
    • 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:同心圆心之间的最小距离,防止多次检测
    • param1Canny 边缘检测的高阈值
    • 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)——直方图均衡化


网站公告

今日签到

点亮在社区的每一天
去签到