05 OpenCV--图像预处理之图像轮廓、直方图均衡化、模板匹配、霍夫变化、图像亮度变化、形态学变化
文章目录
- 一、图像轮廓特征查找
- 1. 外接矩阵
- 2. 最小外接矩形
- 3.最小外接圆
- 二、直方图均衡化
- 1.绘制直方图
- 2.直方图均衡化
- 2.1 标准直方图均衡化
- 2.2 对比度受限的自适应直方图均衡化
- 三、模板匹配方法
- 1. 平方差匹配
- 2. 归一化平方差匹配
- 3. 相关匹配
- 4. 归一化相关匹配
- 5. 相关系数匹配
- 6. 归一化相关系数匹配
- 7 绘制轮廓
- 四、霍夫变换
- 1 标准霍夫变换(Standard Hough Transform, SHT)
- 2. 概率霍夫变换(Probabilistic Hough Transform, PHough)
- 3. 霍夫圆变换(Hough Circle Transform)
- 五、图像亮度变换
- 线性变换(对比度与亮度调整)
- 直接像素值更改
- 六、形态学变换
- 核(Structuring Element)
- 腐蚀(Erosion)
- 膨胀(Dilation)
- 开运算(Opening)
- 闭运算(Closing)
- 礼帽运算(Top Hat)
- 黑帽运算(Black Hat)
- 形态学梯度(Morphological Gradient)
一、图像轮廓特征查找
- 轮廓是图像中物体的边界,通常由连续的边缘点构成,用于对象检测、形状分析和图像分割。
- 关键步骤
- 预处理:将图像转化为灰度图,并进行高斯模糊或二值化处理以减少噪音
- 边缘检测:使用Canny算法提取边缘
- 查找轮廓:**cv2.findContours()**获取轮廓
- 绘制轮廓:使用**cv2.drawContours()**绘制检测到的轮廓
1. 外接矩阵
外接矩阵是指完全包含目标轮廓且边与坐标轴平行的最小矩形。它用四个参数表示:左上角坐标 (x, y)
、宽度 w
和高度 h
。
x, y, w, h = cv2.boundingRect(contour)
contour
:单个轮廓点集(numpy
数组)。由**cv2.findContours()**获取轮廓返回获得。- 返回值
(x, y)
:矩形左上角坐标。w, h
:矩形的宽和高。
import cv2 as cv#读图
img = cv.imread('../images/num.png')
#灰度化
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
#二值化
_,binary = cv.threshold(gray,127,255,cv.THRESH_BINARY_INV)
#查找轮廓
contours,hirarchy = cv.findContours(binary,cv.RETR_EXTERNAL,cv.CHAIN_APPROX_SIMPLE)
#获取外接矩形点
for i in contours:x,y,w,h = cv.boundingRect(i)cv.rectangle(img,(x,y),(x+w,y+h),(0,0,255),5,cv.LINE_AA) cv.imshow('img',img)
cv.waitKey(0)
cv.destroyAllWindows()
2. 最小外接矩形
最小外接矩形是指完全包含目标轮廓且面积最小的矩形,它可以是旋转的(边不与坐标轴平行)。通常用中心点坐标 (cx, cy)
、宽高 (w, h)
和旋转角度 θ
表示。
寻找最小外接矩形使用的算法叫做旋转卡壳法,旋转卡壳法有一个很重要的前提条件:对于多边形P的一个外接矩形存在一条边与原多边形的边共线。
假设某凸包图如下所示:
根据前提条件,上面的凸多边形的最小外接矩形与凸多边形的某条边是共线的。因此我们只需要以其中的一条边为起始边,然后按照逆时针方向计算每个凸包点与起始边的距离,并将距离最大的点记录下来。
如上图所示,我们首先以a、b两点为起始边,并计算出e点离起始边最远,那么e到起始边的距离就是一个矩形的高度。对于矩形的最右边,以向量ab‾\overline{{a b}}ab为基准,然后分别计算凸包点在向量ab‾\overline{{a b}}ab上的投影的长度,投影最长的凸包点所在的垂直于起始边的直线就是矩形最右边所在的直线。
如上图所示,d点就是在向量ab‾\overline{{a b}}ab上投影最长的凸包点,那么通过d点垂直于直线ab的直线就是矩形的右边界所在的直线。矩形的左边界的也是这么计算的,不同的是使用的向量不是ab‾\overline{{a b}}ab而是ba‾\overline{{b a}}ba。
如上图所示,h点垂直于ab的直线就是以ab为起始边所计算出来的矩形所在的左边界所在的直线。其中矩形的高就是e点到直线ab的距离,矩形的宽是h点在向量上ba‾\overline{{b a}}ba的投影加上d点在向量ab‾\overline{{a b}}ab上的投影减去ab的长度,即:
width=∣bh‾∣×cosa+∣ad‾∣×cosθ−∣ab‾∣w i d t h=|\overline{{{b h}}}|\times\cos a+|\overline{{{a d}}}|\times\cos\theta-|\overline{{{a b}}}| width=∣bh∣×cosa+∣ad∣×cosθ−∣ab∣
于是我们就有了以ab为起始边所构成的外接矩形的宽和高,这样就可以得到该矩形的面积。然后再以bc为起始边,并计算其外接矩形的面积。也就是说凸多边形有几个边,就要构建几次外接矩形,然后找到其中面积最小的矩形作为该凸多边形的最小外接矩形。
min_rect = cv2.minAreaRect(cnt)
box = cv2.boxPoints(rect).astype(int)
contour
:单个轮廓点集。- 返回值
min_rect
:(center_x, center_y), (width, height), angle
。box
:矩形的四个顶点坐标[(x1,y1), (x2,y2), (x3,y3), (x4,y4)]
。
cv2.drawContours(image, contours, contourIdx, color, thickness)
- contourIdx:要绘制的轮廓索引。如果设置为
-1
,则绘制所有轮廓。 - color:轮廓的颜色,可以是 BGR 颜色格式的三元组,例如
(0, 0, 255)
表示红色。 - thickness:轮廓线的粗细,如果是正数,则绘制实线;如果是 0,则绘制轮廓点;如果是负数,则填充轮廓内部区域。
import cv2 as cv
#读图
img = cv.imread('../images/num.png')
#灰度化
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
#二值化
_,binary = cv.threshold(gray,125,255,cv.THRESH_BINARY_INV)
#查找轮廓
contours,hirarchy = cv.findContours(binary,cv.RETR_EXTERNAL,cv.CHAIN_APPROX_SIMPLE)#图像轮廓特征查找--`(x, y)`、宽度、高度 和旋转角度
#循环遍历每条轮廓
for cont in contours:#计算出每个轮廓的小面积外接矩形rect = cv.minAreaRect(cont)#处理点坐标--边界框的四个角点points = cv.boxPoints(rect).astype(int)#绘制最小外接矩形cv.drawContours(img,[points],-1,(0,255,0),5)cv.imshow('img',img)
cv.waitKey(0)
cv.destroyAllWindows()
3.最小外接圆
最小外接圆是指完全包含目标轮廓的最小圆,由圆心坐标 (cx, cy)
和半径 r
确定。
寻找最小外接圆使用的算法是Welzl算法。其基本思想是基于一个定理:对于平面上任意n个点,若在其最小覆盖圆外取第(n+1)个点,那么第(n+1)个点一定在这(n+1)个点的最小覆盖圆的圆周上。算法先将所有点随机排列,然后按顺序逐个加入点,若当前点在当前最小圆外,则该点一定在最小覆盖圆边界上,需重新确定包含该点的最小圆。Welzl 算法的时间复杂度为(O(n)),可以在线性时间内求出覆盖n个点的最小圆。
(center_x, center_y), radius = cv2.minEnclosingCircle(points)
- 参数
contour
:单个轮廓点集。 - 返回值
(center_x, center_y)
:圆心坐标(浮点数)。radius
:圆半径(浮点数)。
cv2.circle(img, center, radius, color, thickness)
img
:输入图像,通常是一个numpy数组,代表要绘制圆形的图像。center
:一个二元组(x, y)
,表示圆心的坐标位置。radius
:整型或浮点型数值,表示圆的半径长度。color
:颜色标识,可以是BGR格式的三元组(B, G, R)
,例如(255, 0, 0)
表示红色。thickness
:整数,表示圆边框的宽度。如果设置为-1
,则会填充整个圆。
#读图
img = cv.imread('../images/num.png')
#灰度化
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
#二值化
_,binary = cv.threshold(gray,127,255,cv.THRESH_BINARY_INV)
#查找轮廓
contours,hirarchy = cv.findContours(binary,cv.RETR_EXTERNAL,cv.CHAIN_APPROX_SIMPLE)
for cont in contours:#获取外接圆center,radius = cv.minEnclosingCircle(cont)#此时center为小数,转为intcenter = (int(center[0]),int(center[1]))radius = int(radius)#绘制外接圆cv.circle(img,center,radius,(0,255,0),3)cv.imshow('img',img)
cv.waitKey(0)
cv.destroyAllWindows()
二、直方图均衡化
直方图均衡化是一种增强图像对比度的方法,通过对图像的像素值进行重分布,使图像的灰度直方图更均匀地分布在整个范围内。
1.绘制直方图
hist=cv2.calcHist(images, channels, mask, histSize, ranges)
images
:- 输入图像列表,需用方括号括起,如
[img]
(即使只有一张图像)。 - 图像必须是单通道(灰度图)或多通道(如 BGR 彩色图)。
- 输入图像列表,需用方括号括起,如
channels
:- 指定计算哪个通道的直方图,用索引表示。
- 对于灰度图:
[0]
(唯一通道)。 - 对于 BGR 彩色图:
[0]
(B 通道)、[1]
(G 通道)、[2]
(R 通道)。
mask
:- 可选参数,用于指定计算直方图的区域。
- 若为
None
,则计算整幅图像的直方图。
histSize
:- 指定直方图的 “bin” 数量(即直方图的柱子数量),用列表表示。
- 例如:
[256]
表示将 0-255 的像素值分成 256 个区间,每个区间对应一个 bin。 - 对于彩色图,可指定不同通道的 bin 数量,如
[16, 16, 16]
(每个通道 16 个 bin)。
ranges
:- 指定像素值的范围,用列表表示。
- 例如:
[0, 256]
表示范围为 0(包含)到 256(不包含)。
返回值hist 是一个长度为255的数组,数组中的每个值表示图像中对应灰度等级的像素计数
minVal, maxVal, minLoc, maxLoc = cv2.minMaxLoc(hist)
获取直方图的最小值、最大值及其对应最小值的位置索引、最大值的位置索引
cv2.line(img, pt1, pt2, color, thickness)
- img:原始图像,即要在上面画线的numpy数组(一般为uint8类型)。
- pt1 和 pt2:直线的起点和终点坐标,格式为
(x, y)
- color:线段的颜色
- thickness:线段的宽度
import cv2 as cv
import numpy as np
#读图
bg = cv.imread('../images/bg.png')
black = np.zeros((256,256,3),np.uint8)
#统计像素
hist = cv.calcHist([bg],[0],None,[256],[0,256])
# print(hist)
# print(hist[241,0]) #[行,列]#获取直方图最大值 [列,行]--(x,y)
minVal,maxVal,minLoc,maxLoc = cv.minMaxLoc(hist)
# print(minVal,maxVal,minLoc,maxLoc)#归一化处理
#定义直方图的高
h_max = np.int32(256)
#循环拿像素个数--[num]
for i in range(256):l = int(hist[i].item()*h_max/maxVal)pt1 = (i,256-l)pt2 = (i,256)cv.line(black,pt1,pt2,(255,0,0),1)cv.imshow('black',black)
cv.waitKey(0)
cv.destroyAllWindows()
2.直方图均衡化
直方图均衡化是一种增强图像对比度的经典方法,通过对图像的灰度值进行非线性变换,使图像的灰度直方图更均匀地分布在整个范围内。这种方法可以显著提升图像的细节,尤其适用于低对比度或偏暗 / 偏亮的图像。
2.1 标准直方图均衡化
适用于图像的灰度分布不均匀,且灰度分布集中在更窄的范围,图像的细节不够清晰且对比度比较低的情况,但是这种方法会引入噪声
dst = cv.equalizeHist(imgGray)
- 参数:单通道灰度图像(8 位无符号整数)。
- 返回值:直方图均衡化后的图像。
import cv2 as cv
# 读图
img = cv.imread('../images/zhifang.png',cv.IMREAD_GRAYSCALE)
# 直方图处理
dst = cv.equalizeHist(img)
cv.imshow('img',img)
cv.imshow('dst',dst)
cv.waitKey()
cv.destroyAllWindows()
2.2 对比度受限的自适应直方图均衡化
对比度受限的自适应直方图均衡化(CLAHE) 通过以下方式改进:
- 将图像划分为多个小区域(tiles)。
- 对每个区域分别进行直方图均衡化。
- 当某个灰度级的像素数量超过阈值(由
clipLimit
参数控制)时,超出部分会被均匀分配到其他灰度级。 - 裁剪后的直方图再进行均衡化,得到更自然的增强效果。
在区域边界使用双线性插值,避免块效应,确保处理后的图像平滑过渡。
clahe = cv2.createCLAHE(clipLimit=None, tileGridSize=None)
clipLimit
:对比度限制阈值(默认 40.0),值越大对比度增强越明显。tileGridSize
:分块的网格大小,默认(8, 8)。
import cv2 as cv
# 读图
img = cv.imread('../images/zhifang.png',cv.IMREAD_GRAYSCALE)
# 创建clahe
clahe = cv.createCLAHE(clipLimit=2.0,tileGridSize=(8,8))
# 使用clahe调用apply方法
dst = clahe.apply(img)cv.imshow('img',img)
cv.imshow('dst',dst)
cv.waitKey()
cv.destroyAllWindows()
三、模板匹配方法
模板匹配就是用模板图(通常是一个小图)在目标图像(通常是一个比模板图大的图片)中不断的滑动比较,通过某种比较方法来判断是否匹配成功,找到模板图所在的位置。
-
不会有边缘填充。
-
类似于卷积,滑动比较,挨个比较象素。
-
返回结果大小是:目标图大小-模板图大小+1。
模板匹配的核心思想是在输入图像上滑动模板,计算模板与当前窗口区域的相似度,找出最匹配的位置
- 基于平方差的方法(值越小表示匹配越好):
cv.TM_SQDIFF
:平方差匹配cv.TM_SQDIFF_NORMED
:归一化平方差匹配
- 基于相关性的方法(值越大表示匹配越好):
cv.TM_CCORR
:相关性匹配cv.TM_CCORR_NORMED
:归一化相关性匹配cv.TM_CCOEFF
:相关系数匹配cv.TM_CCOEFF_NORMED
:归一化相关系数匹配
返回值res
函数在完成图像模板匹配后返回一个结果矩阵,这个矩阵的大小与原图像不相同。矩阵的每个元素表示原图像中相应位置与模板图像匹配的相似度。
1. 平方差匹配
对于模板 T 和图像区域 I,平方差匹配的计算公式为:
SQD(x,y)=∑i,j[T(i,j)−I(x+i,y+j)]2\text{SQD}(x,y) = \sum_{i,j} [T(i,j) - I(x+i, y+j)]^2 SQD(x,y)=i,j∑[T(i,j)−I(x+i,y+j)]2
- ((x,y)) 是图像中当前区域的左上角坐标。
- (i,j) 是模板内的像素坐标。
特点:
- 值越小匹配越好:完全匹配时值为 0,差异越大值越大。
- 对光照敏感:若模板与目标区域光照条件不同,匹配效果较差。
- 计算简单:仅需像素值减法和平方运算。
cv2.TM_SQDIFF
result = cv.matchTemplate(img, template, cv.TM_SQDIFF)
2. 归一化平方差匹配
归一化平方差匹配通过计算模板与图像中每个可能位置的像素值平方差,并进行归一化处理,从而找出最匹配的区域。值越小表示匹配度越高,最小值 0 表示完全匹配。
cv2.TM_SQDIFF_NORMED
result = cv2.matchTemplate(image, template, cv2.TM_SQDIFF_NORMED)
3. 相关匹配
cv2.TM_CCORR
是 OpenCV 中用于模板匹配的一种方法,属于基于相关性的相似度计算。它通过计算模板与图像区域的像素值乘积和来评估匹配程度,值越大表示匹配越好。
对于模板 T 和图像区域 I,TM_CCORR
的计算公式为:CCORR(x,y)=∑i,j[T(i,j)⋅I(x+i,y+j)]
其中:
- ((x,y)) 是图像中当前区域的左上角坐标。
- (i,j) 是模板内的像素坐标。
特点
- 值越大匹配越好:当模板与图像区域完全相同时,结果达到最大值。
- 对光照敏感:若模板与目标区域光照条件不同,可能产生虚假匹配。
- 计算复杂度中等:涉及像素值乘法和累加运算。
cv2.TM_CCORR
result = cv.matchTemplate(img, template, cv.TM_CCORR)
4. 归一化相关匹配
值越大表示匹配度越高,最大值 1 表示完全匹配。
cv2.TM_CCORR_NORMED
result = cv2.matchTemplate(image, template, cv2.TM_CCORR_NORMED)
5. 相关系数匹配
cv2.TM_CCOEFF
是 OpenCV 中用于模板匹配的相关系数方法,通过计算模板与图像区域的协方差来评估相似度。与直接相关性匹配(TM_CCORR
)不同,它消除了亮度变化的影响,对光照差异有更好的鲁棒性
对于模板 T 和图像区域 I,TM_CCOEFF
的计算公式为:
CCOEFF(x,y)=∑i,j[T′(i,j)⋅I′(x+i,y+j)]\text{CCOEFF}(x,y) = \sum_{i,j} [T'(i,j) \cdot I'(x+i, y+j)] CCOEFF(x,y)=i,j∑[T′(i,j)⋅I′(x+i,y+j)]
其中:
- T′(i,j)=T(i,j)−Tˉ ,Tˉ 是模板的均值。
- Iˉ 是图像区域的均值。
特点
- 值越大匹配越好:当模板与图像区域完全相同时,结果达到最大值。
- 消除亮度影响:通过减去均值,对整体亮度变化具有鲁棒性。
- 仍受对比度影响:若模板与目标对比度差异较大,匹配效果可能下降。
cv2.TM_CCOEFF
result = cv.matchTemplate(img, template, cv.TM_CCOEFF)
6. 归一化相关系数匹配
它通过计算模板与图像中每个可能位置的像素值相关系数,并进行归一化处理,从而找出最匹配的区域。值越接近 1 表示匹配度越高,1 表示完全匹配,0 表示无相关性,-1 表示完全不匹配。
cv2.TM_CCOEFF_NORMED
result = cv2.matchTemplate(image, template, cv2.TM_CCOEFF_NORMED)
7 绘制轮廓
找的目标图像中匹配程度最高的点,我们可以设定一个匹配阈值来筛选出多个匹配程度高的区域。
- loc=np.where(array > 0.8) #loc包含array中所有大于0.8的元素索引的数组
np.where(condition) 是 NumPy 的一个函数,当条件为真时,返回满足条件的元素的索引。
- *zip(loc)
*loc
是解包操作,将loc
中的多个数组拆开,作为单独的参数传递给zip
。zip
将这些数组按元素一一配对,生成一个迭代器,每个元素是一个元组,表示一个坐标点。
import cv2 as cv
import numpy as np
# 读图
img = cv.imread('../images/game.png')
gray_img = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
temp = cv.imread('../images/temp.png')
gray_temp = cv.cvtColor(temp,cv.COLOR_BGR2GRAY)
h,w = gray_temp.shape# 模板匹配 -->匹配结果矩阵
res = cv.matchTemplate(gray_img,gray_temp,cv.TM_CCOEFF_NORMED)# 设置阈值
thresh = 0.8
# 获取匹配上的结果的索引
loc = np.where(res>=thresh)# 解包,拿到成对的x y坐标
for i in zip(*loc):x,y = i[1],i[0]res1 = cv.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)cv.imshow('匹配结果', img)
cv.waitKey(0)
cv.destroyAllWindows()
四、霍夫变换
基本思想就是将图像空间中的点映射到参数空间中,通过在参数空间中寻找累计最大值实现对特定形状的检测。
对于一条直线(不垂直于x轴的直线),都可以用y=kx+by=k x+by=kx+b来表示,此时,x和y是横纵坐标,k和b是一个固定参数。当我们换一种方式来看待这个式子,我们就可以得到:
b=−kx+yb=-kx+y b=−kx+y
此时,以k和b 为横纵坐标,x和y为固定参数,也就是说k和b成了自变量和因变量,变换如下图所示:
从上图可以看出,在直角坐标系下的一个直线,在变换后的空间中仅仅表示为一点,对于变换后的空间,我们称其为霍夫空间,也就是参数空间。也就是说,直角坐标系下的一条直线对应了霍夫空间中的一个点。类似的,霍夫空间中的一条直线也对应了直角坐标系中的一个点,如下图所示:
那么对于一个二值化后的图形来说,其中的每一个目标像素点(这里假设目标像素点为白色像素点)都对应了霍夫空间的一条直线,当霍夫空间中有两条直线相交时,就代表了直角坐标系中某两个点所构成的直线。而当霍夫空间中有很多条线相交于一点时,说明直角坐标系中有很多点能构成一条直线,也就意味着这些点共线,因此我们就可以通过检测霍夫空间中有最多直线相交的交点来找到直角坐标系中的直线。
然而对于x=1这种直线(垂直于x轴)来说,y已经不存在了,斜率无穷大,无法映射到霍夫空间中去,那么就没办法使用上面的方法进行检测了,为了解决这个问题,我们就将直角坐标系转化为极坐标系,然后通过极坐标系与霍夫空间进行相互转化。
在极坐标系下是一样的,极坐标中的点对于霍夫空间中的线,霍夫空间中的点对应极坐标中的直线。并且此时的霍夫空间不再是以k为横轴、b为纵轴,而是以为θ横轴、ρ(上图中的r)为纵轴。上面的公式中,x、y是直线上某点的横纵坐标(直角坐标系下的横纵坐标),和是极坐标下的坐标,因此我们只要知道某点的x和y的坐标,就可以得到一个关于θ-ρ的表达式,如下图所示:
根据上图,霍夫空间在极坐标系下,一点可以产生一条三角函数曲线,而多条这样的曲线可能会相交于同一点。因此,我们可以通过设定一个阈值,来检测霍夫空间中的三角函数曲线相交的次数。如果一个交点的三角函数曲线相交次数超过阈值,那么这个交点所代表的直线就可能是我们寻找的目标直线。
1 标准霍夫变换(Standard Hough Transform, SHT)
- 用途:检测直线。
- OpenCV 函数:
cv.HoughLines()
- 原理:对每个边缘点,计算其可能属于的所有直线参数 ((ρ, θ)),并在霍夫空间中累加投票。
lines = cv.HoughLines(image, rho, theta, threshold[, srn[, stn[, min_theta[, max_theta]]]])
image
:输入图像,必须是单通道二值图像(通常是 Canny 边缘检测的结果)。rho
:(ρ) 的分辨率(像素),通常设为 1。theta
:(θ) 的分辨率(弧度),通常设为np.pi/180
(即 1 度)。threshold
:投票阈值,只有投票数超过该值的点才被认为是直线。值越大,检测到的直线越少、越可靠。srn
和stn
:多尺度霍夫变换的参数,默认 0 表示使用标准霍夫变换。min_theta
和max_theta
:(θ) 的检测范围(弧度),默认 0 到 π。
import cv2 as cv
import numpy as np
# 读图
img = cv.imread('../images/huofu.png')
# 灰度化
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
# 二值化
_,binary = cv.threshold(gray,127,255,cv.THRESH_BINARY)
# 边缘检测
edges = cv.Canny(binary,50,150)
# 霍夫变换-->[r theta]
dst = cv.HoughLines(edges,0.8,np.pi/180,100)
print(dst)
for line in dst :r,thrta = line[0]sin_theta = np.sin(thrta)cos_theta = np.cos(thrta)x1,x2 = 0,img.shape[1]y1 = int((r-x1*cos_theta)/sin_theta)y2 = int((r-x2*cos_theta)/sin_theta)cv.line(img,(x1,y1),(x2,y2),(0,0,255),2)cv.imshow('img',img)
cv.waitKey(0)
cv.destroyAllWindows()
2. 概率霍夫变换(Probabilistic Hough Transform, PHough)
- 用途:检测直线。
- OpenCV 函数:
cv.HoughLinesP()
- 随机选择边缘点并计算其可能的直线参数。
- 对参数空间进行投票并积累。
- 对投票数超过阈值的参数,跟踪其对应的边缘点以确定线段端点。
lines = cv.HoughLinesP(image, rho, theta, threshold,**
minLineLength=0, maxLineGap=0)**
image
:输入图像,必须是单通道二值图像(通常是 Canny 边缘检测的结果)。rho
:(ρ) 的分辨率(像素),通常设为 1。theta
:(θ) 的分辨率(弧度),通常设为np.pi/180
(即 1 度)。threshold
:投票阈值,只有投票数超过该值的直线才会被保留。minLineLength
:线段的最小长度。小于此长度的线段会被过滤。maxLineGap
:同一线段中允许的最大断裂距离。若两点间距离小于此值,会被视为同一条线段。
import cv2 as cv
import numpy as np
# 读图
img = cv.imread('../images/huofu.png')
# 灰度化
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
# 二值化
_,binary = cv.threshold(gray,127,255,cv.THRESH_BINARY)
# 边缘检测
edges = cv.Canny(binary,50,150)
# 霍夫变换-->[r theta]
lines = cv.HoughLinesP(edges,0.8,np.pi/180,90,minLineLength=50, maxLineGap=100)
for line in lines:x1,y1,x2,y2 = line[0]cv.line(img,(x1,y1),(x2,y2),(0,255,0),2)cv.imshow('img',img)
cv.waitKey(0)
cv.destroyAllWindows()
3. 霍夫圆变换(Hough Circle Transform)
- 用途:检测圆形。
- OpenCV 函数:
cv.HoughCircles()
- 原理:在 ((a, b, r)) 参数空间中进行投票,但计算复杂度较高,通常采用梯度法优化。
圆在图像中可由三个参数定义:圆心坐标 ((a, b)) 和半径 r,方程为:
((x - a)^2 + (y - b)^2 = r^2)
因此,霍夫圆变换需要在三维参数空间 ((a, b, r)) 中进行投票。
cv.HoughCircles()
使用梯度法(Hough Gradient Method),通过以下步骤优化:
- 边缘和梯度计算:使用 Canny 边缘检测和 Sobel 算子计算梯度方向。
- 圆心定位:根据边缘点的梯度方向,推测圆心位置并投票。
- 半径确定:对投票数超过阈值的圆心,统计其可能的半径。
circles = cv.HoughCircles(image, method, dp, minDist,param1=None, param2=None, minRadius=None, maxRadius=None)
image
:输入图像,单通道灰度图。method
:检测方法,目前仅支持cv.HOUGH_GRADIENT
。dp
累加器图像的分辨率与原图之比。dp=1
时,累加器与原图大小相同。dp=2
时,累加器为原图的一半,计算速度更快。
minDist
:检测到的圆的圆心之间的最小距离。若过小,可能导致多个同心圆被检测;若过大,可能漏检真实圆。param1
:Canny 边缘检测的高阈值,低阈值自动设为高阈值的一半。param2
:累加器阈值,值越小越容易检测到圆,但可能增加误检。minRadius
和maxRadius
:圆半径的最小和最大值(像素)。若maxRadius=0
,则使用图像最大尺寸作为半径上限。
import cv2 as cv
import numpy as np
# 读图
img = cv.imread('../images/huofu.png')
# 灰度化
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
# 二值化
_,binary = cv.threshold(gray,127,255,cv.THRESH_BINARY)
# 边缘检测
edges = cv.Canny(binary,50,150)
# 霍夫圆变换
circles = cv.HoughCircles(edges,cv.HOUGH_GRADIENT,1,10,param2=20)
# print(circles) --> [x,y,r]for circle in circles:x,y,r = np.int32(circle[0])cv.circle(img,(x,y),r,(0,255,0),2)cv.imshow('img',img)
cv.waitKey(0)
cv.destroyAllWindows()
五、图像亮度变换
- 亮度:图像的整体明亮程度,对应像素值的平均大小。调整亮度即对所有像素值进行加减操作。
- 对比度:图像中亮部和暗部的差异程度。高对比度图像中,亮部更亮,暗部更暗;低对比度图像则显得模糊。
线性变换(对比度与亮度调整)
dst(x,y) = α · src(x,y) + β对原始图像 src
的每个像素 (x,y)
进行线性变换,得到输出图像 dst
:
src(x,y)
:原始图像在位置(x,y)
的像素值dst(x,y)
:变换后图像在位置(x,y)
的像素值α
(增益):控制对比度(α > 1
增强对比度,0 < α < 1
降低对比度)β
(偏置):控制亮度(β > 0
变亮,β < 0
变暗)
cv2.addWeighted(src1, alpha, src2, beta, gamma)
-
src1
:第一张输入图像,它将被赋予权重alpha
。 -
alpha
:第一个输入图像的权重。 -
src2
:第二张输入图像,它将被赋予权重beta
。 -
beta
:第二个输入图像的权重。 -
gamma
:一个标量,将被添加到权重求和的结果上,可用于调整总体亮度。计算公式为: dst = src1 * alpha + src2 * beta + gamma
import cv2 as cv
# 读图
img = cv.imread('../images/2.jpg')
img = cv.resize(img,(480,480))
# 亮度调整
dst = cv.addWeighted(img,1,img,0,100)
dst1 = cv.addWeighted(img,0.5,img,0.5,-100)
cv.imshow('img',img)
cv.imshow('dst',dst)
cv.imshow('dst1',dst1)
cv.waitKey(0)
cv.destroyAllWindows()
直接像素值更改
直接遍历图像的每个像素,对其值进行加减操作以调整亮度。对于多通道图像(如 RGB),需对每个通道分别处理。
实现步骤
- 将图像转换为浮点数类型(避免溢出)
- 对每个像素值进行加减操作
- 将结果限制在
[0, 255]
范围内 - 转换回整数类型
import cv2 as cv
import numpy as np
# 读图
img = cv.imread('../images/2.jpg')
# 创建窗口 显示滑条
window_name = 'slide'
cv.namedWindow(window_name)img1 = cv.imread('../images/2.jpg')def change(p):x = p/256*511-255dst = np.uint8(np.clip(img1 + x,0,255))cv.imshow('dst',dst)# print(x)
# 创建滑条
initial_value =100
change(initial_value)
cv.createTrackbar('add_p',window_name,initial_value,255,change)cv.waitKey(0)
cv.destroyAllWindows()
六、形态学变换
形态学变换是基于形状的图像处理技术,主要用于二值图像和灰度图像。
核(Structuring Element)
核是一个由 0 和 1 组成的小矩阵,用于在形态学操作中与图像进行卷积。核的中心称为锚点(anchor point),它决定了操作的参考位置。核的大小和形状会影响形态学操作的效果,常见的形状有矩形、圆形和十字形。
腐蚀(Erosion)
腐蚀是一种将图像中的亮区域 “收缩” 的操作。具体来说,对于二值图像中的每个像素,如果核覆盖的区域内所有像素值都为 1,则该像素保持为 1,否则变为 0。腐蚀操作可以用来:
cv2.erode(src, kernel[, dst[, anchor[, iterations[, borderType[,
borderValue]]]]])
src
:输入图像kernel
:结构元素(核)iterations
:腐蚀次数,默认 1
用途:
- 消除小的噪点
- 分离相互连接的物体
- 缩小物体的尺寸
import cv2 as cv
import numpy as np
# 读图
car = cv.imread('../images/car.png',cv.IMREAD_GRAYSCALE)
# 定义核
kernel = np.ones((5,5),np.uint8)
# 腐蚀
erosion = cv.erode(car,kernel,iterations=1)
# 膨胀
dilation = cv.dilate(car,kernel,iterations=1)cv.imshow('car',car)
cv.imshow('erosion',erosion)
cv.imshow('dilation',dilation)
cv.waitKey(0)
cv.destroyAllWindows()
膨胀(Dilation)
膨胀是一种将图像中的亮区域 “扩张” 的操作。对于二值图像中的每个像素,如果核覆盖的区域内至少有一个像素值为 1,则该像素变为 1,否则保持为 0。膨胀操作可以用来:
cv2.dilate(src, kernel[, dst[, anchor[, iterations[, borderType[,borderValue]]]]])
用途:
- 填充物体内部的小孔
- 连接相邻的物体
- 增大物体的尺寸
开运算(Opening)
核心思想:
先腐蚀后膨胀,用于消除小物体、分离物体和平滑边界。
效果:
- 消除小的亮区域(如噪点)
- 分离相互靠近的物体
- 对大物体的形状影响较小
opening = cv2.morphologyEx(image, cv2.MORPH_OPEN, kernel)
闭运算(Closing)
先膨胀后腐蚀,用于填充小孔、连接相邻物体,同时平滑物体边界。
- 效果:
- 填充物体内部的小孔(黑色区域)
- 连接相邻的物体
- 消除物体边界的小凹陷
closing = cv2.morphologyEx(image, cv2.MORPH_CLOSE, kernel)
礼帽运算(Top Hat)
原图减去开运算结果,用于提取图像中的小凸起或噪声。
- 公式:
礼帽 = 原图 - 开运算(原图)
- 效果:
- 提取比结构元素小的亮区域
- 常用于背景校正或噪声过滤
tophat = cv2.morphologyEx(image, cv2.MORPH_TOPHAT, kernel)
黑帽运算(Black Hat)
闭运算结果减去原图,用于提取图像中的小孔或暗区域。
- 公式:
黑帽 = 闭运算(原图) - 原图
- 效果:
- 提取比结构元素小的暗区域
- 常用于增强暗细节或分离与背景相连的物体
blackhat = cv2.morphologyEx(image, cv2.MORPH_BLACKHAT, kernel)
形态学梯度(Morphological Gradient)
膨胀结果减去腐蚀结果,用于突出物体的边界。
-
公式:
梯度 = 膨胀(图像) - 腐蚀(图像)
-
效果:
- 提取物体的边缘(轮廓)
- 结果中,物体的边界为白色,内部和背景为黑色
gradient = cv2.morphologyEx(image, cv2.MORPH_GRADIENT, kernel)