Python-OpenCV 轮廓进阶:旋转最小外接矩形、图像矩求质心、嵌套层级 hierarchy、轮廓形状匹配

目录

[一、普通 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 颜色分割,按照颜色提取画面目标物体,不依赖灰度阈值,机器视觉另一种重要目标提取方案。

相关推荐
用户8356290780511 小时前
如何使用 Python 将 PowerPoint 转换为 PDF 文档
后端·python
菜冻鱼1 小时前
Python-pytorch-高级技巧
开发语言·人工智能·pytorch·python·深度学习·神经网络·聚类
lpfasd1231 小时前
python webview打包版卡死、开发版正常
开发语言·python
用户8356290780511 小时前
如何使用 Python 为 PowerPoint 幻灯片添加切换效果
后端·python
菜冻鱼1 小时前
Python-pytorch-模型保存与加载
开发语言·人工智能·pytorch·python·深度学习·机器学习
亿牛云爬虫专家2 小时前
爬虫调度系统设计:用 Celery 实现按媒体权重动态调整的抓取频率控制
爬虫·python·隧道代理·舆情采集·媒体权重·频率控制·ip自动轮换
Code额3 小时前
Python 连接 DeepSeek API,OpenAI
开发语言·python·ai·ai编程
Suhan423 小时前
SQLAlchemy的两种查询query()查询、stmt=select()查询
数据库·python·sql·oracle
AAA@峥4 小时前
Python 基础开篇:认识 Python、版本选择、环境部署与首个 HelloWorld
开发语言·python
Metaphor6924 小时前
使用 Python 快速在 Word 文档中插入图片
python·word