1. 项目概述从轮廓到几何特征的精准捕捉在计算机视觉和图像处理的实际项目中我们常常需要从一张图片中识别出目标物体并对其进行量化和分析。比如在工业质检中我们需要知道一个零件的长宽尺寸在自动驾驶中需要框出车辆或行人的位置甚至在文档扫描应用中需要自动矫正倾斜的纸张。这些需求的核心都离不开一个基础操作从目标轮廓中提取出能够描述其位置、大小和形状的几何特征。opencv-python库为我们提供了强大的轮廓处理工具链其中外接矩形、最小外接矩形、多边形拟合和外接圆是四种最常用、也最核心的几何特征描述方法。它们就像是给一个形状模糊的物体套上不同“模具”从而得到标准化的、可度量的几何参数。很多新手可能会混淆这些概念或者只知道调用函数却不清楚背后的原理和适用场景。比如一个倾斜的条形码用普通外接矩形框出来的面积会很大且包含大量背景而最小外接矩形则能紧密贴合更准确地反映其真实朝向和尺寸。这篇文章我将结合自己多年在图像算法开发中踩过的坑为你彻底拆解这四种几何特征。我不会只给你罗列函数API而是会深入讲解它们各自的数学原理、计算过程、在OpenCV中的具体实现以及最重要的——在什么场景下该用哪一个以及如何避免常见的陷阱。无论你是正在做课程设计的学生还是需要解决实际工程问题的开发者相信这些从实战中总结出的经验都能让你少走弯路。2. 核心概念辨析四种几何特征的本质差异在深入代码之前我们必须从几何意义上厘清这四者的区别。这是正确选型的基础否则很容易“张冠李戴”得出错误的分析结果。2.1 外接矩形 (Bounding Rectangle)这是最直观、计算最快的一种。外接矩形顾名思义就是一个能完全包围住目标轮廓的、边与图像坐标轴平行即水平/垂直的矩形。它只关心轮廓在X轴和Y轴方向上的最大、最小范围。计算原理遍历轮廓上的所有像素点分别找出X坐标的最大值(x_max)、最小值(x_min)和Y坐标的最大值(y_max)、最小值(y_min)。那么矩形的左上角顶点就是(x_min, y_min)宽度为(x_max - x_min)高度为(y_max - y_min)。特点由于它的边平行于坐标轴所以这个矩形不会随着物体的旋转而旋转。当物体倾斜时外接矩形会变得很大包含很多非物体区域的背景。OpenCV函数cv2.boundingRect(contour)生活类比就像给一个任意形状的玩具找一个刚好能装下它的、正放的方形纸盒。玩具如果斜着放纸盒就必须做得更大。2.2 最小外接矩形 (Minimum Area Rectangle / Rotated Rectangle)这是外接矩形的“升级版”。它寻找的是能够包围轮廓的所有可能的矩形中面积最小的那一个。这个矩形的边不一定平行于坐标轴它可以旋转任意角度来紧密贴合目标。计算原理OpenCV通常采用“旋转卡壳”算法。简单理解算法会尝试用一对平行的“卡尺”夹住轮廓并旋转这对卡尺寻找在某个角度下平行线之间的垂直距离即矩形的一边和另一对垂直平行线之间的距离矩形的另一边乘积即面积最小的状态。特点能更精确地反映倾斜物体的实际朝向和最小包围尺寸。返回的结果通常包含矩形的中心点(cx, cy)、宽度(width)、高度(height)和旋转角度(angle)。这里有个巨坑OpenCV中这个角度angle的范围是[-90, 0)度表示矩形从水平位置宽度边为水平逆时针旋转的角度。当矩形“站立”时高度大于宽度宽度和高度可能会互换需要小心处理。OpenCV函数cv2.minAreaRect(contour)返回一个RotatedRect对象。用cv2.boxPoints(rotated_rect)可以获取它的四个顶点坐标。生活类比给同一个倾斜的玩具找一个可以定制角度的、刚好紧密包裹它的最小长方形礼品盒。这个盒子是贴着玩具边缘的。2.3 多边形拟合 (Polygon Approximation)前面两种方法得到的都是矩形但很多物体并不是矩形的。多边形拟合的目标是用一个顶点更少的、更简单的多边形来近似表示复杂的轮廓保留其主要形状特征同时过滤掉噪声和细节。计算原理最常用的算法是Douglas-Peucker算法OpenCV中cv2.approxPolyDP的实现。它的思想很巧妙轮廓是一条由许多点组成的曲线。算法首先连接曲线的首尾两点得到一条线段。然后找出原始曲线上离这条线段最远的点如果这个距离大于设定的阈值epsilon就把这个点保留为关键顶点。接着用这个关键点将曲线分成两段对每一段递归地重复这个过程。最终所有保留下的点就构成了拟合后的多边形。特点epsilon参数是关键它是精度阈值。值越大拟合出的多边形越简单顶点越少但与原始轮廓差异越大值越小拟合越精确但顶点越多越接近原始轮廓。常用于判断轮廓形状如通过顶点数判断是三角形、矩形还是多边形、轮廓简化或压缩存储。OpenCV函数cv2.approxPolyDP(curve, epsilon, closed)生活类比用有限的几根直线段比如6根或8根去拼出一个复杂地图如中国地图的轮廓。线段越少轮廓越抽象线段越多越接近真实形状。2.4 外接圆 (Minimum Enclosing Circle)寻找一个能完全包围轮廓的最小的圆。它用一个中心点和半径来描述轮廓。计算原理OpenCV内部通常采用一种迭代算法或者更高效的Welzl算法随机增量法。算法目标是找到一个圆使得所有轮廓点都在圆内或圆上并且圆的半径尽可能小。特点对于长宽比接近1的、近似圆形的物体如细胞、硬币、瓶盖外接圆是非常好的描述符。它的表示非常简洁一个中心点半径并且具有旋转不变性。但对于细长的物体如一支笔外接圆会非常大效率低下且不精确。OpenCV函数cv2.minEnclosingCircle(contour)返回圆心(x, y)和半径radius。生活类比给一个物体套上一个最小尺寸的“泡泡”或“光环”。注意这四种方法都是基于已检测到的轮廓(contour)进行计算的。轮廓检测如使用cv2.findContours的质量直接决定了这些几何特征提取的准确性。如果轮廓本身有断裂、噪声或粘连那么提取出的几何特征也是不可靠的。3. OpenCV实战函数详解与代码实现理论清楚了我们直接上代码看看在opencv-python中如何具体使用这些函数并可视化结果。我会用一个同时包含矩形、三角形和圆形物体的图片作为示例。3.1 环境准备与图像预处理首先确保你已经安装了OpenCV。如果没有通过pip安装pip install opencv-python。我们将创建一个模拟图像并对其进行预处理以得到清晰的轮廓。import cv2 import numpy as np import matplotlib.pyplot as plt # 创建一个黑色背景的画布 canvas np.zeros((400, 600, 3), dtypenp.uint8) # 在画布上绘制几个形状作为我们的“目标物体” # 1. 一个红色的倾斜矩形 (模拟一个倾斜的卡片) rect_points np.array([[100, 50], [250, 80], [230, 180], [80, 150]], np.int32) cv2.fillPoly(canvas, [rect_points], (0, 0, 255)) # 红色 # 2. 一个绿色的三角形 triangle_points np.array([[400, 100], [500, 100], [450, 200]], np.int32) cv2.fillPoly(canvas, [triangle_points], (0, 255, 0)) # 绿色 # 3. 一个蓝色的圆形 (模拟一个硬币或瓶盖) center (300, 300) radius 40 cv2.circle(canvas, center, radius, (255, 0, 0), -1) # 蓝色填充 # 为了轮廓检测需要将彩色图像转为灰度并二值化 gray cv2.cvtColor(canvas, cv2.COLOR_BGR2RGB) # 注意matplotlib用RGBOpenCV用BGR gray cv2.cvtColor(gray, cv2.COLOR_RGB2GRAY) _, binary cv2.threshold(gray, 1, 255, cv2.THRESH_BINARY) # 查找轮廓 # 使用cv2.RETR_EXTERNAL只检测最外层轮廓cv2.CHAIN_APPROX_SIMPLE压缩轮廓点 contours, _ cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 创建一个用于绘制结果的图像副本 result_img canvas.copy()这段代码创建了一个包含三种形状的图像并将其二值化然后使用cv2.findContours找到了三个独立的轮廓。cv2.CHAIN_APPROX_SIMPLE参数很重要它会压缩水平、垂直和对角线方向的冗余点只保留端点从而节省内存。对于我们的几何计算这足够了。3.2 外接矩形与最小外接矩形的绘制与对比接下来我们分别对每个轮廓计算并绘制外接矩形和最小外接矩形直观感受它们的区别。# 为了清晰我们逐个轮廓处理 for i, cnt in enumerate(contours): # 1. 计算并绘制外接矩形 (绿色) x, y, w, h cv2.boundingRect(cnt) cv2.rectangle(result_img, (x, y), (xw, yh), (0, 255, 0), 2) # 绿色矩形框 # 2. 计算并绘制最小外接矩形 (青色) rotated_rect cv2.minAreaRect(cnt) box_points cv2.boxPoints(rotated_rect) # 获取旋转矩形的四个顶点 box_points np.int0(box_points) # 转换为整数 cv2.drawContours(result_img, [box_points], 0, (255, 255, 0), 2) # 青色多边形框 # 打印信息进行对比 print(f轮廓 {i}:) print(f 外接矩形: 左上角({x}, {y}), 宽{w}, 高{h}, 面积{w*h}) print(f 最小外接矩形: 中心{rotated_rect[0]}, 宽高{rotated_rect[1]}, 角度{rotated_rect[2]:.2f}度) # 使用matplotlib显示结果 plt.figure(figsize(12, 5)) plt.subplot(121), plt.imshow(cv2.cvtColor(canvas, cv2.COLOR_BGR2RGB)), plt.title(原始形状) plt.subplot(122), plt.imshow(cv2.cvtColor(result_img, cv2.COLOR_BGR2RGB)), plt.title(外接矩形(绿) vs 最小外接矩形(青)) plt.show()运行这段代码你会清晰地看到对于倾斜的红色矩形绿色的外接矩形平行于坐标轴很大而青色的最小外接矩形则紧密地贴合了红色矩形的边缘并且是倾斜的。打印出的面积也会显示最小外接矩形的面积远小于外接矩形。对于绿色的正三角形两种矩形框出的区域可能比较接近但最小外接矩形依然会找到一个更贴合的朝向。对于蓝色的圆形两种矩形都会是一个正方形因为圆在各个方向上的投影范围相同且面积相近。实操心得处理cv2.minAreaRect返回的角度时务必小心。它的角度定义是将水平线与矩形宽度边平行逆时针旋转第一次遇到矩形任意一边时所转过的角度范围在[-90, 0)度。这意味着当矩形“站立”高度宽度时OpenCV可能会交换宽和高以保证宽度是较大的那个尺寸不实际上OpenCV的size(宽高)是未交换的角度总是关于“宽度”边。一个更稳妥的做法是永远根据角度将矩形归一化到[0, 90)度区间并相应地处理宽高或者直接使用四个顶点boxPoints进行计算这比解析角度和宽高更不容易出错。3.3 多边形拟合的精度控制实战多边形拟合的核心在于参数epsilon的选择。我们以倾斜的矩形轮廓为例看看不同epsilon值的效果。# 我们取第一个轮廓倾斜矩形来做多边形拟合演示 cnt_example contours[0] # 计算轮廓的周长epsilon通常取周长的某个百分比 peri cv2.arcLength(cnt_example, True) # 尝试不同的epsilon值 epsilon_list [0.01 * peri, 0.03 * peri, 0.05 * peri, 0.1 * peri] titles [1% Perimeter, 3% Perimeter, 5% Perimeter, 10% Perimeter] plt.figure(figsize(15, 10)) for i, (eps, title) in enumerate(zip(epsilon_list, titles)): # 进行多边形拟合 approx cv2.approxPolyDP(cnt_example, epsiloneps, closedTrue) # 创建新图像用于绘制 img_approx canvas.copy() # 绘制原始轮廓红色细线 cv2.drawContours(img_approx, [cnt_example], -1, (0, 0, 255), 1) # 绘制拟合多边形黄色粗线 cv2.drawContours(img_approx, [approx], -1, (0, 255, 255), 3) # 标记拟合多边形的顶点蓝色圆点 for point in approx: cv2.circle(img_approx, tuple(point[0]), 5, (255, 0, 0), -1) plt.subplot(2, 2, i1) plt.imshow(cv2.cvtColor(img_approx, cv2.COLOR_BGR2RGB)) plt.title(f{title} (Epsilon{eps:.1f}), Vertices{len(approx)}) plt.axis(off) plt.tight_layout() plt.show()观察结果epsilon0.01*周长拟合非常精确生成的多边形顶点很多可能超过4个几乎和原始轮廓重合。这适用于需要高精度还原形状的场景。epsilon0.03*周长开始简化但可能仍能识别出这是一个四边形顶点数接近4。这是常用的平衡点。epsilon0.05周长 或 0.1周长过度简化。对于一个矩形轮廓拟合结果可能变成一个三角形甚至更简单的形状丢失了关键特征。注意事项cv2.approxPolyDP的closed参数应设为True因为我们处理的是闭合轮廓。通过顶点数len(approx)我们可以做简单的形状识别顶点数≈3是三角形≈4是矩形/四边形4是多边形顶点数很多且拟合误差很小时可能是圆形。但要注意一个正方形用大epsilon拟合也可能变成四边形或三角形所以阈值选择需要根据实际物体和图像分辨率反复调试。3.4 外接圆的计算与应用场景最后我们计算并绘制每个轮廓的外接圆。result_img_circle canvas.copy() for i, cnt in enumerate(contours): # 计算外接圆 (x, y), radius cv2.minEnclosingCircle(cnt) center (int(x), int(y)) radius int(radius) # 绘制外接圆 cv2.circle(result_img_circle, center, radius, (255, 0, 255), 2) # 洋红色圆 # 绘制圆心 cv2.circle(result_img_circle, center, 3, (255, 255, 0), -1) # 青色点 print(f轮廓 {i} 外接圆: 圆心{center}, 半径{radius}) plt.figure(figsize(10, 4)) plt.subplot(121), plt.imshow(cv2.cvtColor(canvas, cv2.COLOR_BGR2RGB)), plt.title(原始形状) plt.subplot(122), plt.imshow(cv2.cvtColor(result_img_circle, cv2.COLOR_BGR2RGB)), plt.title(外接圆(洋红)与圆心(青)) plt.show()从图像和打印信息可以看出对于圆形外接圆完美匹配圆心和半径就是其真实几何参数。对于倾斜矩形和三角形外接圆为了完全包围它们半径会比较大圆心也基本位于物体的几何中心。这直观地展示了外接圆对非圆形物体的“不经济性”。4. 综合应用与场景选型指南掌握了单个技术后如何在实际项目中组合使用并做出正确选择下面我通过几个典型场景来分析。4.1 场景一文档扫描与透视矫正需求用手机拍一张倾斜的文档如发票、书页希望自动矫正成正面视角的矩形图像。方案轮廓检测通过边缘检测或二值化找到文档这个最大的四边形轮廓。多边形拟合使用cv2.approxPolyDP设置一个合适的epsilon如周长的0.02倍将轮廓拟合为一个四边形4个顶点。如果得到的顶点数不是4可能需要调整阈值或预处理步骤。顶点排序得到的4个顶点顺序是随机的。我们需要将其排序为[左上 右上 右下 左下]的顺序这是进行透视变换所必需的。可以通过计算顶点xy的和最小为左上最大为右下以及x-y的差等方法进行排序。透视变换使用cv2.getPerspectiveTransform计算变换矩阵然后用cv2.warpPerspective将倾斜文档“拉直”。为什么不用最小外接矩形最小外接矩形虽然能给出倾斜角度但它返回的是一个矩形而拍摄变形的文档是一个梯形或任意四边形透视效果。最小外接矩形会丢失这种透视信息矫正效果不自然。多边形拟合得到了四个角点才能进行真正的透视矫正。4.2 场景二工业零件尺寸测量需求在传送带上检测螺栓并测量其头部对边宽度即扳手尺寸。方案轮廓检测定位出每个螺栓的轮廓。最小外接矩形对每个螺栓轮廓计算cv2.minAreaRect。螺栓头部通常是正六边形其最小外接矩形恰好能反映对边距离。尺寸计算从RotatedRect中取出宽度(width)和高度(height)。对于规则的正六边形螺栓头这个矩形的短边长度就近似等于对边宽度S值。你需要根据已知的像素-实际尺寸比例比如先拍一个标定块进行换算。角度判断旋转角度angle可以判断零件在传送带上的朝向用于后续的机械臂抓取定位。为什么不用外接圆或多边形拟合外接圆无法给出对边距离。多边形拟合可以得到六边形的顶点通过计算对边顶点距离也能得到尺寸但计算比直接取最小外接矩形的宽/高更复杂且对轮廓完整性要求更高。最小外接矩形在这里是效率与精度兼顾的最佳选择。4.3 场景三细胞计数与大小分析需求在显微镜图像中统计圆形细胞的数量并分析其直径分布。方案图像分割与轮廓检测通过阈值分割或更高级的分水岭算法将粘连细胞分开得到每个细胞的独立轮廓。外接圆计算对每个轮廓计算cv2.minEnclosingCircle。因为细胞近似圆形外接圆能很好地逼近其实际大小。参数获取直接读取圆的半径(radius)直径即为2 * radius。根据标定的微米/像素比例换算成实际尺寸。圆心(center)可用于细胞的空间分布分析。过滤与统计可以根据圆的半径或面积过滤掉过小可能是噪声或过大可能是细胞团块的检测结果然后进行计数和直径分布统计。为什么不用矩形细胞是圆形的用矩形无论是哪种来描述都会引入系统误差。外接圆的表示最自然计算也方便。4.4 选型决策流程图为了帮助你快速决策我总结了一个简单的决策流程开始 │ ├─ 目标物体是否近似圆形 (如细胞、硬币、孔洞) │ ├─ 是 → 使用【外接圆】获取圆心和半径。 │ └─ 否 → │ ├─ 是否需要紧密包围盒且物体有明确朝向 (如零件、车牌、文本行) │ │ ├─ 是 → 使用【最小外接矩形】获取中心、长宽和角度。 │ │ └─ 否 → │ │ ├─ 是否只需一个简单的、轴对齐的包围框 (如目标检测的初始框、区域统计) │ │ │ ├─ 是 → 使用【外接矩形】计算最快。 │ │ │ └─ 否 → │ │ └─ 物体的形状复杂需要简化表示或识别多边形 (如文档角点、自定义形状) │ │ └─ 是 → 使用【多边形拟合】通过控制epsilon平衡精度与简化程度。 │ └─ └─ 结束5. 常见问题排查与性能优化技巧在实际编码中你肯定会遇到各种奇怪的问题。下面是我踩过的一些坑和解决方案。5.1 轮廓查找失败或结果异常问题cv2.findContours返回的轮廓列表为空或者轮廓非常破碎。排查检查二值图像在调用findContours前先用cv2.imshow或保存图像的方式查看你的二值图(binary)。确保目标物体是白色255背景是黑色0并且物体区域是连通的。调整阈值如果使用固定阈值cv2.threshold光照变化会导致二值化效果差。尝试使用自适应阈值cv2.adaptiveThreshold或大津法cv2.THRESH_OTSU。预处理在二值化前加入高斯模糊cv2.GaussianBlur可以平滑噪声帮助获得更干净的轮廓。使用形态学操作如开运算cv2.morphologyEx可以去除小噪点闭合细小裂缝。轮廓检索模式cv2.RETR_EXTERNAL只找最外层轮廓。如果你的目标物体内部有空洞并且你需要内外轮廓应使用cv2.RETR_TREE或cv2.RETR_LIST。5.2 最小外接矩形的角度与宽高困惑问题从cv2.minAreaRect得到的角度angle和宽高(width, height)有时和视觉直觉不符宽高可能会互换。解决方案不要依赖angle的原始值来做绝对判断。一个健壮的处理方法是def get_ordered_rect(rotated_rect): 获取一个规范化表示的最小外接矩形确保宽度 高度角度在[0, 90)度 center, size, angle rotated_rect width, height size # 确保宽度总是大于等于高度 if width height: width, height height, width angle 90 # 角度调整 # 将角度规范到 [0, 90) 度区间 angle angle % 90 return (center, (width, height), angle)或者更简单直接地使用四个顶点boxPoints。你可以通过计算顶点之间的距离来确定长边和短边通过计算长边与水平线的夹角来得到你需要的角度。5.3 多边形拟合顶点数不稳定问题同一个物体在不同图片或不同光照下cv2.approxPolyDP返回的顶点数飘忽不定。解决方案固定epsilon策略不要用绝对像素值作为epsilon而是使用轮廓周长的百分比如0.02 * peri。这样算法对物体大小不敏感。轮廓平滑在拟合前可以对轮廓点进行轻微的平滑处理比如使用cv2.convexHull先求取凸包再对凸包进行多边形拟合可以消除轮廓上的凹坑干扰。后处理判断不要期望顶点数绝对等于3、4、5。可以设置一个范围例如如果顶点数在[3,5]之间我们可以认为它是三角形或四边形。结合轮廓面积、长宽比等其他特征进行综合判断会更可靠。5.4 性能优化建议当需要处理视频流或大量图片时性能至关重要。减少轮廓数量在cv2.findContours后根据轮廓面积cv2.contourArea或外接矩形大小过滤掉过小的轮廓可能是噪声。降低分辨率如果精度允许可以先将图像缩放(cv2.resize)到更小的尺寸进行处理轮廓点数量会大幅减少所有几何计算都会变快。避免重复计算如果你需要外接矩形、面积、周长等多个属性尽量在一次循环中计算完避免对同一个轮廓多次遍历。使用Numpy向量化操作在遍历轮廓点寻找极值等操作时利用Numpy的数组操作如cnt[:,:,0].min()比纯Python循环快得多。5.5 一个综合示例识别并测量图片中的卡片让我们用一个完整的例子结束假设我们要识别图片中所有的矩形卡片并输出它们的长、宽和面积以像素为单位。def detect_and_measure_cards(image_path): # 1. 读取并预处理图像 img cv2.imread(image_path) if img is None: print(图像读取失败) return gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 使用高斯模糊去噪 blurred cv2.GaussianBlur(gray, (5, 5), 0) # 使用自适应阈值应对光照不均 binary cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2) # 2. 形态学操作闭合卡片内部文字造成的空洞连接边缘 kernel np.ones((3,3), np.uint8) closed cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel, iterations2) # 3. 查找轮廓 contours, _ cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) result_img img.copy() card_info [] for cnt in contours: # 4. 面积过滤去掉太小的轮廓 area cv2.contourArea(cnt) if area 500: # 根据图片大小调整阈值 continue # 5. 多边形拟合判断是否为四边形 peri cv2.arcLength(cnt, True) approx cv2.approxPolyDP(cnt, epsilon0.02*peri, closedTrue) if len(approx) 4: # 认为是四边形 # 6. 计算最小外接矩形获取精确尺寸 rect cv2.minAreaRect(cnt) (cx, cy), (w, h), angle rect # 使用规范化函数 (cx, cy), (w_norm, h_norm), angle_norm get_ordered_rect(rect) # 7. 根据长宽比过滤避免将细长条误判为卡片 aspect_ratio max(w_norm, h_norm) / (min(w_norm, h_norm) 1e-5) if aspect_ratio 5: # 长宽比过大可能是线条 continue # 8. 绘制并记录信息 box cv2.boxPoints(rect) box np.int0(box) cv2.drawContours(result_img, [box], 0, (0, 255, 0), 2) cv2.putText(result_img, fW:{w_norm:.0f},H:{h_norm:.0f}, (int(cx)-50, int(cy)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255,0,0), 2) card_info.append({ center: (cx, cy), width: w_norm, height: h_norm, area: w_norm * h_norm, angle: angle_norm }) # 9. 显示结果 plt.figure(figsize(15, 6)) plt.subplot(131), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)), plt.title(原图) plt.subplot(132), plt.imshow(closed, gray), plt.title(预处理后二值图) plt.subplot(133), plt.imshow(cv2.cvtColor(result_img, cv2.COLOR_BGR2RGB)), plt.title(检测与测量结果) plt.show() for i, info in enumerate(card_info): print(f卡片 {i1}: 中心{info[center]}, 宽{info[width]:.1f}px, 高{info[height]:.1f}px, f面积{info[area]:.1f}px^2, 角度{info[angle]:.1f}°) return card_info # 假设你有一张名为 cards.jpg 的图片 # card_measurements detect_and_measure_cards(cards.jpg)这个例子融合了轮廓过滤、多边形形状判断、最小外接矩形测量等多个技巧是一个接近实际应用的完整流程。记住没有一套参数能通吃所有场景area阈值、epsilon系数、长宽比阈值等都需要根据你的具体图片进行调整。最好的办法是写一个简单的GUI或者用Jupyter Notebook实时调整这些参数并观察效果找到最适合你当前任务的“黄金参数”。