文章目录
- 前言
- 一、凸包特征检测
- 1.穷举法
- 2.QuickHull法
- 二、图像轮廓特征查找
- 1.外接矩形
- 2.最小外接矩形
- 3.最小外接圆
前言
- 通过今天的学习,我掌握了OpenCV中有关凸包特征检测,图像轮廓特征查找的相关原理和操作
一、凸包特征检测
-
- 通俗的讲,凸包其实就是将一张图片中物体的最外层的点连接起来构成的凸多边形,它能包含物体中所有的内容。
- 凸包检测常用在物体识别、手势识别、边界检测等领域。
tips:凸包与图像的轮廓的区别在于:图像轮廓可能含有凹集,保留了图像的细节;而凸包只含有凸集,是图像的凸近似
1.穷举法
- 对集合中的所有点进行两两配对后连接,对于每条直线,检查其余所有点是否处于直线的同一侧,如果是,那么这两个点是凸包点
-
- 用向量的思想,点都是有坐标的,连起来就可以构成一个向量。再以其中一个点,连接另一个点,构成另一个向量,让两个向量做外积,就是叉积。也就是 s t d = ∣ 向量 a ∣ ∗ ∣ 向量 b ∣ ∗ s i n ( θ ) std=|向量a|*|向量b|*sin(\theta) std=∣向量a∣∗∣向量b∣∗sin(θ) ,能控制 s t d std std的正负的只能是 θ \theta θ,如果计算出来的 s t d std std的正负都相同,说明这些点都在这条直线的同一侧,那么这两个点就是凸包的边界点。
2.QuickHull法
- 将所有点放在二维坐标系中,找到横坐标最小和最大的两个点 P 1 P_1 P1和 P 2 P_2 P2并连线。此时整个点集被分为两部分,直线上为上包,直线下为下包。
- 以上包为例,找到上包中的点距离该直线最远的点 P 3 P_3 P3,连线并寻找直线 P 1 P 3 P1P3 P1P3左侧的点和 P 2 P 3 P2P3 P2P3右侧的点,然后重复本步骤,直到找不到为止。对下包也是这样操作。
对于未经处理的图像,我们无法直接获取点的坐标。特别是对于彩色图像,我们需要将其转换为二值图像,并使用轮廓检测技术来获取轮廓边界的点的坐标。然后,我们才能进行上述寻找凸包点的过程。
1.获取凸包点
- cv2.convexHull(points)
- points:输入参数,图像的轮廓
2.绘制凸包
- cv2.polylines(image, pts, isClosed, color, thickness=1)
- pts:输入凸包点组成的列表
- isclosed:表示是否为闭合多边形
# 获取图像
img = cv.imread(r'D:\AI\笔记课件\images\num.png')
# 灰度化
img_gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
# 二值化
_,img_binary = cv.threshold(img_gray,127,255,cv.THRESH_OTSU+cv.THRESH_BINARY_INV)
# 获取轮廓
contours,hierarchy = cv.findContours(img_binary,mode=cv.RETR_EXTERNAL,method=cv.CHAIN_APPROX_SIMPLE)
# 获取凸包
points = [cv.convexHull(i) for i in contours]
# 绘制凸包
cv.polylines(img,points,True,(255,0,0),3,cv.LINE_AA)
# 显示图像
cv.imshow('img',img)
cv.waitKey(0)
cv.destroyAllWindows()
二、图像轮廓特征查找
- 查找图像的轮廓特征就是查找图像的外接轮廓
- 根据轮廓点进行查找,所以需要先找到轮廓点
- 对图像进行灰度化和二值化,目标区域设为白色,其余部分为黑色
1.外接矩形
- cv2.boundingRect(轮廓点坐标)
- 返回的是外接矩形的空间位置信息,即轮廓坐标中最上、最下、最左、最右的点的坐标
img = cv.imread(r"D:\AI\笔记课件\images\flower2.png")
img_copy = img.copy()
# 灰度化
img_gray = cv.cvtColor(img_copy,cv.COLOR_BGR2GRAY)
# 二值化
_,img_binary = cv.threshold(img_gray,127,255,cv.THRESH_OTSU+cv.THRESH_BINARY_INV)
# 查找轮廓
contours,hierarchy = cv.findContours(img_binary,mode=cv.RETR_EXTERNAL,method=cv.CHAIN_APPROX_SIMPLE)
# 绘制轮廓
cv.drawContours(img_copy,contours,-1,(255,0,0),3)
# 获取外接矩阵位置
for i in contours:
x_min,y_min,w,h = cv.boundingRect(i)
# 绘制外接矩形
cv.rectangle(img_copy,(x_min,y_min),(x_min+w,y_min+h),(0,0,255),3,cv.LINE_AA)
# 显示效果
cv.imshow('img',img_copy)
cv.waitKey(0)
cv.destroyAllWindows()
2.最小外接矩形
-
使用旋转卡壳算法
-
对于多边形P的一个外接矩形存在一条边与原多边形的边共线。
-
对于每一条边,找到距离边变最远的点的距离作为矩形的高
-
对于每一条边,找到投影到边最大的距离计算矩形的宽
-
比较通过每一条边计算得到的矩形面积进行比较,找到最小的矩形面积
w i d t h = ∣ b h ‾ ∣ × cos a + ∣ a d ‾ ∣ × cos θ − ∣ a b ‾ ∣ 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∣ -
在OpenCV中,可以直接使用cv2.minAreaRect()来获取最小外接矩形,该函数只需要输入一个参数,就是凸包点的坐标,然后会返回最小外接矩形的中心点坐标、宽高以及旋转角度。
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),顺序按照逆时针或顺时针方向排列
-
将最小外接矩形转换为边界框的四个角点,并转换为整数坐标
img = cv.imread(r"D:\AI\笔记课件\images\num.png")
# 灰度化
img_gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
# 二值化
_,img_binary = cv.threshold(img_gray,127,255,cv.THRESH_OTSU+cv.THRESH_BINARY_INV)
# 查找轮廓
contours,hierarchy = cv.findContours(img_binary,cv.RETR_EXTERNAL,cv.CHAIN_APPROX_SIMPLE)
# 获取最小外接矩形
for i in contours:
rect = cv.minAreaRect(i)
# 获取每个顶点的坐标
box = cv.boxPoints(rect).astype(np.int32)
# 绘制图像
cv.drawContours(img,[box],-1,(255,0,0),2,cv.LINE_AA)
# 显示效果
cv.imshow('img',img)
cv.waitKey(0)
cv.destroyAllWindows()
3.最小外接圆
- 寻找最小外接圆使用的算法是Welzl算法。Welzl算法基于一个定理:希尔伯特圆定理,对于平面上的任意三个不在同一直线上的点,存在一个唯一的圆同时通过这三个点,且该圆是最小面积的圆(即包含这三个点的圆中半径最小的圆,也称为最小覆盖圆)。
- 新加入的点一定在新的最小覆盖圆的圆周上
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
,则会填充整个圆。
img = cv.imread(r"D:\AI\笔记课件\images\num.png")
# 灰度化
img_gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
# 二值化
_,img_binary = cv.threshold(img_gray,127,255,cv.THRESH_OTSU+cv.THRESH_BINARY_INV)
# 查找轮廓
contours,_ = cv.findContours(img_binary,mode=cv.RETR_EXTERNAL,method=cv.CHAIN_APPROX_SIMPLE)
# 获取最小外接圆
for i in contours:
# 获取每个点的位置信息
(x,y),radius = cv.minEnclosingCircle(i)
# 数据类型转换,元组不能直接使用astype进行类型转换
x,y,radius = np.int_(x),np.int_(y),np.int_(radius)
# 绘制图像
cv.circle(img,(x,y),radius,(255,0,0),3,cv.LINE_AA)
# 显示效果
cv.imshow('img',img)
cv.waitKey(0)
cv.destroyAllWindows()
THE END