首页
/ OpenCV.js 轮廓进阶函数实战:凸包缺陷、点在多边形内测试与形状匹配

OpenCV.js 轮廓进阶函数实战:凸包缺陷、点在多边形内测试与形状匹配

2026-09-04 10:08:16作者:郁楠烈Hubert

本篇基于 OpenCV.js 官方教程 Contours : More Functions 展开,聚焦轮廓分析中的三个进阶函数:cv.convexityDefects(凸包缺陷)、cv.pointPolygonTest(点到轮廓的最短距离)与 cv.matchShapes(形状相似度匹配)。读完本篇,你将掌握如何用这三个函数量化轮廓的凹陷程度、判断点与轮廓的位置关系、比较任意两个轮廓的形状相似性,并能对照 OpenCV 源码理解每个函数的返回格式与边界行为,配合仓库中现成的浏览器端可运行示例(Web 示例页面 + 输入图像)直接复现实验。

一、功能定位:这三个函数解决什么问题

在 OpenCV 的轮廓分析体系中,cv.findContours 提取出轮廓点集之后,还需要一系列“几何度量”函数来刻画轮廓的形态。本篇三个函数分别对应三类典型需求:

函数 解决的问题 输入 输出
cv.convexityDefects 找出轮廓相对其凸包的所有凹陷位置及凹陷深度 轮廓 + 凸包索引 4 元素缺陷向量(起点、终点、最远点、深度)
cv.pointPolygonTest 计算点到轮廓的最短距离,并带内外符号 轮廓 + 待测点 带符号距离(正/负/零)
cv.matchShapes 比较两个轮廓(或灰度图)的形状相似度 两个轮廓 相似度度量值(越小越相似)

这三个函数在 C++ 端属于几何算法核心,在当前仓库中实现于 modules/geometry/src/convhull.cpp 等文件,函数声明见 modules/geometry/include/opencv2/geometry/2d.hpp

凸包缺陷示例使用的输入图 shape.jpg

二、凸包缺陷 Convexity Defects

2.1 概念:什么是凸包缺陷

回顾轮廓特征章节(js_contour_features)中对凸包(convex hull)的定义:凸包是包裹整个轮廓的最小凸多边形。物体轮廓上任何偏离该凸包的位置,都可以被视为一个“凸包缺陷”(convexity defect),即轮廓的凹陷处。

OpenCV 的可视化方式是:连接凸包边的起点和终点画一条直线,然后在轮廓上离这条直线最远的点处画一个圆。这个最远点就是缺陷的代表点,它到弦的距离就是缺陷深度。

注意:求凸包时必须传 returnPoints = false(JS 中即 cv.convexHull(cnt, hull, false, false)),得到的凸包才是轮廓点索引cv.convexityDefects 需要这种索引形式的输入。若返回的是坐标点,缺陷计算无法把索引对应回原始轮廓。

2.2 函数接口与参数

cv.convexityDefects(contour, convexhull, convexityDefect)
参数 说明
contour 输入轮廓,要求为 CV_32SC2 点集(findContours 的默认输出)
convexhull cv.convexHull 求得、且必须包含轮廓点索引的凸包
convexityDefect 输出的凸包缺陷向量。每个缺陷是一个 4 元素数组 (start_index, end_index, farthest_pt_index, fixpt_depth):前三个是缺陷起点、终点、最远点在原始轮廓中的 0 基索引fixpt_depth 是最远点到凸包边的距离的定点近似(8 位小数位),要得到浮点深度需除以 256,即 depth = fixpt_depth / 256.0

2.3 完整示例代码

仓库中附有可直接在浏览器运行的示例页 js_contours_more_functions_convexityDefects.html,其核心代码如下:

let src = cv.imread('canvasInput');
let dst = cv.Mat.zeros(src.rows, src.cols, cv.CV_8UC3);
cv.cvtColor(src, src, cv.COLOR_RGBA2GRAY, 0);
cv.threshold(src, src, 100, 200, cv.THRESH_BINARY);
let contours = new cv.MatVector();
let hierarchy = new cv.Mat();
cv.findContours(src, contours, hierarchy, cv.RETR_CCOMP, cv.CHAIN_APPROX_SIMPLE);
let hull = new cv.Mat();
let defect = new cv.Mat();
let cnt = contours.get(0);
let lineColor = new cv.Scalar(255, 0, 0);
let circleColor = new cv.Scalar(255, 255, 255);
cv.convexHull(cnt, hull, false, false);   // 第 4 个参数 returnPoints 必须为 false
cv.convexityDefects(cnt, hull, defect);
for (let i = 0; i < defect.rows; ++i) {
    // defect.data32S 中每 4 个 int 描述一个缺陷
    let start = new cv.Point(cnt.data32S[defect.data32S[i * 4] * 2],
                             cnt.data32S[defect.data32S[i * 4] * 2 + 1]);
    let end = new cv.Point(cnt.data32S[defect.data32S[i * 4 + 1] * 2],
                           cnt.data32S[defect.data32S[i * 4 + 1] * 2 + 1]);
    let far = new cv.Point(cnt.data32S[defect.data32S[i * 4 + 2] * 2],
                           cnt.data32S[defect.data32S[i * 4 + 2] * 2 + 1]);
    cv.line(dst, start, end, lineColor, 2, cv.LINE_AA, 0);
    cv.circle(dst, far, 3, circleColor, -1);
}
cv.imshow('canvasOutput', dst);
src.delete(); dst.delete(); hierarchy.delete(); contours.delete(); hull.delete(); defect.delete();

几个关键点:

  • defect.data32S[i * 4] 取的是轮廓点索引,而 cnt.data32S[k * 2] / [k * 2 + 1] 才是该点的 x、y 坐标(CV_32SC2 每点占 2 个 32 位整数),二者必须区分;
  • 示例对输入图(shape.jpg)做灰度化 + 二值化(阈值 100/200),用 cv.RETR_CCOMP 取轮廓,最终在 dst 上用红色画“起点—终点”弦线、白色画最远点圆,直观呈现每个凹陷;
  • 示例末尾统一调用 delete() 释放 Mat,这是 OpenCV.js 中避免内存泄漏的惯用写法。

2.4 源码级行为解析

阅读 modules/geometry/src/convhull.cppconvexHull 的实现,可以确认“必须传索引”的原因:当 returnPoints 为 false 时,函数把结果写成 CV_32S 的一维索引向量(Mat(nout, 1, CV_32S, hullbuf));为 true 时才拷贝回坐标点。

再看 convexityDefects 的 C++ 实现(自 L353 起),可以得到几条实践边界:

  • 点集过少时直接返回空:轮廓点数 npoints <= 3 时缺陷向量被置空返回——三角形本身就是凸的,没有缺陷可言;
  • 凸包退化为 1~2 点时视为恒凸:源码注释明确 if hull consists of one or two points, contour is always convex,同样返回空缺陷;
  • 凸包索引必须单调:函数会校验凸包索引序列的单调性,若轮廓存在自相交导致索引不单调,会抛出 StsBadArg 错误("The convex hull indices are not monotonous..."),这是遇到异常输入时值得排查的方向;
  • 深度的几何含义:对每一对相邻凸包点 pt0 → pt1,实现沿轮廓逐点计算点到弦线的垂直距离 dist = |−dy0*dx + dx0*dy| * scale,其中 scale = 1/|pt1−pt0|,记录最大距离点作为 farthest_pt_index,深度即该距离的定点量化值。

三、点在多边形内测试 Point Polygon Test

3.1 功能与返回值的符号约定

cv.pointPolygonTest 求图像中一个点与轮廓之间的最短距离,返回带符号距离

  • 在轮廓内部 → 正值;
  • 在轮廓外部 → 负值;
  • 恰好在轮廓上 → 零。

这意味着一次调用同时完成了“内外判断”与“距离度量”两件事,常用于前景/背景判定、质心校验等场景。

3.2 函数接口

cv.pointPolygonTest(contour, pt, measureDist)
参数 说明
contour 输入轮廓
pt 待测点,cv.Point 类型
measureDist true 时估计点到最近轮廓边的带符号距离;为 false 时只判断点在轮廓内/外/上(返回 ±1 或 0)

3.3 使用示例

let dist = cv.pointPolygonTest(cnt, new cv.Point(50, 50), true);

若只想判断内外,把第三个参数改为 false 即可,函数只返回 1(内)、-1(外)或 0(在轮廓上),不计算实际距离。该函数的 C++ 实现位于 modules/geometry/src/geometry.cpp,与前述凸包函数同属 geometry 模块的 2D 几何工具集。

四、形状匹配 Match Shapes

4.1 基于 Hu 矩的相似度度量

cv.matchShapes() 用于比较两个形状(两个轮廓,或两张灰度图),返回一个相似度度量值——结果越小,匹配越好。它基于 Hu 矩(Hu-moment values)计算:Hu 矩具有平移、缩放、旋转不变性,因此两个形状即使经过平移或大小缩放,仍能得到较小的匹配误差,适合做形状级别的识别与归类。不同的比较方法(method)对应不同的不变矩组合,OpenCV 提供了多种 cv::ShapeMatchModes,可选方法可在官方 API 文档中查阅。

4.2 函数接口

cv.matchShapes(contour1, contour2, method, parameter)
参数 说明
contour1 第一个轮廓或灰度图像
contour2 第二个轮廓或灰度图像
method 比较方法,取值见 cv::ShapeMatchModes
parameter 方法相关参数(当前版本暂不支持)

4.3 完整示例代码:在 coins.jpg 上比较两枚硬币轮廓

仓库提供示例页 js_contours_more_functions_shape.html,输入图像为 coins.jpg,核心逻辑如下:

形状匹配示例使用的输入图 coins.jpg

let src = cv.imread('canvasInput');
let dst = cv.Mat.zeros(src.rows, src.cols, cv.CV_8UC3);
cv.cvtColor(src, src, cv.COLOR_RGBA2GRAY, 0);
cv.threshold(src, src, 177, 200, cv.THRESH_BINARY);
let contours = new cv.MatVector();
let hierarchy = new cv.Mat();
cv.findContours(src, contours, hierarchy, cv.RETR_CCOMP, cv.CHAIN_APPROX_SIMPLE);
let contourID0 = 10;
let contourID1 = 5;
let color0 = new cv.Scalar(255, 0, 0);
let color1 = new cv.Scalar(0, 0, 255);
// 可尝试更多不同的参数
let result = cv.matchShapes(contours.get(contourID0), contours.get(contourID1), 1, 0);
matchShapesOutput.innerHTML = result;
cv.drawContours(dst, contours, contourID0, color0, 1, cv.LINE_8, hierarchy, 100);
cv.drawContours(dst, contours, contourID1, color1, 1, cv.LINE_8, hierarchy, 100);
cv.imshow('canvasOutput', dst);
src.delete(); dst.delete(); contours.delete(); hierarchy.delete();

要点说明:

  • 示例中 method = 1,对应 cv::ShapeMatchModes 中的 CONTOURS_MATCH_I1;换用其他枚举值(如 2、3)即可对比不同度量方法的结果差异;
  • 由于输入是二值化后的硬币图,findContours 会得到多个轮廓,示例取索引 10 和 5 两条轮廓分别画成红色与蓝色以便目视对照,并输出二者匹配得分;
  • drawContours 的倒数第二个参数传了 hierarchy 与偏移量 100,是为了在有层级(外轮廓/孔洞)的轮廓集合中正确绘制指定 contourID

五、实践要点小结

结合文档与源码,使用这三个函数时建议注意:

  1. 凸包索引先行cv.convexHull 第 4 个参数(returnPoints)必须为 false,缺陷函数才拿得到索引;这是文档明确强调的前置条件,源码中 CV_32S 索引向量的输出形式也印证了这一点。
  2. 深度换算convexityDefects 返回的 fixpt_depth 是定点数,展示或阈值过滤前记得除以 256 得到真实像素距离。
  3. 符号距离的复用价值pointPolygonTest 的正负号可直接当作内外掩码使用;measureDist=false 时是更廉价的纯判定模式。
  4. 形状匹配的不变性前提matchShapes 依赖 Hu 矩的平移/缩放/旋转不变性,适合比较“形状”而非“位置”;若两图形形状本身差异大,得分会明显偏大。
  5. OpenCV.js 内存管理:所有示例遵循“用完即 delete()”的原则,MatMatVectorhulldefect 等对象都应显式释放,尤其在循环或连续帧处理场景中。

六、延伸阅读

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
916
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388