一、轮廓特征应用
1. 图像分割
2. 形状分析
3. 物体检测与识别
二、外接轮廓
2.1、外界矩形
如上图所示,红色方框为外接矩形,表示不考虑旋转并且能包含整个轮廓的矩形。其中,外接矩形可根据获得到的轮廓坐标中最上、最下、最左、最右的点的坐标来绘制外接矩形。
2.2、最小外接矩形
旋转卡壳法
旋转卡壳法有一个很重要的前提条件:对于多边形P的一个外接矩形存在一条边与原多边形的边共线。
根据前提条件,上面的凸多边形的最小外接矩形与凸多边形的某条边是共线的。因此我们只需要以其中 的一条边为起始边,然后按照逆时针方向计算每个凸包点与起始边的距离,并将距离最大的点记录下 来。
如上图所示,我们首先以a、b两点为起始边,并计算出e点离起始边最远,那么e到起始边的距离就是一 个矩形的高度,因此我们只需要再找出矩形的宽度即可。对于矩形的最右边,以向量 为基准,然后分 别计算凸包点在向量
上的投影的长度,投影最长的凸包点所在的垂直于起始边的直线就是矩形最右边 所在的直线。
如上图所示,c点就是在向量 上投影最长的凸包点,那么通过c点垂直于直线ab的直线就是矩形的右边 界所在的直线。矩形的左边界的也是这么计算的,不同的是使用的向量不是而是
。
如上图所示,h点垂直于ab的直线就是以ab为起始边所计算出来的矩形所在的左边界所在的直线。其中 矩形的高就是e点到直线ab的距离,矩形的宽是h点在向量上 的投影加上c点在向量
上的投影减去ab 的长度,即:
于是我们就有了以ab为起始边所构成的外接矩形的宽和高,这样就可以得到该矩形的面积。然后再以bc 为起始边,并计算其外接矩形的面积。也就是说凸多边形有几个边,就要构建几次外接矩形,然后找到 其中面积最小的矩形作为该凸多边形的最小外接矩形。 在OpenCV中,可以直接使用cv2.minAreaRect()来获取最小外接矩形,该函数只需要输入一个参数,就 是凸包点的坐标,然后会返回最小外接矩形的中心点坐标、宽高以及旋转角度。通过返回的内容信息, 即可绘制凸多边形的的最小外接矩形。
2.3、最小外接圆
寻找最小外接圆使用的算法是Welzl算法。Welzl算法基于一个定理:对于平面上任意n个点,在其最小覆 盖圆外取第n+1个点,那么第n+1个点一定在这n+1个点的最小覆盖圆的圆周上。
有了这个定理,就可以先取3个点建立一个圆(不共线的三个点即可确定一个圆,如果共线就取距离最远 的两个点作为直径建立圆),然后遍历剩下的所有点,对于遍历到的点P来说:
如果该点在圆内,那么最小覆盖圆不变。
如果该点在圆外,根据上述定理,该点一定在想要求得的最小覆盖圆的圆周上,又因为三个点才能确定 一个圆,所以需要枚举P点之前的点来找其余的两个点。当找到与P点组成的圆能够将所有点都包含在圆 内或圆上,该圆就是这些点的最小外接圆。
在OpenCV中,可以直接使用cv2.minEnclosingCircle()来获取最小外接圆,该函数只需要输入一个参 数,就是要绘制最小外接圆的点集的坐标,然后会返回最小外接圆的圆心坐标与半径。通过该函数返回 的内容信息即可绘制某点集的最小外接圆。
contour_type:外接轮廓的类别:
boundingRect:外接矩形,
minAreaRect:最小外接矩形,
minEnclosingCircle:最小外接圆。
color:绘制外接轮廓的颜色。 thickness:绘制外接轮廓的线宽。
三、图像轮廓特征查找
3.1、外接矩形
导入模块
python">import cv2
import numpy as np
输入图像
python">img = cv2.imread('img_.png')
灰度化
python">img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
二值化
python">ret, img_threshold = cv2.threshold(img_gray, 127, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY)
寻找轮廓
python">contours, hierarch = cv2.findContours(img_threshold, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
绘制轮廓
python">cv2.drawContours(img, contours, -1, (0, 0, 255), thickness=2)
绘制外接轮廓
python">for i in contours:x,y,w,h=cv2.boundingRect(i)top_left=(x,y)botton_right=(x+w,y+h)cv2.rectangle(img,top_left,botton_right,color=(0,0,255),thickness=2)
输出图像
python">cv2.imshow('img', img)
cv2.waitKey(0)
完整代码
python">import cv2
import numpy as np # 读取图像文件 'img_.png'
img = cv2.imread('img_.png') # 将彩色图像转换为灰度图像
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 使用 Otsu 方法对灰度图像进行阈值处理,生成二值图像
# cv2.threshold() 的参数:
# 1. 输入图像 (img_gray)
# 2. 阈值 (127): 用于二值化的固定阈值,Otsu 方法会自动选择
# 3. 最大值 (255): 二值化后像素的最大值
# 4. 阈值类型 (cv2.THRESH_OTSU + cv2.THRESH_BINARY): 使用 Otsu 方法进行自适应阈值处理
ret, img_threshold = cv2.threshold(img_gray, 127, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY) # 查找轮廓
# cv2.findContours() 的参数:
# 1. 输入图像 (img_threshold)
# 2. 轮廓检索模式 (cv2.RETR_TREE): 检索所有轮廓并重建一个完整的层次结构
# 3. 轮廓近似方法 (cv2.CHAIN_APPROX_SIMPLE): 仅保留轮廓的端点信息
contours, hierarch = cv2.findContours(img_threshold, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) # 在原图像上绘制所有轮廓
# cv2.drawContours() 的参数:
# 1. 图像 (img): 要绘制轮廓的图像
# 2. 轮廓 (contours): 要绘制的轮廓列表
# 3. -1: 表示绘制所有轮廓
# 4. 颜色 (0, 0, 255): 红色(BGR格式)
# 5. 厚度 (2): 绘制轮廓的线条厚度
cv2.drawContours(img, contours, -1, (0, 0, 255), thickness=2) # 遍历每个轮廓,计算其外接矩形并绘制
for i in contours: # 获取轮廓的外接矩形,返回值为 (x, y, w, h) x, y, w, h = cv2.boundingRect(i) # 计算矩形的左上角和右下角坐标 top_left = (x, y) # 左上角坐标 bottom_right = (x + w, y + h) # 右下角坐标 # 在图像上绘制外接矩形 cv2.rectangle(img, top_left, bottom_right, color=(0, 0, 255), thickness=2) # 显示处理后的图像
cv2.imshow('img', img) # 等待用户按键后关闭窗口
cv2.waitKey(0)
3.2、最小外接矩形
导入模块
python">import cv2
import numpy as np
输入图像
python">img = cv2.imread('img_.png')
灰度化
python">img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
二值化
python">ret, img_threshold = cv2.threshold(img_gray, 127, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY)
寻找轮廓
python">contours, hierarch = cv2.findContours(img_threshold, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
绘制轮廓
python">cv2.drawContours(img, contours, -1, (0, 0, 255), thickness=2)
绘制外接轮廓
python">for i in contours:rect=cv2.minAreaRect(i)box=np.int32(cv2.boxPoints(rect))cv2.drawContours(img, [box], -1, (0, 255, 0), thickness=2)
输出图像
python">cv2.imshow('img', img)
cv2.waitKey(0)
完整代码
python">import cv2
import numpy as np # 读取图像文件 'img_.png'
img = cv2.imread('img_.png') # 将彩色图像转换为灰度图像
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 使用 Otsu 方法对灰度图像进行阈值处理,生成二值图像
# cv2.threshold() 的参数:
# 1. 输入图像 (img_gray)
# 2. 阈值 (127): 用于二值化的固定阈值,Otsu 方法会自动选择
# 3. 最大值 (255): 二值化后像素的最大值
# 4. 阈值类型 (cv2.THRESH_OTSU + cv2.THRESH_BINARY): 使用 Otsu 方法进行自适应阈值处理
ret, img_threshold = cv2.threshold(img_gray, 127, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY) # 查找轮廓
# cv2.findContours() 的参数:
# 1. 输入图像 (img_threshold)
# 2. 轮廓检索模式 (cv2.RETR_TREE): 检索所有轮廓并重建一个完整的层次结构
# 3. 轮廓近似方法 (cv2.CHAIN_APPROX_SIMPLE): 仅保留轮廓的端点信息
contours, hierarch = cv2.findContours(img_threshold, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) # 在原图像上绘制所有轮廓
# cv2.drawContours() 的参数:
# 1. 图像 (img): 要绘制轮廓的图像
# 2. 轮廓 (contours): 要绘制的轮廓列表
# 3. -1: 表示绘制所有轮廓
# 4. 颜色 (0, 0, 255): 红色(BGR格式)
# 5. 厚度 (2): 绘制轮廓的线条厚度
cv2.drawContours(img, contours, -1, (0, 0, 255), thickness=2) # 遍历每个轮廓,计算其最小外接矩形并绘制
for i in contours: # 获取轮廓的最小外接矩形 rect = cv2.minAreaRect(i) # 将矩形的四个角点获取为整数类型 box = np.int32(cv2.boxPoints(rect)) # box_points 返回的四个点是浮点数,需转为整数 # 在原图像上绘制最小外接矩形 cv2.drawContours(img, [box], -1, (0, 255, 0), thickness=2) # 使用绿色来绘制矩形 # 显示处理后的图像
cv2.imshow('img', img) # 等待用户按键后关闭窗口
cv2.waitKey(0)
3.3、最小外接圆
导入模块
python">import cv2
import numpy as np
输入图像
python">img = cv2.imread('img_.png')
灰度化
python">img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
二值化
python">ret, img_threshold = cv2.threshold(img_gray, 127, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY)
寻找轮廓
python">contours, hierarch = cv2.findContours(img_threshold, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
绘制轮廓
python">cv2.drawContours(img, contours, -1, (0, 0, 255), thickness=2)
绘制外接轮廓
python">for i in contours:(x,y),radius=cv2.minEnclosingCircle(i)(x,y,radius)=np.int32((x,y,radius))cv2.circle(img,(x,y),radius,(255,0,0),thickness=2)
输出图像
python">cv2.imshow('img', img)
cv2.waitKey(0)
完整代码
python">import cv2
import numpy as np # 读取图像文件 'img_.png'
img = cv2.imread('img_.png') # 将彩色图像转换为灰度图像
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 使用 Otsu 方法对灰度图像进行阈值处理,生成二值图像
# cv2.threshold() 的参数:
# 1. 输入图像 (img_gray)
# 2. 阈值 (127): 用于二值化的固定阈值,Otsu 方法会自动选择
# 3. 最大值 (255): 二值化后像素的最大值
# 4. 阈值类型 (cv2.THRESH_OTSU + cv2.THRESH_BINARY): 使用 Otsu 方法进行自适应阈值处理
ret, img_threshold = cv2.threshold(img_gray, 127, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY) # 查找轮廓
# cv2.findContours() 的参数:
# 1. 输入图像 (img_threshold)
# 2. 轮廓检索模式 (cv2.RETR_TREE): 检索所有轮廓并重建一个完整的层次结构
# 3. 轮廓近似方法 (cv2.CHAIN_APPROX_SIMPLE): 仅保留轮廓的端点信息
contours, hierarch = cv2.findContours(img_threshold, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) # 在原图像上绘制所有轮廓
# cv2.drawContours() 的参数:
# 1. 图像 (img): 要绘制轮廓的图像
# 2. 轮廓 (contours): 要绘制的轮廓列表
# 3. -1: 表示绘制所有轮廓
# 4. 颜色 (0, 0, 255): 红色(BGR格式)
# 5. 厚度 (2): 绘制轮廓的线条厚度
cv2.drawContours(img, contours, -1, (0, 0, 255), thickness=2) # 遍历每个轮廓,计算其最小外接圆并绘制
for i in contours: # 计算轮廓的最小外接圆 (x, y), radius = cv2.minEnclosingCircle(i) # 将圆心坐标和半径转换为整数 (x, y, radius) = np.int32((x, y, radius)) # 在原图像上绘制最小外接圆 cv2.circle(img, (x, y), radius, (255, 0, 0), thickness=2) # 使用蓝色来绘制圆 # 显示处理后的图像
cv2.imshow('img', img) # 等待用户按键后关闭窗口
cv2.waitKey(0)
四、库函数
4.1、findContours()
python">cv.findContours( image, mode, method[, contours[, hierarchy[, offset]]] ) -> contours, hierarchy
方法 | 描述 |
---|---|
image | 源,一个 8 位单通道图像。非零像素被视为 1。零像素仍为 0,因此图像被视为 binary 。您可以使用 compare、inRange、threshold、adaptiveThreshold、Canny 等创建灰度或彩色图像的二进制图像。如果 mode 等于 RETR_CCOMP 或 RETR_FLOODFILL,则输入也可以是标签的 32 位整数图像 (CV_32SC1)。 |
contours | 检测到的轮廓。每个轮廓都存储为点向量(例如 std::vector<std::vector<cv::P oint> >)。 |
hierarchy | 可选的输出向量(例如 std::vector<cv::Vec4i>),包含有关图像拓扑的信息。它的单元数与等值线的数量一样多。对于每个第 i 个轮廓轮廓[i],元素 hierarchy[i][0] 、hierarchy[i][1] 、hierarchy[i][2] 和 hierarchy[i][3] 在同一层次结构级别的下一个和上一个轮廓的轮廓中分别设置为从 0 开始的索引,即第一个子轮廓和父轮廓。如果轮廓 i 没有 next、previous 、parent 或 nested 等值线,则 hierarchy[i] 的相应元素将为负数。 |
mode | 等值线检索模式,参见 RetrievalModes |
method | 等值线近似方法,请参阅 ContourApproximationModes |
offset | 每个等值线点移动的可选偏移量。如果轮廓是从图像 ROI 中提取的,然后应该在整个图像上下文中对其进行分析,这将非常有用。 |
RETR_EXTERNAL Python:cv.RETR_EXTERNAL | 仅检索极端外轮廓。它为所有轮廓设置。 |
RETR_LIST Python:cv.RETR_LIST | 检索所有等值线,而不建立任何分层关系。 |
RETR_CCOMP Python:cv.RETR_CCOMP | 检索所有等值线并将它们组织到一个两级层次结构中。在顶层,有组件的外部边界。在第二层,有孔的边界。如果已连接零部件的孔内有其他轮廓,则仍将其置于顶层。 |
RETR_TREE Python:cv.RETR_TREE | 检索所有等值线并重新构建嵌套等值线的完整层次结构。 |
RETR_FLOODFILL Python:cv.RETR_FLOODFILL |
CHAIN_APPROX_NONE Python:cv.CHAIN_APPROX_NONE | 绝对存储所有等高线点。也就是说,等值线的任意 2 个后续点 (x1,y1) 和 (x2,y2) 将是水平、垂直或对角线相邻点,即 max(abs(x1-x2),abs(y2-y1))==1。 |
CHAIN_APPROX_SIMPLE Python:cv.CHAIN_APPROX_SIMPLE | 压缩水平、垂直和对角线段,仅保留其端点。例如,一个直立的矩形轮廓用 4 个点编码。 |
CHAIN_APPROX_TC89_L1 Python:cv.CHAIN_APPROX_TC89_L1 | 应用了 Teh-Chin 链近似算法的一种风格 [266] |
CHAIN_APPROX_TC89_KCOS Python:cv.CHAIN_APPROX_TC89_KCOS | 应用了 Teh-Chin 链近似算法的一种风格 opencv.org/4.11.0/d0/de3/citelist.html#CITEREF_tehchin89" rel="nofollow" title="[266]">[266] |
4.2、drawContours()
python">cv.drawContours( image, contours, contourIdx, color[, thickness[, lineType[, hierarchy[, maxLevel[, offset]]]]] ) -> image
方法 | 描述 |
---|---|
image | 目标图像。 |
contours | 所有输入等值线。每个等值线都存储为点向量。 |
contourIdx | 指示要绘制的轮廓的参数。如果为负数,则绘制所有等值线。 |
color | 等值线的颜色。 |
thickness | 绘制等高线时使用的线条粗细。如果为负数(例如, thickness=FILLED ),则绘制等值线内部 |
lineType | 线路连接。请参阅线型 |
hierarchy | 有关层次结构的可选信息。仅当您只想绘制部分等值线时才需要它(请参阅 maxLevel )。 |
maxLevel | 绘制轮廓的最大级别。如果为 0,则仅绘制指定的轮廓。如果为 1,则函数绘制 contour 和所有嵌套 contour。如果为 2,则函数绘制等值线、所有嵌套等值线、所有嵌套到嵌套的等值线,依此类推。仅当有可用的层次结构时,才会考虑此参数。 |
offset | 可选的轮廓偏移参数。将所有绘制的轮廓移动指定![]() |
注意
当 thickness=FILLED 时,该函数旨在正确处理具有孔的连通零部件,即使未提供层次结构数据也是如此。这是通过使用奇偶规则一起分析所有轮廓来完成的。如果您有单独检索的轮廓的联合集合,这可能会产生不正确的结果。为了解决这个问题,你需要为每个 contour 的子组单独调用 drawContours,或者使用 contourIdx 参数迭代集合。
4.3、boundingRect()
计算点集的右上边界矩形或灰度图像的非零像素。
该函数计算并返回指定点集或灰度图像的非零像素的最小右上边界矩形。
python">cv.boundingRect( array ) -> retval
函数 | 描述 |
---|---|
array | 输入灰度图像或 2D 点集,存储在 std::vector 或 Mat 中。 |
4.4、rectangle()
python">cv.rectangle( img, pt1, pt2, color[, thickness[, lineType[, shift]]] ) -> img
cv.rectangle( img, rec, color[, thickness[, lineType[, shift]]] ) -> img
函数 | 描述 |
---|---|
img | 图像。 |
pt1 | 矩形的顶点。 |
pt2 | 与 pt1 相对的矩形的顶点。 |
color | 矩形颜色或亮度(灰度图像)。 |
thickness | 构成矩形的线条的粗细。负值(如 FILLED)表示函数必须绘制填充矩形 -1表示实心 1,2,...表示线的粗细 |
lineType | 线路的类型。 |
shift | 点坐标中的小数位数。 |
4.5、minAreaRect()
查找包含输入 2D 点集的最小区域的旋转矩形。
该函数计算并返回指定点集的最小面积边界矩形(可能已旋转)。开发人员应记住,当数据接近包含的 Mat 元素边界时,返回的 RotatedRect 可以包含负索引。
python">cv.minAreaRect( points ) -> retval
函数 | 描述 |
---|---|
points | 点的输入向量,存储在 std::vector 或 Mat <> 中 |
4.6、boxPoints()
查找旋转矩形的四个顶点。用于绘制旋转的矩形。
该函数查找旋转矩形的四个顶点。此函数可用于绘制矩形。
cv.boxPoints( box[, points] ) -> points
函数 | 描述 |
---|---|
box | 输入旋转矩形。它可能是 minAreaRect 的输出。 |
points | 矩形的四个顶点的输出数组。 |
4.7、minEnclosingCircle()
查找包含 2D 点集的最小面积的圆。
该函数使用迭代算法找到 2D 点集的最小封闭圆。
cv.minEnclosingCircle( points ) -> center, radius
函数 | 描述 |
---|---|
points | 2D 点的输入向量,存储在 std::vector 或 Mat <> 中 |
center | 圆的输出中心。 |
radius | 圆的输出半径。 |
4.8、circle()
绘制一个圆。
函数 cv::circle 绘制一个具有给定圆心和半径的简单圆或实心圆。
cv.circle( img, center, radius, color[, thickness[, lineType[, shift]]] ) -> img
函数 | 描述 |
---|---|
img | 绘制圆的图像。 |
center | 圆心。 |
radius | 圆的半径。 |
color | 圆形颜色。 |
thickness | 圆轮廓的粗细(如果为正)。负值(如 FILLED)表示要绘制实心圆。 |
lineType | 圆边界的类型。请参阅线型 |
shift | center 坐标和 radius 值中的小数位数。 |