Skip to content

Repository files navigation

opencvjs

OpenCV 5.0.0 WebAssembly

opencv.js plus a layer of extra cv.Mat convenience methods. Baseline: OpenCV 5.0.0, WebAssembly (self-built — see Build from source).

OpenCV upstream is actively maintained — 5.0.0 shipped on 2026-06-06, and the 4.x line is still receiving releases in parallel (4.14.0 on 2026-07-19). What upstream does not ship is a standalone, production-oriented opencv.js: the official build is a reduced-function tutorial build, bundled inside opencv-{VERSION}-docs.zip on each release and mirrored at docs.opencv.org/{VERSION}/opencv.js. An OpenCV maintainer states it plainly in opencv#25425: "Only reduced subset of functions is provided here (size vs functionality compromise). Due to this there is no official OpenCV JS release. We don't recommend to use that file directly in your projects."

Up to 1.x this project shipped a 13.9 MB asm.js file derived from the official OpenCV 4.0.1 build, patched in place and never rebased. 2.0 builds the artifact from source (Docker + emsdk, pinned to build/opencv-version.txt) with its own export whitelist, and keeps the extension layer as separate modules under src/js/.

What changed in 3.0

  • Baseline moved from OpenCV 4.14.0 to 5.0.0. This is a genuinely breaking upgrade.
  • cv.CascadeClassifier, cv.HOGDescriptor, cv.AKAZE, cv.BRISK, cv.KAZE, cv.AgastFeatureDetector and cv.groupRectangles are gone. Not a whitelist choice — OpenCV 5 deleted them from the C++ API, so they cannot be whitelisted back. Code using them throws TypeError. See Known Issues for what replaces what.
  • cv.SVDecomp and cv.mulSpectrums are unaffected — still native, still whitelisted; their 5.0.0 declarations are byte-identical to 4.14.0's.
  • Everything else in the API — the 20 extra Mat methods, cv.norm2, the SIMD/baseline runtime probe, the entry point — is unchanged.

What changed in 2.1

  • The package now ships two wasm builds and picks one at runtime. loadOpenCV() probes for WebAssembly SIMD and loads dist/simd/ or dist/baseline/ accordingly. You do not have to do anythingawait require("@haoking/opencvjs")() is unchanged.
  • Speed-ups are large but not universal: 12.81x/13.13x on absdiff (arm64/x86-64), but dft is 0.91x on both architectures — a real regression. See SIMD 实测加速比 for the full table and when to turn SIMD off.
  • dist/ layout changed: dist/opencv.js and dist/opencv_js.wasm moved into dist/baseline/ and dist/simd/. Only code that deep-imports @haoking/opencvjs/dist/opencv.js breaks; main, types and the entry point are unchanged.
  • Package size: 8.3 MB → 19.4 MB unpacked (6.0 MB tarball) — the cost of carrying a baseline fallback, since SIMD browser coverage is only 93.57%.
  • Plus the argument-validation and TypeScript-declaration work that had accumulated since 2.0.0 shipped. Several calls that used to fail silently now throw — full list in CHANGELOG.md.

What changed in 2.0

  • Artifact: asm.js / OpenCV 4.0.1 → WebAssembly / OpenCV 4.14.0, built by build/build.sh and verified in CI.
  • Entry point is async: const cv = await require("@haoking/opencvjs")().
  • Native OpenCV methods are no longer overridden. roi / col / diag / reshape are back to their upstream behaviour; this project's fixed versions moved to roiClone / colClone / diagClone / reshapeRows.
  • 8 hand-written methods deleted in favour of native equivalents (three of them were broken: mds() always threw, mulSpectrums() returned NaN).
  • Full list with before/after code: docs/MIGRATION-2.0.md.

Features

  • 20 extra cv.Mat methods plus cv.norm2 on top of a stock OpenCV build — region copies, in-place writes, scalar arithmetic, and type-dispatching accessors (DATA() / PTR())
  • Two wasm builds, picked at runtime. The package ships both a -msimd128 build and a non-SIMD fallback; loadOpenCV() probes the engine with WebAssembly.validate() and loads the right one — callers do nothing. Measured on two architectures: up to 13.13x (absdiff), but dft is 0.91x on both — see SIMD 实测加速比 and 何时该手动关掉 SIMD. Override with loadOpenCV({ simd }) or OPENCV_SIMD; a variant named explicitly but missing throws rather than silently falling back
  • Reproducible build from OpenCV source (Docker + emsdk, version pinned in build/opencv-version.txt); the export whitelist lives in src/config/opencv_js.config.py and additionally exposes mulSpectrums and SVDecomp, which the upstream tutorial build does not
  • Arguments are checked before they reach wasm (src/js/guards.js) — on every extension method and on PTR() itself: out-of-range rects and row/column indices, mismatched sizes/types, non-finite constants and already-delete()d Mats all raise a standard TypeError / RangeError naming the function, the argument, the value received and the range expected. Without that layer a bad Rect aborts inside C++ and emscripten rethrows it as a bare number (a heap pointer — not an Error, e.message is undefined), while a bad row/column index does not fail at all and silently writes past the end of the Mat
  • TypeScript declarations generated from the artifact itself, not hand-written: dist/index.d.ts dumps Object.keys(cv) and Mat's prototype chain, and a test asserts the declared symbol set equals the runtime one in both directions. The .d.ts files in the ecosystem declare SIFT / PCA / FlannBasedMatcher, which this build does not have, and omit FaceDetectorYN, which it does — so code type-checks and then throws at runtime
  • Zero runtime dependencies, zero test dependencies (node:test only)
  • npm test runs 205 assertions (0 fail, node v22.22.2): 84 region-op correctness cases across 7 depths × 4 channel counts × 3 APIs, 29 clone() deep-copy and copy-semantics cases, 21 regression cases for the defects 2.0 fixed, 50 argument-validation cases, 12 SIMD-detection cases, 6 .d.ts-vs-runtime consistency cases, 2 baseline-vs-SIMD output-parity cases, and 1 wasm-artifact smoke test. The 3 artifact-dependent ones skip unless OPENCV_ARTIFACT / dist/simd/ are present, and turn from skip into hard failure under OPENCV_SMOKE_REQUIRED=1 / OPENCV_PARITY_REQUIRED=1 (both set in CI)
  • The same 205 assertions run twice in CI — once forced onto baseline, once onto simd. Plus an output-parity check that feeds identical deterministic input to both variants and compares 14 operations element-wise (bit-exact for integer kernels and the extension layer, 1e-5 relative for float kernels, whose SIMD paths may reassociate accumulation)
  • npm run bench runs two performance gates, each comparing the shipped method against the primitive it is built on (alternating rounds, warm-up round discarded, minimum taken): region-opsroiClone() 14.6–14.8 ms vs the native roi() + clone() it wraps 13.7–14.3 ms (20000 iterations, 64×64 CV_32FC1, Rect(1, 1, 32, 32)); inplace-ops — the five per-pixel write methods vs a raw-accessor reference loop, which catches the one shortcut that would otherwise slip through: routing those loops back through PTR() costs 1.8–2.1x and region-ops cannot see it (roiClone never touches PTR)

Known Issues

  • cv.CascadeClassifier / HOGDescriptor / AKAZE / BRISK / KAZE / AgastFeatureDetector / groupRectangles 在 3.0 起不存在。 用到它们的代码会 直接 TypeError(不是静默降级)。 这不是本项目的白名单取舍——OpenCV 5 把它们从 C++ API 里删了:5.0.0 的 modules/objdetect/include/opencv2/objdetect.hpp 里已无 CascadeClassifier / HOGDescriptor / groupRectanglesmodules/features/include/opencv2/features.hpp 里也已无 AKAZE / BRISK / KAZE / AgastFeatureDetector,整棵源码树里连 haarcascade 数据文件都不剩。加白名单救不回来。
    • 人脸检测的替代是 cv.FaceDetectorYN(DNN 路径,模型是 YuNet ONNX)。 绑定确实在产物里,但它不是 create() 静态方法——embind 把 OpenCV 的 静态工厂映射成了构造函数,实际写法是 new cv.FaceDetectorYN(modelPath, configPath, size, ...)(3–9 个参数)。 modelPath 指向 emscripten 虚拟文件系统里的路径,模型得先自己写进去。 ⚠️ 本项目没有验证过这条路径(仓库里没有模型文件,测试也没覆盖), 上面这段只描述绑定形状,不构成「能用」的保证。
    • 特征点AKAZE/BRISK/KAZE/AgastFeatureDetector 没有等价替代。 产物里仍有 ORBMSERFastFeatureDetectorGFTTDetectorSimpleBlobDetector
  • dftSplit() 的正确性没有任何证据支撑(已标 @deprecated,代码保留)。它把 cv.dft() 的 CCS 紧凑输出拆成实部/虚部两个 Mat,但这个展开约定从未被独立验证过; 1.x 时代它唯一的消费者是那个返回 NaN 的手写 mulSpectrums(),所以也不存在 「端到端跑通过」这回事。需要复数谱相乘请直接用原生 cv.mulSpectrums()——它在 CCS 格式上直接做乘法,根本不需要先拆分。
  • 浏览器路径整体未验证。 本仓库的自动化测试只覆盖 Node(CI 矩阵 18 / 20 / 22), 浏览器里一行都没跑过——包括 SIMD 探测在浏览器中的行为(WebAssembly.validate 有、 process.env 没有;resolveVariantprocess 做了 typeof 保护,逻辑上安全, 但没有实测过)。 这不是「拿不到单文件形态」的意思:build/build.sh --single-file 会产出 1.x 那样 的单文件产物(wasm base64 内联,约 11 MB),它只是不进 npm 包——见 Single-file build。 npm 包里是拆分形态:opencv.js(约 143 KB 的 glue)+ opencv_js.wasm,必须同目录、 文件名不能改;扩展层是 CommonJS 模块,<script> 直接引 glue 只能拿到原生 OpenCV, 要用扩展层得走打包器。
  • SIMD 在部分算子上更慢,且哪些算子更慢与架构有关。 实测 dft CV_32FC1arm64 与 x86-64 上都是 0.91x(比 baseline 慢 9%,两个架构复现,是真实退化); cvtColor RGBA2GRAY 在 arm64 上 1.00x、在 x86-64 上 0.84x。SIMD 不是无脑赢。 完整的两架构数据见 SIMD 实测加速比, 应对办法见 何时该手动关掉 SIMD

Requirements

  • Node.js >= 18(CI 覆盖 18 / 20 / 22)
  • 从源码构建产物还需要 Docker(build/build.sh 在 emsdk 容器里跑,宿主机不需要工具链)

Communication

  • If you found a bug, open an issue.
  • If you have a feature request, open an issue.
  • If you want to contribute, submit a pull request.

Installation

npm install @haoking/opencvjs
const loadCV = require("@haoking/opencvjs");

(async () => {
  const cv = await loadCV();
  const mat = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
  console.log("mat::" + mat.data32F); //mat::1,2,3,4,5,6,7,8,9
  mat.delete(); //Don't forget to delete cv.Mat when you don't want to use it any more.
})();

⚠️ 就绪判据只能看 typeof cv.Mat === "function" await 之后 cv.onRuntimeInitialized 这个属性依然存在(实测 typeoffunction)。拿它判断「还没就绪」会恒真,于是去等一个永不再触发的回调—— 本项目第一次 CI 冒烟测试就是这么挂掉的。

包里的 dist/ 有两份 wasm 产物,各自一个子目录:

dist/
  index.js  simd-detect.js  guards.js  typed-access.js  mat-region.js
  arithmetic.js  dft.js  index.d.ts        ← 与变体无关的扩展层
  baseline/opencv.js + opencv_js.wasm      ← 无 SIMD,任何环境都能跑
  simd/opencv.js     + opencv_js.wasm      ← -msimd128 编译

必须分目录,不是布局偏好。 两个变体的 .wasm 文件名都是编译期烘焙进 glue 的 常量 "opencv_js.wasm",改名会让运行时去取一个不存在的路径;同名文件放同一个目录 必然互相覆盖。glue 在 Node 下按 __dirname 定位 .wasm,所以每个子目录里必须各放 一份 glue,与同目录的 .wasm 原样配对。

ℹ️ 本文档此前在这里写着「两个变体的 glue 内容也不同,不能共用一份」。那句话是 错的:实测这两份 glue 逐字节相同(SHA-256 均为 da1f9d19…,各 143,365 B)。 分目录的理由只有 .wasm 同名这一条。每个目录仍要各放一份 glue,但那是因为 glue 按 __dirname.wasm,不是因为内容不同;反过来也不能依赖「它们永远相同」。

loadOpenCV() 会用 WebAssembly.validate() 探测运行时是否支持 SIMD,自动选择 对应变体:

const loadCV = require("@haoking/opencvjs");

// 下面三行是三种**互斥**的写法,一个进程里只能选一种调一次(见下方「一个进程里
// 只能加载一个变体」)。照抄整段会因为重复声明 cv 而直接语法错误。
await loadCV(); // 自动:支持 SIMD 就用 simd,否则 baseline
await loadCV({ simd: false }); // 强制 baseline
await loadCV({ simd: true }); // 强制 simd

loadCV.detectSimd(); // boolean:当前引擎支不支持 SIMD(不需要先加载,也不反映实际加载了谁)

也可以用环境变量 OPENCV_SIMD=0 / 1(认 0/1false/trueoff/onno/yes)。 优先级是 loadOpenCV({ simd }) > OPENCV_SIMD > 自动探测。

⚠️ 被明确点名的变体如果不存在,会抛错,不会悄悄换成另一个。 只有自动探测那条 路径才回落(回落时打一条 warning)。这条是刻意的:如果 OPENCV_SIMD=1 在缺 SIMD 产物时静默回落,那么「强制 SIMD 跑一遍测试」实际测的是 baseline,而结果会宣称测的 是 SIMD。同理,OPENCV_SIMD 的值拼错(ture)会抛错而不是被忽略。

⚠️ 一个进程里只能加载一个变体。 OpenCV 的 UMD 外壳把 Module 泄漏成了隐式 全局变量(Module = {},没有声明关键字,而外壳不是严格模式),第二个变体会撞上 第一个的 embind 注册表并抛 Cannot register public name 'IntVector' twice。 正常用法不受影响;确实要对比两个变体请开两个进程(test/simd-compare.js 就是这么做的)。

SIMD 的浏览器覆盖率是 93.57%(Chrome 91+ / Firefox 89+ / Safari 16.4+),所以 baseline 是必需的回退,两份都会随包发布——代价是包体积从 8.3 MB 涨到 19.4 MB (解包;tarball 6.0 MB)。SIMD 那份 wasm 本身就比 baseline 大 21.7% (10,363,503 B vs 8,515,975 B;brotli 后 2.25 MB vs 1.99 MB)。

SIMD 实测加速比

两个架构各测了一趟,真实产物,每个变体独立进程、启动顺序前后各一趟取最小值 (npm run simd:compare):

  • arm64 —— node v22.22.2 / darwin-arm64(本机)。噪声底用两份相同的二进制 标定过:±3%,所以 0.97–1.03 之间的比值不代表真实差异。
  • x86-64 —— node v22.23.1 / linux-x64(GitHub Actions ubuntu-24.04)。共享 runner,没有做同样的噪声标定,个位数百分比的差异不必当真。
操作 arm64 x86-64 上游 2020 年数据
absdiff 8UC3 256² 12.81x 13.13x
add 8UC1 256² 10.37x 11.23x
resize 8UC4 256²→128² 4.37x 4.36x 1.77x
GaussianBlur 8UC1 256² k=5 3.32x 2.60x 3.36x
pyrDown 32FC4 256² 3.27x 3.41x 3.09x
Sobel 32FC1 256² 2.07x 1.84x
warpAffine 8UC1 256² 1.65x 2.00x
blur 32FC1 256² k=5 1.55x 1.44x 0.519x
roiClone 64² 取 32²(扩展层) 1.00x 1.03x
replaceMatOnRect(扩展层) 0.95x 1.04x
cvtColor RGBA2GRAY 8UC4 256² 1.00x 0.84x
dft 32FC1 256² 0.91x 0.91x

三件值得注意的事:

  • dft 在两个架构上都是 0.91x —— 同一个数字在两台不同架构、不同 OS、不同 node 小版本的机器上复现,排除了偶然。这是真实退化,不是噪声。应对办法见下一节。
  • cvtColor 的退化有架构差异:arm64 上 1.00x(噪声底内,等于无变化),x86-64 上 掉到 0.84x。x86-64 那趟只有一个 CI 样本、未做噪声标定,但 16% 的差距远超任何 合理的噪声幅度,倾向于认为是真实的。也就是说「SIMD 在某算子上更慢」这件事本身还 依赖架构,不能只测一台机器就下结论。
  • 上游那个反例在两个平台都没有复现。 上游 2020 年测得 blur CV_32FC1 是 0.519x (慢一倍),这里是 arm64 1.55x / x86-64 1.44x,都是加速。而同一组数据里 GaussianBlur(3.32x vs 上游 3.36x)与 pyrDown(3.27x vs 3.09x)在 arm64 上几乎 吻合——所以不是整体标定问题,是逐算子的差异。结论:那组六年前的逐 kernel 数据 不能整体照搬(六年里 OpenCV 的 SIMD 内核与 emscripten 的代码生成都变了), 具体算子只能自己实测。顺带一提,GaussianBlur 在 x86-64 上是 2.60x、arm64 上 3.32x —— 同一个算子跨架构也能差这么多。

两个扩展层用例(roiClone / replaceMatOnRect)在 0.95–1.04x 之间,符合预期: 它们是 JS 侧的逐像素循环,不走 wasm 内核,本来就不该有变化。

何时该手动关掉 SIMD

默认不用管。 12 个算子里 10 个更快,其中 4 个是 3x 以上,两个是 10x 以上。

唯一有明确证据的例外是 DFT。 如果你的负载以 cv.dft() / cv.idft() 为主, SIMD 变体会慢约 9%(两个架构一致)。这时显式关掉:

const cv = await require("@haoking/opencvjs")({ simd: false });

或者进程级:

OPENCV_SIMD=0 node your-app.js

代价是全进程的。 变体是进程级的,不能按算子切换(一个进程只能加载一个变体, 见上)。关掉 SIMD 意味着同一进程里 absdiff / add 那两位数的加速比也一起没了 —— 它们很容易把 DFT 的 9% 赚回来。所以只有在 DFT 确实占主导时才值得这么做, 并且请自己实测,不要照搬这里的结论。

如果你在 x86-64 上跑大量 cvtColor,那里实测是 0.84x,同样可以考虑;但那只有一个 CI 样本,不如 DFT 那条结论硬。

拿不准就自己跑一趟:

npm run simd:compare   # 需要本地已 assemble 两个变体

它只打印数字、不作门禁(刻意永远 exit 0)——实测确实有更慢的项,把「必须更快」 做成门禁,结果只会是以后有人删门禁或只挑有利的算子来测。

TypeScript

package.jsontypes 指向 dist/index.d.ts,装完即可用,不需要 @types/*

import loadOpenCV = require("@haoking/opencvjs");

const cv = await loadOpenCV();
const mat = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
const roi = mat.roiClone(new cv.Rect(1, 1, 2, 2)); // roi: Mat
const data: Float32Array = roi.data32F;
(mat.delete(), roi.delete());

这份声明由构建产物自动 dumpbuild/gen-types.js,组装时执行),不是手写的: 顶层符号来自运行时的 Object.keys(cv),Mat 成员来自其整条原型链, test/types/dts-consistency.test.js 双向断言两个集合严格相等。

ℹ️ 为什么要这么做:生态里现有的 OpenCV.js 声明(@opencvjs/types、TechStark 的 src/)与运行时不符——声明了运行时没有的 SIFT / PCA / FlannBasedMatcher, 又漏掉了确实存在的 FaceDetectorYN。血统能追到 2019 年的 mirada,此后靠人手维护。 后果是最难受的那种:代码通过类型检查,然后运行时抛异常。

范围与限制:

  • 保证符号存在性与运行时严格一致(数量由产物决定,不是写死的;4.14.0 实测 1450 个顶层符号、75 个 Mat 成员,5.0.0 的数字待构建后回填)。
  • 不保证每个原生函数的参数类型精确——那需要解析 OpenCV 的 C++ 签名并复现 embind 的重载分发规则,超出本项目范围。未逐条标注的原生绑定一律是 (...args: any[]): any。 本项目自己写的扩展层(roiClone / DATA / PTR / replaceMatOn* 等)有准确签名。
  • 没有 Symbol.dispose,所以用不了 TS 5.2 的 using(实测本产物的 Mat 原型链上 没有任何 symbol 属性)。Mat 一律由调用方显式 delete()

Build from source

产物不入 git。本地要跑测试或自己出包:

./build/build.sh                # → build/out/baseline/   opencv.js + opencv_js.wasm
./build/build.sh --simd         # → build/out/simd/       opencv.js + opencv_js.wasm
./build/build.sh --single-file  # → build/out/singlefile/ 只有 opencv.js(wasm 已内联)
npm run assemble                # 两个变体 + src/js/ → dist/(含 index.d.ts)
npm test                        # 205 项
npm run bench                   # 两个性能门禁(test/bench/*.bench.js 全跑)
npm run simd:compare            # baseline vs simd 的实测加速比(只报数字,不是门禁)

npm run assemble 接受两个目录参数(例如 CI 下载下来的产物目录): npm run assemble -- /path/to/baseline /path/to/simd。simd 那个可以缺——本地没有 Docker 时只构得出 baseline,那时 dist/ 只含 baseline,一致性测试整组跳过。 在 CI 那种「本来就该有两个变体」的场合,设 OPENCV_REQUIRE_SIMD=1 让缺失从告警 升级为失败。不会拿 baseline 冒充 simd——那会让强制 SIMD 测到的其实是 baseline。

第三个变体 --single-file 不参与 npm 打包,见下一节。

CI 里 build-wasm.yml 用三变体矩阵并行构建、逐个跑冒烟测试,再用一个 verify 作业下载 baseline + simd 当场验双产物一致性;ci.yml 取它最近一次成功运行的 两个产物再组装,并把同一套测试在两个变体上各跑一遍。

Single-file build

给浏览器 <script> 直接引用的单文件形态:build/build.sh --single-file 把 wasm 以 base64 内联进 opencv.js(体积 +33%,约 11 MB 单个文件)。

它不在 npm 包里。 assemble.sh 会拒绝大于 2 MB 的 glue,正是为了挡住它被误打 进包——19.4 MB 的包已经够大,再塞一份 11 MB 的重复产物没有道理。

拿它的两个途径:

  1. GitHub Release —— 每个版本的 Release 页面附有该版本的单文件产物。
  2. 自己构建 —— ./build/build.sh --single-file,产物在 build/out/singlefile/ (需要 Docker)。也可以从 Build WASM 工作流那次运行的 CI artifact opencv-wasm-singlefile 直接下载。

⚠️ 仓库里没有自动上传 Release 的步骤,单文件产物由维护者在发版时手工附上。 如果某个版本的 Release 页面上没有它,那就是漏了——请开 issue,或按上面第 2 条 自己构建。(不做自动上传是因为 gh release upload 在 Release 尚不存在时会失败, 等于给每次打 tag 埋一个必红的步骤。)

单文件变体刻意走 baseline 而非 SIMD:它的使用场景是 <script> 直接引用, 那条路径上没有任何回退机制,而 SIMD 浏览器覆盖率是 93.57%——发一个无回退的 SIMD 单文件版等于让 6.4% 的浏览器直接白屏。也就是说单文件形态拿不到 SIMD 加速

⚠️ 单文件形态给到的是原生 OpenCV,没有本项目的扩展层(roiClone / DATA / PTR / replaceMatOn* 等)——扩展层是 CommonJS 模块,得走打包器。而且如 Known Issues 所述,浏览器路径整体没有验证过


Usage

下面所有示例都假定 cv 已经拿到:

const cv = await require("@haoking/opencvjs")();

示例里注释掉的输出值都是在 2.0 的产物上实际跑出来的(node v22.22.2)。

Argument validation

本项目的每个扩展方法都会在调用 wasm 之前校验参数,失败时抛标准 TypeError / RangeError,消息里带上函数名、参数名、实际收到的值和期望的范围:

let mat1 = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
try {
  mat1.roiClone(new cv.Rect(1, 1, 10, 10));
} catch (e) {
  console.log(e instanceof RangeError); //true
  console.log(e.message);
  //Mat.roiClone(rect): Rect(x=1, y=1, width=10, height=10) 超出 3×3(行×列)的 Mat —— 要求 0 ≤ x 且 x+width ≤ cols=3,0 ≤ y 且 y+height ≤ rows=3
}
mat1.delete();

其余几条的实际消息(同样逐字实测):

Mat.addOnCol(constant, col): col = 7 越界,有效范围 0..2

cv.matFromArray(rows, cols, type, array): CV_32FC1(5) 的 3×3 Mat 需要 9 个元素
(3×3×1 通道),实际收到 3 个 —— 元素不足时 TypedArray.set() 不报错,Mat 剩余
部分是未初始化的堆内存

cv.norm2(src1, src2, normType): src1 是 CV_32FC1(5),src2 是 CV_8UC1(0)
—— 两者类型必须一致

Mat.sum(): 接收者 Mat 已被 delete() —— 释放后的 Mat 不能再使用

这层校验解决的是两类都很难查的故障:

  1. abort 抛出的不是 Error 实例。 本产物在异常被编译掉的配置下构建,C++ 侧的 CV_Assert 失败会走 abort,emscripten 把它转成 throw <裸数字>——实测 mat.roi(new cv.Rect(1, 1, 10, 10)) 抛出的是一个纯数字(typeof e === "number"), e instanceof Errorfalsee.messageundefined,调用方想「记条日志再 降级」都会让自己再崩一次,而那个数字对定位问题毫无帮助。(模块本身在 abort 之后 仍可继续使用。)

    ℹ️ 这里不写具体数值,因为它是个堆指针而不是错误码,会随变体和分配历史变化。 本文档此前写的是 1914504:那个值在 baseline 上确实能复现,但同一段代码在 simd 变体上是 1955632——而 2.1 起支持 SIMD 的引擎默认加载的正是 simd。 同一进程内后续每次 abort 还会继续递增。可靠的判据只有 typeof e === "number", 不要依赖具体数值。

  2. 越界的行列号根本不会 abort。 embind 生成的 *Ptr(row, col) 不做边界检查: 3×3 CV_32FC1(共 36 字节)上 mat.PTR(9, 9) 返回 base+144 字节处的 Float32Array,读写都落在别人的堆上,不报任何错。replaceMatOnRect / rectAdd / rectSubtract / replaceMatOnCol / addOnCol / replaceMatOnPoint / replaceMatOnRow 全都由 PTR() 逐像素驱动,所以一个越界的 Rect 或列号就是一次 静默的堆破坏。这类比第 1 类危险得多。

PTR() 自己也查边界(它是公开 API,用户会绕开上面那些方法直接调):

let mat2 = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
try {
  mat2.PTR(9, 9);
} catch (e) {
  console.log(e.message);
  //Mat.PTR(row, col): row = 9 越界,有效范围 0..2
}
mat2.delete();

⚠️ 每个下标只校验一次。 rows / cols 是 embind getter,每读一次都是一次跨 语言调用——给 PTR() 加边界检查实测让它慢 49%(+23.7 ns/次)。所以扩展层内部 那些逐像素的循环不走 PTR():它们在入口用一次校验证明整个循环的下标范围 合法,循环里直接用原生访问器。不这么分工的话这些方法要慢 1.8–2.1x (倍数以 npm run bench 的 inplace-ops 门禁每次打印的那一行为准)。

DATA() 不收参数,没有可越界的入参。

标量运算数的取值范围addConstant / constantSubtract / mulConstant / constantDivide / addOnCol / replaceMatOnPoint 拒绝非数与 NaN,但放行 ±Infinity —— 它是合法的 IEEE-754 值,在代价图 / 距离图上是标准哨兵:

let mat3 = cv.matFromArray(2, 2, cv.CV_32FC1, [1, 2, 3, 4]);
console.log("" + mat3.addConstant(Infinity).DATA()); //Infinity,Infinity,Infinity,Infinity
mat3.delete();

写进数据里的 NaN / Infinity 完全不受限制(replaceMatOnRow / replaceMatOnCol 只查数组长度、不查元素值)——往某些像素写 NaN 表示「此处无效」是正当写法。 被拒的只有作为运算数NaNx + NaN 会把整个 Mat 一次性毁掉,而那通常是 上游已经出错的信号。

Commonly

add()

void cv.add(src1, src2, dst)

( dst = src1 + src2 )

src1 First input mat

src2 Second input mat

dst Output mat that has the same size and number of channels as the input mat

let mat1 = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
let mat2 = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
let dst = new cv.Mat();
cv.add(mat1, mat2, dst);
(mat1.delete(), mat2.delete());
//Don't forget to delete cv.Mat when you don't want to use it any more.
console.log("dst::" + dst.data32F); //dst::2,4,6,8,10,12,14,16,18

addConstant()

cv.Mat dst = src1.addConstant(constant)

( dst = src1 + constant )

src1 First input mat

constant Constant added to each element.

dst Output mat that has the same size and number of channels as the input mat

let mat1 = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
let dst = mat1.addConstant(10);
mat1.delete();
console.log("dst::" + dst.data32F); //dst::11,12,13,14,15,16,17,18,19

subtract()

void cv.subtract(src1, src2, dst)

( dst = src1 - src2 )

src1 First input mat

src2 Second input mat

dst Output mat that has the same size and number of channels as the input mat

let mat1 = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
let mat2 = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
let dst = new cv.Mat();
cv.subtract(mat1, mat2, dst);
(mat1.delete(), mat2.delete());
console.log("dst::" + dst.data32F); //dst::0,0,0,0,0,0,0,0,0

constantSubtract()

cv.Mat dst = src1.constantSubtract(constant)

( dst = constant - src1 )

constant Constant subtract each element.

src1 First input mat

dst Output mat that has the same size and number of channels as the input mat

let mat1 = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
let dst = mat1.constantSubtract(10);
mat1.delete();
console.log("dst::" + dst.data32F); //dst::9,8,7,6,5,4,3,2,1

mmul() → cv.gemm()

🔀 2.0 起 mat.mmul(b) 已删除,改用原生 cv.gemm(src1, src2, alpha, src3, beta, dst, flags)beta = 0src3 不参与计算,传一个空 Mat 即可。

let mat1 = cv.matFromArray(2, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6]);
let mat2 = cv.matFromArray(3, 2, cv.CV_32FC1, [7, 8, 9, 10, 11, 12]);
let noop = new cv.Mat();
let dst = new cv.Mat();
cv.gemm(mat1, mat2, 1, noop, 0, dst, 0);
(mat1.delete(), mat2.delete(), noop.delete());
console.log("dst::" + dst.data32F + ":::" + dst.rows + ":::" + dst.cols);
//dst::58,64,139,154:::2:::2

mul()

cv.Mat dst = src1.mul(src2, scale)

( dst = src1 • src2*scale ) —— 逐元素乘,OpenCV 原生方法。

src1 First input mat

src2 Second input mat

scale Optional scale factor.

dst Output mat that has the same size and number of channels as the input mat

let mat1 = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
let mat2 = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
let dst = mat1.mul(mat2, 2);
(mat1.delete(), mat2.delete());
console.log("dst::" + dst.data32F); //dst::2,8,18,32,50,72,98,128,162

mulConstant()

cv.Mat dst = src1.mulConstant(constant)

( dst = src1 * constant )

src1 First input mat

constant Constant multiplied with each element.

dst Output mat that has the same size and number of channels as the input mat

let mat1 = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
let dst = mat1.mulConstant(10);
mat1.delete();
console.log("dst::" + dst.data32F); //dst::10,20,30,40,50,60,70,80,90

divide()

void cv.divide(src1, src2, dst)

( dst = src1 / src2 )

src1 First input mat

src2 Second input mat

dst Output mat that has the same size and number of channels as the input mat

let mat1 = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
let mat2 = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
let dst = new cv.Mat();
cv.divide(mat1, mat2, dst);
(mat1.delete(), mat2.delete());
console.log("dst::" + dst.data32F); //dst::1,1,1,1,1,1,1,1,1

constantDivide()

cv.Mat dst = src1.constantDivide(constant)

( dst = constant / src1 )

constant Constant divided by each element.

src1 First input mat

dst Output mat that has the same size and number of channels as the input mat

let mat1 = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
let dst = mat1.constantDivide(10);
mat1.delete();
console.log("dst::" + dst.data32F);
//dst::10,5,3.3333332538604736,2.5,2,1.6666666269302368,1.4285714626312256,1.25,1.1111111640930176

2.0 修复:1.x 在多通道 Mat 上把通道 1+ 除以 0(被除数用 new cv.Scalar(c) 填充,而 Scalar 缺省分量是 0——实测填 CV_32FC316,0,0,16,0,0)。

let mat3 = new cv.Mat(1, 2, cv.CV_32FC3, new cv.Scalar(2, 2, 2, 2));
console.log("dst::" + mat3.constantDivide(16).data32F); //dst::8,8,8,8,8,8

reshapeRows() (1.x 名为 reshape()

cv.Mat dst = src1.reshapeRows(rows)

src1 First input mat

rows Reshape to rows

dst A new copy whose data equals src1's, laid out with the requested row count

let mat1 = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
let dst = mat1.reshapeRows(1);
console.log("dst::" + dst.data32F + ":::" + dst.rows + ":::" + dst.cols);
//dst::1,2,3,4,5,6,7,8,9:::1:::9
mat1.reshapeRows(4);
//RangeError: reshapeRows(4):3×3 的 9 个像素无法整除为 4 行
mat1.delete();
dst.delete();

🔀 改名理由与其它几个不同:这个产物的 embind 绑定里根本没有 Mat::reshape (实测 typeof cv.Mat.prototype.reshape === "undefined"),1.x 并没有覆盖任何东西。 改名是为腾出这个名字,同时把语义差异摆明——OpenCV 原生的 reshape() 返回共享内存的 新 header,这里返回的是副本。整除校验是 2.0 新加的(1.x 静默产出错误形状)。

sum()

Number dst = src1.sum()

src1 First input mat

dst 所有元素(含各通道)的标量和

let mat1 = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
console.log("sum::" + mat1.sum()); //sum::45
mat1.delete();

let mat2 = cv.matFromArray(
  2,
  2,
  cv.CV_32FC3,
  [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
);
console.log("sum::" + mat2.sum()); //sum::78
mat2.delete();

ℹ️ 与 OpenCV 的 cv.sum() 语义不同——后者返回逐通道的 Scalar,且不在本产物的 白名单里(实测 typeof cv.sum === "undefined")。

norm()

Number dst = cv.norm(src1)

src1 First input mat

dst Norm of src1

let mat1 = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
let dst = cv.norm(mat1);
mat1.delete();
console.log("dst::" + dst); //dst::16.881943016134134

norm2()

Number dst = cv.norm2(src1, src2, normType = cv.NORM_L2)

src1 First input mat

src2 Second input mat

dst ‖src1 − src2‖

let mat1 = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
// prettier-ignore
let mat2 = cv.matFromArray(3, 3, cv.CV_32FC1, [9, 10, 11, 12, 13, 14, 15, 16, 17]);
let dst2 = cv.norm2(mat1, mat2);
(mat1.delete(), mat2.delete());
console.log("dst2::" + dst2); //dst2::24

ℹ️ 不要改用原生 cv.norm(a, b, normType)——这个产物里没有那个重载。 embind 只按参数个数分发,而 C++ 的两组 norm 重载(norm(src, normType, mask)norm(src1, src2, normType, mask))在 2 参和 3 参上撞车,绑定生成器只保留了前一组。 实测 cv.norm(a, b, cv.NORM_L2)BindingError: Cannot pass "4" as a Mat;更糟的是 cv.norm(a, b) 不报类型错,而是把 Mat 的 wire 指针当整数 normType 传进 C++, 到 OpenCV 内部的断言处才炸。

diagClone() (1.x 名为 Diag()

cv.Mat dst = src1.diagClone(d = 0)

src1 First input mat

d Index of the diagonal

dst A continuous copy of the diagonal

let mat1 = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
let dst = mat1.diagClone();
mat1.delete();
console.log("dst::" + dst.data32F + ":::" + dst.rows + ":::" + dst.cols);
//dst::1,5,9:::3:::1

🔀 OpenCV 原生的 mat.diag(d) 仍然可用,返回的是视图(非连续,读它的 .data* 会得到错误数据;但写它会写回源 Mat)。2.0 不再覆盖它。

vconcat() / hconcat() → cv.vconcat() / cv.hconcat()

🔀 2.0 起 mat.vconcat(b) / mat.hconcat(b) 已删除,改用原生同名函数, 参数由 Mat 变成 MatVector。

let mat1 = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
let vec = new cv.MatVector();
vec.push_back(mat1);
vec.push_back(mat1);

let dstV = new cv.Mat();
cv.vconcat(vec, dstV);
console.log("dstV::" + dstV.data32F + ":::" + dstV.rows + ":::" + dstV.cols);
//dstV::1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9:::6:::3

let dstH = new cv.Mat();
cv.hconcat(vec, dstH);
console.log("dstH::" + dstH.data32F + ":::" + dstH.rows + ":::" + dstH.cols);
//dstH::1,2,3,1,2,3,4,5,6,4,5,6,7,8,9,7,8,9:::3:::6

(mat1.delete(), vec.delete());

row()

cv.Mat dst = src1.row(row) —— OpenCV 原生方法,返回视图

src1 First input mat

row Index of the rows

dst Output mat that has one row

let mat1 = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
let dst = mat1.row(2);
console.log("dst::" + dst.data32F + ":::" + dst.rows + ":::" + dst.cols);
//dst::7,8,9:::1:::3
(mat1.delete(), dst.delete());

ℹ️ 单独一行本来就是连续的(实测 isContinuous() === true),所以直读 .data* 没问题。 列和矩形区域不是,见下面的 colClone() / roiClone()

colClone() (1.x 名为 col()

cv.Mat dst = src1.colClone(col)

src1 First input mat

col Index of the cols

dst A continuous copy of that column

let mat1 = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
console.log("colClone::" + mat1.colClone(2).data32F); //colClone::3,6,9
console.log("col::" + mat1.col(2).data32F); //col::3,4,5   ← 原生视图,非连续,直读是错的
mat1.delete();

多通道上差得更远(3×3 CV_32FC2,值 1..18):

// prettier-ignore
let mat2 = cv.matFromArray(3, 3, cv.CV_32FC2,
  [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]);
console.log("colClone::" + mat2.colClone(2).data32F); //colClone::5,6,11,12,17,18
console.log("col::" + mat2.col(2).data32F); //col::5,6,7,8,9,10   ← 错的
mat2.delete();

🔀 2.0 不再覆盖原生 col() 要读数据用 colClone(),要写回源 Mat 用原生 col()(视图)。

roiClone() (1.x 名为 roi()

cv.Mat dst = src1.roiClone(rect)

src1 First input mat

rect a rect

dst A continuous copy of that sub-rectangle

let mat1 = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
let rect1 = new cv.Rect(1, 1, 2, 2);
let dst = mat1.roiClone(rect1);
console.log("dst::" + dst.data32F + ":::" + dst.rows + ":::" + dst.cols);
//dst::5,6,8,9:::2:::2

// 原生 roi() 仍是视图:写它会写回源 Mat
let view = mat1.roi(rect1);
view.setTo(new cv.Scalar(0, 0, 0, 0));
console.log("mat1::" + mat1.data32F); //mat1::1,2,3,4,0,0,7,0,0

(mat1.delete(), dst.delete(), view.delete());

ℹ️ 副本是有代价的:256×256 CV_32FC2 上取 Rect(0, 0, 128, 128)、5000 次, 原生视图 1.2–1.9 ms vs roiClone() 20.8–23.3 ms(node v22.22.2 / darwin-arm64, 3 次独立测量各取第 2–4 轮)。只需要写回源 Mat 时别用 *Clone

replaceMatOnRect()

void src1.replaceMatOnRect(src2, rect)

src1 First input mat will be changed as output

src2 Second input mat as rect mat

rect rect input to replace

let mat1 = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
let rect1 = new cv.Rect(1, 1, 2, 2);
let rectmat = cv.matFromArray(2, 2, cv.CV_32FC1, [11, 12, 13, 14]);
mat1.replaceMatOnRect(rectmat, rect1);
console.log("mat1::" + mat1.data32F + ":::" + mat1.rows + ":::" + mat1.cols);
//mat1::1,2,3,4,11,12,7,13,14:::3:::3

replaceMatOnRow()

void src1.replaceMatOnRow(arr, row)

src1 First input mat will be changed as output

arr Second input Array as row array

row row input to replace

let mat1 = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
mat1.replaceMatOnRow([11, 12, 13], 1);
console.log("mat1::" + mat1.data32F + ":::" + mat1.rows + ":::" + mat1.cols);
//mat1::1,2,3,11,12,13,7,8,9:::3:::3

2.0 修复:1.x 硬编码 this.floatPtr(d),在非 CV_32F 的 Mat 上静默写坏内存 并越界(embind 的 floatPtr 不做类型校验:CV_8UC1 的 2×3 Mat 只有 6 字节, floatPtr(0) 却返回长度 3 的 Float32Array,即 12 字节)。实测 1.x 在该 Mat 上把 数据改成 0,0,32,65,0,0。2.0 改走 PTR(),七种深度实测全部得到 10,20,30,4,5,6

replaceMatOnCol()

void src1.replaceMatOnCol(arr, col)

src1 First input mat will be changed as output

arr Second input Array as col array

col col input to replace

let mat1 = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
mat1.replaceMatOnCol([11, 12, 13], 1);
console.log("mat1::" + mat1.data32F + ":::" + mat1.rows + ":::" + mat1.cols);
//mat1::1,11,3,4,12,6,7,13,9:::3:::3

replaceMatOnPoint()

void src1.replaceMatOnPoint(value, row, col)

void src1.replaceMatOnPoint(value, point) —— cv.Point 约定 x = 列、y = 行

src1 First input mat will be changed as output

value The value to write

let mat1 = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
mat1.replaceMatOnPoint(30, 1, 1);
console.log("mat1::" + mat1.data32F + ":::" + mat1.rows + ":::" + mat1.cols);
//mat1::1,2,3,4,30,6,7,8,9:::3:::3

let mat2 = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
mat2.replaceMatOnPoint(30, new cv.Point(2, 0)); // x=2 列、y=0 行
console.log("mat2::" + mat2.data32F); //mat2::1,2,30,4,5,6,7,8,9

mat2.replaceMatOnPoint(1, 0);
//TypeError: replaceMatOnPoint(value, row, col) 或 replaceMatOnPoint(value, point):缺少 col

2.0 修复:1.x 的形参名是 (constant, x, y) 而实现是 PTR(x, y)——x 其实是 行号;文档里记的 (value, point) 重载则根本不存在(传对象抛 TypeError: Cannot convert "[object Object]" to int)。 位置参数的行为与 1.x 逐字相同(第 2 个参数一直是行、第 3 个一直是列), 已经在跑的三参调用不用改。

addOnCol()

void src1.addOnCol(constant, col)

src1 First input mat will be changed as output

constant Constant added to each element of that column

col Col location

let mat1 = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
mat1.addOnCol(30, 1);
console.log("mat1::" + mat1.data32F + ":::" + mat1.rows + ":::" + mat1.cols);
//mat1::1,32,3,4,35,6,7,38,9:::3:::3

rectAdd()

void src1.rectAdd(src2, rect)

src1 First input mat will be changed as output

src2 Second input mat as rect mat

rect rect input to add location

let mat1 = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
let rect1 = new cv.Rect(1, 1, 2, 2);
let rectmat = cv.matFromArray(2, 2, cv.CV_32FC1, [11, 12, 13, 14]);
mat1.rectAdd(rectmat, rect1);
console.log("mat1::" + mat1.data32F + ":::" + mat1.rows + ":::" + mat1.cols);
//mat1::1,2,3,4,16,18,7,21,23:::3:::3

rectSubtract()

void src1.rectSubtract(src2, rect)

src1 First input mat will be changed as output

src2 Second input mat as rect mat

rect rect input to subtract location

let mat1 = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
let rect1 = new cv.Rect(1, 1, 2, 2);
let rectmat = cv.matFromArray(2, 2, cv.CV_32FC1, [11, 12, 13, 14]);
mat1.rectSubtract(rectmat, rect1);
console.log("mat1::" + mat1.data32F + ":::" + mat1.rows + ":::" + mat1.cols);
//mat1::1,2,3,4,-6,-6,7,-5,-5:::3:::3

DATA() / PTR()

TypedArray dst = src1.DATA() —— 覆盖整个 Mat,按元素类型定型

TypedArray dst = src1.PTR(row) —— 第 row 行的全部元素(cols × channels 个)

TypedArray dst = src1.PTR(row, col) —— 该像素的各通道(channels 个)

let mat1 = cv.matFromArray(3, 3, cv.CV_8UC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
console.log("DATA::" + mat1.DATA()); //DATA::1,2,3,4,5,6,7,8,9
console.log("PTR(1)::" + mat1.PTR(1)); //PTR(1)::4,5,6
console.log("PTR(1,2)::" + mat1.PTR(1, 2)); //PTR(1,2)::6
mat1.delete();

depth() 分发到对应的 data* / *Ptr,省掉调用方自己挑访问器(挑错不报错、 结果全错)。⚠️ DATA() 只对连续 Mat 有意义——原生 roi()/col()/diag() 返回的 视图上它会按连续内存直读,得到错误数据。

mds() → cv.meanStdDev()

🔀 2.0 起 mat.mds() 已删除。 1.x 版本 100% 抛 TypeError: src1Array.reduce is not a function(实现里写的是 this.DATA 而非 this.DATA(),取到的是函数对象),不存在「以前能用」这回事。

let mat1 = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
let mean = new cv.Mat();
let stddev = new cv.Mat();
cv.meanStdDev(mat1, mean, stddev);
console.log("mean::" + mean.data64F[0] + " stddev::" + stddev.data64F[0]);
//mean::5 stddev::2.5819888974716108
(mat1.delete(), mean.delete(), stddev.delete());

svd() → cv.SVDecomp()

🔀 2.0 起 mat.svd() 已删除。 1.x 走的是当年内联进产物的 numeric.js 库; 那份库随 2.0 一起删掉,改用原生 cv.SVDecomp(src, w, u, vt, flags)。 (注意:只有函数式的 SVDecompcv.SVD 这个类不在白名单里。)

let mat1 = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
let w = new cv.Mat();
let u = new cv.Mat();
let vt = new cv.Mat();
cv.SVDecomp(mat1, w, u, vt, 0);
console.log("w::" + w.data32F);
//w::16.848102569580078,1.0683696269989014,1.1560786106201704e-8
(mat1.delete(), w.delete(), u.delete(), vt.delete());

奇异值与 1.x 的 numeric.svd 一致;第三个是 0——[[1,2,3],[4,5,6],[7,8,9]] 秩为 2。

RodriguesFromArray() / RodriguesFromMat() → cv.Rodrigues()

🔀 2.0 起两者都已删除,改用原生 cv.Rodrigues(src, dst)——双向都走同一个函数, 按输入形状自动判断方向。

let rvec = cv.matFromArray(3, 1, cv.CV_32FC1, [0.1, 0.2, 0.3]);
let R = new cv.Mat();
cv.Rodrigues(rvec, R); // 3×3 旋转矩阵

let back = new cv.Mat();
cv.Rodrigues(R, back);
console.log("back::" + back.data32F);
//back::0.10000000149011612,0.20000000298023224,0.30000004172325134
(rvec.delete(), R.delete(), back.delete());

dftSplit()

⚠️ @deprecated——正确性没有任何证据支撑,请勿在新代码里使用。 它的 CCS 展开约定从未被独立验证过;1.x 时代唯一的消费者是那个返回 NaN 的手写 mulSpectrums(),所以也不存在端到端参照。需要复数谱相乘请直接用原生 cv.mulSpectrums()。保留只是因为它属于 1.x 的公开 API。

{r: cv.Mat, i: cv.Mat} dst = src1.dftSplit()

let mat1 = cv.matFromArray(3, 3, cv.CV_32FC1, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
let dst = mat1.dftSplit();
console.log("dst::" + dst.r.data32F + ":::" + dst.i.data32F);
//dst::1,3,0,4,6,0,0,8,0:::0,3,0,7,9,0,0,9,0
(mat1.delete(), dst.r.delete(), dst.i.delete());

mulSpectrums() (原生;取代 1.x 的 cv.mulSpectrumscv.mulSpectrums2Channel

🔀 签名变了cv.mulSpectrums(src1, src2, dst, flags, conjB)。 1.x 的两个手写版都返回无效结果——部分元素恒为 NaN,另一部分每次运行都不同 (读到了未初始化的堆内存)。2.0 在构建白名单里放行了原生 mulSpectrums

// CCS 紧凑格式:cv.dft() 不带 DFT_COMPLEX_OUTPUT 时的单通道输出
let mat1 = cv.matFromArray(1, 4, cv.CV_32FC1, [1, 2, 3, 4]);
let mat2 = cv.matFromArray(1, 4, cv.CV_32FC1, [5, 6, 7, 8]);
let dst = new cv.Mat();
cv.mulSpectrums(mat1, mat2, dst, 0, false);
console.log("dst::" + dst.data32F); //dst::5,-9,32,32

// 双通道复数格式(取代 mulSpectrums2Channel)
let c1 = cv.matFromArray(1, 2, cv.CV_32FC2, [1, 2, 3, 4]); // 1+2i, 3+4i
let c2 = cv.matFromArray(1, 2, cv.CV_32FC2, [5, 6, 7, 8]); // 5+6i, 7+8i
let dst2 = new cv.Mat();
cv.mulSpectrums(c1, c2, dst2, 0, false);
console.log("dst2::" + dst2.data32F + ":::channels=" + dst2.channels());
//dst2::-7,16,-11,52:::channels=2      ← (1+2i)(5+6i) = -7+16i, (3+4i)(7+8i) = -11+52i

cv.mulSpectrums(c1, c2, dst2, 0, true);
console.log("dst2::" + dst2.data32F); //dst2::17,4,53,4      ← conjB

原生版返回的 channels() 与输入一致(1.x 手写版对 CV_32FC2 输入返回 channels() === 1)。

Others

default constructor

let mat = new cv.Mat();
let mat = new cv.Mat(size, type);
let mat = new cv.Mat(rows, cols, type);
let mat = new cv.Mat(rows, cols, type, new cv.Scalar());
let mat = cv.matFromArray(rows, cols, type, array);

let ctx = canvas.getContext("2d");
let imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
let mat = cv.matFromImageData(imgData);

let mat = cv.Mat.zeros(rows, cols, type);
let mat = cv.Mat.ones(rows, cols, type);
let mat = cv.Mat.eye(rows, cols, type);

copy Mat

let dst = src.clone();
src.copyTo(dst, mask);

convert type

src.convertTo(m, rtype, (alpha = 1), (beta = 0));

MatVector

let mat = new cv.Mat();
let matVec = new cv.MatVector();
matVec.push_back(mat);
let cnt = matVec.get(0);
mat.delete();
matVec.delete();
cnt.delete();

data

[Data Properties]	[C++ Type]	[JavaScript Typed Array]	[Mat Type]
data				uchar		Uint8Array					CV_8U
data8S				char		Int8Array					CV_8S
data16U				ushort		Uint16Array					CV_16U
data16S				short		Int16Array					CV_16S
data32S				int			Int32Array					CV_32S
data32F				float		Float32Array				CV_32F
data64F				double		Float64Array				CV_64F

// row = 3, col = 4, channels = 4
let R = src.data[row * src.cols * src.channels() + col * src.channels()];
let G = src.data[row * src.cols * src.channels() + col * src.channels() + 1];
let B = src.data[row * src.cols * src.channels() + col * src.channels() + 2];
let A = src.data[row * src.cols * src.channels() + col * src.channels() + 3];

at

[Mat Type]		[At Manipulation]
CV_8U			ucharAt
CV_8S			charAt
CV_16U			ushortAt
CV_16S			shortAt
CV_32S			intAt
CV_32F			floatAt
CV_64F			doubleAt

//row = 3, col = 4, channels = 4
let R = src.ucharAt(row, col * src.channels());
let G = src.ucharAt(row, col * src.channels() + 1);
let B = src.ucharAt(row, col * src.channels() + 2);
let A = src.ucharAt(row, col * src.channels() + 3);

ptr

[Mat Type]		[Ptr Manipulation]		[JavaScript Typed Array]
CV_8U			ucharPtr				Uint8Array
CV_8S			charPtr					Int8Array
CV_16U			ushortPtr				Uint16Array
CV_16S			shortPtr				Int16Array
CV_32S			intPtr					Int32Array
CV_32F			floatPtr				Float32Array
CV_64F			doublePtr				Float64Array

//row = 3, col = 4, channels = 4
let pixel = src.ucharPtr(row, col);
let R = pixel[0];
let G = pixel[1];
let B = pixel[2];
let A = pixel[3];

Bitwise Operations

cv.bitwise_not();
cv.bitwise_and();
cv.bitwise_or();
cv.bitwise_xor();

Point

let point = new cv.Point(x, y);
let point = { x: x, y: y };

Scalar

let scalar = new cv.Scalar(R, G, B, Alpha);
let scalar = [R, G, B, Alpha];

Size

let size = new cv.Size(width, height);
let size = { width: width, height: height };

Circle

let circle = new cv.Circle(center, radius);
let circle = { center: center, radius: radius };

Rect

let rect = new cv.Rect(x, y, width, height);
let rect = { x: x, y: y, width: width, height: height };

RotatedRect

let rotatedRect = new cv.RotatedRect(center, size, angle);
let rotatedRect = { center: center, size: size, angle: angle };

let vertices = cv.RotatedRect.points(rotatedRect);
let point1 = vertices[0];
let point2 = vertices[1];
let point3 = vertices[2];
let point4 = vertices[3];

let boundingRect = cv.RotatedRect.boundingRect(rotatedRect);

cvtColor

cv.cvtColor(src, dst, cv.COLOR_RGBA2GRAY, 0);

inRange

cv.inRange(src, low, high, dst);

Scaling

cv.resize(
  src,
  dst,
  dsize,
  (fx = 0),
  (fy = 0),
  (interpolation = cv.INTER_LINEAR),
);

Translation

cv.warpAffine(
  src,
  dst,
  M,
  dsize,
  (flags = cv.INTER_LINEAR),
  (borderMode = cv.BORDER_CONSTANT),
  (borderValue = new cv.Scalar()),
);

Rotation

cv.getRotationMatrix2D(center, angle, scale);

Affine Transformation

cv.getAffineTransform(src, dst);

Perspective Transformation

let M = cv.getPerspectiveTransform(srcTri, dstTri);
cv.warpPerspective(
  src,
  dst,
  M,
  dsize,
  cv.INTER_LINEAR,
  cv.BORDER_CONSTANT,
  new cv.Scalar(),
);

Simple Thresholding

cv.threshold(src, dst, 177, 200, cv.THRESH_BINARY);

Adaptive Thresholding

//cv.adaptiveThreshold (src, dst, maxValue, adaptiveMethod, thresholdType, blockSize, C)
cv.adaptiveThreshold(
  src,
  dst,
  200,
  cv.ADAPTIVE_THRESH_GAUSSIAN_C,
  cv.THRESH_BINARY,
  3,
  2,
);

2D Convolution ( Image Filtering )

//cv.filter2D (src, dst, ddepth, kernel, anchor = new cv.Point(-1, -1), delta = 0, borderType = cv.BORDER_DEFAULT)
cv.filter2D(src, dst, cv.CV_8U, M, anchor, 0, cv.BORDER_DEFAULT);

Image Blurring (Image Smoothing)

//cv.blur (src, dst, ksize, anchor = new cv.Point(-1, -1), borderType = cv.BORDER_DEFAULT)
cv.blur(src, dst, ksize, anchor, cv.BORDER_DEFAULT);

//cv.boxFilter (src, dst, ddepth, ksize, anchor = new cv.Point(-1, -1), normalize = true, borderType = cv.BORDER_DEFAULT)
cv.boxFilter(src, dst, -1, ksize, anchor, true, cv.BORDER_DEFAULT);

//cv.GaussianBlur (src, dst, ksize, sigmaX, sigmaY = 0, borderType = cv.BORDER_DEFAULT)
cv.GaussianBlur(src, dst, ksize, 0, 0, cv.BORDER_DEFAULT);

//cv.medianBlur (src, dst, ksize)
cv.medianBlur(src, dst, 5);

//cv.bilateralFilter (src, dst, d, sigmaColor, sigmaSpace, borderType = cv.BORDER_DEFAULT)
cv.bilateralFilter(src, dst, 9, 75, 75, cv.BORDER_DEFAULT);

Erosion

//cv.erode (src, dst, kernel, anchor = new cv.Point(-1, -1), iterations = 1, borderType = cv.BORDER_CONSTANT, borderValue = cv.morphologyDefaultBorderValue())
cv.erode(
  src,
  dst,
  M,
  anchor,
  1,
  cv.BORDER_CONSTANT,
  cv.morphologyDefaultBorderValue(),
);

Dilation

//cv.dilate (src, dst, kernel, anchor = new cv.Point(-1, -1), iterations = 1, borderType = cv.BORDER_CONSTANT, borderValue = cv.morphologyDefaultBorderValue())
cv.dilate(
  src,
  dst,
  M,
  anchor,
  1,
  cv.BORDER_CONSTANT,
  cv.morphologyDefaultBorderValue(),
);

Opening

//cv.morphologyEx (src, dst, op, kernel, anchor = new cv.Point(-1, -1), iterations = 1, borderType = cv.BORDER_CONSTANT, borderValue = cv.morphologyDefaultBorderValue())
cv.morphologyEx(
  src,
  dst,
  cv.MORPH_OPEN,
  M,
  anchor,
  1,
  cv.BORDER_CONSTANT,
  cv.morphologyDefaultBorderValue(),
);

Closing

cv.morphologyEx(src, dst, cv.MORPH_CLOSE, M);

Morphological Gradient

cv.morphologyEx(src, dst, cv.MORPH_GRADIENT, M);

Top Hat

cv.morphologyEx(src, dst, cv.MORPH_TOPHAT, M);

Black Hat

cv.morphologyEx(src, dst, cv.MORPH_BLACKHAT, M);

Structuring Element

//cv.getStructuringElement (shape, ksize, anchor = new cv.Point(-1, -1))
M = cv.getStructuringElement(cv.MORPH_CROSS, ksize);
cv.morphologyEx(src, dst, cv.MORPH_GRADIENT, M);

Sobel and Scharr Derivatives

//cv.Sobel (src, dst, ddepth, dx, dy, ksize = 3, scale = 1, delta = 0, borderType = cv.BORDER_DEFAULT)
cv.Sobel(src, dstx, cv.CV_8U, 1, 0, 3, 1, 0, cv.BORDER_DEFAULT);

//cv.Scharr (src, dst, ddepth, dx, dy, scale = 1, delta = 0, borderType = cv.BORDER_DEFAULT)
cv.Scharr(src, dstx, cv.CV_8U, 1, 0, 1, 0, cv.BORDER_DEFAULT);

Laplacian Derivatives

//cv.Laplacian (src, dst, ddepth, ksize = 1, scale = 1, delta = 0, borderType = cv.BORDER_DEFAULT)
cv.Laplacian(src, dst, cv.CV_8U, 1, 1, 0, cv.BORDER_DEFAULT);

Image AbsSobel

cv.Sobel(src, dstx, cv.CV_8U, 1, 0, 3, 1, 0, cv.BORDER_DEFAULT);
cv.Sobel(src, absDstx, cv.CV_64F, 1, 0, 3, 1, 0, cv.BORDER_DEFAULT);
cv.convertScaleAbs(absDstx, absDstx, 1, 0);

draw the contours

//cv.findContours (image, contours, hierarchy, mode, method, offset = new cv.Point(0, 0))
cv.findContours(
  src,
  contours,
  hierarchy,
  cv.RETR_CCOMP,
  cv.CHAIN_APPROX_SIMPLE,
);

//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))
cv.drawContours(dst, contours, i, color, 1, cv.LINE_8, hierarchy, 100);

Moments

//cv.moments (array, binaryImage = false)
let Moments = cv.moments(cnt, false);

Contour Area

//cv.contourArea (contour, oriented = false)
let area = cv.contourArea(cnt, false);

Contour Perimeter

//cv.arcLength (curve, closed)
let perimeter = cv.arcLength(cnt, true);

Contour Approximation

//cv.approxPolyDP (curve, approxCurve, epsilon, closed)
cv.approxPolyDP(cnt, tmp, 3, true);

Convex Hull

//cv.convexHull (points, hull, clockwise = false, returnPoints = true)
cv.convexHull(cnt, tmp, false, true);

Checking Convexity

cv.isContourConvex(cnt);

Straight Bounding Rectangle

//cv.boundingRect (points)
let rect = cv.boundingRect(cnt);

Rotated Rectangle

//cv.minAreaRect (points)
let rotatedRect = cv.minAreaRect(cnt);

Minimum Enclosing Circle

//cv.minEnclosingCircle (points)
let circle = cv.minEnclosingCircle(cnt);

//cv.circle (img, center, radius, color, thickness = 1, lineType = cv.LINE_8, shift = 0)
cv.circle(dst, circle.center, circle.radius, circleColor);

Fitting an Ellipse

//cv.fitEllipse (points)
let rotatedRect = cv.fitEllipse(cnt);

//cv.ellipse1 (img, box, color, thickness = 1, lineType = cv.LINE_8)
cv.ellipse1(dst, rotatedRect, ellipseColor, 1, cv.LINE_8);

Fitting a Line

//cv.fitLine (points, line, distType, param, reps, aeps)
cv.fitLine(cnt, line, cv.DIST_L2, 0, 0.01, 0.01);

//cv.line (img, pt1, pt2, color, thickness = 1, lineType = cv.LINE_8, shift = 0)
cv.line(dst, point1, point2, lineColor, 2, cv.LINE_AA, 0);

Aspect Ratio

let rect = cv.boundingRect(cnt);
let aspectRatio = rect.width / rect.height;

Extent

let area = cv.contourArea(cnt, false);
let rect = cv.boundingRect(cnt));
let rectArea = rect.width * rect.height;
let extent = area / rectArea;

Solidity

let area = cv.contourArea(cnt, false);
cv.convexHull(cnt, hull, false, true);
let hullArea = cv.contourArea(hull, false);
let solidity = area / hullArea;

Equivalent Diameter

let area = cv.contourArea(cnt, false);
let equiDiameter = Math.sqrt((4 * area) / Math.PI);

Orientation

let rotatedRect = cv.fitEllipse(cnt);
let angle = rotatedRect.angle;

Mask and Pixel Points

//cv.transpose (src, dst)
cv.transpose(src, dst);

Maximum Value, Minimum Value and their locations

//cv.minMaxLoc(src, mask)
let result = cv.minMaxLoc(src, mask);
let minVal = result.minVal;
let maxVal = result.maxVal;
let minLoc = result.minLoc;
let maxLoc = result.maxLoc;

Mean Color or Mean Intensity

cv.mean(src, mask);

Convexity Defects

//cv.convexityDefects (contour, convexhull, convexityDefect)
cv.convexityDefects(cnt, hull, defect);

Point Polygon Test

//cv.pointPolygonTest (contour, pt, measureDist)
let dist = cv.pointPolygonTest(cnt, new cv.Point(50, 50), true);

Match Shapes

//cv.matchShapes (contour1, contour2, method, parameter)
let result = cv.matchShapes(
  contours.get(contourID0),
  contours.get(contourID1),
  1,
  0,
);

Find Histogram

//cv.calcHist (image, channels, mask, hist, histSize, ranges, accumulate = false)
cv.calcHist(srcVec, channels, mask, hist, histSize, ranges, accumulate);

Histograms Equalization

cv.equalizeHist(src, dst);

CLAHE (Contrast Limited Adaptive Histogram Equalization)

//cv.CLAHE (clipLimit = 40, tileGridSize = new cv.Size(8, 8))
let clahe = new cv.CLAHE(40, tileGridSize);

Backprojection

//cv.calcBackProject (images, channels, hist, dst, ranges, scale)
cv.calcBackProject(dstVec, channels, hist, backproj, ranges, 1);

//cv.normalize (src, dst, alpha = 1, beta = 0, norm_type = cv.NORM_L2, dtype = -1, mask = new cv.Mat())
cv.normalize(hist, hist, 0, 255, cv.NORM_MINMAX, -1, none);

Fourier Transform

//cv.dft (src, dst, flags = 0, nonzeroRows = 0)
cv.dft(complexI, complexI);

//cv.getOptimalDFTSize (vecsize)
let optimalRows = cv.getOptimalDFTSize(src.rows);

//cv.copyMakeBorder (src, dst, top, bottom, left, right, borderType, value = new cv.Scalar())
cv.copyMakeBorder(
  src,
  padded,
  0,
  optimalRows - src.rows,
  0,
  optimalCols - src.cols,
  cv.BORDER_CONSTANT,
  s0,
);

//cv.magnitude (x, y, magnitude)
cv.magnitude(planes.get(0), planes.get(1), planes.get(0));

//cv.split (m, mv)
cv.split(complexI, planes);

//cv.merge (mv, dst)
cv.merge(planes, complexI);

Template Matching

//cv.matchTemplate (image, templ, result, method, mask = new cv.Mat())
cv.matchTemplate(src, templ, dst, cv.TM_CCOEFF, mask);

Hough Transform

//cv.HoughLines (image, lines, rho, theta, threshold, srn = 0, stn = 0, min_theta = 0, max_theta = Math.PI)
cv.HoughLines(src, lines, 1, Math.PI / 180, 30, 0, 0, 0, Math.PI);

Probabilistic Hough Transform

//cv.HoughLinesP (image, lines, rho, theta, threshold, minLineLength = 0, maxLineGap = 0)
cv.HoughLinesP(src, lines, 1, Math.PI / 180, 2, 0, 0);

Hough Circle Transform

//cv.HoughCircles (image, circles, method, dp, minDist, param1 = 100, param2 = 100, minRadius = 0, maxRadius = 0)
cv.HoughCircles(src, circles, cv.HOUGH_GRADIENT, 1, 45, 75, 40, 0, 0);

Threshold

cv.threshold(gray, gray, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU);

Distance Transform

//cv.distanceTransform (src, dst, distanceType, maskSize, labelType = cv.CV_32F)
cv.distanceTransform(opening, distTrans, cv.DIST_L2, 5);

mage Watershed

//cv.connectedComponents (image, labels, connectivity = 8, ltype = cv.CV_32S)
cv.connectedComponents(coinsFg, markers);

//cv.watershed (image, markers)
cv.watershed(src, markers);

Foreground Extraction

//cv.grabCut (image, mask, rect, bgdModel, fgdModel, iterCount, mode = cv.GC_EVAL)
cv.grabCut(src, mask, rect, bgdModel, fgdModel, 1, cv.GC_INIT_WITH_RECT);

Meanshift

//cv.meanShift (probImage, window, criteria)
[, trackWindow] = cv.meanShift(dst, trackWindow, termCrit);

Camshift

//cv.CamShift (probImage, window, criteria)
[trackBox, trackWindow] = cv.CamShift(dst, trackWindow, termCrit);

Lucas-Kanade Optical Flow

//cv.calcOpticalFlowPyrLK (prevImg, nextImg, prevPts, nextPts, status, err, winSize = new cv.Size(21, 21), maxLevel = 3, criteria = new cv.TermCriteria(cv.TermCriteria_COUNT+ cv.TermCriteria_EPS, 30, 0.01), flags = 0, minEigThreshold = 1e-4)
let criteria = new cv.TermCriteria(
  cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT,
  10,
  0.03,
);
cv.calcOpticalFlowPyrLK(
  oldGray,
  frameGray,
  p0,
  p1,
  st,
  err,
  winSize,
  maxLevel,
  criteria,
);

Dense Optical Flow

//cv.calcOpticalFlowFarneback (prev, next, flow, pyrScale, levels, winsize, iterations, polyN, polySigma, flags)
cv.calcOpticalFlowFarneback(prvs, next, flow, 0.5, 3, 15, 3, 5, 1.2, 0);

BackgroundSubtractorMOG2

//cv.BackgroundSubtractorMOG2 (history = 500, varThreshold = 16, detectShadows = true)
let fgbg = new cv.BackgroundSubtractorMOG2(500, 16, true);

//cv.apply (image, fgmask, learningRate = -1)
fgbg.apply(frame, fgmask);

Face Detection

Haar-cascade 检测(cv.CascadeClassifier在 3.0 起不再存在——OpenCV 5 把它 连同 HOGDescriptor / AKAZE / BRISK / KAZE 一起从 C++ API 里删除了。 本产物保留的是基于 DNN 的 cv.FaceDetectorYN,但它需要一个 ONNX 模型文件, 用法与 Haar 不是一一对应的替换,且本项目没有验证过它——详见 Known Issues

image && video

cv.imread();
cv.imshow();
cv.VideoCapture();

other

cv.rectangle();
cv.Canny();
cv.goodFeaturesToTrack();
cv.cartToPolar();
cv.randu();
new cv.ORB();

To Do List

  • Performance, up speed performance.
  • Methods complete all the opencv functions.

License

OpenCVJS is released under the MIT license. See LICENSE for details.

About

Complete opencvjs (With the lastest OpenCV 4.0.0+)

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages