OpenCV(05)直方图均衡化,模板匹配,霍夫变换,图像亮度变换,形态学变换
【OpenCV(01)】基本图像操作、绘制,读取视频
【OpenCV(02)】图像颜色处理,灰度化,二值化,仿射变换
【OpenCV(03)】插值方法,边缘填充,透视变换,水印制作,噪点消除
【OpenCV(04)】梯度处理,边缘检测,绘制轮廓,凸包特征检测,轮廓特征查找
目录
- 直方图
- 什么是直方图
- 绘制直方图
- 直方图均衡化
- 自适应均衡化
- 对比度受限的自适应均衡化
- 模板匹配
- 匹配方法
- 平方差匹配
- 归一化平方差匹配
- 相关匹配
- 归一化相关匹配
- 相关系数匹配
- 归一化相关系数匹配
- 绘制轮廓
- 霍夫变换
- 标准霍夫变换
- 统计概率霍夫直线变换
- 霍夫圆变换
- 图像亮度变换
- 亮度变换
- 线性变换
- 直接像素值修改
- 形态学变换
直方图
什么是直方图
- 直方图:反映图像像素分布的统计图,横坐标就是图像像素的取值,纵坐标是该像素的个数。也就是对一张图像中不同像素值的像素个数的统计。
- 增加对比度:黑的更黑,白的更白。
绘制直方图
- 绘制直方图: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('E:\hqyj\code\opencv\images\\bg.png')
#创建黑图,绘制直方图
black = np.zeros((256,256,3),np.uint8)#统计像素
hist = cv.calcHist([bg],[0],None,[256],[0,256])
print(hist)#获取直方图的最小值、最大值及其对应的最小值的位置索引、最大值的位置索引
minval,maxval,minloc,maxloc = cv.minMaxLoc(hist)#由于直方图只有一列,列索引始终为 0,行索引则对应于具体的灰度值
#最小值 461.0 出现在位置 (0, 249),即灰度值为 249 的像素频率为 0。
#最大值 32380.0 出现在位置 (0, 5),即灰度值为 5 的像素频率为 5000
print(minval,maxval,minloc,maxloc)#定义直方图的高
h_hist = np.int32(256)#循环遍历每一个灰度值
for i in range(256):#当前像素的个数占直方图的高,归一化处理l = int(hist[i].item()*h_hist/maxval)point1 = (i,256-l)point2 = (i,256)cv.line(black,point1,point2,(255,255,0),1)cv.imshow('dst',black)
cv.waitKey(0)
cv.destroyAllWindows()
直方图均衡化
遍历图像的像素统计出灰度值的个数、比例与累计比例,并重新映射到0-255范围(也可以是其他范围)内,其实从观感上就可以发现,下面两幅图中前面那幅图对比度不高,偏灰白。
直方图均衡化作用:
- 增强对比度
- 提高图像质量
自适应均衡化
自适应直方图均衡化(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('E:\hqyj\code\opencv\images\\zhifang.png',cv.IMREAD_GRAYSCALE)
#直方图均衡化
dst = cv.equalizeHist(img)
cv.imshow('img',img)
cv.imshow('dst',dst)
cv.waitKey(0)
cv.destroyAllWindows()
对比度受限的自适应均衡化
因为全局调整亮度和对比度的原因,脸部太亮,大部分细节都丢失了。自适应均衡化就是用来解决这一问题的:它在每一个小区域内(默认8×8)进行直方图均衡化。当然,如果有噪点的话,噪点会被放大,需要对小区域内的对比度进行了限制,所以这个算法全称叫:对比度受限的自适应直方图均衡化(Contrast Limited Adaptive Histogram Equalization, 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 cvimg = cv.imread('E:\hqyj\code\opencv\images\\zhifang.png',cv.IMREAD_GRAYSCALE)
#创建clahe对象
clahe = cv.createCLAHE(clipLimit=2.0,tileGridSize=(8,8))
#使用clach调用apply()方法
cl1 = clahe.apply(img)
cv.imshow('old',img)
cv.imshow('dst',cl1)
cv.waitKey(0)
cv.destroyAllWindows()
模板匹配
-
不会有边缘填充。
-
类似于卷积,滑动比较,挨个比较象素。
-
返回结果大小是:目标图大小-模板图大小-1。
import cv2 as cv
import numpy as np#读图
img = cv.imread('E:\hqyj\code\opencv\images\\game.png')
gray_img = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
#print(gray_img.shape) # (266, 283)temp = cv.imread('E:\hqyj\code\opencv\images\\temp.png')
gray_temp = cv.cvtColor(temp,cv.COLOR_BGR2GRAY)
#print(gray_temp.shape) # (24, 18)
h,w = temp.shape[:2]#模板匹配 拿到匹配结果矩阵
res = cv.matchTemplate(gray_img,gray_temp,cv.TM_CCOEFF_NORMED)
#print(res.shape) # (243, 266)#设置阈值
thresh = 0.8
#获取匹配上的结果的索引
loc = np.where(res >= thresh)
#print(loc) # (array([ 63,......,143]), array([ 87,......,173]))#解包,拿到成对的x y索引
#print(zip(*loc)) # <zip object at 0x000001C601C79BC0>
for i in zip(*loc):#print(i) #x,y = i[1],i[0]#print(x,y)cv.rectangle(img, (x,y), (x+w,y+h), (0,255,255), 2)cv.imshow('img',img)
cv.waitKey(0)
cv.destroyAllWindows()
temp(模板图):
game(原图):
在game图中用temp图作为模板图匹配得到目标图:
匹配方法
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(cv2.TM_CCOEFF_NORMED)
- 这些方法决定了如何度量模板图像与原图像子窗口之间的相似度。
-
返回值res
函数在完成图像模板匹配后返回一个结果矩阵,这个矩阵的大小与原图像不相同。矩阵的每个元素表示原图像中相应位置与模板图像匹配的相似度。
匹配方法不同,返回矩阵的值的含义也会有所区别。以下是几种常用的匹配方法及其返回值含义:
-
cv2.TM_SQDIFF
或cv2.TM_SQDIFF_NORMED
:返回值越接近0,表示匹配程度越好。最小值对应的最佳匹配位置。
-
cv2.TM_CCORR
或cv2.TM_CCORR_NORMED
:返回值越大,表示匹配程度越好。最大值对应的最佳匹配位置。
-
cv2.TM_CCOEFF
或cv2.TM_CCOEFF_NORMED
:返回值越大,表示匹配程度越好。最大值对应的最佳匹配位置。
-
平方差匹配
cv2.TM_SQDIFF
以模板图与目标图所对应的像素值使用平方差公式来计算,其结果越小,代表匹配程度越高,计算过程举例如下。
注意:模板匹配过程皆不需要边缘填充,直接从目标图像的左上角开始计算。
归一化平方差匹配
cv2.TM_SQDIFF_NORMED
与平方差匹配类似,只不过需要将值统一到0到1,计算结果越小,代表匹配程度越高,计算过程举例如下。
相关匹配
cv2.TM_CCORR
使用对应像素的乘积进行匹配,乘积的结果越大其匹配程度越高,计算过程举例如下。
归一化相关匹配
cv2.TM_CCORR_NORMED
与相关匹配类似,只不过是将其值统一到0到1之间,值越大,代表匹配程度越高,计算过程举例如下。
相关系数匹配
cv2.TM_CCOEFF
需要先计算模板与目标图像的均值,然后通过每个像素与均值之间的差的乘积再求和来表示其匹配程度,1表示完美的匹配,-1表示最差的匹配,计算过程举例如下。
归一化相关系数匹配
cv2.TM_CCOEFF_NORMED
也是将相关系数匹配的结果统一到0到1之间,值越接近1代表匹配程度越高,计算过程举例如下。
绘制轮廓
找的目标图像中匹配程度最高的点,我们可以设定一个匹配阈值来筛选出多个匹配程度高的区域。
- loc=np.where(array > 0.8) #loc包含array中所有大于0.8的元素索引的数组
np.where(condition) 是 NumPy 的一个函数,当条件为真时,返回满足条件的元素的索引。
- *zip(loc)
*loc
是解包操作,将loc
中的多个数组拆开,作为单独的参数传递给zip
。zip
将这些数组按元素一一配对,生成一个迭代器,每个元素是一个元组,表示一个坐标点。
x=list([[1,2,3,4,3],[23,4,2,4,2]])
print(list(zip(*x))) #[(1, 23), (2, 4), (3, 2), (4, 4), (3, 2)]
霍夫变换
原图:(后面代码都是基于此图执行的)
标准霍夫变换
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('E:\hqyj\code\opencv\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)
print(lines) #[[[25.6 1.5707964]]# [[21.6 1.5707964]] # [[42.4 2.146755 ]] # [[46.4 2.146755 ]]]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()
统计概率霍夫直线变换
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('E:\hqyj\code\opencv\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 = cv.HoughLinesP(edges,0.8,np.pi/180,90,minLineLength=50,maxLineGap=20)
print(lines)for line in lines:x1,y1,x2,y2 = line[0]cv.line(img,(x1,y1),(x2,y2),(0,0,255),2)cv.imshow('img',img)
cv.waitKey(0)
cv.destroyAllWindows()
霍夫圆变换
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('E:\hqyj\code\opencv\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,param1=20,param2=20,minRadius=0,maxRadius=0)
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,(0,255,255),2)cv.imshow('img',img)
cv.waitKey(0)
cv.destroyAllWindows()
图像亮度变换
亮度变换
对比度调整:图像暗处像素强度变低,图像亮处像素强度变高,从而拉大中间某个区域范围的显示精度。
亮度调整:图像像素强度整体变高或者变低。
上图中,(a)把亮度调高,就是图片中的所有像素值加上了一个固定值;(b)把亮度调低,就是图片中的所有像素值减去了一个固定值;(c)增大像素对比度(白的地方更白,黑的地方更黑);(d)减小像素对比度(整幅图都趋于一个颜色);
OpenCV调整图像对比度和亮度时,公式为:g(i,j)=αf(i,j)+βg(i,j)=\alpha f(i,j)+\betag(i,j)=αf(i,j)+β。但是不能浅显的讲α\alphaα是控制对比度,β\betaβ是控制亮度的。
对比度:需要通过α、β\alpha、\betaα、β一起控制。
亮度:通过β\betaβ控制。
线性变换
使用 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('E:\hqyj\code\opencv\images\\cat1.png')#线性变换
dst = cv.addWeighted(cat,0.8,cat,0,100)
cv.imshow('dst',dst)
cv.imshow('cat',cat)cv.waitKey(0)
cv.destroyAllWindows()
直接像素值修改
如果只需要增加或减少固定的亮度值,可以直接遍历图像像素并对每个像素值进行加减操作。
使用的API:
numpy.clip(a, a_min, a_max)
用于对数组中的元素进行限定,将超出指定范围的元素值截断至指定的最小值和最大值之间
-
a
:输入数组。 -
a_min
:指定的最小值,数组中所有小于a_min
的元素将被替换为a_min
。 -
a_max
:指定的最大值,数组中所有大于a_max
的元素将被替换为a_max
。
#读图
cat = cv.imread('E:\hqyj\code\opencv\images\\cat1.png')#创建窗口 用于显示滑条
window_name = 'slide'
cv.namedWindow(window_name)
img = cv.imread('E:\hqyj\code\opencv\images\\cat1.png')def chage(p):x = p/256*511-255dst=np.uint8(np.clip(img+x,0,255))cv.imshow(window_name,dst)print(x)#创建滑动条
initial_value = 127
chage(initial_value)
cv.createTrackbar('add_p',window_name,initial_value,255,chage)cv.waitKey(0)
cv.destroyAllWindows()
形态学变换
import cv2 as cv
import numpy as np#读图
car = cv.imread('E:\hqyj\code\opencv\images\\circle.png')#二值化
_,car = cv.threshold(car,127,255,cv.THRESH_BINARY_INV)
cv.imshow('car',car)#定义核 n*n的一个小区域
kernel = np.ones((5,5),np.uint8)#腐蚀 用全1核覆盖,碰到前景时核中有背景值,就变为背景(缩小前景)
erosion = cv.erode(car,kernel,iterations = 1)
cv.imshow('erosion',erosion)#膨胀 用全1核覆盖,碰到背景时核中有前景值,就变为前景(扩大前景)
dilation = cv.dilate(car,kernel,iterations = 1)
cv.imshow('dilation',dilation)#开运算 先腐蚀后膨胀
#作用:分离物体,消除小区域。特点:消除噪点,去除小的干扰块,而不影响原来的图像
opening = cv.morphologyEx(car,cv.MORPH_OPEN,kernel)
cv.imshow('opening',opening)#闭运算 先膨胀后腐蚀
#作用:是消除/“闭合”物体里面的孔洞。特点:可以填充闭合区域
closing = cv.morphologyEx(car,cv.MORPH_CLOSE,kernel)
cv.imshow('closing',closing)#礼帽运算 原图和开运算的差
tophat = cv.morphologyEx(car,cv.MORPH_TOPHAT,kernel)
cv.imshow('tophat',tophat)#黑帽运算 原图和闭运算的差
blackhat = cv.morphologyEx(car,cv.MORPH_BLACKHAT,kernel)
cv.imshow('blackhat',blackhat)#形态学梯度 膨胀和腐蚀的差
gradient = cv.morphologyEx(car,cv.MORPH_GRADIENT,kernel)
cv.imshow('gradient',gradient)cv.imshow('closing',closing)
cv.waitKey(0)
cv.destroyAllWindows()