17 图像轮廓特征查找
图像轮廓特征查找其实就是他的外接轮廓。
应用:
图像分割
形状分析
物体检测与识别
根据轮廓点进行,所以要先找到轮廓。
先灰度化、二值化。目标物体白色,非目标物体黑色,选择合适的儿值化方式。
有了轮廓点就可以找到最上、最下、最左、最右的四个坐标,X_{min}、X_{max}、Y_{min}、Y_{max}。就可以绘制出矩形。
17.1 外接矩形
boundingRect(轮廓点)
形状的外接矩形有两种,如下图,绿色的叫外接矩形,表示不考虑旋转并且能包含整个轮廓的矩形。其中,外接矩形可根据获得到的轮廓坐标中最上、最下、最左、最右的点的坐标来绘制外接矩形,也就是下图中的绿色矩形。蓝色的是最小外接矩形,会考虑面积。

cv.boundingRect
函数用于计算一组点(通常是轮廓)的最小外接矩形。这个矩形是一个 轴对齐的矩形,也就是说,它的边与图像坐标轴平行。
作用
快速获取一个能够包含目标轮廓的矩形边界。
简单直观地定位目标轮廓的大致区域。
返回参数
x
:外接矩形左上角的 x 坐标。
y
:外接矩形左上角的 y 坐标。
w
:外接矩形的宽度。
h
:外接矩形的高度。
python
import cv2 as cv
import numpy as np
img = cv.imread('images/ikun4.jpg')
#灰度图
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
#二值化处理,阈值法 因为目标是白色我们需要白色
ret, binary = cv.threshold(gray, 200, 255, cv.THRESH_BINARY_INV)
#查找轮廓
contours, hierarchy = cv.findContours(binary, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
for i in range(len(contours)):
#查找凸包
hull = cv.convexHull(contours[i])
#绘制凸包
cv.polylines(img, [hull], True, (100, 100, 255), 3)
#画出外接矩形
for i in range(len(contours)):
x, y, w, h = cv.boundingRect(contours[i])
cv.rectangle(img, (x, y), (x + w, y + h), (255, 255, 0), 2)
cv.imshow('1', img)
# cv.imshow('2', binary)
cv.waitKey(0)
cv.destroyAllWindows()

获取方式
在代码中,通过循环遍历查找到的轮廓
contours
,对每个轮廓调用cv.boundingRect
函数来获取其外接矩形的位置和大小参数,包括左上角顶点坐标(x,y)、宽度 w 和高度 h。绘制目的
使用
cv.rectangle
函数在原图上绘制出这些外接矩形,通过矩形的边界将目标轮廓大致框选出来,便于更直观地观察和分析目标在图像中的分布情况,以及与其他图像元素的相对位置关系。
17.2 最小外接矩形
定义与原理
最小外接矩形 是能够完全包含目标轮廓的最小面积的矩形,它允许矩形相对于图像坐标轴旋转,因此可以更好地贴合轮廓的形状,相比普通外接矩形(轴对齐的外接矩形),它可以更精确地描述轮廓的形状和方向。
其基本原理是通过计算轮廓点集的最小面积矩形,这个矩形的边不一定与图像坐标轴对齐。它需要找到一组四个点,使得这四个点构成的矩形能够包含所有轮廓点,并且面积是所有可能的包含该轮廓的矩形中最小的。
计算方法
从轮廓点集出发,利用几何算法来确定最小外接矩形的边界。
一种常见的方法是基于旋转卡壳(Rotating Calipers)算法,它通过旋转两条平行的切线来寻找能够包含整个点集的最小矩形。具体步骤为:首先找到点集的凸包;然后在凸包上选择两个点作为初始两个的顶点,这两点确定一条边;接着,寻找与该边相对的另一条边,使得这两条边之间的距离最大,从而形成一个初始的矩形;最后,逐步旋转这两条边,找到使矩形面积最小的那一对边,从而确定最小外接矩形。
在 OpenCV 中,可以通过 cv.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,则绘制轮廓点;如果是负数,则填充轮廓内部区域。
python
#最小外接矩形 旋转卡壳法
#读取图像
img = cv.imread('images/feiwu.png')
gary = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
#二值化
ret, binary = cv.threshold(gary, 200, 255, cv.THRESH_BINARY_INV)
#查找轮廓
contours, hierarchy = cv.findContours(binary, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
for i in range(len(contours)):
#最小外接矩形 rect = (center(x,y), (width, height), angle)
rect = cv.minAreaRect(contours[i])
box = cv.boxPoints(rect).astype(np.int32)
cv.drawContours(img, [box], -1, (0, 0, 255), 2,cv.LINE_AA)
#画出外接矩形
for i in range(len(contours)):
x, y, w, h = cv.boundingRect(contours[i])
cv.rectangle(img, (x, y), (x + w, y + h), (0, 0, 0), 2)
cv.imshow("1", img)
cv.waitKey(0)
cv.destroyAllWindows()
绘制外接矩形和最小外接矩形是有区别的
17.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
,则会填充整个圆。
python
img = cv.imread('images/haimbb.jpg')
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, binary = cv.threshold(gray, 100, 255, cv.THRESH_BINARY_INV)
#查找轮廓
contours, hierarchy = cv.findContours(binary, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
for i in range(len(contours)):
#绘制外接圆
(x, y), radius = cv.minEnclosingCircle(contours[i])
center = (int(x), int(y))
radius = int(radius)
cv.circle(img, center, radius, (128, 128, 128), 2)
#绘制外接矩形
rect = cv.minAreaRect(contours[i])
box = cv.boxPoints(rect)
cv.imshow("1", img)
cv.imshow("2", binary)
cv.waitKey(0)
cv.destroyAllWindows()