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_TREE、RETR_CCOMP等层级模式的基础。
此外,源码注释还指出一个输入细节:当mode为RETR_CCOMP或RETR_FLOODFILL时,输入也可以是 32 位整型标签图像(CV_32SC1)(opencv2/imgproc.hpp)。
2.1 轮廓检索模式(cv.RetrievalModes)
mode参数决定了提取哪些轮廓以及是否建立层级关系。仓库头文件中 RetrievalModes 枚举给出了完整定义:
| 枚举值 | JS 中的常量 | 行为 |
|---|---|---|
RETR_EXTERNAL = 0 | cv.RETR_EXTERNAL | 只提取最外层轮廓,所有轮廓的hierarchy[i][2] = hierarchy[i][3] = -1 |
RETR_LIST = 1 | cv.RETR_LIST | 提取所有轮廓,但不建立任何层级关系 |
RETR_CCOMP = 2 | cv.RETR_CCOMP | 提取所有轮廓并组织为两级层级:第一级是连通域的外部边界,第二级是孔洞的边界;若孔洞内还嵌套其他轮廓,仍放在第一级 |
RETR_TREE = 3 | cv.RETR_TREE | 提取所有轮廓并重建完整的嵌套层级树 |
RETR_FLOODFILL = 4 | cv.RETR_FLOODFILL | 从指定位置洪水填充并提取轮廓 |
从源码结构看,当前版本还有一个值得关注的性能优化:头文件注释指出,从 OpenCV 4.14 起,当mode为RETR_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,其中canvasInput与canvasOutput两个 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(); // 释放资源这段代码把本文的核心要点串成了完整的流水线,值得逐行对照:
- 二值化是前提:
cv.threshold(src, src, 120, 200, cv.THRESH_BINARY)先把灰度图变成黑底白前景,满足"白色对象、黑色背景"的约定; cv.RETR_CCOMP+cv.CHAIN_APPROX_SIMPLE是常用组合:两级层级足够表达"外边界 + 孔洞",简单近似法压缩冗余点、节省内存;- 逐条轮廓绘制并传入 hierarchy 与 maxLevel:示例中
cv.drawContours(dst, contours, i, color, 1, cv.LINE_8, hierarchy, 100)每次只绘制第i条轮廓(contourIdx = i),maxLevel = 100表示在其之下递归绘制嵌套轮廓,每条轮廓使用随机颜色以便区分; - 显式释放资源:OpenCV.js 基于 WASM 的
Mat、MatVector需要手动调用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 = 1 | cv.CHAIN_APPROX_NONE | 存储轮廓上的所有点,相邻两点必然是水平、垂直或对角邻居(max(abs(x1-x2), abs(y2-y1)) == 1) |
CHAIN_APPROX_SIMPLE = 2 | cv.CHAIN_APPROX_SIMPLE | 压缩水平、垂直和对角线段,只保留端点。例如一个矩形轮廓只需 4 个点即可编码 |
CHAIN_APPROX_TC89_L1 / TC89_KCOS | cv.CHAIN_APPROX_TC89_L1等 | Teh-Chin 链近似算法的两种变体,用于更激进的轮廓压缩 |
教程中的例子很直观:如果你找到一条直线的轮廓,需要用线上所有的点来表示它吗?不需要,只需要两个端点即可。这正是cv.CHAIN_APPROX_SIMPLE的作用——它删除所有冗余点并压缩轮廓,从而节省内存。
因此实际使用中的经验法则是:做形状分析(面积、周长、边界框、多边形拟合等)时优先用CHAIN_APPROX_SIMPLE;只有在需要逐像素的边界信息(例如链码分析、精细描边)时才用CHAIN_APPROX_NONE。
六、相关源码与验证入口
围绕本文涉及的 API,仓库中还可以从以下几处深入验证:
- C++ 权威定义:opencv2/imgproc.hpp 中的
findContours声明注释、opencv2/imgproc.hpp 中的drawContours声明注释,是mode、method、hierarchy、maxLevel语义的最终出处; - 轻量替代算法
findContoursLinkRuns:头文件(opencv2/imgproc.hpp)指出它不分配临时图像、内存占用更低,但仅支持CV_8UC1、仅输出两级层级(RETR_CCOMP模式)、近似方式固定为CHAIN_APPROX_SIMPLE。OpenCV.js 测试用例 modules/js/test/test_imgproc.js 正是用它配合minEnclosingTriangle、minAreaRect等轮廓后处理函数做回归验证; - 教程原文:js_contours_begin.markdown,以及其中嵌入的交互式示例页面 js_contours_begin_contours.html。
七、要点回顾
- 轮廓是等强度边界点的连线,查找轮廓前应先二值化,且保证"白前景、黑背景";OpenCV 3.2 起
findContours不修改源图像; cv.findContours(image, contours, hierarchy, mode, method, offset)的两个枚举参数——mode(RETR_EXTERNAL / RETR_LIST / RETR_CCOMP / RETR_TREE / RETR_FLOODFILL)与method(CHAIN_APPROX_NONE / CHAIN_APPROX_SIMPLE / TC89 变体)——分别决定"找哪些轮廓"和"存多少点";cv.drawContours通过contourIdx选择单条或全部轮廓(负数绘制全部),thickness < 0时按奇偶规则填充轮廓内部,hierarchy + maxLevel控制嵌套绘制深度;- 在 OpenCV.js 中,
Mat、MatVector等对象用完后务必调用delete()释放 WASM 堆上的内存; - 典型组合
RETR_CCOMP + CHAIN_APPROX_SIMPLE兼顾了层级信息与内存效率,是轮廓检测入门的推荐起点。
【免费下载链接】opencvOpen Source Computer Vision Library项目地址: https://gitcode.com/GitHub_Trending/opencv31/opencv
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考