
简介面向OpenCV开发者的轮廓提取专题资料采用PDF格式系统讲述了如何利用查找轮廓函数从二值图像中提取轮廓以及如何利用绘制轮廓函数将结果可视化。文档从轮廓的基本概念讲起指出轮廓是由连续像素点构成的有序集合常用于边缘检测之后的目标分析随后对查找轮廓函数的每个参数做了细致解读包括输入图像要求、输出轮廓向量、层级记录向量、四种检索模式外围优先、全部独立、两级结构、树状层级与四种近似策略全点保留、仅拐点、链近似变体等还说明了偏移量的作用绘制部分同样覆盖了颜色、线宽、线型、最大绘制层级等参数。包内仅有一个PDF文件大小仅95KB内容紧凑便于离线查阅。自发布以来已有2254人学习值得图像处理初学者参考。文档最后附有完整的C示例展示阈值滑条调节下的边缘检测、轮廓提取与绘制流程读者可亲手实现并观察不同参数对轮廓结果的影响高效掌握轮廓提取的核心要点。1. 轮廓提取不只是找边OpenCV 轮廓与边缘的本质区别做图像处理的人早晚会碰到轮廓提取。OpenCV 的 findContours 拿到的不只是一圈亮边而是一组有序点集每条轮廓都代表一个连通区域的边界。很多新手把轮廓提取和边缘检测混在一起上来就 Canny结果发现找回来的轮廓断成一段一段或者把噪点也当成目标。轮廓提取真正要解决的是「把二值图里连成一片的区域外边界描述出来」它服务于面积测量、形状识别、目标计数和定位适合做工业视觉、硬币计数、OCR 预处理和物体检测的工程师。理解轮廓提取的关键是先分清楚两件事边缘是像素级的梯度响应轮廓是区域级的拓扑描述后者才是 OpenCV 里可计算、可度量的对象。2. 先用 findContours 把轮廓拿到手预处理与三行核心代码2.1 预处理为什么比找轮廓本身更影响结果灰度、去噪与二值化findContours 的输入是二值图不是彩色图也不是灰度图。这一步卡住了很多人。常见的做法是拿到图像后先转灰度、再做去噪、最后用阈值分割得到二值图。为什么强调预处理因为轮廓提取的质量基本由二值图决定二值图里一个白色连通域就是一条轮廓的候选白色区域粘连轮廓就粘连白色区域有噪点轮廓就会出现一堆小碎块。轮廓提取真正的工作量在预处理阶段后面三行代码反而是最省事的。import cv2 import numpy as np # 读取图片转灰度并做高斯去噪 image cv2.imread(target.jpg) gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) blur cv2.GaussianBlur(gray, (5, 5), 0) # 高斯核 5x5sigma 自动计算 # 大津法自动求阈值返回阈值和二值图 _, binary cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)第一步转灰度没什么可说的彩色图的轮廓在 OpenCV 里没有直接实现必须降维。GaussianBlur 的核大小我一般先用 5x5如果目标边缘比较细改成 3x3如果图像噪声重可以加到 7x7但核太大会让细小边缘消失。阈值这里用 Otsu 是省事的做法它根据灰度直方图自动找一个把前景和背景分得最开的值不用人工调阈值。THRESH_BINARY_INV 是因为硬币、零件这类目标一般是暗背景亮目标反转后目标变成白色前景方便后续找轮廓。如果背景是白色、目标是黑色用 THRESH_BINARY 即可这个方向选反了轮廓会找到背景上去这是最常犯的错误之一。如果光照不均匀全局阈值会翻车。这时候优先试自适应阈值代码差别不大但参数要重新理解# 自适应阈值blockSize 必须是奇数C 是常量修正值 binary cv2.adaptiveThreshold(blur, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 15, 10)adaptiveThreshold 的 blockSize 决定局部窗口大小。窗口太小字迹或零件内部的纹理也会被当成边界窗口太大又退化成全局阈值的效果。我之前在处理金属零件反光时blockSize 从 15 到 31 之间来回试C 值从 2 试到 10最后用 25 和 8 才把反光斑压住。这个参数没有万能组合只能按图调但记住一个原则blockSize 要大于目标内部最大纹理的尺寸。否则轮廓提取出来的不是目标边界而是纹理边界。2.2 findContours 的两个检索模式和一个近似方法参数怎么选二值图准备好之后核心调用是 cv2.findContours。这里有一个历史包袱OpenCV 3 返回三个值OpenCV 4 返回两个值。很多老教程还在用 OpenCV 2.4 时代的写法代码一跑就报 ValueError: not enough values to unpack。我能给的建议是统一按 OpenCV 4 的写法来同时在代码里兼容一下旧版本省得换环境就翻车。# OpenCV 4直接接收两个返回值 contours, hierarchy cv2.findContours( binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE )第一个参数是输入的二值图注意 findContours 会修改这个图如果后面还要用原二值图先 copy 一份。第二个参数是检索模式RETR_EXTERNAL 只取最外层轮廓适合计数场景RETR_LIST 取所有轮廓但不建层级适合只想遍历所有边界的场景RETR_TREE 建完整层级树适合有嵌套目标的场景。第三个参数是近似方法CHAIN_APPROX_SIMPLE 会把一条直线上的中间点全部压掉只保留两端点内存和后续计算都省CHAIN_APPROX_NONE 保留全部点一般不需要。很多教程默认用 RETR_TREE其实大部分测量场景 RETR_EXTERNAL 就够了。用 RETR_TREE 的副作用是会把二值图里的内孔也当成轮廓返回比如一个圆环会返回外圆和内圆两条如果做计数就会把 1 个目标数成 2 个。我第一次做硬币计数就踩了这个坑把 5 枚硬币数出来 10 个轮廓。另外 RETR_CCOMP 这个模式比较冷门它把轮廓组织成两层外层是目标边界内层是孔洞边界如果你确实想区分目标与内部孔又不想背 RETR_TREE 的全嵌套结构可以试试它。findContours 的输入还有一点要注意它接受的是单通道 8 位图像通常就是二值图。如果你传进去一张三通道彩图会直接报错说输入不是单通道。有些 C 场景下还会遇到ContourArea() 未定义标识符这种编译错误多半是没包含 imgproc 头文件或者没用 cv:: 前缀这不是算法问题是环境问题后面避坑章节再细说。2.3 绘制与确认drawContours 不是只画着看看拿到轮廓后第一件事是画出来确认不是直接算面积。cv2.drawContours 可以指定画哪几条轮廓、用什么颜色、线宽多少这段代码建议每个项目都保留排错时非常有用。# 在原始图像的拷贝上绘制避免覆盖原图 debug image.copy() cv2.drawContours(debug, contours, -1, (0, 0, 255), 2) cv2.imshow(contours, debug) cv2.waitKey(0)第三个参数传 -1 表示画全部轮廓传某个索引就只画那一条。画完之后看两件事第一轮廓是否闭合闭合的目标边界不应该有缺口第二轮廓数量是否符合预期如果多出一堆小碎块说明二值图的前景不干净需要回去调形态学操作而不是调 findContours 参数。很多人在这里反复调 RETR 模式其实问题根本不在检索模式而在预处理阶段。轮廓提取里最贵的调试时间往往花在二值图上findContours 本身是一个确定性很强的函数输入什么就输出什么它不背这个锅。到这里最小闭环已经跑通预处理 - findContours - drawContours 确认。如果是在 VS Code 里配 OpenCV 环境记得先确认安装的是 opencv-python 而不是把它装进了别的解释器否则 import cv2 就失败后面全白搭。接下来要考虑的是怎么从轮廓里拿到数据。3. 从轮廓里读出信息面积、周长、外接矩形与质心3.1 contourArea 与 arcLength闭合区域与曲线的度量差异轮廓拿来不是为了画的是为了量。最常用的两个度量是面积和周长分别对应 cv2.contourArea 和 cv2.arcLength。contourArea 计算的是闭合轮廓围成的区域面积单位是像素的平方arcLength 计算的是轮廓曲线的长度第二个参数传 True 表示按闭合曲线算传 False 表示按开放曲线算。area cv2.contourArea(contours[0]) # 面积像素² perimeter cv2.arcLength(contours[0], True) # 周长像素contourArea 对输入轮廓的点顺序没有要求它是按格林公式算的但要注意如果轮廓自相交面积可能出现负值这个问题后面避坑章节会细说。arcLength 的 closed 参数很容易被忽略当你拿到的轮廓来自 CHAIN_APPROX_SIMPLE轮廓点并不包含起点副本如果 closed 传 False算出来的周长会比实际少一段反之如果轮廓本身是开放的边缘片段closed 传 True 会强行连一条假线段。我的习惯是对 findContours 返回的轮廓统一传 True因为它本来就是区域边界。面积和周长很少单独用更多是组合成形状特征。比如圆度指标4 * pi * area / (perimeter ^ 2)圆形的值接近 1长条形和带锯齿的轮廓值明显小于 1。这个指标在硬币检测、药片分拣里比单纯比面积实用得多。做这类计算时如果发现轮廓面积忽大忽小先别急着怀疑算法回去看一眼二值图是不是在边缘处有闪烁的噪点这属于玄学之外的确定性问题。3.2 boundingRect、minAreaRect 与 minEnclosingCircle把轮廓转成几何除了面积周长轮廓提取后经常要得到外接矩形或外接圆用于定位和裁剪。OpenCV 给了三个函数适用场景完全不同。x, y, w, h cv2.boundingRect(contours[0]) # 轴对齐外接矩形 rect cv2.minAreaRect(contours[0]) # 最小外接矩形可旋转 (cx, cy), radius cv2.minEnclosingCircle(contours[0]) # 最小外接圆boundingRect 返回的是 (x, y, w, h)x、y 是矩形左上角坐标计算最快适合做 ROI 裁剪比如 OCR 里的字符切分。minAreaRect 返回的是 RotatedRect包含中心点 (x, y)、尺寸 (w, h) 和旋转角度 angle适合细长零件因为它能贴着轮廓转出最小面积boundingRect 在斜 45 度的零件上会包进大量背景。minEnclosingCircle 用最小圆去逼近轮廓适合圆形目标定位。minAreaRect 的返回值很多人第一次用会懵它返回的是一个嵌套结构要取四个角点还得用 cv2.boxPoints(rect)。而且 OpenCV 返回的 angle 范围是 [-90, 0)和数学上的角度方向相反做旋转校正时经常要加一个负号。这些问题不踩一遍很难记住我第一次把 RotatedRect 直接当成普通矩形去取 width 和 height结果在斜矩形上把长边短边弄反旋转校正后目标被转了 90 度血泪经验。这些几何信息在目标跟踪里也常用跟踪算法经常把目标的轮廓质心作为跟踪点质心的计算方式是 cv2.momentsmoments cv2.moments(contours[0]) if moments[m00] ! 0: cx moments[m10] / moments[m00] cy moments[m01] / moments[m00]m00 是轮廓面积m10 / m00 和 m01 / m00 就是质心坐标。如果 m00 接近 0说明轮廓面积太小直接做除法会得到离谱的坐标所以要加判空。3.3 轮廓筛选实战硬币检测里的面积与圆度过滤把上面的度量组合起来就能做一个经典的轮廓筛选任务硬币检测与计数。硬币在二值图里是近圆形的连通域但图像里常有噪声、反光斑、桌面纹理直接用 findContours 会把它们都算进去。筛选目标是面积在合理区间、圆度接近 1。import math min_area 500 # 硬币最小面积按图像分辨率调 max_area 20000 # 硬币最大面积 coins [] for c in contours: area cv2.contourArea(c) if area min_area or area max_area: continue perimeter cv2.arcLength(c, True) if perimeter 0: continue circularity 4 * math.pi * area / (perimeter * perimeter) if circularity 0.8: coins.append(c) print(detected coins:, len(coins))面积阈值上下限怎么定先跑一遍不带筛选的轮廓用 debug 图看每个轮廓的面积分布取目标面积上下浮动 30% 到 50%。圆度 0.8 是我在普通光照下常用的起点如果硬币边缘有锯齿可以降到 0.7如果要求严格调到 0.9但会把一部分边缘断掉的硬币漏掉。圆度对轮廓平滑度很敏感预处理里的高斯模糊稍微大一点圆度就会升上去这不是玄学是周长对噪声敏感而面积相对稳定导致的。所以先定面积再定圆度这个顺序别反。4. 轮廓层级与拓扑关系为什么同一张图会返回两层结果4.1 RETR_EXTERNAL 与 RETR_TREE父子轮廓怎么区分findContours 的 hierarchy 参数是很多人的知识盲区。hierarchy 是一个形状为 (1, N, 4) 的数组N 是轮廓数量每个轮廓对应四个整型值[Next, Previous, First_Child, Parent]存的是轮廓索引没有关系时是 -1。理解这四个值就能回答「为什么一个圆环会返回两个轮廓」。用 RETR_TREE 时外圆轮廓的 First_Child 指向内圆轮廓内圆轮廓的 Parent 指向外圆轮廓。如果用 RETR_EXTERNALfindContours 直接忽略内孔只返回最外层hierarchy 里所有 Parent 都是 -1。选择哪个模式取决于任务数目标数量、量目标外边界用 RETR_EXTERNAL要检测带孔零件、区分内部结构用 RETR_TREE。contours, hierarchy cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) for i, c in enumerate(contours): parent hierarchy[0][i][3] # 第 4 个值是父轮廓索引 child hierarchy[0][i][2] # 第 3 个值是第一个子轮廓索引 if parent -1 and child ! -1: print(f轮廓 {i} 是外边界且包含内孔)这里有个细节hierarchy 的索引一定要用 hierarchy[0][i]因为 hierarchy 本身带了一个维度写成 hierarchy[i][3] 会报 IndexError 或者取到错误数据。这个错误我在代码评审里见过好几次大家都不愿意看文档最后靠打印 shape 才明白。4.2 用层级过滤内孔与嵌套目标轮廓数不等于目标数实际项目中目标内部常有文字、图案、反光区域比如硬币上的国徽、IC 芯片上的丝印。二值化之后这些内部细节会变成前景空洞在 RETR_TREE 模式下会各自形成子轮廓。如果直接 len(contours)目标数会被严重高估。正确的做法是只统计没有父轮廓的顶层轮廓也就是 Parent -1 的轮廓。这一步通常叫「取外层轮廓」但注意它和 RETR_EXTERNAL 并不完全等价RETR_EXTERNAL 是在检索阶段就丢弃内孔如果内孔本身也有嵌套结构还会丢信息而用 RETR_TREE 加 Parent 过滤可以在需要时把内孔轮廓也保留下来。比如做 PCB 缺陷检测时焊盘上的过孔在层级里是子轮廓你既想统计焊盘数量又想检查过孔是否偏移这时候顶层过滤和子轮廓遍历要同时做。另一个常见需求是「去掉太小或太大的内孔」遍历子轮廓时用 contourArea 算面积小于阈值的直接忽略。这个场景在零件缺陷检测里很常用因为缺陷往往表现为目标内部多了一个不该有的子轮廓。判断逻辑不是「有没有子轮廓」而是「子轮廓的面积/形状是否在正常范围」否则会把正常的文字图案也当成缺陷。4.3 连通域分析connectedComponentsWithStats 和轮廓提取怎么互补轮廓提取不是万能的。当任务是「统计连通域数量 每个连通域的面积、外接框」时cv2.connectedComponentsWithStats 往往比 findContours 更直接。它对二值图做连通域标记输出每个域的面积、外接框、中心点速度也更快。num_labels, labels, stats, centroids cv2.connectedComponentsWithStats(binary) for i in range(1, num_labels): # 0 是背景 area stats[i, cv2.CC_STAT_AREA] left stats[i, cv2.CC_STAT_LEFT] top stats[i, cv2.CC_STAT_TOP] width stats[i, cv2.CC_STAT_WIDTH] height stats[i, cv2.CC_STAT_HEIGHT]和 findContours 比connectedComponentsWithStats 返回的是像素掩膜而不是轮廓点所以它不适合做形状描述比如算周长、算圆度就不方便。它最大的优势是直接拿到了每个连通域的统计量不需要再遍历轮廓算面积。我的习惯是只需要计数和面积/位置用连通域需要形状特征、轮廓逼近、轮廓点参与计算时用 findContours。两者不是替代关系是互补关系项目里经常先用 findContours 画调试图再用 connectedComponentsWithStats 做正式统计。5. 轮廓提取避坑指南这些坑我基本都踩过5.1 findContours 返回值数量不一致OpenCV 3 和 4 的差异现象同一段代码在有的机器上正常运行换台机器就报 ValueError: not enough values to unpack (expected 3, got 2)或者反过来 expected 2, got 3。 原因OpenCV 3.x 的 findContours 返回 (image, contours, hierarchy)OpenCV 4.x 移除了第一个返回值只返回 (contours, hierarchy)。很多环境里 pip 默认装的是 OpenCV 4老教程代码直接翻车。C 场景下对应的报错是ContourArea() 未定义标识符多半是没加 cv:: 前缀或者漏了头文件但 Python 场景最典型的就是这个解包错误。 解决统一按两个返回值写并做兼容result cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if len(result) 3: _, contours, hierarchy result else: contours, hierarchy result这样在 OpenCV 3 和 4 下都能跑。不过如果项目从零开始建议直接钉住 OpenCV 4.x 版本少一个兼容分支少一类问题。安装 OpenCV 时最常见的报错是 ModuleNotFoundError: No module named cv2pip install opencv-python 装的是官方预编译包如果装完还是找不到先检查当前 Python 环境是不是搞混了比如装了两次 Python 或者用了虚拟环境但 pip 装到了全局。conda 环境下用 conda install opencv 更稳但版本往往比 pip 旧。还有人在 VS Code 里配置 OpenCV明明终端能 import编辑器里却报错那是因为选了不同的解释器这是环境问题不是代码问题。5.2 contourArea 出现负数或接近零点顺序与自相交轮廓现象cv2.contourArea(contour) 返回负数或者面积和肉眼看到的目标完全对不上。 原因contourArea 按格林公式计算有向面积轮廓点如果按顺时针排列结果就是负的。更隐蔽的是轮廓自相交时正负区域会抵消面积数值没意义。findContours 返回的轮廓一般是逆时针正面积但经过 approxPolyDP 或手工构建点集后方向可能变。 解决对面积取绝对值同时检查轮廓是否自相交。最省事的做法是在算面积前先用 cv2.convexHull(contour) 检查凸包面积和原面积差多少如果差很多说明轮廓凹得厉害或自相交需要回看预处理。C 场景如果报 contourArea 未定义标识符确认写的不是ContourAreaOpenCV 里函数名是大小写敏感的Python 里调用 cv2.contourArea 同理少个点或者大小写不对都会报 AttributeError。5.3 二值化后轮廓断裂或连成一片kernel 大小与形态学操作现象同一个目标被检测成两三条轮廓或者两个相邻目标粘成一条数量和面积都不对。 原因二值图里目标内部的细缝、噪点、光照暗区会把连通域切断反过来前景和背景对比弱时两个目标挨得太近会把间隙填上。很多人直接调 findContours 的模式参数其实问题在二值图。 解决用形态学闭运算把断裂接上用开运算把粘连和噪点去掉。核的大小要按目标尺寸设计kernel cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) closed cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel, iterations2) opened cv2.morphologyEx(closed, cv2.MORPH_OPEN, kernel, iterations1)闭运算填补内部小孔和细缝开运算去掉微小噪点。kernel 尺寸是个需要反复试的参数我一般从目标直径的 1/50 到 1/20 起步。kernel 太大会把两个目标焊在一起太小断口接不上。这两个方向是矛盾的只能按目标间距取中间值。调完形态学再看轮廓很多「轮廓数量不对」的问题根本不需要动 findContours 的参数就解决了。5.4 Canny 边缘结果直接喂给 findContours为什么轮廓断成锯齿现象用 Canny 得到边缘图然后直接 findContours返回的轮廓断成一截一截而且每段轮廓都算不出正确的闭合面积。 原因Canny 输出的是像素级边缘不是连通区域的边界。边缘线可能存在断点二值图也不是「白色前景 黑色背景」的连通域形式。findContours 期望输入是区域掩膜边缘图里的细线会被当成区域处理结果自然不对。 解决如果确实要用边缘检测结果先把边缘图做膨胀让断线接上变成连通区域再喂给 findContourscanny cv2.Canny(blur, 50, 150) dilated cv2.dilate(canny, np.ones((3, 3), np.uint8), iterations1) contours, _ cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)但更推荐的做法是直接基于阈值二值图做轮廓提取Canny 只用于边缘展示或辅助判断。不要混用混用的结果往往是两边都不讨好。我见过有人在 Canny 输出上反复调膨胀迭代次数试图让断裂边缘闭合成完整轮廓折腾半天还是不如直接调阈值二值化省心。5.5 轮廓点数太多导致程序卡顿CHAIN_APPROX_SIMPLE 和 approxPolyDP现象轮廓提取后遍历每个点做几何计算程序明显变慢特别是大分辨率图像或者视频流里每帧都做。 原因直接用 CHAIN_APPROX_NONE 会保留轮廓上每个像素点一条复杂轮廓可能有几千个点后续每个点的计算都白白消耗 CPU。树莓派这类嵌入式平台上做物体识别这个开销尤其明显。 解决检索模式用 CHAIN_APPROX_SIMPLE它只保留线段端点。如果还不够用 approxPolyDP 做轮廓逼近把点数压到几十个epsilon 0.02 * cv2.arcLength(contour, True) approx cv2.approxPolyDP(contour, epsilon, True)epsilon 是逼近精度通常取轮廓周长的 1% 到 5%。取 0.02 * arcLength 意味着允许最大 2% 的偏离点数会显著下降但形状基本不变。实时视频流里可以在每帧只计算必要轮廓或者把分辨率降下来别让 findContours 在 4K 图上一帧做 30 次。6. 把轮廓提取用起来轮廓逼近、骨架提取与一个完整的筛选流程6.1 approxPolyDP把轮廓变成多边形再判断形状轮廓提取得到的是点集但很多场景需要的是「这个轮廓像不像四边形、三角形、圆」。approxPolyDP 用 Douglas-Peucker 算法把轮廓逼近成更少点的多边形逼近结果可以直接判断顶点数顶点数是 4 就是四边形是 3 就是三角形。注意 epsilon 不要贪小取 0.02 倍周长是一个稳妥起点太小会把锯齿保留成多余顶点太大又把直角磨成斜线。6.2 细化与骨架提取轮廓提取之后经常接的一步对细长目标轮廓提取只能给边界不能给中心线。这时候要做细化骨架提取OpenCV 的扩展模块 ximgproc 里有一个 thinning 函数普通 opencv-python 包里没有需要装 opencv-contrib-python。骨架提取的意义是把手写笔迹、血管、道路这类目标的形状变成单像素线方便算长度和方向。轮廓提取在前、细化在后两者配合才能做出道路中心线提取这类功能。6.3 一个可复现的验证流程最后给一个小流程读入测试图提取轮廓筛选面积把外接矩形画出来。跑通这个流程轮廓提取的基本功就算过关了。import cv2 image cv2.imread(test.png) gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) _, binary cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU) contours, _ cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) for i, c in enumerate(contours): area cv2.contourArea(c) if area 100: continue x, y, w, h cv2.boundingRect(c) cv2.rectangle(image, (x, y), (x w, y h), (0, 255, 0), 2) cv2.putText(image, str(i), (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) cv2.imwrite(result.png, image)这段代码把面积小于 100 的碎轮廓过滤掉在剩余轮廓上画出外接矩形并编号。验证时看两个点编号是否连续稳定外接矩形是否紧贴目标。如果矩形明显偏大或偏移回看二值图和阈值方向如果编号时有时无说明面积阈值压到了目标边缘。我现在的习惯是先打印每个轮廓的面积看一眼分布再定阈值而不是拍脑袋写死。轮廓提取这东西预处理占七分参数占两分运气占一分把前两分做好最后一分自然就有了。希望帮到你。本文还有配套的精品资源点击获取