一、图像轮廓查找
图像轮廓查找是指在二值图像中识别并连接具有相同颜色或强度的连续点,形成物体边界曲线的过程。这些轮廓代表了图像中物体的边界信息,是计算机视觉中形状分析和目标识别的基础。图像轮廓特征查找其实就是他的外接轮廓。
应用场景:
-
图像分割
-
形状分析
-
物体检测与识别
实现步骤:
-
图像预处理
-
读取图片 cv.imread()
-
转换为灰度图像 cv.cvtColor()
-
应用二值化处理(阈值分割)_,binary=cv.threshold()
-
-
轮廓查找
-
使用
cv.findContours()
函数查找轮廓
-
-
轮廓绘制
-
有了轮廓点就可以找到最上、最下、最左、最右的四个坐标,X_{min}、X_{max}、Y_{min}、Y_{max}。就可以绘制出矩形。
-
使用
cv.drawContours()
在图像上绘制轮廓
-
1、外接矩形
外接矩形是完全包含目标轮廓的最小矩形,其边与图像坐标轴平行(水平/垂直方向)
-
矩形边平行于图像坐标轴
-
不一定是面积最小的矩形
-
计算速度快,适用于快速定位
示例:
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) #查找轮廓 conts,th =cv.findContours(binary,cv.RETR_EXTERNAL,cv.CHAIN_APPROX_NONE) #获取外接矩形点 for cont in conts: x,y,w,h=cv.boundingRect(cont) cv.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2,cv.LINE_AA) cv.imshow('img',img) cv.waitKey(0) cv.destroyAllWindows()
2、最小外接矩形
定义: 最小外接矩形是完全包含目标轮廓且面积最小的矩形,可以任意旋转角度。
特点:
-
矩形可以旋转任意角度
-
面积最小,紧密包裹目标
-
包含旋转角度信息
-
计算比普通外接矩形复杂
最小外接矩形就是上图所示的蓝色矩形,寻找最小外接矩形使用的算法叫做旋转卡壳法。下面简单说明一下旋转卡壳法的思路:
在上一章节中,我们了解到了凸包的概念,凸包就是一个点集的凸多边形,它是这个点集所有点的凸壳,点集中所有的点都处于凸包里,构成凸包的点我们叫做凸包点。而旋转卡壳法就是基于凸包点进行的,
旋转卡壳法有一个很重要的前提条件:对于多边形P的一个外接矩形存在一条边与原多边形的边共线。
假设某凸包图如下所示:
根据前提条件,上面的凸多边形的最小外接矩形与凸多边形的某条边是共线的。因此我们只需要以其中的一条边为起始边,然后按照逆时针方向计算每个凸包点与起始边的距离,并将距离最大的点记录下来。
如上图所示,我们首先以a、b两点为起始边,并计算出e点离起始边最远,那么e到起始边的距离就是一个矩形的高度,因此我们只需要再找出矩形的宽度即可。对于矩形的最右边,以向量\overline{{a b}}为基准,然后分别计算凸包点在向量\overline{{a b}}上的投影的长度,投影最长的凸包点所在的垂直于起始边的直线就是矩形最右边所在的直线。
如上图所示,d点就是在向量\overline{{a b}}上投影最长的凸包点,那么通过d点垂直于直线ab的直线就是矩形的右边界所在的直线。矩形的左边界的也是这么计算的,不同的是使用的向量不是\overline{{a b}}而是\overline{{b a}}。
如上图所示,h点垂直于ab的直线就是以ab为起始边所计算出来的矩形所在的左边界所在的直线。其中矩形的高就是e点到直线ab的距离,矩形的宽是h点在向量上\overline{{b a}}的投影加上d点在向量\overline{{a b}}上的投影减去ab的长度,即:
$$
w i d t h=|\overline{{{b h}}}|\times\cos a+|\overline{{{a d}}}|\times\cos\theta-|\overline{{{a b}}}|
$$
于是我们就有了以ab为起始边所构成的外接矩形的宽和高,这样就可以得到该矩形的面积。然后再以bc为起始边,并计算其外接矩形的面积。也就是说凸多边形有几个边,就要构建几次外接矩形,然后找到其中面积最小的矩形作为该凸多边形的最小外接矩形。
在OpenCV中,可以直接使用cv2.minAreaRect()来获取最小外接矩形,该函数只需要输入一个参数,就是凸包点的坐标,然后会返回最小外接矩形的中心点坐标、宽高以及旋转角度。通过返回的内容信息,即可绘制凸多边形的的最小外接矩形。
需要使用到的API说明:
-
contours, hierarchy = cv2.findContours(image_np_thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours为二值图像上查找所有的外部轮廓
-
rect = cv2.minAreaRect(cnt)
传入的cnt参数为contours中的轮廓,可以遍历contours中的所有轮廓,然后计算出每个轮廓的小面积外接矩形
-
rect 是计算轮廓最小面积外接矩形:rect 结构通常包含中心点坐标
(x, y)
、宽度width
、高度height
和旋转角度angle
cv2.boxPoints(rect).astype(int)
cv2.boxPoints(rect)返回 是一个形状为 4行2列的数组,每一行代表一个点的坐标(x, y),顺序按照逆时针或顺时针方向排列
将最小外接矩形转换为边界框的四个角点,并转换为整数坐标
cv2.drawContours(image, contours, contourIdx, color, thickness)
-
image:原图像,一般为 numpy 数组,通常为灰度或彩色图像。
-
contours:一个包含多个轮廓的列表,可以用上一个api得到的 [box]
-
contourIdx:要绘制的轮廓索引。如果设置为
-1
,则绘制所有轮廓。 -
color:轮廓的颜色,可以是 BGR 颜色格式的三元组,例如
(0, 0, 255)
表示红色。 -
thickness:轮廓线的粗细,如果是正数,则绘制实线;如果是 0,则绘制轮廓点;如果是负数,则填充轮廓内部区域。
示例:
import cv2 as cv import numpy as np #读取图片 num = cv.imread("./images/num.png") #转灰度 gray = cv.cvtColor(num,cv.COLOR_BGR2GRAY) #二值化处理 _,binary = cv.threshold(gray,200,255,cv.THRESH_BINARY_INV) #查找轮廓 最外层+最有用 conts,th = cv.findContours(binary,cv.RETR_EXTERNAL,cv.CHAIN_APPROX_SIMPLE) #绘制轮廓 cv.drawContours(num,conts,-1,(255,0,0),2) #图像轮廓查找:最小外接矩形 中心点(x,y) 宽高w,h 旋转角度angle result = cv.minAreaRect(conts[0]) #循环遍历每一条轮廓 for cont in conts: #获取最小外接矩形 result = cv.minAreaRect(cont) #处理坐标点 points = cv.boxPoints(result).astype(np.int32) # print(points) cv.drawContours(num,[points],-1,(0,255,0),2) #外接矩形 x,y,w,h = cv.boundingRect(cont) cv.rectangle(num,(x,y),(x+w,y+h),(0,0,255),cv.LINE_AA) #查找最小外接圆 (x,y),r= cv.minEnclosingCircle(cont) cv.imshow("num",num) cv.waitKey(0) cv.destroyAllWindows()
3、最小外接圆
定义: 最小外接圆形是完全包含目标轮廓且面积最小的圆形。
特点:
-
圆形完全包裹目标轮廓
-
面积最小的圆形
-
包含圆心和半径信息
-
计算复杂度较高
寻找最小外接圆使用的算法是Welzl算法。Welzl算法基于一个定理:希尔伯特圆定理表明,对于平面上的任意三个不在同一直线上的点,存在一个唯一的圆同时通过这三个点,且该圆是最小面积的圆(即包含这三个点的圆中半径最小的圆,也称为最小覆盖圆)。
进一步推广到任意 n 个不在同一圆上的点,总存在一个唯一的最小覆盖圆包含这 n 个点。
若已经存在平面上互不共线(或共圆)的 n 个点,并确定了它们的最小覆盖圆,那么添加第 n+1 个点,并且要求这个点不在原来的最小覆盖圆内(即在圆外),为了使新的包含 n+1 个点的最小覆盖圆的半径增大,新加入的点必须位于由原 n 个点确定的最小覆盖圆的边界上(即圆周上)。这是因为,如果新点在原最小覆盖圆的内部,显然不会影响最小覆盖圆;如果新点在原最小覆盖圆之外但不在圆周上,那么通过新点和至少两个原有圆上的点可以构造出一个更大的圆,这个圆必然比原最小覆盖圆更大,因此不是包含所有 n+1 个点的最小覆盖圆。所以,按照这一逻辑,当第 n+1 个点在原 n 个点的最小覆盖圆外时,确实这个点会位于包含所有 n+1 个点的新最小覆盖圆的圆周上。
有了这个定理,就可以先取3个点建立一个圆(不共线的三个点即可确定一个圆,如果共线就取距离最远的两个点作为直径建立圆),然后遍历剩下的所有点,对于遍历到的点P来说:
如果该点在圆内,那么最小覆盖圆不变。
如果该点在圆外,根据上述定理,该点一定在想要求得的最小覆盖圆的圆周上,又因为三个点才能确定一个圆,所以需要枚举P点之前的点来找其余的两个点。当找到与P点组成的圆能够将所有点都包含在圆内或圆上,该圆就是这些点的最小外接圆。
在OpenCV中,可以直接使用cv2.minEnclosingCircle()来获取最小外接圆,该函数只需要输入一个参数,就是要绘制最小外接圆的点集的坐标,然后会返回最小外接圆的圆心坐标与半径。通过该函数返回的内容信息即可绘制某点集的最小外接圆。如下图所示:
需要使用的API说明
cv2.minEnclosingCircle(points) -> (center, radius)
参数说明:
-
points
:输入参数图片轮廓数据
返回值:
-
center
:一个包含圆心坐标的二元组(x, 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
,则会填充整个圆。
示例:
import cv2 as cv import numpy as np #读取图片 num = cv.imread("./images/num.png") #灰度化 gray = cv.cvtColor(num,cv.COLOR_BGR2GRAY) #二值化处理 _,binary = cv.threshold(gray,200,255,cv.THRESH_BINARY_INV) #查找轮廓 最外层+最有用 conts,th =cv.findContours(binary,cv.RETR_EXTERNAL,cv.CHAIN_APPROX_NONE) #循环遍历轮廓 for cont in conts: #查找最小外接圆 (x,y),r = cv.minEnclosingCircle(cont) x,y,r = np.int32(x),np.int32(y),np.int32(r) cv.circle(num,(x,y),r,(255,0,0),2) #显示效果 cv.imshow("num",num) cv.waitKey(0) cv.destroyAllWindows()
二、直方图
1、直方图定义
直方图是图像处理中用于量化图像像素强度分布的统计工具。它通过计算每个强度值(0-255)在图像中出现的频率,直观展示图像的亮度分布特征。
直方图是对数据进行统计的一种方法,并且将统计值组织到一系列实现定义好的 bin 当中。其中, bin 为直方图中经常用到的一个概念,可以译为 “直条” 或 “组距”,其数值是从数据中计算出的特征统计量,这些数据可以是诸如梯度、方向、色彩或任何其他特征。
-
直方图:反映图像像素分布的统计图,横坐标就是图像像素的取值,纵坐标是该像素的个数。也就是对一张图像中不同像素值的像素个数的统计。
-
增加对比度:黑的更黑,白的更白
直方图特性:
-
灰度直方图:显示各灰度级的像素数量
-
颜色直方图:分别显示RGB通道的强度分布
-
关键信息:
-
图像整体亮度(左偏暗/右偏亮)
-
对比度范围(峰宽表示对比度)
-
峰值位置(主要灰度区域)
-
2、绘制直方图
-
读取图像并转换为灰度
-
计算直方图
-
绘制直方图
就是以像素值为横坐标,像素值的个数为纵坐标绘制一个统计图。
hist=cv2.calcHist(images, channels, mask, histSize, ranges)
-
images
:输入图像列表,可以是一幅或多幅图像(通常是灰度图像或者彩色图像的各个通道)。 -
channels
:一个包含整数的列表,指示在每个图像上计算直方图的通道编号。如果输入图像是灰度图,它的值就是 [0];如果是彩色图像的话,传入的参数可以是 [0],[1],[2] 它们分别对应着通道 B,G,R。 -
mask
(可选):一个与输入图像尺寸相同的二值掩模图像,其中非零元素标记了参与直方图计算的区域,None为全部计算。 -
histSize
:一个整数列表,也就是直方图的区间个数(BIN 的数目)。用中括号括起来,例如:[256]。 -
ranges
:每维数据的取值范围,它是一个二维列表,每一维对应一个通道的最小值和最大值,例如对灰度图像可能是[0, 256]
。
返回值hist 是一个长度为255的数组,数组中的每个值表示图像中对应灰度等级的像素计数
minVal, maxVal, minLoc, maxLoc = cv2.minMaxLoc(hist)
获取直方图的最小值、最大值及其对应最小值的位置索引、最大值的位置索引
cv2.line(img, pt1, pt2, color, thickness)
-
img:原始图像,即要在上面画线的numpy数组(一般为uint8类型)。
-
pt1 和 pt2:分别为线段的起点和终点坐标,它们都是元组类型,例如
(x1, y1)
和(x2, y2)
分别代表线段两端的横纵坐标。 -
color:线段的颜色,通常是一个包含三个元素的元组
(B, G, R)
表示BGR色彩空间的像素值,也可以是灰度图像的一个整数值。 -
thickness:线段的宽度,默认值是1,如果设置为负数,则线宽会被填充。
示例:
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],[1],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_hist = np.int32(256) #循环拿像素的个数 for i in range(256): #[num] l=int(hist[i].item()*h_hist/maxval) point1=(i,256-l) point2=(i,256) cv.line(black,point1,point2,(0,255,0),1) cv.imshow("black",black) cv.waitKey(0) cv.destroyAllWindows()
3、直方图均衡化
一副效果好的图像通常在直方图上的分布比较均匀,直方图均衡化就是用来改善图像的全局亮度和对比度。
如果一幅图像整体很亮,那所有的像素值的取值个数应该都会很高,所以应该把它的直方图做一个横向拉伸,就可以扩大图像像素值的分布范围,提高图像的对比度
通俗的讲,就是遍历图像的像素统计出灰度值的个数、比例与累计比例,并重新映射到0-255范围(也可以是其他范围)内,其实从观感上就可以发现,下面两幅图中前面那幅图对比度不高,偏灰白。
-
直方图均衡化作用:
-
增强对比度
-
提高图像质量
-
可以看到均衡化后图片的亮度和对比度效果明显好于原图。
3.1自适应直方图均衡化
原理:对整个图像应用均衡化变换函数
自适应直方图均衡化(Adaptive Histogram Equalization, AHE),通过调整图像像素值的分布,使得图像的对比度和亮度得到改善。
具体过程如下所示:
假设有一个3*3的图像,其灰度图的像素值如上图所示,现在我们要对其进行直方图均衡化,首先就是统计其每个像素值的个数、比例以及其累计比例。如下图所示。
接下来我们就要进行计算,就是将要缩放的范围(通常是缩放到0-255,所以就是255-0)乘以累计比例,得到新的像素值,并将新的像素值放到对应的位置上,比如像素值为50的像素点,将其累计比例乘以255,也就是0.33乘以255得到84.15,取整后得到84,并将84放在原图像中像素值为50的地方,像素值为100、210、255的计算过程类似,最终会得到如下图所示的结果,这样就完成了最基本的直方图均衡化的过程。
dst = cv.equalizeHist(imgGray)
imgGray为需要直方图均衡化的灰度图,返回值为处理后的图像
该方法适用于图像的灰度分布不均匀,且灰度分布集中在更窄的范围,图像的细节不够清晰且对比度较低的情况,然而,传统的直方图均衡化方法会引入噪声,并导致图像中出现过度增强的区域。这是因为直方图均衡化方法没有考虑到图像的局部特征和全局对比度的差异。
示例:
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(0) cv.destroyAllWindows()
3.2对比度受限的自适应直方图均衡化
很明显,因为全局调整亮度和对比度的原因,脸部太亮,大部分细节都丢失了。自适应均衡化就是用来解决这一问题的:它在每一个小区域内(默认8×8)进行直方图均衡化。当然,如果有噪点的话,噪点会被放大,需要对小区域内的对比度进行了限制,所以这个算法全称叫:对比度受限的自适应直方图均衡化(Contrast Limited Adaptive Histogram Equalization, CLAHE)。
其主要步骤为:
-
图像分块(Tiling):
-
图像首先被划分为多个不重叠的小块(tiles)。这样做的目的是因为在全局直方图均衡化中,单一的直方图无法反映图像各个局部区域的差异性。通过局部处理,AHE能够更好地适应图像内部的不同光照和对比度特性。(tiles 的 大小默认是 8x8)
-
-
计算子区域直方图:
-
对于每个小块,独立计算其内部像素的灰度直方图。直方图反映了该区域内像素值的分布情况。
-
-
子区域直方图均衡化:
-
对每个小块的直方图执行直方图均衡化操作。这涉及重新分配像素值,以便在整个小块内更均匀地分布。均衡化过程会增加低频像素的数量,减少高频像素的数量,从而提高整个小块的对比度。
-
-
对比度限制(Contrast Limiting):
-
如果有噪声的话,噪声会被放大。为了防止过大的对比度增强导致噪声放大,出现了限制对比度自适应直方图均衡化(CLAHE)。CLAHE会在直方图均衡化过程中引入一个对比度限制参数。当某一小块的直方图在均衡化后出现极端值时,会对直方图进行平滑处理(使用线性或非线性的钳制函数),确保对比度增强在一个合理的范围内。
-
-
重采样和邻域像素融合:
-
由于小块之间是不重叠的,直接拼接经过均衡化处理的小块会产生明显的边界效应。因此,在CLAHE中通常采用重采样技术来消除这种效应,比如通过双线性插值将相邻小块的均衡化结果进行平滑过渡,使最终图像看起来更为自然和平滑。
-
-
合成输出图像:
-
将所有小块均衡化后的结果整合在一起,得到最终的自适应直方图均衡化后的图像。
-
clahe = cv2.createCLAHE(clipLimit=None, tileGridSize=None)
-
clipLimit(可选):对比度限制参数,用于控制直方图均衡化过程中对比度增强的程度。如果设置一个大于1的值(如2.0或4.0),CLAHE会限制对比度增强的最大程度,避免过度放大噪声。如果不设置,OpenCV会使用一个默认值。
-
tileGridSize(可选):图像分块的大小,通常是一个包含两个整数的元组,如
(8, 8)
,表示将图像划分成8x8的小块进行独立的直方图均衡化处理。分块大小的选择会影响到CLAHE的效果以及处理速度。
创建CLAHE对象后,可以使用 .apply()
方法对图像进行CLAHE处理:
img=clahe.apply(image)
-
image:要均衡化的图像。
-
img均衡后的图像
示例:
import cv2 as cv img = cv.imread("./images/zhifang.png",cv.IMREAD_GRAYSCALE) #创建clahe对象 chahe = cv.createCLAHE(clipLimit=10.0,tileGridSize=(8,8)) dst = chahe.apply(img) cv.imshow("img",img) cv.imshow("dst",dst) cv.waitKey(0) cv.destroyAllWindows()
三、模板匹配
1、模板匹配
模板匹配就是用模板图(通常是一个小图)在目标图像(通常是一个比模板图大的图片)中不断的滑动比较,通过某种比较方法来判断是否匹配成功,找到模板图所在的位置。
-
不会有边缘填充。
-
类似于卷积,滑动比较,挨个比较象素。
-
返回结果大小是:目标图大小-模板图大小+1。
2、匹配方法
res=cv2.matchTemplate(image, templ, method)**
-
image:原图像,这是一个灰度图像或彩色图像(在这种情况下,匹配将在每个通道上独立进行)。
-
templ:模板图像,也是灰度图像或与原图像相同通道数的彩色图像。
-
method:匹配方法,可以是以下之一:
-
cv2.TM_CCOEFF
-
cv2.TM_CCOEFF_NORMED
-
cv2.TM_CCORR
-
cv2.TM_CCORR_NORMED
-
cv2.TM_SQDIFF
-
cv2.TM_SQDIFF_NORMED
-
这些方法决定了如何度量模板图像与原图像子窗口之间的相似度。
-
-
返回值res
函数在完成图像模板匹配后返回一个结果矩阵,这个矩阵的大小与原图像不相同。矩阵的每个元素表示原图像中相应位置与模板图像匹配的相似度。
匹配方法不同,返回矩阵的值的含义也会有所区别。以下是几种常用的匹配方法及其返回值含义:
-
cv2.TM_SQDIFF
或cv2.TM_SQDIFF_NORMED
:返回值越接近0,表示匹配程度越好。最小值对应的最佳匹配位置。
-
cv2.TM_CCORR
或cv2.TM_CCORR_NORMED
:返回值越大,表示匹配程度越好。最大值对应的最佳匹配位置。
-
cv2.TM_CCOEFF
或cv2.TM_CCOEFF_NORMED
:返回值越大,表示匹配程度越好。最大值对应的最佳匹配位置。
-
2.1平方差匹配
原理:计算模板与图像局部区域的平方差之和
特点:
-
完全匹配时结果为0
-
不匹配时结果值较大
-
对亮度变化敏感
cv2.TM_SQDIFF
以模板图与目标图所对应的像素值使用平方差公式来计算,其结果越小,代表匹配程度越高,计算过程举例如下。
注意:模板匹配过程皆不需要边缘填充,直接从目标图像的左上角开始计算。
2.2归一化平方差匹配
原理:对平方差结果进行归一化处理
特点:
-
结果范围[0,1]
-
对光照变化具有鲁棒性
-
完全匹配时为0
cv2.TM_SQDIFF_NORMED
与平方差匹配类似,只不过需要将值统一到0到1,计算结果越小,代表匹配程度越高,计算过程举例如下。
2.3相关匹配
原理:计算模板与图像局部区域的互相关
特点:
-
匹配程度高时结果值大
-
对亮度变化非常敏感
-
计算速度快
cv2.TM_CCORR
使用对应像素的乘积进行匹配,乘积的结果越大其匹配程度越高,计算过程举例如下。
2.4归一化相关匹配
原理:对互相关结果进行归一化
特点:
-
结果范围[0,1]
-
对光照变化有一定鲁棒性
-
完全匹配时为1
cv2.TM_CCORR_NORMED
与相关匹配类似,只不过是将其值统一到0到1之间,值越大,代表匹配程度越高,计算过程举例如下。
2.5相关系数匹配
原理:计算模板与图像局部区域的相关系数
特点:
-
消除亮度变化影响
-
结果范围[-1,1]
-
计算量较大
cv2.TM_CCOEFF
需要先计算模板与目标图像的均值,然后通过每个像素与均值之间的差的乘积再求和来表示其匹配程度,1表示完美的匹配,-1表示最差的匹配,计算过程举例如下。
2.6归一化相关系数匹配
原理:对相关系数结果进行归一化
特点:
-
结果范围[-1,1]
-
对光照和对比度变化高度鲁棒
-
最常用的匹配方法
-
完全匹配时为1
cv2.TM_CCOEFF_NORMED
也是将相关系数匹配的结果统一到0到1之间,值越接近1代表匹配程度越高,计算过程举例如下。
3、绘制轮廓
找的目标图像中匹配程度最高的点,我们可以设定一个匹配阈值来筛选出多个匹配程度高的区域。
-
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) # print(gray_img.shape) temp = cv.imread("./images/temp.png") gray_temp = cv.cvtColor(temp,cv.COLOR_BGR2GRAY) h,w = gray_temp.shape # print(gray_temp.shape) #模板匹配 拿到匹配结果矩阵 res = cv.matchTemplate(gray_img,gray_temp,cv.TM_CCOEFF_NORMED) # print(res.shape) #设置阈值 thresh = 0.8 #获取匹配上的结果的索引 loc = np.where(res >= thresh) #解包 拿到成对的x,y索引 for i in zip(*loc): #print(i) x,y = i[1],i[0] cv.rectangle(img,(x,y),(x+w,y+h),(0,255,255),2) #显示 cv.imshow("img",img) cv.waitKey(0) cv.destroyAllWindows()
四、霍夫变换
1、霍夫变换定义
霍夫变换是一种用于检测图像中特定几何形状(如直线、圆等)的特征提取技术。它通过将图像空间中的点映射到参数空间(霍夫空间),在参数空间中寻找累积峰值来实现形状检测。
核心思想:
-
空间转换:将图像空间中的点转换为参数空间中的曲线
-
投票机制:参数空间中的每个点代表一个可能的几何形状
-
峰值检测:在参数空间中寻找投票数最多的点,对应图像中的几何形状
特点:
-
对噪声和部分遮挡具有鲁棒性
-
能检测不完整的几何形状
-
适用于直线、圆、椭圆等多种几何形状
-
计算复杂度随参数维度增加而增加
2、霍夫直线变换
原理:
-
参数空间:使用极坐标参数 (ρ, θ) 表示直线
-
ρ:原点到直线的垂直距离
-
θ:直线与x轴的夹角
-
-
映射关系:图像空间中的每个点对应霍夫空间中的一条正弦曲线
-
交点检测:多条曲线相交于一点表示这些点共线
数学表示:
对于图像中的点 (x, y),在霍夫空间中满足:
$$
ρ = x·cosθ + y·sinθ
$$
对于一条直线(不垂直于x轴的直线),都可以用y=k x+b来表示,此时,x和y是横纵坐标,k和b是一个固定参数。当我们换一种方式来看待这个式子,我们就可以得到:
$$
b=-kx+y
$$
此时,以k和b 为横纵坐标,x和y为固定参数,也就是说k和b成了自变量和因变量,变换如下图所示:
从上图可以看出,在直角坐标系下的一个直线,在变换后的空间中仅仅表示为一点,对于变换后的空间,我们称其为霍夫空间,也就是参数空间。也就是说,直角坐标系下的一条直线对应了霍夫空间中的一个点。类似的,霍夫空间中的一条直线也对应了直角坐标系中的一个点,如下图所示:
那么对于一个二值化后的图形来说,其中的每一个目标像素点(这里假设目标像素点为白色像素点)都对应了霍夫空间的一条直线,当霍夫空间中有两条直线相交时,就代表了直角坐标系中某两个点所构成的直线。而当霍夫空间中有很多条线相交于一点时,说明直角坐标系中有很多点能构成一条直线,也就意味着这些点共线,因此我们就可以通过检测霍夫空间中有最多直线相交的交点来找到直角坐标系中的直线。
然而对于x=1这种直线(垂直于x轴)来说,y已经不存在了,斜率无穷大,无法映射到霍夫空间中去,那么就没办法使用上面的方法进行检测了,为了解决这个问题,我们就将直角坐标系转化为极坐标系,然后通过极坐标系与霍夫空间进行相互转化。
在极坐标系下是一样的,极坐标中的点对于霍夫空间中的线,霍夫空间中的点对应极坐标中的直线。并且此时的霍夫空间不再是以k为横轴、b为纵轴,而是以为θ横轴、ρ(上图中的r)为纵轴。上面的公式中,x、y是直线上某点的横纵坐标(直角坐标系下的横纵坐标),和是极坐标下的坐标,因此我们只要知道某点的x和y的坐标,就可以得到一个关于θ-ρ的表达式,如下图所示:
根据上图,霍夫空间在极坐标系下,一点可以产生一条三角函数曲线,而多条这样的曲线可能会相交于同一点。因此,我们可以通过设定一个阈值,来检测霍夫空间中的三角函数曲线相交的次数。如果一个交点的三角函数曲线相交次数超过阈值,那么这个交点所代表的直线就可能是我们寻找的目标直线。
使用API
lines=cv2.HoughLines(image, rho, theta, threshold)
-
image
:输入图像,通常为二值图像,其中白点表示边缘点,黑点为背景。 -
rho
:r的精度,以像素为单位,表示霍夫空间中每一步的距离增量, 值越大,考虑越多的线。 -
theta
:角度θ的精度,通常以弧度为单位,表示霍夫空间中每一步的角度增量。值越小,考虑越多的线。 -
threshold
:累加数阈值,只有累积投票数超过这个阈值的候选直线才会被返回。
返回值:cv2.HoughLines
函数返回一个二维数组,每一行代表一条直线在霍夫空间中的参数 `(rho, theta)
示例:
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,30,70) #霍夫变换 返回的是[r,theta] lines = cv.HoughLines(edges,0.8,np.pi/180,90) for line in lines: r,theta = line[0] sin_theta = np.sin(theta) cos_theta = np.cos(theta) 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),1) cv.imshow("img",img) cv.waitKey(0) cv.destroyAllWindows()
3、统计概率霍夫直线变换
改进点:
-
端点检测:不仅检测直线,还返回起点和终点坐标
-
概率采样:随机采样边缘点,减少计算量
-
线段筛选:通过最小长度和最大间隙过滤短线段
前面的方法又称为标准霍夫变换,它会计算图像中的每一个点,计算量比较大,另外它得到的是整一条线(r和θ),并不知道原图中直线的端点。所以提出了统计概率霍夫直线变换(Probabilistic Hough Transform),是一种改进的霍夫变换,它在获取到直线之后,会检测原图中在该直线上的点,并获取到两侧的端点坐标,然后通过两个点的坐标来计算该直线的长度,通过直线长度与最短长度阈值的比较来决定该直线要不要被保留。
使用API
lines=cv2.HoughLinesP(image, rho, theta, threshold, lines=None, minLineLength=0, maxLineGap=0)
-
image
:输入图像,通常为二值图像,其中白点表示边缘点,黑点为背景。 -
rho
:极径分辨率,以像素为单位,表示极坐标系中的距离分辨率。 -
theta
:极角分辨率,以弧度为单位,表示极坐标系中角度的分辨率。 -
threshold
:阈值,用于过滤掉弱检测结果,只有累计投票数超过这个阈值的直线才会被返回。 -
lines
(可选):一个可初始化的输出数组,用于存储检测到的直线参数。 -
minLineLength
(可选):最短长度阈值,比这个长度短的线会被排除。 -
maxLineGap
(可选):同一直线两点之间的最大距离。当霍夫变换检测到一系列接近直角的线段时,这些线段可能是同一直线的不同部分。maxLineGap
参数指定了在考虑这些线段属于同一直线时,它们之间最大可接受的像素间隔。
返回值lines:cv2.HoughLinesP
函数返回一个二维数组,每个元素是一个包含4个元素的数组,分别表示每条直线的起始点和结束点在图像中的坐标(x1, y1, x2, y2)。
示例:
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,30,70) # lines=cv2.HoughLinesP(image, rho, theta, threshold, lines=None, minLineLength=0, maxLineGap=0)** lines=cv.HoughLinesP(edges,0.8,np.pi/180,90,minLineLength=50,maxLineGap=10) print(lines) for line in lines: x1,y1,x2,y2=line[0] cv.line(img,(x1,y1),(x2,y2),(0,0,255),1) cv.imshow('img',img) cv.waitKey(0) cv.destroyAllWindows()
4、霍夫圆变换
原理:
-
参数空间:使用三维参数(x, y, r)表示圆
-
(x, y):圆心坐标
-
r:半径
-
-
梯度信息:利用图像梯度减少计算量
-
累积投票:对可能的圆心进行投票
数学表示:
对于图像中的点 (x, y),在霍夫空间中满足:
$$
(x - a)² + (y - b)² = r²
$$
霍夫圆变换跟直线变换类似,它可以从图像中找出潜在的圆形结构,并返回它们的中心坐标和半径。只不过线是用(r,θ)表示,圆是用(x_center,y_center,r)来表示,从二维变成了三维,数据量变大了很多;所以一般使用霍夫梯度法减少计算量。
使用API
circles=cv2.HoughCircles(image, method, dp, minDist, param1, param2)
-
image
:输入图像,通常是灰度图像。 -
method
:使用的霍夫变换方法:霍夫梯度法,可以是cv2.HOUGH_GRADIENT
,这是唯一在OpenCV中用于圆检测的方法。 -
dp
:累加器分辨率与输入图像分辨率之间的降采样比率,用于加速运算但不影响准确性。设置为1表示霍夫梯度法中累加器图像的分辨率与原图一致 -
minDist
:检测到的圆心之间的最小允许距离,以像素为单位。在霍夫变换检测圆的过程中,可能会检测到许多潜在的圆心。minDist
参数就是为了过滤掉过于接近的圆检测结果,避免检测结果过于密集。当你设置一个较小的minDist
值时,算法会尝试找出尽可能多的圆,即使是彼此靠得很近的圆也可能都被检测出来。相反,当你设置一个较大的minDist
值时,算法会倾向于只检测那些彼此间存在一定距离的独立的圆。 -
param1
和param2
:这两个参数是在使用cv2.HOUGH_GRADIENT
方法时的特定参数,分别为:-
param1
(可选):阈值1,决定边缘强度的阈值。 -
param2
:阈值2,控制圆心识别的精确度。较大的该值会使得检测更严格的圆。param2
通常被称为圆心累积概率的阈值。在使用霍夫梯度方法时,param2
设置的是累加器阈值,它决定了哪些候选圆点集合被认为是有效的圆。较高的param2
值意味着对圆的检测更严格,只有在累加器中积累了足够高的响应值才认为是真实的圆;较低的param2
值则会降低检测的门槛,可能会检测到更多潜在的圆,但也可能包含更多的误检结果。
-
返回值:cv2.HoughCircles
返回一个二维numpy数组,包含了所有满足条件的圆的参数。
示例
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,30,70) # 霍夫圆变换 circles = cv.HoughCircles(edges,cv.HOUGH_GRADIENT,1,20,param2=20) # print(circles) for circle in circles: x,y,r = circle[0] # print(x,y,r) x,y,r = np.int_(np.round(x)),np.int_(np.round(y)),np.int_(np.round(r)) # print(x,y,r) cv.circle(img,(x,y),r,(255,0,0),1) cv.imshow("img",img) cv.waitKey(0) cv.destroyAllWindows()
五、图像亮度变换
1、亮度变换
亮度与对比度概念
-
亮度调整:图像像素强度整体变高或变低(所有像素值加上/减去固定值)
-
对比度调整:拉大暗处和亮处像素强度差异(白更白,黑更黑)
2、线性变换
使用 cv2.addWeighted()
函数,可以对图像的像素值进行加权平均,进而改变图像的整体亮度。亮度增益可以通过向每个像素值添加一个正值来实现。
-
cv2.addWeighted(src1, alpha, src2, beta, gamma)
-
src1
:第一张输入图像,它将被赋予权重alpha
。 -
alpha
:第一个输入图像的权重。 -
src2
:第二张输入图像,它将被赋予权重beta
。 -
beta
:第二个输入图像的权重。 -
gamma
:一个标量,将被添加到权重求和的结果上,可用于调整总体亮度。计算公式为: dst = src1 * alpha + src2 * beta + gamma
-
示例:
import cv2 as cv #读图 cat = cv.imread("./images/cat1.png") #线性变换 dst = cv.addWeighted(cat,1,cat,0,50) cv.imshow("cat",cat) cv.imshow("dst",dst) cv.waitKey(0) cv.destroyAllWindows()
3、直接像素值修改
如果只需要增加或减少固定的亮度值,可以直接遍历图像像素并对每个像素值进行加减操作。
使用的API:
numpy.clip(a, a_min, a_max)
用于对数组中的元素进行限定,将超出指定范围的元素值截断至指定的最小值和最大值之间
-
a
:输入数组。 -
a_min
:指定的最小值,数组中所有小于a_min
的元素将被替换为a_min
。 -
a_max
:指定的最大值,数组中所有大于a_max
的元素将被替换为a_max
。
示例:
import cv2 as cv import numpy as np #读图 cat = cv.imread("./images/cat1.png") #创建窗口 用于显示滑条 window_name = "slide" cv.namedWindow(window_name) img = cv.imread("./images/cat1.png") def chage(p): x=p/256*511-255 dst=np.uint8(np.clip(img+x,0,255)) cv.imshow("dst",dst) print(x) #创建滑动条 initial_value = 100 chage(initial_value) cv.createTrackbar("add_p",window_name,initial_value,255,chage) cv.waitKey(0) cv.destroyAllWindows()
六、形态学变换
1、核
-
小区域(3×3、5×5等)
-
不同形状:矩形、椭圆、十字形等
-
决定形态学操作的效果
2、腐蚀
-
取核覆盖区域的最小值
-
效果:白色区域缩小,消除小噪点
-
应用:消除细小连接、分离物体
示例:
import cv2 import numpy as np # 读取图像并二值化 img = cv2.imread('example.jpg', 0) _, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY) # 定义3x3矩形核 kernel = np.ones((3,3), np.uint8) # 腐蚀操作 eroded = cv2.erode(binary, kernel, iterations=1) cv2.imshow('Original', binary) cv2.imshow('Eroded', eroded) cv2.waitKey(0)
3、膨胀
-
取核覆盖区域的最大值
-
效果:白色区域扩大
-
应用:连接断裂部分、填充空洞
示例
# 使用同样的二值图像和核 dilated = cv2.dilate(binary, kernel, iterations=1) cv2.imshow('Original', binary) cv2.imshow('Dilated', dilated) cv2.waitKey(0)
4、开运算(先腐蚀后膨胀)
-
消除小物体/噪点
-
平滑物体轮廓
示例:
opening = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel) cv2.imshow('Original', binary) cv2.imshow('Opening', opening) cv2.waitKey(0)
5、闭运算(先膨胀后腐蚀)
-
填充小孔洞
-
连接邻近物体
示例
closing = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel) cv2.imshow('Original', binary) cv2.imshow('Closing', closing) cv2.waitKey(0)
6、礼帽运算(原图 - 开运算)
-
突出比周围亮的区域
-
用于背景提取
示例:
tophat = cv2.morphologyEx(binary, cv2.MORPH_TOPHAT, kernel) cv2.imshow('Original', binary) cv2.imshow('Top Hat', tophat) cv2.waitKey(0)
7、黑帽运算(闭运算 - 原图)
-
突出比周围暗的区域
-
用于检测暗部特征
blackhat = cv2.morphologyEx(binary, cv2.MORPH_BLACKHAT, kernel) cv2.imshow('Original', binary) cv2.imshow('Black Hat', blackhat) cv2.waitKey(0)
8、形态学梯度(膨胀图 - 腐蚀图)
-
突出物体边缘
-
用于边缘检测
gradient = cv2.morphologyEx(binary, cv2.MORPH_GRADIENT, kernel) cv2.imshow('Original', binary) cv2.imshow('Gradient', gradient) cv2.waitKey(0)
9、API
-
基本操作:
-
cv2.erode(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]])
-
cv2.dilate(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]])
-
-
高级操作:
-
cv2.morphologyEx(src, op, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]])
-
op
参数:-
cv2.MORPH_OPEN
- 开运算 -
cv2.MORPH_CLOSE
- 闭运算 -
cv2.MORPH_TOPHAT
- 礼帽运算 -
cv2.MORPH_BLACKHAT
- 黑帽运算 -
cv2.MORPH_GRADIENT
- 形态学梯度
-
-
-
-
核创建:
-
矩形核:
kernel = np.ones((3,3), np.uint8)
-
自定义核:
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
-
可选形状:
-
cv2.MORPH_RECT
- 矩形 -
cv2.MORPH_ELLIPSE
- 椭圆 -
cv2.MORPH_CROSS
- 十字形
-
-
-
参数说明:
-
iterations
:操作执行的次数 -
borderType
:边界处理方式(通常使用默认值) -
borderValue
:边界值(腐蚀/膨胀时使用)