目录
[一、普通 boundingRect 垂直外接矩形的局限性](#一、普通 boundingRect 垂直外接矩形的局限性)
[二、minAreaRect 旋转最小外接矩形(倾斜物体框选)](#二、minAreaRect 旋转最小外接矩形(倾斜物体框选))
[三、图像矩 moments,计算物体质心(中心点)](#三、图像矩 moments,计算物体质心(中心点))
[四、hierarchy 嵌套轮廓层级解析](#四、hierarchy 嵌套轮廓层级解析)
[五、matchShapes 轮廓形状相似度比对](#五、matchShapes 轮廓形状相似度比对)
[6.1 轮廓凸包 convexHull](#6.1 轮廓凸包 convexHull)
[6.2 点多边形测试 pointPolygonTest](#6.2 点多边形测试 pointPolygonTest)
[七、综合实战:摄像头实时 ------ 倾斜物体 + 质心检测 + 实时计数](#七、综合实战:摄像头实时 —— 倾斜物体 + 质心检测 + 实时计数)
[八、轮廓进阶 API 汇总表](#八、轮廓进阶 API 汇总表)
上一章我们学习了轮廓基础操作,掌握 findContours 查找轮廓、普通垂直外接矩形、面积周长、多边形逼近,完成简单物体计数。但普通垂直外接矩形 boundingRect 只能生成边平行图片边框的矩形,面对倾斜摆放的零件、带孔洞圆环工件、求取物体中心点、比对物体外形相似度等场景,就无法满足需求。
本章作为轮廓进阶专题,讲解工业视觉高频 API:旋转最小外接矩形 minAreaRect、图像矩 moments 计算物体质心、hierarchy 嵌套轮廓层级解析、matchShapes 轮廓形状相似度比对。同时补充轮廓凸包、点多边形测试拓展知识点。
一、普通 boundingRect 垂直外接矩形的局限性
cv2.boundingRect() 输出矩形四条边始终平行图片坐标轴。当目标物体倾斜旋转,包围框会纳入大量背景像素,无法贴合物体真实外轮廓。
python
import cv2
import numpy as np
#=========【请替换为自己本地图片路径】=========
img = cv2.imread(r"D:\PYTHON\test.jpg")
if img is None:
print("图片读取失败,请检查路径")
exit()
#标准预处理流程
blur = cv2.GaussianBlur(img, (3, 3), 0)
gray = cv2.cvtColor(blur, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray, 120, 255, cv2.THRESH_BINARY)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
binary = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)
contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
draw_img = img.copy()
for cnt in contours:
area = cv2.contourArea(cnt)
# 面积过滤,剔除噪声生成的微小轮廓
if area < 100:
continue
# 普通垂直外接矩形,四条边平行图片坐标轴
x, y, w, h = cv2.boundingRect(cnt)
cv2.rectangle(draw_img, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv2.imshow("普通垂直外接矩形 boundingRect", draw_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
现象:物体发生倾斜时,绿色包围框包含多余背景,不能紧贴物体。处理倾斜物体需要使用 minAreaRect 旋转最小外接矩形。
二、minAreaRect 旋转最小外接矩形(倾斜物体框选)
函数说明:
python
rot_rect = cv2.minAreaRect(cnt)
输入单个轮廓对象 cnt,返回元组 ((cx, cy), (w, h), angle):
- (cx, cy):旋转矩形中心点坐标
- (w, h):矩形的宽度、高度
- angle:旋转角度
重要注意:minAreaRect 返回的是抽象参数,不能直接绘图,需要 cv2.boxPoints() 转换为四个角点;boxPoints 输出浮点数,绘图接口要求整数,必须使用 np.int32() 强转;OpenCV 中该角度取值范围为 [-90, 0),受版本、图像坐标系影响,工程不要直接拿来做精确角度测量,仅作演示参考。
python
import cv2
import numpy as np
#=========【请替换为自己本地图片路径】=========
img = cv2.imread(r"D:\PYTHON\test.jpg")
if img is None:
print("图片读取失败,请检查路径")
exit()
blur = cv2.GaussianBlur(img, (3, 3), 0)
gray = cv2.cvtColor(blur, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray, 120, 255, cv2.THRESH_BINARY)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
binary = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)
contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
draw_img = img.copy()
for cnt in contours:
area = cv2.contourArea(cnt)
if area < 100:
continue
# 获取旋转最小外接矩形
rot_rect = cv2.minAreaRect(cnt)
(cx, cy), (w, h), angle = rot_rect
print(f"旋转矩形中心({cx:.1f},{cy:.1f}), 宽{w:.1f}, 高{h:.1f}, 角度{angle:.1f}")
#转换得到矩形四个角点(浮点)
box = cv2.boxPoints(rot_rect)
#转为 int 整数,绘图函数要求整数坐标
box = np.int32(box)
#使用 drawContours 绘制旋转四边形
cv2.drawContours(draw_img, [box], 0, (0, 0, 255), 2)
#绘制中心点
cv2.circle(draw_img, (int(cx), int(cy)), 3, (0, 255, 0), -1)
cv2.imshow("minAreaRect旋转最小外接矩形", draw_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
适用场景:倾斜零件、歪斜药片、工件定位。
三、图像矩 moments,计算物体质心(中心点)
图像矩基于轮廓内像素权重分布,可以计算目标物体几何质心,常用于目标跟踪、输出抓取坐标。
计算公式(文本替代 LaTeX,兼容全部博客平台):
- M00:轮廓像素面积,等价于 cv2.contourArea()
- M10、M01:一阶空间矩
- 质心:cx = M10 / M00;cy = M01 / M00
必须做分母非零判断!M00 等于 0 会触发除零异常。
python
import cv2
import numpy as np
#=========【请替换为自己本地图片路径】=========
img = cv2.imread(r"D:\PYTHON\test.jpg")
if img is None:
print("图片读取失败,请检查路径")
exit()
blur = cv2.GaussianBlur(img, (3, 3), 0)
gray = cv2.cvtColor(blur, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray, 120, 255, cv2.THRESH_BINARY)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
binary = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)
contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
draw_img = img.copy()
for cnt in contours:
area = cv2.contourArea(cnt)
if area < 100:
continue
# 计算轮廓矩
M = cv2.moments(cnt)
#防止除零报错
if M["m00"] != 0:
cx = int(M["m10"] / M["m00"])
cy = int(M["m01"] / M["m00"])
# 质心绘制蓝色圆点
cv2.circle(draw_img, (cx, cy), 4, (255, 0, 0), -1)
#打印坐标文本
cv2.putText(draw_img, f"({cx},{cy})", (cx + 8, cy), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 0, 0), 1)
cv2.imshow("轮廓质心(蓝色圆点)", draw_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
用途:目标跟踪、机器视觉抓取点位输出。
四、hierarchy 嵌套轮廓层级解析
当图像中存在嵌套结构(如带孔洞的圆环工件、同心圆、字符内外轮廓)时,RETR_EXTERNAL 只能提取最外层轮廓,无法表达轮廓之间的父子关系。此时需要借助 hierarchy 层级信息。
findContours 返回的 hierarchy 形状为 (1, N, 4),每个轮廓对应四个值 Next, Previous, First_Child, Parent:
- Next:同一层级下一条轮廓的索引,无则 -1
- Previous:同一层级上一条轮廓的索引,无则 -1
- First_Child:第一个子轮廓的索引,无则 -1
- Parent:父轮廓的索引,无则 -1
使用 RETR_TREE 模式可以完整提取所有层级的嵌套关系,再通过 Parent 字段判断轮廓是否为内孔(父轮廓存在即为孔洞)。
python
import cv2
import numpy as np
#=========【请替换为自己本地图片路径】=========
img = cv2.imread(r"D:\PYTHON\test.jpg")
if img is None:
print("图片读取失败,请检查路径")
exit()
blur = cv2.GaussianBlur(img, (3, 3), 0)
gray = cv2.cvtColor(blur, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray, 120, 255, cv2.THRESH_BINARY)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
binary = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)
#使用 RETR_TREE 提取全部层级
contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
draw_img = img.copy()
for i, cnt in enumerate(contours):
area = cv2.contourArea(cnt)
if area < 100:
continue
# 读取当前轮廓的层级信息
next_idx, prev_idx, child_idx, parent_idx = hierarchy[0][i]
#有父轮廓说明是内孔,用红色绘制;否则为外轮廓,用绿色绘制
if parent_idx != -1:
cv2.drawContours(draw_img, [cnt], -1, (0, 0, 255), 2)
print(f"轮廓{i}: 内孔, 父轮廓{parent_idx}")
else:
cv2.drawContours(draw_img, [cnt], -1, (0, 255, 0), 2)
print(f"轮廓{i}: 外轮廓, 子轮廓{child_idx}")
cv2.imshow("hierarchy 嵌套层级(绿外红内)", draw_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
适用场景:带孔洞圆环工件检测、字符内外轮廓分离、同心圆定位。
五、matchShapes 轮廓形状相似度比对
matchShapes 基于 Hu 矩计算两个轮廓的形状相似度,返回值越小表示形状越接近,完全相同时返回 0。该函数对平移、旋转、缩放具有一定不变性,适合做外形相似度比对。注意:matchShapes 受轮廓噪声影响很大,使用前务必先做降噪预处理;返回值不是"相似度百分比",只是差异度量,没有固定阈值万能适配,需要根据自己样本调试阈值。
python
import cv2
import numpy as np
#=========【请替换为自己本地图片路径】=========
img = cv2.imread(r"D:\PYTHON\test.jpg")
if img is None:
print("图片读取失败,请检查路径")
exit()
blur = cv2.GaussianBlur(img, (3, 3), 0)
gray = cv2.cvtColor(blur, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray, 120, 255, cv2.THRESH_BINARY)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
binary = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)
contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#以第一个有效轮廓作为模板
template = None
for cnt in contours:
if cv2.contourArea(cnt) > 100:
template = cnt
break
if template is None:
print("未找到有效轮廓")
exit()
draw_img = img.copy()
for i, cnt in enumerate(contours):
area = cv2.contourArea(cnt)
if area < 100:
continue
# 计算与模板的形状相似度,返回值越小越相似
ret = cv2.matchShapes(template, cnt, cv2.CONTOURS_MATCH_I2, 0.0)
print(f"轮廓{i} 与模板相似度: {ret:.4f}")
# 注意:0.1 只是演示阈值,不是通用万能值,需根据自己样本调试
if ret < 0.1:
cv2.drawContours(draw_img, [cnt], -1, (0, 255, 0), 2)
else:
cv2.drawContours(draw_img, [cnt], -1, (0, 0, 255), 2)
cv2.imshow("matchShapes 形状匹配(绿匹配红不匹配)", draw_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
适用场景:工件分拣、外形缺陷筛选、模板匹配。
六、拓展:轮廓凸包与点多边形测试
6.1 轮廓凸包 convexHull
凸包是包围轮廓所有点的最小凸多边形,常用于检测物体凹陷缺陷、指尖识别、手势分析等场景。
python
import cv2
import numpy as np
#=========【请替换为自己本地图片路径】=========
img = cv2.imread(r"D:\PYTHON\test.jpg")
if img is None:
print("图片读取失败,请检查路径")
exit()
blur = cv2.GaussianBlur(img, (3, 3), 0)
gray = cv2.cvtColor(blur, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray, 120, 255, cv2.THRESH_BINARY)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
binary = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)
contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
draw_img = img.copy()
for cnt in contours:
area = cv2.contourArea(cnt)
if area < 100:
continue
# 计算凸包,返回凸包顶点索引
hull = cv2.convexHull(cnt)
# 绘制凸包
cv2.drawContours(draw_img, [hull], -1, (0, 255, 255), 2)
cv2.imshow("convexHull 凸包(黄色)", draw_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
6.2 点多边形测试 pointPolygonTest
pointPolygonTest 判断一个点与轮廓的位置关系,返回值为正表示点在轮廓内部,为负表示在外部,为 0 表示在轮廓边界上。配合 measureDist=True 可返回点到轮廓的最短距离。
python
import cv2
import numpy as np
#=========【请替换为自己本地图片路径】=========
img = cv2.imread(r"D:\PYTHON\test.jpg")
if img is None:
print("图片读取失败,请检查路径")
exit()
blur = cv2.GaussianBlur(img, (3, 3), 0)
gray = cv2.cvtColor(blur, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray, 120, 255, cv2.THRESH_BINARY)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
binary = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)
contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#取第一个有效轮廓
cnt = None
for c in contours:
if cv2.contourArea(c) > 100:
cnt = c
break
if cnt is None:
print("未找到有效轮廓")
exit()
#测试点坐标(请根据实际图像调整)
test_point = (200, 200)
#measureDist=True 返回带符号距离
dist = cv2.pointPolygonTest(cnt, test_point, True)
print(f"测试点{test_point} 到轮廓距离: {dist:.2f}")
if dist > 0:
print("点在轮廓内部")
elif dist < 0:
print("点在轮廓外部")
else:
print("点在轮廓边界上")
七、摄像头实时 ------ 倾斜物体 + 质心检测 + 实时计数
前面我们分别学习了 minAreaRect 旋转最小外接矩形、moments 质心计算和轮廓面积过滤。本节把它们整合起来,直接对接摄像头,实现实时检测画面中的倾斜物体、绘制旋转外接框、标注质心,并统计当前画面中的物体总数。
python
import cv2
import numpy as np
vc = cv2.VideoCapture(0)
cv2.namedWindow("摄像头轮廓进阶", cv2.WINDOW_NORMAL)
cv2.resizeWindow("摄像头轮廓进阶", 800, 600)
min_area = 180
while vc.isOpened():
ret, frame = vc.read()
if not ret:
break
# 标准预处理:高斯降噪 → 灰度 → 阈值 → 形态学去噪
blur = cv2.GaussianBlur(frame, (3, 3), 0)
gray = cv2.cvtColor(blur, cv2.COLOR_BGR2GRAY)
ret_bin, binary = cv2.threshold(gray, 110, 255, cv2.THRESH_BINARY)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
binary = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)
contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
draw_frame = frame.copy()
obj_count = 0
for cnt in contours:
area = cv2.contourArea(cnt)
if area < min_area:
continue
obj_count += 1
# 旋转最小外接矩形,贴合倾斜物体
rot_rect = cv2.minAreaRect(cnt)
box = np.int32(cv2.boxPoints(rot_rect))
cv2.drawContours(draw_frame, [box], 0, (0, 0, 255), 2)
#计算质心
M = cv2.moments(cnt)
if M["m00"] != 0:
cx = int(M["m10"] / M["m00"])
cy = int(M["m01"] / M["m00"])
cv2.circle(draw_frame, (cx, cy), 4, (255, 0, 0), -1)
#普通外接矩形,用于放置文字
x, y, w, h = cv2.boundingRect(cnt)
cv2.putText(draw_frame, f"Obj:{obj_count} Area:{int(area)}",
(x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
#左上角显示实时总数
cv2.putText(draw_frame, f"Total: {obj_count}", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
cv2.imshow("摄像头轮廓进阶", draw_frame)
key = cv2.waitKey(1000 // 24)
if key & 0xFF == ord('q'):
break
vc.release()
cv2.destroyAllWindows()
代码要点说明:
- minAreaRect 配合 boxPoints 和 np.int32 绘制旋转外接框,解决倾斜物体框选问题;
- moments 通过 M10/M00、M01/M00 计算质心,务必判断 M"m00" != 0 防止除零;
- min_area 面积过滤剔除噪声产生的微小伪轮廓,只统计有效物体;
- 每帧实时统计 obj_count,左上角显示 Total 总数,按 q 键退出。
提醒:轮廓检测函数输入必须是单通道二值图像。标准预处理流水线:原图 → 高斯降噪 → 灰度转换 → 阈值分割 → 形态学去噪 → 轮廓查找。掌握这些进阶 API,即可应对工业视觉中倾斜定位、中心点输出、嵌套结构解析和形状筛选等常见需求。
八、轮廓进阶 API 汇总表
| 函数 | 功能说明 |
|---|---|
| cv2.minAreaRect(cnt) | 获取轮廓旋转最小外接矩形,返回中心点、宽高、角度 |
| cv2.boxPoints(rot_rect) | 将旋转矩形转为 4 个角点坐标,绘图必备 |
| cv2.moments(cnt) | 计算轮廓矩,求解物体质心 cx, cy |
| cv2.matchShapes(cnt1, cnt2, ...) | 比对两个轮廓外形相似度;数值越小外形越相似 |
| hierarchy0i | 轮廓层级 next, prev, child, parent,用于处理孔洞嵌套 |
| cv2.convexHull(cnt) | 获取轮廓凸包,得到最小凸多边形 |
| cv2.pointPolygonTest(cnt, (x, y), True) | 判断点和轮廓内外位置关系,返回距离 |
九、本章高频踩坑清单
- minAreaRect 返回的结构不能直接绘图,必须使用 boxPoints 转换角点,并且转为 np.int32 整数;
- 使用 moments 求取质心,必须判断 M"m00" != 0,避免除零程序崩溃;
- 需要解析孔洞嵌套轮廓,findContours 检索模式设置 RETR_TREE / RETR_CCOMP,RETR_EXTERNAL 直接丢弃子轮廓;
- matchShapes 对旋转比较敏感,工程使用前建议做图像降噪平滑;
- minAreaRect 输出角度存在版本差异,不要直接用于高精度工程角度测量;
- findContours 输入必须是单通道二值图,传入彩色图会出现异常结果;
- 文件路径使用 r 原始字符串,防止转义字符造成图片读取返回 None;
- 面积过滤必不可少,设置合理最小面积阈值,过滤噪声产生的微小伪轮廓。
十、本章知识小结
boundingRect 输出平行坐标轴的垂直矩形,倾斜物体效果差;minAreaRect 得到旋转最小外接矩形,适配歪斜目标,配合 boxPoints 拿到角点再绘图;图像矩 moments 基于像素权重分布计算物体质心,用于目标跟踪、点位输出,务必做 M00 非零判断,防止除零报错;hierarchy 轮廓层级可以区分父轮廓(外层物体)、子轮廓(内部孔洞),检索模式选择 RETR_TREE;matchShapes 实现轮廓外形比对,返回数值越接近 0 外形相似度越高,对旋转较为敏感;拓展 API:convexHull 获取凸包,pointPolygonTest 检测点相对轮廓的位置;标准处理链路:降噪 → 灰度 → 阈值 → 形态学清理噪点;可以直接对接摄像头实现实时检测计数。
下一章:OpenCV HSV 颜色空间与 inRange 颜色分割,按照颜色提取画面目标物体,不依赖灰度阈值,机器视觉另一种重要目标提取方案。