ms-vite-plugin 1.4.39 → 1.4.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1328 @@
1
+ # OpenCV 模块 (cv)
2
+
3
+ 提供图像读写、颜色与阈值、滤波、几何变换、绘制、模板/特征匹配、轮廓、形态学、直方图、霍夫、扫码等能力。模块名为 `cv`,无中文别名。
4
+
5
+ 注意:
6
+
7
+ - Mat 用字符串句柄表示,形如 `$CV_MAT$_…`。
8
+ - 多数算子返回新句柄;`rectangle` / `circle` / `line` / `putText` / `fillPoly` / `polylines` / `drawContours` / `floodFill` / `ellipse` / `drawMarker` / `arrowedLine` / `fillConvexPoly` 等会就地修改输入 Mat(或 options 中指定的目标句柄)。
9
+ - 与 `image` 可通过 `fromImageId` / `toImageId` 互通(独立拷贝)。
10
+ - 路径 / msbundle / imageId / `screen` 等读图只用 `imread`;其它 API 只接受 Mat 句柄;`imdecode` 只解 base64。
11
+ - 用完请 `release` / `releaseAll`(同时存活句柄上限约 32)。
12
+ - 颜色参数为 **BGR**;也可用 `#RRGGBB`。
13
+
14
+ ## 功能一览
15
+
16
+ | 类别 | API |
17
+ | --- | --- |
18
+ | 读图 / 截屏 | `capture` · `imread` · `imdecode` · `imwrite` · `imencode` · `fromImageId` / `toImageId` |
19
+ | Mat | `Mat` / `zeros` / `ones` · `clone` · `empty` / `isRelease` · `release` / `releaseAll` · `getSize` / `at` / `set` · `copyTo` |
20
+ | 颜色 / 阈值 | `cvtColor` · `threshold` · `adaptiveThreshold` · `inRange` · `CLAHE` / `equalizeHist` |
21
+ | 算术 / 位运算 | `add` / `subtract` / `multiply` / `divide` · bitwise_* · `absdiff` · `addWeighted` · `convertScaleAbs` · `normalize` / `norm` · `meanStdDev` · `findNonZero` · `countNonZero` · `minMaxLoc` / `mean` / `sum` · `split` / `merge` · `hconcat` / `vconcat` · `LUT` · `compare` · `min` / `max` · `extractChannel` |
22
+ | 滤波边缘 | `GaussianBlur` / `blur` / `medianBlur` / `bilateralFilter` / `boxFilter` · `Canny` / `Sobel` / `Laplacian` / `Scharr` · `filter2D` · `pyrDown` / `pyrUp` · `fastNlMeansDenoising` / `fastNlMeansDenoisingColored` · `inpaint` |
23
+ | 几何 | `resize` / `crop` / `rotate` / `flip` · `copyMakeBorder` · `warpAffine` / `warpPerspective` · `getRotationMatrix2D` · `getAffineTransform` · `getPerspectiveTransform` · `invertAffineTransform` · `findHomography` · `perspectiveTransform` · `estimateAffine2D` / `estimateAffinePartial2D` · `getRectSubPix` · `phaseCorrelate` |
24
+ | 绘制 | `rectangle` / `circle` / `line` / `ellipse` / `arrowedLine` / `drawMarker` · `putText` / `fillPoly` / `fillConvexPoly` / `polylines` / `drawContours` · `getTextSize` |
25
+ | 匹配 | `matchTemplate` / `matchTemplateLoc` · `SIFT` / `ORB` / `FAST` · `BFMatcher` / `knnMatch` · `FlannBasedMatcher` / `flannKnnMatch` · `drawKeypoints` / `drawMatches` |
26
+ | 轮廓 | `findContours` · `boundingRect` / `minAreaRect` / `boxPoints` · `contourArea` / `arcLength` / `approxPolyDP` / `convexHull` / `convexityDefects` / `isContourConvex` · `moments` / `HuMoments` / `matchShapes` · `pointPolygonTest` · `fitEllipse` / `fitLine` / `minEnclosingCircle` |
27
+ | 霍夫 / 连通域 | `HoughLinesP` / `HoughCircles` · `connectedComponents` / `connectedComponentsWithStats` · `distanceTransform` · `floodFill` · `goodFeaturesToTrack` / `cornerHarris` / `cornerSubPix` |
28
+ | 直方图 | `calcHist` · `compareHist` · `calcBackProject` |
29
+ | 扫码 | `encodeQRCode` · `detectQRCode` · `detectBarcode` |
30
+
31
+ ---
32
+
33
+ ## 快速示例
34
+
35
+ ```javascript
36
+ const src = cv.capture();
37
+ const gray = cv.cvtColor(src, { code: cv.COLOR_BGR2GRAY });
38
+ const edges = cv.Canny(gray, { threshold1: 100, threshold2: 200 });
39
+ const feat = cv.SIFT(gray, { nfeatures: 500 });
40
+ logi(`keypoints=${feat.keypoints.length}`);
41
+ cv.imwrite(`${file.getInternalDir("documents")}/edges.png`, edges);
42
+ cv.release(src);
43
+ cv.release(gray);
44
+ cv.release(edges);
45
+ cv.release(feat.descriptors);
46
+ ```
47
+
48
+ 与 `image` 模块衔接:
49
+
50
+ ```javascript
51
+ const imageId = image.captureScreen();
52
+ const mat = cv.imread(imageId); // 或 cv.fromImageId(imageId)
53
+ image.release(imageId);
54
+ const gray = cv.cvtColor(mat, { code: cv.COLOR_BGR2GRAY });
55
+ const outId = cv.toImageId(gray);
56
+ cv.release(mat);
57
+ cv.release(gray);
58
+ image.saveTo(outId, `${file.getInternalDir("documents")}/gray.jpg`);
59
+ image.release(outId);
60
+ ```
61
+
62
+ ---
63
+
64
+ ## 句柄与生命周期
65
+
66
+ ### capture - 截屏为 Mat
67
+
68
+ ```typescript
69
+ function capture(
70
+ x?: number,
71
+ y?: number,
72
+ ex?: number,
73
+ ey?: number,
74
+ ): Mat | null;
75
+ ```
76
+
77
+ **参数:**
78
+
79
+ | 参数名 | 类型 | 是否必填 | 默认值 | 描述 |
80
+ | --- | --- | --- | --- | --- |
81
+ | `x` | number | 否 | 0 | 区域左上角 X 坐标 |
82
+ | `y` | number | 否 | 0 | 区域左上角 Y 坐标 |
83
+ | `ex` | number | 否 | 0 | 区域右下角 X 坐标 |
84
+ | `ey` | number | 否 | 0 | 区域右下角 Y 坐标 |
85
+
86
+ 无参或全 0 表示全屏。
87
+
88
+ **返回值:**
89
+
90
+ | 类型 | 描述 |
91
+ | --- | --- |
92
+ | `string` | Mat 句柄,失败返回 `null` |
93
+
94
+ **示例:**
95
+
96
+ ```javascript
97
+ const full = cv.capture();
98
+ const roi = cv.capture(100, 100, 300, 300);
99
+ cv.release(full);
100
+ cv.release(roi);
101
+ ```
102
+
103
+ ### imread - 读取图片为 Mat(唯一路径类入口)
104
+
105
+ ```typescript
106
+ function imread(path: string): Mat | null;
107
+ ```
108
+
109
+ **参数:**
110
+
111
+ | 参数名 | 类型 | 是否必填 | 默认值 | 描述 |
112
+ | --- | --- | --- | --- | --- |
113
+ | `path` | string | 是 | - | 见下方支持的输入源 |
114
+
115
+ **支持的输入源:**
116
+
117
+ | 输入 | 说明 |
118
+ | --- | --- |
119
+ | msbundle/res 相对路径 | 如 `"template.png"`、`"images/logo.jpg"` |
120
+ | 手机绝对路径 | 如 documents 下文件 |
121
+ | `$MS_IMG$_…` | `image` 模块 imageId |
122
+ | `"screen"` | 当前屏幕截图 |
123
+ | `"shortcut"` | HID 快捷指令截图 |
124
+ | `"actionScreenshot"` | HID 系统截图 |
125
+ | `"vpnScreenshot"` | VPN/iDevice 截图 |
126
+ | `http://` / `https://` | 网络图片 |
127
+ | `media:n` | 相册第 n 张(从 1 起) |
128
+ | `data:image/...;base64,...` | data URI |
129
+
130
+ **返回值:**
131
+
132
+ | 类型 | 描述 |
133
+ | --- | --- |
134
+ | `string` | Mat 句柄,失败返回 `null` |
135
+
136
+ **示例:**
137
+
138
+ ```javascript
139
+ const a = cv.imread("template.png");
140
+ const dir = file.getInternalDir("documents");
141
+ const b = cv.imread(`${dir}/shot.jpg`);
142
+ const imageId = image.captureScreen();
143
+ const c = cv.imread(imageId);
144
+ image.release(imageId);
145
+ cv.release(a);
146
+ cv.release(b);
147
+ cv.release(c);
148
+ ```
149
+
150
+ ### imdecode - 解码 base64 为 Mat
151
+
152
+ ```typescript
153
+ function imdecode(data: string): Mat | null;
154
+ ```
155
+
156
+ **参数:**
157
+
158
+ | 参数名 | 类型 | 是否必填 | 默认值 | 描述 |
159
+ | --- | --- | --- | --- | --- |
160
+ | `data` | string | 是 | - | 纯 base64,或 `data:image/...;base64,...` |
161
+
162
+ **说明:** 不解析文件路径 / msbundle / imageId;路径类请用 `imread`。常与 `imencode` 成对使用。
163
+
164
+ **返回值:** Mat 句柄;失败 `null`。
165
+
166
+ **示例:**
167
+
168
+ ```javascript
169
+ const m = cv.imread("template.png");
170
+ const b64 = cv.imencode(".png", m);
171
+ const again = cv.imdecode(b64);
172
+ cv.release(m);
173
+ cv.release(again);
174
+ ```
175
+
176
+ ### imwrite - 保存 Mat 到本地文件
177
+
178
+ ```typescript
179
+ function imwrite(path: string, mat: Mat): boolean;
180
+ ```
181
+
182
+ **参数:**
183
+
184
+ | 参数名 | 类型 | 是否必填 | 默认值 | 描述 |
185
+ | --- | --- | --- | --- | --- |
186
+ | `path` | string | 是 | - | 本地绝对路径(写入磁盘,不是 msbundle) |
187
+ | `mat` | Mat | 是 | - | Mat 句柄 |
188
+
189
+ **返回值:** 成功 `true`,失败 `false`。
190
+
191
+ **示例:**
192
+
193
+ ```javascript
194
+ const src = cv.capture();
195
+ cv.imwrite(`${file.getInternalDir("documents")}/out.png`, src);
196
+ cv.release(src);
197
+ ```
198
+
199
+ ### imencode - 编码 Mat 为 base64
200
+
201
+ ```typescript
202
+ function imencode(ext: string, mat: Mat, quality?: number): string | null;
203
+ ```
204
+
205
+ **参数:**
206
+
207
+ | 参数名 | 类型 | 是否必填 | 默认值 | 描述 |
208
+ | --- | --- | --- | --- | --- |
209
+ | `ext` | string | 是 | - | 如 `.png` / `.jpg` / `png` |
210
+ | `mat` | Mat | 是 | - | Mat 句柄 |
211
+ | `quality` | number | 否 | 90 | JPEG 质量(仅 jpg 有效) |
212
+
213
+ **返回值:** 纯 base64 字符串(无 data URI 前缀);失败 `null`。
214
+
215
+ **示例:**
216
+
217
+ ```javascript
218
+ const b64 = cv.imencode(".jpg", mat, { quality: 85 });
219
+ ```
220
+
221
+ ### fromImageId / toImageId - 与 image 互通
222
+
223
+ ```typescript
224
+ function fromImageId(imageId: string): Mat | null;
225
+ function toImageId(mat: Mat): string | null;
226
+ ```
227
+
228
+ **参数:**
229
+
230
+ | 参数名 | 类型 | 是否必填 | 默认值 | 描述 |
231
+ | --- | --- | --- | --- | --- |
232
+ | `imageId` | string | 是 | - | `image` 模块句柄 |
233
+ | `mat` | Mat | 是 | - | `cv` Mat 句柄 |
234
+
235
+ **说明:** 均为独立拷贝。`fromImageId(id)` 与 `imread(id)` 对 imageId 效果相同。
236
+
237
+ **示例:**
238
+
239
+ ```javascript
240
+ const imageId = image.captureScreen();
241
+ const mat = cv.fromImageId(imageId);
242
+ image.release(imageId);
243
+ const outId = cv.toImageId(mat);
244
+ cv.release(mat);
245
+ image.release(outId);
246
+ ```
247
+
248
+ ### Mat / zeros / ones - 创建矩阵
249
+
250
+ ```typescript
251
+ function Mat(options: { rows: number; cols: number; type?: number; scalar?: number[] }): Mat | null;
252
+ function zeros(options: { rows: number; cols: number; type?: number }): Mat | null;
253
+ function ones(options: { rows: number; cols: number; type?: number }): Mat | null;
254
+ ```
255
+
256
+ **参数:**
257
+
258
+ | 参数名 | 类型 | 是否必填 | 默认值 | 描述 |
259
+ | --- | --- | --- | --- | --- |
260
+ | `rows` / `cols` | number | 是 | - | 行 / 列 |
261
+ | `type` | number | 否 | `CV_8UC3` | 如 `cv.CV_8UC1` |
262
+ | `scalar` | number[] | 否 | - | 仅 `Mat`:填充值 |
263
+
264
+ **示例:**
265
+
266
+ ```javascript
267
+ const z = cv.zeros({ rows: 100, cols: 100, type: cv.CV_8UC3 });
268
+ const filled = cv.Mat({ rows: 10, cols: 10, type: cv.CV_8UC1, scalar: [255] });
269
+ cv.release(z);
270
+ cv.release(filled);
271
+ ```
272
+
273
+ ### clone / empty / isRelease / release / releaseAll
274
+
275
+ ```typescript
276
+ function clone(mat: Mat): Mat | null;
277
+ function empty(mat: Mat): boolean;
278
+ function isRelease(mat: Mat): boolean;
279
+ function release(mat: Mat): void;
280
+ function releaseAll(): void;
281
+ ```
282
+
283
+ **说明:**
284
+
285
+ - `clone` 返回新句柄。
286
+ - `empty`:无效句柄或空 Mat 为 `true`。
287
+ - `isRelease`:句柄已释放为 `true`。
288
+ - `releaseAll`:释放当前全部 Mat 句柄。
289
+
290
+ **示例:**
291
+
292
+ ```javascript
293
+ const a = cv.capture();
294
+ const b = cv.clone(a);
295
+ cv.release(a);
296
+ logi(cv.isRelease(a)); // true
297
+ cv.release(b);
298
+ ```
299
+
300
+ ### getSize / at / set / copyTo
301
+
302
+ ```typescript
303
+ function getSize(mat: Mat): MatInfo | null;
304
+ logi(`${info.width}x${info.height} ch=${info.channels}`);
305
+ cv.set(mat, { row: 10, col: 10, value: [0, 0, 255] });
306
+ const px = cv.at(mat, { row: 10, col: 10 });
307
+ const copy = cv.copyTo(mat);
308
+ cv.release(copy);
309
+ ```
310
+
311
+ ---
312
+
313
+ ## 颜色 / 运算
314
+
315
+ ### cvtColor - 颜色空间转换
316
+
317
+ ```typescript
318
+ function cvtColor(mat: Mat, code: number): Mat | null;
319
+ const hsv = cv.cvtColor(src, { code: cv.COLOR_BGR2HSV });
320
+ ```
321
+
322
+ ### threshold - 固定阈值
323
+
324
+ ```typescript
325
+ function threshold(mat: Mat, options: { thresh?: number; maxval?: number; type?: number }): Mat | null;
326
+ ```
327
+
328
+ **参数:**
329
+
330
+ | 参数名 | 类型 | 是否必填 | 默认值 | 描述 |
331
+ | --- | --- | --- | --- | --- |
332
+ | `thresh` | number | 否 | 127 | 阈值 |
333
+ | `maxval` | number | 否 | 255 | 最大值 |
334
+ | `type` | number | 否 | `THRESH_BINARY` | 如 `THRESH_BINARY` / `THRESH_OTSU` |
335
+
336
+ **示例:**
337
+
338
+ ```javascript
339
+ const bin = cv.threshold(gray, { thresh: 127, maxval: 255, type: cv.THRESH_BINARY });
340
+ const otsu = cv.threshold(gray, { type: cv.THRESH_BINARY | cv.THRESH_OTSU });
341
+ ```
342
+
343
+ ### adaptiveThreshold - 自适应阈值
344
+
345
+ ```typescript
346
+ function adaptiveThreshold(mat: Mat, options: {
347
+ maxValue?: number;
348
+ adaptiveMethod?: number;
349
+ thresholdType?: number;
350
+ blockSize?: number;
351
+ C?: number;
352
+ }): Mat | null;
353
+ ```
354
+
355
+ **参数:**
356
+
357
+ | 参数名 | 类型 | 是否必填 | 默认值 | 描述 |
358
+ | --- | --- | --- | --- | --- |
359
+ | `maxValue` | number | 否 | 255 | 最大值 |
360
+ | `adaptiveMethod` | number | 否 | `ADAPTIVE_THRESH_GAUSSIAN_C` | 或 `ADAPTIVE_THRESH_MEAN_C` |
361
+ | `thresholdType` | number | 否 | `THRESH_BINARY` | |
362
+ | `blockSize` | number | 否 | 11 | 奇数邻域 |
363
+ | `C` | number | 否 | 2 | 常数偏移 |
364
+
365
+ **示例:**
366
+
367
+ ```javascript
368
+ const bin = cv.adaptiveThreshold(gray, {
369
+ maxValue: 255,
370
+ adaptiveMethod: cv.ADAPTIVE_THRESH_GAUSSIAN_C,
371
+ thresholdType: cv.THRESH_BINARY,
372
+ blockSize: 11,
373
+ C: 2,
374
+ });
375
+ ```
376
+
377
+ ### inRange - 范围掩膜
378
+
379
+ ```typescript
380
+ function inRange(mat: Mat, options: { lowerb: number[]; upperb: number[] }): Mat | null;
381
+ ```
382
+
383
+ **参数:** `lowerb` / `upperb` 为各通道下/上限数组。
384
+
385
+ **示例:**
386
+
387
+ ```javascript
388
+ const hsv = cv.cvtColor(src, { code: cv.COLOR_BGR2HSV });
389
+ const mask = cv.inRange(hsv, { lower: [0, 80, 80], upper: [20, 255, 255] });
390
+ ```
391
+
392
+ ### bitwise_and / bitwise_or / bitwise_xor / bitwise_not
393
+
394
+ ```typescript
395
+ const inv = cv.bitwise_not(mask);
396
+ ```
397
+
398
+ ### absdiff / addWeighted / convertScaleAbs
399
+
400
+ ```typescript
401
+ const abs = cv.convertScaleAbs(sobel, { alpha: 1, beta: 0 });
402
+ ```
403
+
404
+ ### countNonZero / minMaxLoc / mean / split / merge
405
+
406
+ ```typescript
407
+ function countNonZero(mat: Mat): number;
408
+ const mm = cv.minMaxLoc(gray);
409
+ logi(`max=${mm.maxVal} at (${mm.maxLoc.x},${mm.maxLoc.y})`);
410
+ const chans = cv.split(src);
411
+ const merged = cv.merge(chans);
412
+ chans.forEach((c) => cv.release(c));
413
+ cv.release(merged);
414
+ ```
415
+
416
+ ---
417
+
418
+ ## 滤波 / 边缘
419
+
420
+ ### GaussianBlur / blur / medianBlur / bilateralFilter / boxFilter
421
+
422
+ ```typescript
423
+ function GaussianBlur(mat: Mat, options: { ksize: number | [number, number] | Size; sigmaX?: number; sigmaY?: number }): Mat | null;
424
+ function blur(mat: Mat, options: { ksize: number | [number, number] | Size }): Mat | null;
425
+ function medianBlur(mat: Mat, options: { ksize: number }): Mat | null;
426
+ function bilateralFilter(mat: Mat, options: { d?: number; sigmaColor?: number; sigmaSpace?: number }): Mat | null;
427
+ function boxFilter(mat: Mat, options: { ksize: number | [number, number] | Size; ddepth?: number }): Mat | null;
428
+ ```
429
+
430
+ `ksize` 可为 `5`、`[5,5]` 或 `{ width, height }`。高斯核尺寸需为奇数。
431
+
432
+ **示例:**
433
+
434
+ ```javascript
435
+ const g = cv.GaussianBlur(src, { ksize: 5, sigmaX: 1.5 });
436
+ const m = cv.medianBlur(src, { ksize: 5 });
437
+ const b = cv.bilateralFilter(src, { d: 9, sigmaColor: 75, sigmaSpace: 75 });
438
+ ```
439
+
440
+ ### Canny / Sobel / Laplacian / Scharr / equalizeHist / CLAHE
441
+
442
+ ```typescript
443
+ function Canny(mat: Mat, options: { threshold1?: number; threshold2?: number; apertureSize?: number; L2gradient?: boolean }): Mat | null;
444
+ function Sobel(mat: Mat, options: { ddepth?: number; dx?: number; dy?: number; ksize?: number; scale?: number; delta?: number }): Mat | null;
445
+ function Laplacian(mat: Mat, options: { ddepth?: number; ksize?: number; scale?: number; delta?: number }): Mat | null;
446
+ function Scharr(mat: Mat, options: { ddepth?: number; dx?: number; dy?: number; scale?: number; delta?: number }): Mat | null;
447
+ function equalizeHist(mat: Mat): Mat | null;
448
+ function CLAHE(mat: Mat, options?: { clipLimit?: number; tileGridSize?: number | [number, number] | Size }): Mat | null;
449
+ ```
450
+
451
+ **Canny 参数:**
452
+
453
+ | 参数名 | 类型 | 是否必填 | 默认值 | 描述 |
454
+ | --- | --- | --- | --- | --- |
455
+ | `threshold1` | number | 否 | 100 | 低阈值 |
456
+ | `threshold2` | number | 否 | 200 | 高阈值 |
457
+ | `apertureSize` | number | 否 | 3 | Sobel 孔径 |
458
+ | `L2gradient` | boolean | 否 | false | 是否用 L2 范数 |
459
+
460
+ **CLAHE 参数:** `clipLimit` 默认 40;`tileGridSize` 默认 `[8,8]`。输入一般为单通道灰度。
461
+
462
+ **示例:**
463
+
464
+ ```javascript
465
+ const edges = cv.Canny(gray, { threshold1: 50, threshold2: 150 });
466
+ const sx = cv.Sobel(gray, { dx: 1, dy: 0, ksize: 3 });
467
+ const eq = cv.CLAHE(gray, { clipLimit: 2.0, tileGridSize: [8, 8] });
468
+ const histEq = cv.equalizeHist(gray);
469
+ ```
470
+
471
+ ### pyrDown / pyrUp - 金字塔采样
472
+
473
+ ```typescript
474
+ function pyrDown(mat: Mat): Mat | null;
475
+ function pyrUp(mat: Mat): Mat | null;
476
+ ```
477
+
478
+ 约缩小 / 放大一倍。大图特征匹配前可先 `pyrDown` 降采样。
479
+
480
+ **示例:**
481
+
482
+ ```javascript
483
+ const small = cv.pyrDown(src);
484
+ const back = cv.pyrUp(small);
485
+ ```
486
+
487
+ ---
488
+
489
+ ## 几何 / 绘制
490
+
491
+ ### resize - 缩放
492
+
493
+ ```typescript
494
+ function resize(mat: Mat, options: {
495
+ width?: number;
496
+ height?: number;
497
+ fx?: number;
498
+ fy?: number;
499
+ interpolation?: number;
500
+ }): Mat | null;
501
+ ```
502
+
503
+ 用 `width`/`height` 指定目标尺寸;也可设为 0 并用 `fx`/`fy` 按比例缩放。`interpolation` 如 `cv.INTER_LINEAR`、`cv.INTER_AREA`。
504
+
505
+ **示例:**
506
+
507
+ ```javascript
508
+ const small = cv.resize(src, { width: 360, height: 640 });
509
+ const half = cv.resize(src, { fx: 0.5, fy: 0.5, interpolation: cv.INTER_AREA });
510
+ ```
511
+
512
+ ### crop - 裁剪 ROI(拷贝)
513
+
514
+ ```typescript
515
+ function crop(mat: Mat, options: {
516
+ x?: number; y?: number;
517
+ ex?: number; ey?: number;
518
+ }): Mat | null;
519
+ ```
520
+
521
+ 参数与 `cv.capture` / `image.captureScreen` 一致,使用左上角与右下角。
522
+
523
+ 参数为 `{ x, y, ex, ey }`。
524
+
525
+ **示例:**
526
+
527
+ ```javascript
528
+ const roi = cv.crop(src, { x: 10, y: 10, ex: 110, ey: 110 });
529
+ ```
530
+
531
+ ### rotate / flip / copyMakeBorder
532
+
533
+ ```typescript
534
+ function rotate(mat: Mat, rotateCode: number): Mat | null;
535
+ const f = cv.flip(src, 1);
536
+ const padded = cv.copyMakeBorder(src, {
537
+ top: 10, bottom: 10, left: 10, right: 10,
538
+ borderType: cv.BORDER_CONSTANT, value: [0, 0, 0],
539
+ });
540
+ ```
541
+
542
+ ### getRotationMatrix2D / warpAffine / warpPerspective
543
+
544
+ ```typescript
545
+ function getRotationMatrix2D(options: { center: Point | [number, number]; angle?: number; scale?: number }): Mat | null;
546
+ function warpAffine(mat: Mat, options: { M: Mat; width?: number; height?: number; flags?: number }): Mat | null;
547
+ function warpPerspective(mat: Mat, options: { M: Mat; width?: number; height?: number; flags?: number }): Mat | null;
548
+ ```
549
+
550
+ **示例:**
551
+
552
+ ```javascript
553
+ const M = cv.getRotationMatrix2D({ center: { x: 100, y: 100 }, angle: 30, scale: 1 });
554
+ const out = cv.warpAffine(src, { M, width: 200, height: 200 });
555
+ cv.release(M);
556
+ cv.release(out);
557
+ ```
558
+
559
+ ### rectangle / circle / line / putText - 就地绘制
560
+
561
+ 就地修改并返回**同一句柄**。`color` 为 BGR 数组或 `#RRGGBB`。
562
+
563
+ ```typescript
564
+ function rectangle(mat: Mat, options: RectangleOptions): Mat | null;
565
+ function circle(mat: Mat, options: CircleOptions): Mat | null;
566
+ function line(mat: Mat, options: LineOptions): Mat | null;
567
+ function putText(mat: Mat, options: PutTextOptions): Mat | null;
568
+ ```
569
+
570
+ **rectangle 参数:** `pt1`+`pt2`,或 `rect`;以及 `color`、`thickness`。
571
+
572
+ **示例:**
573
+
574
+ ```javascript
575
+ cv.rectangle(src, {
576
+ pt1: { x: 10, y: 10 },
577
+ pt2: { x: 100, y: 80 },
578
+ color: [0, 0, 255],
579
+ thickness: 2,
580
+ });
581
+ cv.circle(src, { center: { x: 50, y: 50 }, radius: 20, color: "#00FF00", thickness: -1 });
582
+ cv.line(src, { pt1: { x: 0, y: 0 }, pt2: { x: 100, y: 100 }, color: [255, 0, 0], thickness: 2 });
583
+ cv.putText(src, {
584
+ text: "hello",
585
+ org: { x: 20, y: 40 },
586
+ fontFace: cv.FONT_HERSHEY_SIMPLEX,
587
+ fontScale: 1,
588
+ color: [255, 255, 255],
589
+ thickness: 2,
590
+ });
591
+ ```
592
+
593
+ ### fillPoly / polylines / drawContours
594
+
595
+ ```typescript
596
+ function fillPoly(mat: Mat, options: { pts: Point[][] | Point[]; color?: number[] | string }): Mat | null;
597
+ function polylines(mat: Mat, options: { pts: Point[][] | Point[]; isClosed?: boolean; color?: number[] | string; thickness?: number }): Mat | null;
598
+ function drawContours(mat: Mat, options: {
599
+ contours?: Point[][];
600
+ pts?: Point[][];
601
+ contourIdx?: number;
602
+ color?: number[] | string;
603
+ thickness?: number;
604
+ }): Mat | null;
605
+ ```
606
+
607
+ 就地绘制。`contourIdx` 为 `-1`(默认)表示画全部。
608
+
609
+ **示例:**
610
+
611
+ ```javascript
612
+ cv.polylines(src, {
613
+ pts: [[{ x: 0, y: 0 }, { x: 100, y: 0 }, { x: 100, y: 80 }, { x: 0, y: 80 }]],
614
+ isClosed: true,
615
+ color: [0, 255, 0],
616
+ thickness: 2,
617
+ });
618
+ ```
619
+
620
+ ---
621
+
622
+ ## 模板匹配 / 轮廓 / 形态学
623
+
624
+ ### matchTemplate / matchTemplateLoc
625
+
626
+ ```typescript
627
+ function matchTemplate(image: Mat, templ: Mat, methodOrOptions?: number | { method?: number; mask?: Mat }): Mat | null;
628
+ function matchTemplateLoc(image: Mat, templ: Mat, methodOrOptions?: number | { method?: number }): MatchLocResult | null;
629
+ ```
630
+
631
+ **参数:**
632
+
633
+ | 参数名 | 类型 | 是否必填 | 默认值 | 描述 |
634
+ | --- | --- | --- | --- | --- |
635
+ | `image` | Mat | 是 | - | 大图 |
636
+ | `templ` | Mat | 是 | - | 模板(须小于大图) |
637
+ | `method` | number | 否 | `TM_CCOEFF_NORMED` | 匹配方法 |
638
+
639
+ **matchTemplateLoc 返回:** `{ x, y, minVal, maxVal }`(最佳位置取自 max 或 min,取决于 method)。
640
+
641
+ **示例:**
642
+
643
+ ```javascript
644
+ const loc = cv.matchTemplateLoc(src, templ, { method: cv.TM_CCOEFF_NORMED });
645
+ logi(`best=(${loc.x},${loc.y}) max=${loc.maxVal}`);
646
+ cv.rectangle(src, {
647
+ pt1: { x: loc.x, y: loc.y },
648
+ pt2: { x: loc.x + tw, y: loc.y + th },
649
+ color: [0, 0, 255],
650
+ thickness: 2,
651
+ });
652
+ ```
653
+
654
+ ### findContours
655
+
656
+ ```typescript
657
+ function findContours(mat: Mat, options?: { mode?: number; method?: number }): { contours: Point[][]; hierarchy: number[][] } | null;
658
+ ```
659
+
660
+ **参数:**
661
+
662
+ | 参数名 | 类型 | 是否必填 | 默认值 | 描述 |
663
+ | --- | --- | --- | --- | --- |
664
+ | `mode` | number | 否 | `RETR_EXTERNAL` | 如 `RETR_LIST` / `RETR_TREE` |
665
+ | `method` | number | 否 | `CHAIN_APPROX_SIMPLE` | 如 `CHAIN_APPROX_NONE` |
666
+
667
+ **返回值:** `contours` 为点集;`hierarchy` 为每个轮廓的 `[next, prev, child, parent]`(`-1` 表示无)。
668
+
669
+ 输入一般为二值图。
670
+
671
+ **示例:**
672
+
673
+ ```javascript
674
+ const { contours, hierarchy } = cv.findContours(bin, { mode: cv.RETR_TREE, method: cv.CHAIN_APPROX_SIMPLE });
675
+ logi(`count=${contours.length} h0=${hierarchy[0]}`);
676
+ ```
677
+
678
+ ### boundingRect / minAreaRect / contourArea / arcLength / approxPolyDP / convexHull
679
+
680
+ ```typescript
681
+ function boundingRect(matOrPoints: Mat | Point[]): Rect | null;
682
+ function minAreaRect(points: Point[]): RotatedRect | null;
683
+ function contourArea(matOrPoints: Mat | Point[], orientedOrOptions?: boolean | { oriented?: boolean }): number;
684
+ function arcLength(points: Point[], closedOrOptions?: boolean | { closed?: boolean; isClosed?: boolean }): number;
685
+ function approxPolyDP(points: Point[], options: { epsilon?: number; closed?: boolean }): Point[] | null;
686
+ function convexHull(points: Point[], options?: { clockwise?: boolean }): Point[] | null;
687
+ ```
688
+
689
+ **示例:**
690
+
691
+ ```javascript
692
+ for (const c of contours) {
693
+ const area = cv.contourArea(c);
694
+ if (area < 100) continue;
695
+ const rect = cv.boundingRect(c);
696
+ const hull = cv.convexHull(c);
697
+ const approx = cv.approxPolyDP(c, { epsilon: 0.02 * cv.arcLength(c, true), closed: true });
698
+ cv.rectangle(src, { rect, color: [0, 255, 0], thickness: 1 });
699
+ }
700
+ ```
701
+
702
+ ### moments / matchShapes
703
+
704
+ ```typescript
705
+ function moments(mat: Mat, options?: { binaryImage?: boolean }): MomentsResult | null;
706
+ function moments(mat: Mat, binaryImage?: boolean): MomentsResult | null;
707
+ function moments(points: Point[]): MomentsResult | null;
708
+ function matchShapes(contour1: Mat, contour2: Mat, options?: { method?: number; parameter?: number }): number;
709
+ const cx = m.m10 / m.m00;
710
+ const cy = m.m01 / m.m00;
711
+ const score = cv.matchShapes(c1, c2, { method: cv.CONTOURS_MATCH_I1 });
712
+ ```
713
+
714
+ ### erode / dilate / morphologyEx / getStructuringElement
715
+
716
+ ```typescript
717
+ function getStructuringElement(options: { shape?: number; ksize: number | [number, number] | Size }): Mat | null;
718
+ const opened = cv.morphologyEx(bin, { op: cv.MORPH_OPEN, kernel: k });
719
+ cv.release(k);
720
+ cv.release(opened);
721
+ ```
722
+
723
+ ### connectedComponentsWithStats / distanceTransform / floodFill / goodFeaturesToTrack
724
+
725
+ ```typescript
726
+ function connectedComponentsWithStats(mat: Mat): {
727
+ count: number;
728
+ labels: Mat | null;
729
+ stats: Mat | null;
730
+ centroids: Mat | null;
731
+ } | null;
732
+
733
+ function distanceTransform(mat: Mat, options?: { distanceType?: number; maskSize?: number }): Mat | null;
734
+
735
+ function floodFill(mat: Mat, options: {
736
+ seedPoint: Point | [number, number];
737
+ newVal: number[] | string;
738
+ loDiff?: number[] | string;
739
+ upDiff?: number[] | string;
740
+ }): { filled: number; rect: Rect } | null;
741
+
742
+ function goodFeaturesToTrack(mat: Mat, options: {
743
+ maxCorners?: number;
744
+ qualityLevel?: number;
745
+ minDistance?: number;
746
+ mask?: Mat;
747
+ }): Point[] | null;
748
+ ```
749
+
750
+ **说明:**
751
+
752
+ - `connectedComponentsWithStats`:`stats` 可用 `CC_STAT_LEFT/TOP/WIDTH/HEIGHT/AREA` 读列。
753
+ - `floodFill` **就地**修改输入图。
754
+ - `goodFeaturesToTrack`:Shi-Tomasi 角点,输入建议灰度。
755
+
756
+ **示例:**
757
+
758
+ ```javascript
759
+ const cc = cv.connectedComponentsWithStats(bin);
760
+ logi(`components=${cc.count}`);
761
+ cv.release(cc.labels);
762
+ cv.release(cc.stats);
763
+ cv.release(cc.centroids);
764
+
765
+ const corners = cv.goodFeaturesToTrack(gray, {
766
+ maxCorners: 50,
767
+ qualityLevel: 0.01,
768
+ minDistance: 10,
769
+ });
770
+ ```
771
+
772
+ ### HoughLinesP / HoughCircles
773
+
774
+ ```typescript
775
+ function HoughLinesP(mat: Mat, options: {
776
+ rho?: number; theta?: number; threshold?: number;
777
+ minLineLength?: number; maxLineGap?: number;
778
+ }): Array<{ x1: number; y1: number; x2: number; y2: number }> | null;
779
+
780
+ function HoughCircles(mat: Mat, options: {
781
+ method?: number; dp?: number; minDist?: number;
782
+ param1?: number; param2?: number;
783
+ minRadius?: number; maxRadius?: number;
784
+ }): Array<{ x: number; y: number; radius: number }> | null;
785
+ ```
786
+
787
+ 输入一般为边缘图(直线)或灰度图(圆)。`method` 常用 `cv.HOUGH_GRADIENT`。
788
+
789
+ **示例:**
790
+
791
+ ```javascript
792
+ const lines = cv.HoughLinesP(edges, {
793
+ rho: 1,
794
+ theta: Math.PI / 180,
795
+ threshold: 80,
796
+ minLineLength: 40,
797
+ maxLineGap: 10,
798
+ });
799
+ for (const l of lines) {
800
+ cv.line(src, { pt1: { x: l.x1, y: l.y1 }, pt2: { x: l.x2, y: l.y2 }, color: [0, 255, 0], thickness: 1 });
801
+ }
802
+
803
+ const circles = cv.HoughCircles(gray, {
804
+ method: cv.HOUGH_GRADIENT,
805
+ dp: 1,
806
+ minDist: 30,
807
+ param1: 100,
808
+ param2: 30,
809
+ minRadius: 10,
810
+ maxRadius: 80,
811
+ });
812
+ ```
813
+
814
+ ---
815
+
816
+ ## 算术 / 直方图 / Flann / 扫码 / 矩阵
817
+
818
+ ### add / subtract / multiply / divide
819
+
820
+ ```typescript
821
+ function add(src1: Mat, options: { src2: Mat; mask?: Mat }): Mat | null;
822
+ function subtract(src1: Mat, options: { src2: Mat; mask?: Mat }): Mat | null;
823
+ function multiply(src1: Mat, options: { src2: Mat; scale?: number }): Mat | null;
824
+ function divide(src1: Mat, options: { src2: Mat; scale?: number }): Mat | null;
825
+ ```
826
+
827
+ **示例:**
828
+
829
+ ```javascript
830
+ const sum = cv.add(a, { src2: b });
831
+ const scaled = cv.multiply(a, { src2: b, scale: 0.5 });
832
+ ```
833
+
834
+ ### normalize / norm / meanStdDev / findNonZero / hconcat / vconcat
835
+
836
+ ```typescript
837
+ function normalize(mat: Mat, options?: { alpha?: number; beta?: number; normType?: number }): Mat | null;
838
+ function norm(mat: Mat, options?: { normType?: number; mask?: Mat; src2?: Mat }): number;
839
+ function meanStdDev(mat: Mat, options?: { mask?: Mat }): { mean: number[]; stddev: number[] } | null;
840
+ function findNonZero(mat: Mat): Point[] | null;
841
+ function hconcat(mats: Mat[]): Mat | null;
842
+ function vconcat(mats: Mat[]): Mat | null;
843
+ ```
844
+
845
+ **示例:**
846
+
847
+ ```javascript
848
+ const n01 = cv.normalize(gray, { alpha: 0, beta: 255, normType: cv.NORM_MINMAX });
849
+ const pts = cv.findNonZero(mask);
850
+ const side = cv.hconcat([left, right]);
851
+ ```
852
+
853
+ ### getPerspectiveTransform / getAffineTransform
854
+
855
+ 已知对应点求变换矩阵。透视 ≥4 点,仿射 ≥3 点。
856
+
857
+ ```typescript
858
+ function getPerspectiveTransform(options: { srcPoints: Point[]; dstPoints: Point[] }): Mat | null;
859
+ function getAffineTransform(options: { srcPoints: Point[]; dstPoints: Point[] }): Mat | null;
860
+ ```
861
+
862
+ **示例:**
863
+
864
+ ```javascript
865
+ const M = cv.getPerspectiveTransform({
866
+ srcPoints: [{ x: 10, y: 10 }, { x: 200, y: 20 }, { x: 190, y: 300 }, { x: 20, y: 280 }],
867
+ dstPoints: [{ x: 0, y: 0 }, { x: 300, y: 0 }, { x: 300, y: 400 }, { x: 0, y: 400 }],
868
+ });
869
+ const warped = cv.warpPerspective(src, { M, width: 300, height: 400 });
870
+ cv.release(M);
871
+ cv.release(warped);
872
+ ```
873
+
874
+ ### FlannBasedMatcher / flannKnnMatch
875
+
876
+ ```typescript
877
+ function FlannBasedMatcher(queryDescriptors: Mat, trainDescriptors: Mat): DMatch[] | null;
878
+ function flannKnnMatch(queryDescriptors: Mat, trainDescriptors: Mat, options?: { k?: number; ratio?: number }): DMatch[][] | null;
879
+ ```
880
+
881
+ SIFT 等浮点描述子可用 Flann;ORB 建议 `BFMatcher` + `NORM_HAMMING`。
882
+
883
+ **示例:**
884
+
885
+ ```javascript
886
+ const knn = cv.flannKnnMatch(f2.descriptors, f1.descriptors, { k: 2, ratio: 0.75 });
887
+ const good = knn.map((g) => g[0]);
888
+ ```
889
+
890
+ ### calcHist / compareHist
891
+
892
+ ```typescript
893
+ function calcHist(mat: Mat, options?: { channels?: number[]; histSize?: number[]; ranges?: number[]; mask?: Mat; accumulate?: boolean }): Mat | null;
894
+ function compareHist(H1: Mat, H2: Mat, options?: { method?: number }): number;
895
+ ```
896
+
897
+ 默认通道 `[0]`、`histSize [256]`、`ranges [0,256]`。
898
+
899
+ **示例:**
900
+
901
+ ```javascript
902
+ const h1 = cv.calcHist(gray1);
903
+ const h2 = cv.calcHist(gray2);
904
+ const score = cv.compareHist(h1, h2, { method: cv.HISTCMP_CORREL });
905
+ cv.release(h1); cv.release(h2);
906
+ ```
907
+
908
+ ### encodeQRCode / detectQRCode / detectBarcode
909
+
910
+ ```typescript
911
+ function encodeQRCode(text: string): Mat | null;
912
+ function detectQRCode(mat: Mat): { decoded: string; points: Point[] } | null;
913
+ function detectBarcode(mat: Mat): { decoded: string[]; types: string[]; points: Point[] } | null;
914
+ ```
915
+
916
+ `encodeQRCode` 返回可显示/可识别的 BGR Mat(已放大并加静区),可直接交给 `detectQRCode`。
917
+
918
+ **示例:**
919
+
920
+ ```javascript
921
+ const qrMat = cv.encodeQRCode("https://example.com");
922
+ const qr = cv.detectQRCode(qrMat); // { decoded, points }
923
+ if (qr) logi(qr.decoded);
924
+ const codes = cv.detectBarcode(src);
925
+ if (codes) logi(codes.decoded.join(","));
926
+ cv.release(qrMat);
927
+ ```
928
+
929
+ ### filter2D / ellipse / getTextSize
930
+
931
+ ```typescript
932
+ function filter2D(mat: Mat, options: { kernel: Mat; ddepth?: number; delta?: number }): Mat | null;
933
+ function ellipse(mat: Mat, options: { center: Point; axes: Size | [number, number]; angle?: number; startAngle?: number; endAngle?: number; color?: number[] | string; thickness?: number }): Mat | null;
934
+ function getTextSize(options: { text: string; fontFace?: number; fontScale?: number; thickness?: number }): { width: number; height: number; baseLine: number } | null;
935
+ ```
936
+
937
+ `ellipse` 就地绘制;`thickness: cv.FILLED` 表示填充。
938
+
939
+ ---
940
+
941
+ ## 特征点 / 描述子 / 匹配 / 单应
942
+
943
+ ### 返回结构
944
+
945
+ **KeyPoint:**
946
+
947
+ | 字段 | 类型 | 描述 |
948
+ | --- | --- | --- |
949
+ | `x` / `y` | number | 坐标 |
950
+ | `size` | number | 邻域直径 |
951
+ | `angle` | number | 方向;不适用为 `-1` |
952
+ | `response` | number | 响应强度 |
953
+ | `octave` | number | 金字塔层 |
954
+ | `class_id` | number | 类别 id |
955
+
956
+ **DMatch:**
957
+
958
+ | 字段 | 类型 | 描述 |
959
+ | --- | --- | --- |
960
+ | `queryIdx` | number | 查询描述子索引 |
961
+ | `trainIdx` | number | 训练描述子索引 |
962
+ | `distance` | number | 距离(越小越相似) |
963
+
964
+ **FeatureResult:** `{ keypoints: KeyPoint[], descriptors: Mat | null }`
965
+ 描述子是 Mat 句柄,用完需 `cv.release(descriptors)`。
966
+
967
+ ### SIFT
968
+
969
+ ```typescript
970
+ function SIFT(mat: Mat, options?: {
971
+ nfeatures?: number;
972
+ nOctaveLayers?: number;
973
+ contrastThreshold?: number;
974
+ edgeThreshold?: number;
975
+ sigma?: number;
976
+ mask?: Mat;
977
+ }): FeatureResult | null;
978
+ ```
979
+
980
+ **参数:**
981
+
982
+ | 参数名 | 类型 | 是否必填 | 默认值 | 描述 |
983
+ | --- | --- | --- | --- | --- |
984
+ | `mat` | Mat | 是 | - | 输入图(建议灰度) |
985
+ | `nfeatures` | number | 否 | 0(不限制) | 保留最佳特征数量 |
986
+ | `nOctaveLayers` | number | 否 | 3 | 每组 octave 层数 |
987
+ | `contrastThreshold` | number | 否 | 0.04 | 对比度阈值,越大特征越少 |
988
+ | `edgeThreshold` | number | 否 | 10 | 边缘过滤阈值 |
989
+ | `sigma` | number | 否 | 1.6 | 第 0 层高斯 sigma |
990
+ | `mask` | Mat | 否 | - | 可选 ROI 掩膜 |
991
+
992
+ **返回值:** `{ keypoints, descriptors }`;失败 `null`。
993
+
994
+ **示例:**
995
+
996
+ ```javascript
997
+ const gray = cv.cvtColor(src, { code: cv.COLOR_BGR2GRAY });
998
+ const feat = cv.SIFT(gray, { nfeatures: 500 });
999
+ if (feat) {
1000
+ logi(`SIFT keypoints=${feat.keypoints.length}`);
1001
+ const vis = cv.drawKeypoints(src, { keypoints: feat.keypoints, color: [0, 255, 0] });
1002
+ cv.imwrite(`${file.getInternalDir("documents")}/sift.png`, vis);
1003
+ cv.release(vis);
1004
+ cv.release(feat.descriptors);
1005
+ }
1006
+ cv.release(gray);
1007
+ ```
1008
+
1009
+ ### ORB
1010
+
1011
+ ```typescript
1012
+ function ORB(mat: Mat, options?: {
1013
+ nfeatures?: number;
1014
+ scaleFactor?: number;
1015
+ nlevels?: number;
1016
+ mask?: Mat;
1017
+ }): FeatureResult | null;
1018
+ ```
1019
+
1020
+ **参数:**
1021
+
1022
+ | 参数名 | 类型 | 是否必填 | 默认值 | 描述 |
1023
+ | --- | --- | --- | --- | --- |
1024
+ | `nfeatures` | number | 否 | 500 | 最大特征数 |
1025
+ | `scaleFactor` | number | 否 | 1.2 | 金字塔尺度因子 |
1026
+ | `nlevels` | number | 否 | 8 | 金字塔层数 |
1027
+
1028
+ **说明:** ORB 描述子为二进制,匹配请用 `cv.NORM_HAMMING`(或 `NORM_HAMMING2`)。实时/循环优先 ORB。
1029
+
1030
+ **示例:**
1031
+
1032
+ ```javascript
1033
+ const a = cv.ORB(img1, { nfeatures: 800 });
1034
+ const b = cv.ORB(img2, { nfeatures: 800 });
1035
+ const matches = cv.BFMatcher(a.descriptors, b.descriptors, {
1036
+ normType: cv.NORM_HAMMING,
1037
+ crossCheck: true,
1038
+ });
1039
+ logi(`matches=${matches.length}`);
1040
+ cv.release(a.descriptors);
1041
+ cv.release(b.descriptors);
1042
+ ```
1043
+
1044
+ ### BFMatcher
1045
+
1046
+ ```typescript
1047
+ function BFMatcher(queryDescriptors: Mat, trainDescriptors: Mat, options?: {
1048
+ normType?: number;
1049
+ crossCheck?: boolean;
1050
+ }): DMatch[] | null;
1051
+ matches.sort((a, b) => a.distance - b.distance);
1052
+ const top = matches.slice(0, 30);
1053
+ ```
1054
+
1055
+ ### knnMatch
1056
+
1057
+ ```typescript
1058
+ function knnMatch(queryDescriptors: Mat, trainDescriptors: Mat, options?: {
1059
+ k?: number;
1060
+ normType?: number;
1061
+ ratio?: number;
1062
+ }): DMatch[][] | null;
1063
+ ```
1064
+
1065
+ **参数:**
1066
+
1067
+ | 参数名 | 类型 | 是否必填 | 默认值 | 描述 |
1068
+ | --- | --- | --- | --- | --- |
1069
+ | `k` | number | 否 | 2 | 每个查询点取 K 个最近邻 |
1070
+ | `normType` | number | 否 | `NORM_L2` | 距离类型 |
1071
+ | `ratio` | number | 否 | - | 如 `0.75`:启用 Lowe ratio test,只保留通过筛选的最近邻 |
1072
+
1073
+ **返回值:**
1074
+
1075
+ - 未设 `ratio`:`DMatch[][]`,外层每个查询一组。
1076
+ - 设了 `ratio`:仅保留通过筛选的组,每组通常只含 1 个 `DMatch`。
1077
+
1078
+ **示例:**
1079
+
1080
+ ```javascript
1081
+ const knn = cv.knnMatch(desc1, desc2, {
1082
+ k: 2,
1083
+ normType: cv.NORM_L2,
1084
+ ratio: 0.75,
1085
+ });
1086
+ const good = knn.map((g) => g[0]);
1087
+ logi(`good matches=${good.length}`);
1088
+ ```
1089
+
1090
+ ### drawKeypoints / drawMatches
1091
+
1092
+ ```typescript
1093
+ function drawKeypoints(mat: Mat, options: { keypoints: KeyPoint[]; color?: number[] | string }): Mat | null;
1094
+ function drawMatches(img1: Mat, options: {
1095
+ img2: Mat;
1096
+ keypoints1: KeyPoint[];
1097
+ keypoints2: KeyPoint[];
1098
+ matches1to2: DMatch[];
1099
+ }): Mat | null;
1100
+ cv.imwrite(`${file.getInternalDir("documents")}/matches.png`, vis);
1101
+ cv.release(vis);
1102
+ ```
1103
+
1104
+ ### findHomography / perspectiveTransform / warpPerspective
1105
+
1106
+ ```typescript
1107
+ function findHomography(options: {
1108
+ srcPoints: Point[];
1109
+ dstPoints: Point[];
1110
+ method?: number;
1111
+ ransacReprojThreshold?: number;
1112
+ }): Mat | null;
1113
+
1114
+ function perspectiveTransform(points: Point[], options: { M: Mat }): Point[] | null;
1115
+ const templ = cv.imread("logo.png");
1116
+ const g1 = cv.cvtColor(src, { code: cv.COLOR_BGR2GRAY });
1117
+ const g2 = cv.cvtColor(templ, { code: cv.COLOR_BGR2GRAY });
1118
+
1119
+ const f1 = cv.SIFT(g1, { nfeatures: 800 });
1120
+ const f2 = cv.SIFT(g2, { nfeatures: 800 });
1121
+ const knn = cv.flannKnnMatch(f2.descriptors, f1.descriptors, {
1122
+ k: 2,
1123
+ ratio: 0.75,
1124
+ });
1125
+ const good = knn.map((g) => g[0]);
1126
+
1127
+ if (good.length >= 4) {
1128
+ const srcPts = good.map((m) => ({
1129
+ x: f2.keypoints[m.queryIdx].x,
1130
+ y: f2.keypoints[m.queryIdx].y,
1131
+ }));
1132
+ const dstPts = good.map((m) => ({
1133
+ x: f1.keypoints[m.trainIdx].x,
1134
+ y: f1.keypoints[m.trainIdx].y,
1135
+ }));
1136
+ const H = cv.findHomography({
1137
+ srcPoints: srcPts,
1138
+ dstPoints: dstPts,
1139
+ method: cv.RANSAC,
1140
+ ransacReprojThreshold: 5,
1141
+ });
1142
+ const w = cv.getSize(templ).width;
1143
+ const h = cv.getSize(templ).height;
1144
+ const corners = cv.perspectiveTransform(
1145
+ [
1146
+ { x: 0, y: 0 },
1147
+ { x: w, y: 0 },
1148
+ { x: w, y: h },
1149
+ { x: 0, y: h },
1150
+ ],
1151
+ { M: H },
1152
+ );
1153
+ cv.polylines(src, { pts: [corners], isClosed: true, color: [0, 255, 0], thickness: 2 });
1154
+ cv.release(H);
1155
+ }
1156
+
1157
+ cv.release(src);
1158
+ cv.release(templ);
1159
+ cv.release(g1);
1160
+ cv.release(g2);
1161
+ cv.release(f1.descriptors);
1162
+ cv.release(f2.descriptors);
1163
+ ```
1164
+
1165
+ ---
1166
+
1167
+ ## Photo / 修复与风格化
1168
+
1169
+ ### inpaint
1170
+
1171
+ ```typescript
1172
+ function inpaint(mat: Mat, options: { mask: Mat; inpaintRadius?: number; flags?: number }): Mat | null;
1173
+ ```
1174
+
1175
+ | 参数名 | 类型 | 是否必填 | 默认值 | 描述 |
1176
+ | --- | --- | --- | --- | --- |
1177
+ | `mask` | Mat | 是 | - | 非零区域待修复 |
1178
+ | `inpaintRadius` | number | 否 | 3 | 邻域半径 |
1179
+ | `flags` | number | 否 | `INPAINT_TELEA` | `INPAINT_NS` / `INPAINT_TELEA` |
1180
+
1181
+ ```javascript
1182
+ const out = cv.inpaint(src, { mask, flags: cv.INPAINT_TELEA });
1183
+ ```
1184
+
1185
+ ### fastNlMeansDenoising / fastNlMeansDenoisingColored
1186
+
1187
+ ```typescript
1188
+ function fastNlMeansDenoising(mat: Mat, options?: {
1189
+ h?: number; templateWindowSize?: number; searchWindowSize?: number;
1190
+ }): Mat | null;
1191
+ function fastNlMeansDenoisingColored(mat: Mat, options?: {
1192
+ h?: number; hColor?: number; templateWindowSize?: number; searchWindowSize?: number;
1193
+ }): Mat | null;
1194
+ ```
1195
+
1196
+ ```javascript
1197
+ const d1 = cv.fastNlMeansDenoising(gray, { h: 3 });
1198
+ const d2 = cv.fastNlMeansDenoisingColored(src, { h: 3, hColor: 3 });
1199
+ ```
1200
+
1201
+ ## Core 扩展
1202
+
1203
+ ### LUT / compare / min / max
1204
+
1205
+ ```typescript
1206
+ function LUT(mat: Mat, options: { lut: Mat }): Mat | null;
1207
+ function compare(src1: Mat, options: { src2: Mat; cmpop: number }): Mat | null;
1208
+ function min(src1: Mat, options: { src2: Mat }): Mat | null;
1209
+ function max(src1: Mat, options: { src2: Mat }): Mat | null;
1210
+ ```
1211
+
1212
+ `cmpop` 使用 `CMP_EQ` / `CMP_GT` / `CMP_GE` / `CMP_LT` / `CMP_LE` / `CMP_NE`。
1213
+
1214
+ ```javascript
1215
+ const mask = cv.compare(a, { src2: b, cmpop: cv.CMP_GT });
1216
+ const lo = cv.min(a, { src2: b });
1217
+ ```
1218
+
1219
+ ## Imgproc 几何 / 滤波扩展
1220
+
1221
+ ### connectedComponents / convexityDefects / isContourConvex / fitLine / fitEllipse
1222
+
1223
+ ```typescript
1224
+ function connectedComponents(mat: Mat, options?: { connectivity?: number }): { count: number; labels: Mat } | null;
1225
+ function convexityDefects(contour: Point[], options: { hull: number[] | Point[] }):
1226
+ Array<{ start: number; end: number; farthest: number; depth: number }> | null;
1227
+ function isContourConvex(contour: Point[]): boolean;
1228
+ function fitLine(points: Point[], options?: {
1229
+ distType?: number; param?: number; reps?: number; aeps?: number;
1230
+ }): [number, number, number, number] | null;
1231
+ function fitEllipse(points: Point[]): RotatedRect | null;
1232
+ ```
1233
+
1234
+ `hull` 可为索引数组,或与 `convexHull` 返回格式相同的 Point[]。`fitEllipse` 至少需要 5 个点。
1235
+
1236
+ ```javascript
1237
+ const cc = cv.connectedComponents(bin, { connectivity: 8 });
1238
+ const hull = cv.convexHull(contour);
1239
+ const defects = cv.convexityDefects(contour, { hull });
1240
+ const line = cv.fitLine(points); // [vx, vy, x0, y0]
1241
+ const ell = cv.fitEllipse(contour); // { center, size, angle }
1242
+ cv.release(cc.labels);
1243
+ ```
1244
+
1245
+ ### getRectSubPix / phaseCorrelate / estimateAffine2D / estimateAffinePartial2D
1246
+
1247
+ ```typescript
1248
+ function getRectSubPix(mat: Mat, options: {
1249
+ patchSize: Size | [number, number]; center: Point;
1250
+ }): Mat | null;
1251
+ function phaseCorrelate(src1: Mat, options: { src2: Mat; window?: Mat }): {
1252
+ x: number; y: number; response?: number;
1253
+ } | null;
1254
+ function estimateAffine2D(options: {
1255
+ srcPoints: Point[]; dstPoints: Point[]; method?: number; ransacReprojThreshold?: number;
1256
+ }): Mat | null;
1257
+ function estimateAffinePartial2D(options: {
1258
+ srcPoints: Point[]; dstPoints: Point[]; method?: number; ransacReprojThreshold?: number;
1259
+ }): Mat | null;
1260
+ ```
1261
+
1262
+ `phaseCorrelate` 估计两帧位移(滚动/拖拽);`estimateAffinePartial2D` 为相似变换(平移+旋转+等比缩放)。
1263
+
1264
+ ```javascript
1265
+ const shift = cv.phaseCorrelate(prevGray, { src2: gray });
1266
+ const M = cv.estimateAffinePartial2D({ srcPoints, dstPoints, method: cv.RANSAC });
1267
+ ```
1268
+
1269
+ ### drawMarker / arrowedLine / fillConvexPoly(就地)
1270
+
1271
+ ```typescript
1272
+ function drawMarker(mat: Mat, options: {
1273
+ position: Point; color?: number[] | string;
1274
+ markerType?: number; markerSize?: number; thickness?: number;
1275
+ }): Mat | null;
1276
+ function arrowedLine(mat: Mat, options: {
1277
+ pt1: Point; pt2: Point; color?: number[] | string; thickness?: number; tipLength?: number;
1278
+ }): Mat | null;
1279
+ function fillConvexPoly(mat: Mat, options: { pts: Point[]; color?: number[] | string }): Mat | null;
1280
+ ```
1281
+
1282
+ 就地修改输入 Mat,返回同一句柄。适合调试标注匹配点、滑动方向等。
1283
+
1284
+ ```javascript
1285
+ cv.drawMarker(src, { position: { x: 100, y: 80 }, markerType: cv.MARKER_CROSS });
1286
+ cv.arrowedLine(src, { pt1: { x: 10, y: 10 }, pt2: { x: 120, y: 80 }, color: [0, 255, 0] });
1287
+ cv.fillConvexPoly(src, { pts: [{ x: 20, y: 20 }, { x: 70, y: 20 }, { x: 45, y: 60 }], color: [128, 0, 128] });
1288
+ ```
1289
+
1290
+ ## 常用常量
1291
+
1292
+ 挂在 `cv` 上,例如:
1293
+
1294
+ - 颜色:`COLOR_BGR2GRAY`、`COLOR_RGB2GRAY`、`COLOR_BGR2HSV`、`COLOR_BGR2RGB`、`COLOR_BGRA2BGR`、…
1295
+ - 阈值:`THRESH_BINARY`、`THRESH_BINARY_INV`、`THRESH_OTSU`、`THRESH_TRUNC`、…
1296
+ - 自适应:`ADAPTIVE_THRESH_MEAN_C`、`ADAPTIVE_THRESH_GAUSSIAN_C`
1297
+ - 模板匹配:`TM_SQDIFF`、`TM_CCORR_NORMED`、`TM_CCOEFF_NORMED`、…
1298
+ - 轮廓:`RETR_EXTERNAL`、`RETR_LIST`、`RETR_TREE`、`CHAIN_APPROX_SIMPLE`、…
1299
+ - 形态学:`MORPH_RECT`、`MORPH_OPEN`、`MORPH_CLOSE`、`MORPH_HITMISS`、…
1300
+ - 插值 / 旋转 / 边界:`INTER_*`、`ROTATE_*`、`BORDER_*`
1301
+ - 线型:`LINE_4` / `LINE_8` / `LINE_AA` / `FILLED`
1302
+ - 字体:`FONT_HERSHEY_SIMPLEX`、…
1303
+ - Mat 类型:`CV_8UC1`、`CV_8UC3`、`CV_8UC4`、`CV_32FC1`、`CV_32FC3`
1304
+ - 匹配距离:`NORM_L1`、`NORM_L2`、`NORM_HAMMING`、`NORM_HAMMING2`、`NORM_INF`、`NORM_MINMAX`
1305
+ - 直方图比较:`HISTCMP_CORREL` / `CHISQR` / `INTERSECT` / `BHATTACHARYYA` / …
1306
+ - 单应:`RANSAC`、`LMEDS`、`RHO`
1307
+ - 霍夫:`HOUGH_GRADIENT`、`HOUGH_GRADIENT_ALT`
1308
+ - 距离变换:`DIST_L1`、`DIST_L2`、`DIST_C`、`DIST_MASK_3`、`DIST_MASK_5`
1309
+ - 形状匹配:`CONTOURS_MATCH_I1` / `I2` / `I3`
1310
+ - 连通域统计列:`CC_STAT_LEFT` / `TOP` / `WIDTH` / `HEIGHT` / `AREA`
1311
+ - 读标志:`IMREAD_COLOR`、`IMREAD_GRAYSCALE`、`IMREAD_UNCHANGED`
1312
+ - 修复:`INPAINT_NS`、`INPAINT_TELEA`
1313
+ - 比较:`CMP_EQ` / `CMP_GT` / `CMP_GE` / `CMP_LT` / `CMP_LE` / `CMP_NE`
1314
+ - 标记:`MARKER_CROSS`、`MARKER_STAR`、`MARKER_DIAMOND`、`MARKER_SQUARE`、…
1315
+
1316
+ 完整列表见类型提示 `types/opencv.d.ts`。
1317
+
1318
+ ---
1319
+
1320
+ ## 限制与建议
1321
+
1322
+ 1. 同时存活的 Mat 句柄有上限(约 32),用完请 `release`,或阶段结束 `releaseAll`。
1323
+ 2. 路径 / msbundle / imageId / `screen` 只走 `imread`;其它 API 只接受 Mat 句柄。
1324
+ 3. SIFT 描述子优先 `flannKnnMatch`;ORB 用 `BFMatcher` + `NORM_HAMMING`。
1325
+ 4. 特征匹配前建议转灰度;大图可先 `pyrDown` / `resize`。
1326
+ 5. `findHomography` 至少需要 4 对对应点,不足会返回 `null`。
1327
+ 6. 绘制类、`floodFill` 会改原图或指定目标句柄;需保留原图请先 `clone`。
1328
+ 7. 与 `image` 互通时,两边句柄各自 `release`。