opencv之图像轮廓特征查找和直方图均衡化(六)
文章目录
一、图像轮廓特征查找
- 图像轮廓特征查找其实就是他的外接轮廓。
- 应用:
- 图像分割
- 形状分析
- 物体检测与识别
- 根据轮廓点进行,所以要先找到轮廓。
- 先灰度化、二值化。目标物体白色,非目标物体黑色,选择合适的儿值化方式。
- 有了轮廓点就可以找到最上、最下、最左、最右的四个坐标, X m i n 、 X m a x 、 Y m i n 、 Y m a x X_{min}、X_{max}、Y_{min}、Y_{max} Xmin、Xmax、Ymin、Ymax。就可以绘制出矩形。
1.1 外接矩形
-
cv2.boundingRect(轮廓点)
形状的外接矩形有两种,如下图,绿色的叫外接矩形,表示不考虑旋转并且能包含整个轮廓的矩形。其中,外接矩形可根据获得到的轮廓坐标中最上、最下、最左、最右的点的坐标来绘制外接矩形,也就是下图中的绿色矩形。蓝色的是最小外接矩形,会考虑面积。
1.2 最小外接矩形
最小外接矩形就是上图所示的蓝色矩形,寻找最小外接矩形使用的算法叫做旋转卡壳法。
需要使用到的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,则绘制轮廓点;如果是负数,则填充轮廓内部区域。
1.3 最小外接圆
寻找最小外接圆使用的算法是Welzl算法。Welzl算法基于一个定理:希尔伯特圆定理表明,对于平面上的任意三个不在同一直线上的点,存在一个唯一的圆同时通过这三个点,且该圆是最小面积的圆(即包含这三个点的圆中半径最小的圆,也称为最小覆盖圆)。
需要使用的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")
img = num.copy()
# 转灰度图
img = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
# 二值化
ret = cv.threshold(img,127,255,cv.THRESH_OTSU+cv.THRESH_BINARY_INV)[1]
# 查找轮廓
contours,hierarchy = cv.findContours(ret,cv.RETR_EXTERNAL,cv.CHAIN_APPROX_SIMPLE)
# 绘制轮廓
cv.drawContours(img,contours,-1,(0,0,255),2)
cv.imshow("img",img)
# 遍历轮廓列表
for cnt in contours:
# x,y左上坐标 w,h:宽高
x,y,w,h = cv.boundingRect(cnt)
cv.rectangle(num,(x,y),(x+w,y+h),(0,0,255),2)
cv.imshow("num",num)
cv.waitKey(0)
cv.destroyAllWindows()
案例二:最小外接矩形
# 最小外接矩阵
import cv2 as cv
import numpy as np
num = cv.imread("images/num.png")
img = num.copy()
# 灰度化
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
# 二值化
ret,thresh = cv.threshold(gray,127,255,cv.THRESH_BINARY_INV+cv.THRESH_OTSU)
# 寻找轮廓
contours,hierarchy = cv.findContours(thresh,cv.RETR_TREE,cv.CHAIN_APPROX_SIMPLE)
# rect = cv2.minAreaRect(contours[0]) ,rect:(x,y),w,h,angle
# 循环遍历轮廓,拿每一条轮廓
for cnt in contours:
# 获取最小外接矩阵信息
rect = cv.minAreaRect(cnt)
# box = cv2.boxPoints(rect) 四行两列的数组,代表每个坐标的(x,y)
box = cv.boxPoints(rect).astype(np.int32)
cv.drawContours(img,[box],-1,(0,0,255),2,cv.LINE_AA)
cv.imshow("img",img)
cv.imshow("num", num)
cv.waitKey()
cv.destroyAllWindows()
案例三:最小外接圆
# 最小外接圆
# 最小外接矩阵
import cv2 as cv
import numpy as np
num = cv.imread("images/num.png")
img = num.copy()
# 灰度化
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
# 二值化
ret,thresh = cv.threshold(gray,127,255,cv.THRESH_BINARY_INV+cv.THRESH_OTSU)
# 寻找轮廓
contours,hierarchy = cv.findContours(thresh,cv.RETR_TREE,cv.CHAIN_APPROX_SIMPLE)
# 遍历轮廓
for cnt in contours:
# 获取最小外接圆相关信息:cv2.minEnclosingCircle(contour),返回圆心坐标和半径
(x,y),r = cv.minEnclosingCircle(cnt)
x,y,r = int(x),int(y),int(r)
cv.circle(img,(x,y),r,(0,255,0),2)
cv.imshow("img",img)
cv.waitKey()
cv.destroyAllWindows()
二、 直方图均衡化
2.1 什么是直方图
直方图是对数据进行统计的一种方法,并且将统计值组织到一系列实现定义好的 bin 当中。其中, bin 为直方图中经常用到的一个概念,可以译为 “直条” 或 “组距”,其数值是从数据中计算出的特征统计量,这些数据可以是诸如梯度、方向、色彩或任何其他特征。
- 直方图:反映图像像素分布的统计图,横坐标就是图像像素的取值,纵坐标是该像素的个数。也就是对一张图像中不同像素值的像素个数的统计。
- 增加对比度:黑的更黑,白的更白。
2.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
def draw(img):
# 直方图
hist = cv.calcHist(img,[0],None,[256],[0,256])
print(hist)
# 查找最大值和最小值
minVal,maxVal,minLoc,maxLoc = cv.minMaxLoc(hist)
print(minVal,maxVal,minLoc,maxLoc)
# 绘制全黑直方图
hist_img = np.zeros([256,256,3],np.uint8)
hpt = int(0.9*256)
for h in range(256):
intensity = int(hist[h].item()/maxVal*hpt)
cv.line(hist_img,(h,256), (h,256-intensity),(0,0,255),1)
cv.imshow('hist',hist_img)
cv.waitKey(0)
cv.destroyAllWindows()
if __name__ == '__main__':
# img = np.zeros([256,256,3],np.uint8)
img = cv.imread("../cao.png")
draw(img)
2.3 直方图均衡化
一副效果好的图像通常在直方图上的分布比较均匀,直方图均衡化就是用来改善图像的全局亮度和对比度。
如果一幅图像整体很亮,那所有的像素值的取值个数应该都会很高,所以应该把它的直方图做一个横向拉伸,就可以扩大图像像素值的分布范围,提高图像的对比度
通俗的讲,就是遍历图像的像素统计出灰度值的个数、比例与累计比例,并重新映射到0-255范围(也可以是其他范围)内,其实从观感上就可以发现,下面两幅图中前面那幅图对比度不高,偏灰白。
- 直方图均衡化作用:
- 增强对比度
- 提高图像质量
2.3.1 自适应直方图均衡化
自适应直方图均衡化(Adaptive Histogram Equalization, AHE),通过调整图像像素值的分布,使得图像的对比度和亮度得到改善。
具体过程如下所示:
接下来我们就要进行计算,就是将要缩放的范围(通常是缩放到0-255,所以就是255-0)乘以累计比例,得到新的像素值,并将新的像素值放到对应的位置上,比如像素值为50的像素点,将其累计比例乘以255,也就是0.33乘以255得到84.15,取整后得到84,并将84放在原图像中像素值为50的地方,像素值为100、210、255的计算过程类似,最终会得到如下图所示的结果,这样就完成了最基本的直方图均衡化的过程。
dst = cv.equalizeHist(imgGray)
imgGray为需要直方图均衡化的灰度图,返回值为处理后的图像
2.3.2 对比度受限的自适应直方图均衡化
很明显,因为全局调整亮度和对比度的原因,脸部太亮,大部分细节都丢失了。自适应均衡化就是用来解决这一问题的:它在每一个小区域内(默认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 cv
import numpy as np
def draw(img, title):
# 计算直方图
hist = cv.calcHist([img], [0], None, [256], [0, 256])
# 查找最大值和最小值
minVal, maxVal, minLoc, maxLoc = cv.minMaxLoc(hist)
# 绘制全黑直方图
hist_img = np.zeros([256, 256, 3], np.uint8)
hpt = int(0.9 * 256)
for h in range(256):
intensity = int(hist[h].item() / maxVal * hpt)
cv.line(hist_img, (h, 256), (h, 256 - intensity), (0, 0, 255), 1)
# 显示图像和直方图
cv.imshow(title, img)
cv.imshow(title + '_hist', hist_img)
return hist # 返回直方图数据
# 读取图像
img = cv.imread("images/zhifang.png")
if img is None:
print("Error: Image not found or path is incorrect.")
exit()
# 转换为灰度图
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# 绘制灰度图及其直方图
draw(gray, "gray")
# 直方图均衡化
equal_gray = cv.equalizeHist(gray)
draw(equal_gray, "equal_gray")
# 对比度受限的自适应直方图均衡化
clahe = cv.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
clahe_gray = clahe.apply(gray)
draw(clahe_gray, "clahe_gray")
# 等待并关闭窗口
cv.waitKey(0)
cv.destroyAllWindows()