OpenCV 轮廓检测入门(OpenCV.js 版):cv.findContours() 与 cv.drawContours() 实战指南
2026/9/5 19:59:15 网站建设 项目流程

OpenCV 轮廓检测入门(OpenCV.js 版):cv.findContours() 与 cv.drawContours() 实战指南

【免费下载链接】opencvOpen Source Computer Vision Library项目地址: https://gitcode.com/GitHub_Trending/opencv31/opencv

本文基于 OpenCV 官方 JS 教程中"轮廓入门"一文展开,系统讲解轮廓(Contour)的概念、cv.findContours()cv.drawContours()两个核心 API 的完整参数、轮廓检索模式与近似方法,并结合 OpenCV 仓库中imgproc模块的源码定义和官方示例,给出可在浏览器环境中直接运行的轮廓检测与绘制方案。

一、什么是轮廓(Contour)

轮廓(Contour)可以被简单地解释为:一条将所有连续点(沿边界)连接起来的曲线,且这些点具有相同的颜色或强度。轮廓是形状分析、目标检测与识别中的常用工具。

在寻找轮廓之前,有几个重要前提值得注意:

  • 尽量使用二值图像以获得更高精度。因此在findContours之前,应先对图像做阈值处理(threshold)或 Canny 边缘检测;
  • 从 OpenCV 3.2 起,该函数不再修改源图像。这一点在当前仓库的头文件注释中仍然明确保留(见 opencv2/imgproc.hpp:Since opencv 3.2 source image is not modified by this function);
  • OpenCV 中查找轮廓相当于从黑色背景中寻找白色物体。记住:要找的对象应该是白色的(非零像素),背景应该是黑色的(零像素)。源码注释同样印证了这一约定:Non-zero pixels are treated as 1's. Zero pixels remain 0's, so the image is treated as binary.(opencv2/imgproc.hpp)。

二、cv.findContours():轮廓查找 API

在 OpenCV.js 中,查找轮廓使用的函数签名为:

cv.findContours(image, contours, hierarchy, mode, method, offset = new cv.Point(0, 0))

各参数含义如下:

参数说明
image源图像,8 位单通道图像。非零像素被视为 1,零像素保持为 0,因此图像被当作二值图处理
contours输出,检测到的轮廓(cv.MatVector),每条轮廓存储为点集
hierarchy输出,包含图像拓扑信息的层次结构(cv.Mat),元素个数与轮廓数相同
mode轮廓检索模式(cv.RetrievalModes,见下文)
method轮廓近似方法(cv.ContourApproximationModes,见下文)
offset可选偏移量,所有轮廓点都会平移该偏移。当轮廓从图像的 ROI 中提取、但需要在整图上下文中分析时很有用

从 opencv2/imgproc.hpp 中的 C++ 声明注释可以看到,hierarchy[i]的四个元素分别是hierarchy[i][0](同一层级的下一条轮廓)、hierarchy[i][1](同一层级的上一条轮廓)、hierarchy[i][2](第一个子轮廓)、hierarchy[i][3](父轮廓),均为 0 起始索引;对应关系不存在时取负值。理解这四个值是使用RETR_TREERETR_CCOMP等层级模式的基础。

此外,源码注释还指出一个输入细节:当modeRETR_CCOMPRETR_FLOODFILL时,输入也可以是 32 位整型标签图像(CV_32SC1)(opencv2/imgproc.hpp)。

2.1 轮廓检索模式(cv.RetrievalModes)

mode参数决定了提取哪些轮廓以及是否建立层级关系。仓库头文件中 RetrievalModes 枚举给出了完整定义:

枚举值JS 中的常量行为
RETR_EXTERNAL = 0cv.RETR_EXTERNAL只提取最外层轮廓,所有轮廓的hierarchy[i][2] = hierarchy[i][3] = -1
RETR_LIST = 1cv.RETR_LIST提取所有轮廓,但不建立任何层级关系
RETR_CCOMP = 2cv.RETR_CCOMP提取所有轮廓并组织为两级层级:第一级是连通域的外部边界,第二级是孔洞的边界;若孔洞内还嵌套其他轮廓,仍放在第一级
RETR_TREE = 3cv.RETR_TREE提取所有轮廓并重建完整的嵌套层级树
RETR_FLOODFILL = 4cv.RETR_FLOODFILL从指定位置洪水填充并提取轮廓

从源码结构看,当前版本还有一个值得关注的性能优化:头文件注释指出,从 OpenCV 4.14 起,当modeRETR_LIST且不需要 hierarchy 时,findContours会自动采用 TRUCO 并行算法这一可扩展的无锁轮廓提取方法;其他情况仍使用顺序执行的 Suzuki85 算法(opencv2/imgproc.hpp)。

三、cv.drawContours():轮廓绘制 API

绘制轮廓使用cv.drawContours函数。只要拥有形状的边界点,它也可以用来绘制任意形状。函数签名:

cv.drawContours(image, contours, contourIdx, color, thickness = 1, lineType = cv.LINE_8, hierarchy = new cv.Mat(), maxLevel = INT_MAX, offset = new cv.Point(0, 0))

各参数含义:

参数说明
image目标图像(绘制在其上)
contours所有输入轮廓
contourIdx指定要绘制的轮廓索引;为负数时绘制全部轮廓
color轮廓颜色(cv.Scalar
thickness线条粗细;为负数时绘制轮廓内部(即填充)
lineType线条连接方式(cv.LineTypes,如cv.LINE_8
hierarchy可选,层级信息。仅在需要按maxLevel只绘制部分轮廓时才需要
maxLevel绘制轮廓的最大层级:为 0 时只画指定轮廓;为 1 时画轮廓及其所有嵌套轮廓;为 2 时再往下再嵌套一层,以此类推。仅当提供了 hierarchy 时该参数才生效
offset可选,所有轮廓点的平移参数

从 C++ 声明的注释(opencv2/imgproc.hpp)可以补充两点实现层面的细节:

  • thickness < 0(即填充模式)时,函数被设计为在没有 hierarchy 数据的情况下也能正确处理带孔的连通域,其做法是把所有轮廓放在一起按"奇偶规则(even-odd rule)"分析;
  • 但如果你把分别提取的多组轮廓合并在同一个集合里一次性填充,可能得到错误结果。此时的做法是按contourIdx迭代逐组调用drawContours,或者对每个轮廓子集单独调用。

四、完整示例:在浏览器中查找并绘制轮廓

OpenCV.js 教程为此提供了一段可直接运行的示例(对应交互式页面 js_contours_begin_contours.html,其中canvasInputcanvasOutput两个 canvas 元素已预置,点击 "Try it" 按钮即可执行):

let src = cv.imread('canvasInput'); // 从 canvas 读取图像(RGBA) let dst = cv.Mat.zeros(src.rows, src.cols, cv.CV_8UC3); // 创建同尺寸 3 通道黑色画布 cv.cvtColor(src, src, cv.COLOR_RGBA2GRAY, 0); // 转为灰度图 cv.threshold(src, src, 120, 200, cv.THRESH_BINARY); // 二值化:前景为白、背景为黑 let contours = new cv.MatVector(); let hierarchy = new cv.Mat(); // You can try more different parameters cv.findContours(src, contours, hierarchy, cv.RETR_CCOMP, cv.CHAIN_APPROX_SIMPLE); // draw contours with random Scalar for (let i = 0; i < contours.size(); ++i) { let color = new cv.Scalar(Math.round(Math.random() * 255), Math.round(Math.random() * 255), Math.round(Math.random() * 255)); cv.drawContours(dst, contours, i, color, 1, cv.LINE_8, hierarchy, 100); } cv.imshow('canvasOutput', dst); src.delete(); dst.delete(); contours.delete(); hierarchy.delete(); // 释放资源

这段代码把本文的核心要点串成了完整的流水线,值得逐行对照:

  1. 二值化是前提cv.threshold(src, src, 120, 200, cv.THRESH_BINARY)先把灰度图变成黑底白前景,满足"白色对象、黑色背景"的约定;
  2. cv.RETR_CCOMP+cv.CHAIN_APPROX_SIMPLE是常用组合:两级层级足够表达"外边界 + 孔洞",简单近似法压缩冗余点、节省内存;
  3. 逐条轮廓绘制并传入 hierarchy 与 maxLevel:示例中cv.drawContours(dst, contours, i, color, 1, cv.LINE_8, hierarchy, 100)每次只绘制第i条轮廓(contourIdx = i),maxLevel = 100表示在其之下递归绘制嵌套轮廓,每条轮廓使用随机颜色以便区分;
  4. 显式释放资源:OpenCV.js 基于 WASM 的MatMatVector需要手动调用delete(),否则会造成内存泄漏——这是 JS 绑定与 C++ 版使用习惯上的关键差异。

如果熟悉 Python 版本,可参考仓库中的交叉示例 samples/python/snippets/contours.py,它用cv.findContours(img.copy(), cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)配合cv.drawContours(..., hierarchy, abs(levels))演示了maxLevel在不同层级下的绘制效果,与上文maxLevel参数的语义完全一致。

五、轮廓近似方法(Contour Approximation Modes)

近似方法是cv.findContours的第五个参数,它决定轮廓"存多少点"。轮廓存储的是形状边界的 (x, y) 坐标——但需要存储所有坐标吗?这正由该参数指定。

仓库头文件 ContourApproximationModes 枚举 给出了完整定义:

枚举值JS 中的常量行为
CHAIN_APPROX_NONE = 1cv.CHAIN_APPROX_NONE存储轮廓上的所有点,相邻两点必然是水平、垂直或对角邻居(max(abs(x1-x2), abs(y2-y1)) == 1
CHAIN_APPROX_SIMPLE = 2cv.CHAIN_APPROX_SIMPLE压缩水平、垂直和对角线段,只保留端点。例如一个矩形轮廓只需 4 个点即可编码
CHAIN_APPROX_TC89_L1 / TC89_KCOScv.CHAIN_APPROX_TC89_L1Teh-Chin 链近似算法的两种变体,用于更激进的轮廓压缩

教程中的例子很直观:如果你找到一条直线的轮廓,需要用线上所有的点来表示它吗?不需要,只需要两个端点即可。这正是cv.CHAIN_APPROX_SIMPLE的作用——它删除所有冗余点并压缩轮廓,从而节省内存

因此实际使用中的经验法则是:做形状分析(面积、周长、边界框、多边形拟合等)时优先用CHAIN_APPROX_SIMPLE;只有在需要逐像素的边界信息(例如链码分析、精细描边)时才用CHAIN_APPROX_NONE

六、相关源码与验证入口

围绕本文涉及的 API,仓库中还可以从以下几处深入验证:

  • C++ 权威定义:opencv2/imgproc.hpp 中的findContours声明注释、opencv2/imgproc.hpp 中的drawContours声明注释,是modemethodhierarchymaxLevel语义的最终出处;
  • 轻量替代算法findContoursLinkRuns:头文件(opencv2/imgproc.hpp)指出它不分配临时图像、内存占用更低,但仅支持CV_8UC1、仅输出两级层级(RETR_CCOMP模式)、近似方式固定为CHAIN_APPROX_SIMPLE。OpenCV.js 测试用例 modules/js/test/test_imgproc.js 正是用它配合minEnclosingTriangleminAreaRect等轮廓后处理函数做回归验证;
  • 教程原文:js_contours_begin.markdown,以及其中嵌入的交互式示例页面 js_contours_begin_contours.html。

七、要点回顾

  1. 轮廓是等强度边界点的连线,查找轮廓前应先二值化,且保证"白前景、黑背景";OpenCV 3.2 起findContours不修改源图像;
  2. cv.findContours(image, contours, hierarchy, mode, method, offset)的两个枚举参数——modeRETR_EXTERNAL / RETR_LIST / RETR_CCOMP / RETR_TREE / RETR_FLOODFILL)与methodCHAIN_APPROX_NONE / CHAIN_APPROX_SIMPLE / TC89 变体)——分别决定"找哪些轮廓"和"存多少点";
  3. cv.drawContours通过contourIdx选择单条或全部轮廓(负数绘制全部),thickness < 0时按奇偶规则填充轮廓内部,hierarchy + maxLevel控制嵌套绘制深度;
  4. 在 OpenCV.js 中,MatMatVector等对象用完后务必调用delete()释放 WASM 堆上的内存;
  5. 典型组合RETR_CCOMP + CHAIN_APPROX_SIMPLE兼顾了层级信息与内存效率,是轮廓检测入门的推荐起点。

【免费下载链接】opencvOpen Source Computer Vision Library项目地址: https://gitcode.com/GitHub_Trending/opencv31/opencv

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询