ms-vite-plugin 1.4.39 → 1.4.40

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