【1】引言
前序学习进程中,已经对边缘检测有了一定的了解,相关文章链接为:
python学opencv|读取图像(六十四)使用cv2.findContours()函数+cv2.drawContours()函数实现图像轮廓识别和标注-CSDN博客
python学opencv|读取图像(六十八)使用cv2.Canny()函数实现图像边缘检测-CSDN博客
在此基础上,如果想绘制图像边缘轮廓中的直线,就会用到霍夫直线变换。实质上,霍夫直线变换这算是一种特征检测,它检测到直线,然后才能画直线。
霍夫直线变换调用的函数为cv2.HoughLinesP()。
【2】官网教程
点击下方链接,直达霍夫直线变换调用的函数cv2.HoughLinesP()的官网教程:
官网页面对cv2.HoughLinesP()函数的说明为:
图1 官网页面对cv2.HoughLinesP()函数的说明
具体的,官网页面对cv2.HoughLinesP()函数的参数说明为:
cv.HoughLinesP(
image #输入图像,8位单通道灰度图
rho #检测长度用的步长,取值=1时检测所有可能的长度
theta #检测角度用的步长,取值=np.pi/180时检测所有可能的角度
threshold #检测阈值,越小时检测的直线越多
lines #输出值,输出直线的两个端点坐标
minLineLength #线段最小长度,小于该长度的线段不输出
maxLineGap) #线段之间的最小距离
【3】代码测试
首先是引入必要的模块和相关的图像:
import cv2 as cv # 引入CV模块
import numpy as np #引入numpy模块
# 读取图片
src = cv.imread('srctt.png') #读取图像
然后对图像进行预处理 :
#预处理图像
srcb=cv.medianBlur(src,3) #中值滤波
gray=cv.cvtColor(srcb,cv.COLOR_BGR2GRAY) #将图像转化为灰度图
r=cv.Canny(gray,100,280) #边缘检测
图像预处理的过程是层层递进的:
首先进行中值滤波处理,可以实现图像降噪;
然后将降噪后的图像转化为灰度图;
之后对灰度图进行边缘检测。
边缘检测的目的是,养这些边缘检测的值来做霍夫直线变换,由此获得紧贴轮廓的直线。
图2 图像处理流程
之后进行霍夫直线变换:
#霍夫直线变换
lines1=cv.HoughLinesP(r,1,np.pi/180,60,minLineLength=80,maxLineGap=10)
然后显示和保存图像:
#绘制霍夫直线变换获得的直线
for line in lines1:
x1,y1,x2,y2=line[0]
cv.line(src,(x1,y1),(x2,y2),(200,150,200),5)
#显示和保存图像
cv.imshow('gr', gray)
cv.imshow('r',r)
cv.imshow('src',src)
cv.imwrite('ll.png', src) #保存图像
cv.waitKey() # 图像不关闭
cv.destroyAllWindows() # 释放所有窗口
代码运行相关的图像有:
图3 初始图像
图4 霍夫变换直线检测后的图像
由图4可见,经过霍夫直线变换检测,部分直线被检测到,并被添加到图像上。
【4】代码优化
实际上,前述代码只描绘了一种情况的霍夫直线变换检测结果,如果改变cv2.HoughLinesP()函数的参数,想多检测几次,就需要扩充代码,比如:
#霍夫直线变换 lines1=cv.HoughLinesP(r,1,np.pi/180,60,minLineLength=80,maxLineGap=10) lines2=cv.HoughLinesP(r,1,np.pi/180,80,minLineLength=80,maxLineGap=10) lines3=cv.HoughLinesP(r,1,np.pi/180,80,minLineLength=100,maxLineGap=10) lines4=cv.HoughLinesP(r,1,np.pi/180,80,minLineLength=100,maxLineGap=30) lines5=cv.HoughLinesP(r,1,np.pi/30,80,minLineLength=100,maxLineGap=30) #绘制霍夫直线变换获得的直线 for line in lines1: x1, y1, x2, y2 = line[0] l1 = cv.line(src, (x1, y1), (x2, y2), (200, 150, 200), 5) cv.imshow('l1', l1) cv.imwrite('l1.png', l1) # 保存图像 for line in lines2: x1, y1, x2, y2 = line[0] l2 = cv.line(src, (x1, y1), (x2, y2), (200, 150, 200), 5) cv.imshow('l2', l2) cv.imwrite('l2.png', l2) # 保存图像 for line in lines3: x1, y1, x2, y2 = line[0] l3 = cv.line(src, (x1, y1), (x2, y2), (200, 150, 200), 5) cv.imshow('l3', l3) cv.imwrite('l3.png', l3) # 保存图像 for line in lines4: x1, y1, x2, y2 = line[0] l4 = cv.line(src, (x1, y1), (x2, y2), (200, 150, 200), 5) cv.imshow('l4', l4) cv.imwrite('l4.png', l4) # 保存图像 for line in lines5: x1, y1, x2, y2 = line[0] l5 = cv.line(src, (x1, y1), (x2, y2), (200, 150, 200), 5) cv.imshow('l5', l5) cv.imwrite('l5.png', l5) # 保存图像
实际上,这样做的目标就是逐个改变cv2.HoughLinesP()函数的参数,这个过程可以用一个for循环完成:
#霍夫直线变换
params_list = [
(1, np.pi/180, 60, 80, 10),
(1, np.pi/180, 80, 80, 10),
(1, np.pi/180, 80, 100, 10),
(1, np.pi/180, 80, 100, 30),
(1, np.pi/30, 80, 100, 30)
]
# 假设 r 和 src 已经定义
transformed_images = [src] # 先将原始图像添加到列表中
# 假设 r 和 src 已经定义
for i, (rho, theta, threshold, minLineLength, maxLineGap) in enumerate(params_list, start=1):
# 霍夫直线变换
lines = cv.HoughLinesP(r, rho, theta, threshold, minLineLength=minLineLength, maxLineGap=maxLineGap)
if lines is not None:
img = src.copy() # 复制原图像,避免多次绘制影响
for line in lines:
x1, y1, x2, y2 = line[0]
img = cv.line(img, (x1, y1), (x2, y2), (200, 150, 200), 5)
transformed_images.append(img)
这里首先用params_list矩阵,收集了所有的cv2.HoughLinesP()函数参数,然后用一个for循环,通过使用enumerate()函数将所有参数按照顺序代入cv2.HoughLinesP()函数进行运算。
enumerate(params_list, start=1)函数是一个枚举器,代表从第一个params_list的子项开始,逐个调用子项中的(rho, theta, threshold, minLineLength, maxLineGap)参数。for后的i其实就是个计数器,代表第几次调用参数,一般情况下,i的起始数据是0,第一次调用会被系统记录是第0次调用,而因为start=1约定了i的起始=1,所以第一次调用会被系统认为是第1次调用。、
然后仔细观察代码,会发现有一行代码使用了src:
transformed_images = [src] # 先将原始图像添加到列表中
这行代码的目的是,想把经过霍夫直线变换的图像和初始图像放在一起比较,先让一个矩阵存储一下初始图像src。
在for循环中,同样还有一行代码:
transformed_images.append(img)
这行代码的作用是,在每一次执行for循环后,会把获得的霍夫直线变换的图像直接添加在src后面。
然后因为一共做了5次霍夫直线变换,加上初始图像,一共6个小图,所以可以组合成一个2X3的大图,这时候为了谨慎,先检测一下是否有6个图:
# 确保图像数量足够进行两行三列的拼接
while len(transformed_images) < 6:
# 如果图像数量不足 6 个,用空白图像填充
blank_image = np.zeros_like(src)
transformed_images.append(blank_image)
这个while循环中的blank_image = np.zeros_like(src)的意思是,生成一个和src等大的纯0矩阵,这是个纯黑色图。
之后按照顺序组合成两行三列的大图:
# 分割图像列表为两行
first_row = transformed_images[:3]
second_row = transformed_images[3:6]
# 水平拼接每行的图像
h_concat_first_row = cv.hconcat(first_row)
h_concat_second_row = cv.hconcat(second_row)
# 垂直拼接两行的图像
final_image = cv.vconcat([h_concat_first_row, h_concat_second_row])
最后显示和保存图像:
# 显示和保存最终拼接图像
cv.imshow('Concatenated Images', final_image)
cv.imwrite('concatenated_images.png', final_image)
# 等待按键关闭窗口
cv.waitKey()
cv.destroyAllWindows()
代码运行后,获得的图像为:
图5 多个霍夫直线变换图像对比
图5显示了多个霍夫直线变换图像对比效果,相对来说,第一行第三列的图像效果好一些。
第一行第三列的图像和第一行第二列的图像相比,调高了阈值,从60到80;
第二行第一列的图像和第一行第三列的图像相比。提高了线段最小长度,从80到100,所以部分短的直线没有输出;
第二行第二列的图像和第二行第一列的图像相比。提高了线段之间的最小距离,从10到30,输出了更多线段;
第二行第三列的图像和第二行第二列的图像相比。增大了检测角度用的步长,从np.pi/180到np.pi/30,输出了更少的线段。
【5】总结
掌握了python+opencv实现调用cv2.HoughLinesP()函数实现图像中的霍夫变换直线检测。