Install
openclaw skills install @junci003/mindar-webar-image-tracking用 MindAR.js + A-Frame 做手机 WebAR 图像识别(扫卡牌/图片/包装出 3D 内容),包括:在纯 Node 下编译 .mind 而绕开 node-canvas、用特征点数在编译期判定目标图质量、用合成假摄像头在无头浏览器里做端到端自动验证。当用户提到 MindAR、WebAR、手机扫一扫出模型、H5 AR、图像识别跟踪、.mind 编译失败、node-canvas 装不上、无头浏览器测 AR、或 tfjs WebGL kernel 报错时使用。
openclaw skills install @junci003/mindar-webar-image-trackingMindAR.js 是手机 WebAR 里成本最低的路线(纯前端、无需 App、无需头显)。
但它的官方工具链有三个硬伤:编译依赖 node-canvas、没有目标图质量反馈、
只能在真机上手测。本 skill 记录绕开这三点的实测做法。
mind-ar 的 OfflineCompiler 只在 createProcessCanvas() 里需要 node-canvas,
其余整条链都是纯 JS。所以在 CI / 无 GUI 环境里,可以继承 CompilerBase
并塞一个假 canvas 进去,完全跳过原生依赖。
npm i canvas 在 Windows 上经常编译失败,这个方法能省掉整个原生工具链。
// compile-mind.mjs
import { CompilerBase } from './pkg/package/src/image-target/compiler-base.js'
import { buildTrackingImageList } from './pkg/package/src/image-target/image-list.js'
import { extractTrackingFeatures } from './pkg/package/src/image-target/tracker/extract-utils.js'
// ⚠️ 必须放在最后 import:tfjs kernel 是「后注册者覆盖先注册者」
import './pkg/package/src/image-target/detector/kernels/cpu/index.js'
class PredecodedCanvas {
constructor(image) { this._image = image } // image 已是 ImageData 形态
getContext() {
return {
drawImage() {},
getImageData() { return { data: this._image.data } },
}
}
}
关键点:
detector.js 会 import WebGL 版 kernel;
CPU 版必须在它之后 import 才会生效。顺序反了,在无 GPU 的 Node 里
会在 Detector 构造时崩,报错完全指不到根因。.mind 文件是 msgpack,CURRENT_VERSION = 2。校验时用
CompilerBase.importData() 走运行时读取路径,不要用浏览器版 Compiler
(它依赖 vite 的 ?worker&inline,在 Node 里会报
does not provide an export named 'default')。node compile-mind.mjs \
--image targets/guanyu.png --image targets/zhangfei.png \
--out site/targets.mind
MindAR 官方不给任何质量反馈——图不好就只是「扫不出来」,你得反复试。 但编译过程内部已经算出了特征点数(FREAK),把它打出来就是质量指标。
实测阈值:
| 可用特征点数 | 判定 | 建议 |
|---|---|---|
< 100 | 不可用 | 换图,别浪费时间 |
100 ~ 300 | 偏弱 | 能扫,但对角度/距离/光照很敏感 |
≥ 300 | 可用 | 正常使用 |
实测参照值(同一套编译参数下):
由此得出的设计结论:给识别目标图加「纹理层」是最划算的优化。 大面积单调色块(渐变、纯色底、大留白)几乎不贡献特征点; 密集的小尺度纹样(云纹、水波、文字排印)贡献极大。 做武将卡/产品卡时,把背景铺满细密纹样,不要留大白边。
<a-scene mindar-image="imageTargetSrc: ./targets.mind;
maxTrack: 4; /* 见下 */
missTolerance: 12; /* 见下 */
warmupTolerance: 8;
autoStart: false;
uiLoading: no; uiError: no; uiScanning: no;">
maxTrack 默认是 1 —— 多卡玩法必须调高MindAR 默认同时只跟踪 1 张图。做「多张卡牌同时出现」的玩法 (卡牌对战、多人互动)如果不改这个参数,第二张卡永远不会被识别, 而且不会报任何错,只是「扫不出来」。
调成 4 即为最多同时 4 张。注意这是计算成本,每张图都要跑一次
FREAK 匹配,手机上 4 张已是可观开销,别盲目调大。
missTolerance 默认 5 —— 实体卡牌手持场景太紧单位是帧。默认 5 帧在「手机拿在手里扫桌面的卡」这种场景下会导致 位姿频繁丢失重来(手抖 + 阴影扫过)。实测调到 12 明显稳定。
warmupTolerance 适当调大(如 8)能让刚进入画面时更宽容。autoStart: false + 手动 .start() 是必要的:必须先等用户手势,
否则 iOS 上 getUserMedia 会被拒。ui*: no 关掉内置 UI,换成自己的引导层,产品体验差很多。搞不清「卡在哪一步」时,按这个顺序打点:
start()
→ getUserMedia() ← 权限/摄像头问题在这里暴露
→ video loadedmetadata
→ _startAR() ← 异步,加载并解析 .mind
→ controller.addImageTargets(.mind)
→ dummyRun
→ emit("arReady") ← 模型就绪,可以开始找了
→ processVideo 循环
→ emit("targetFound") / ("targetLost")
arReady 是「模型解析成功」的分界点。 如果迟迟不触发,
问题在模型文件或 WebGL,不在摄像头。
建议在页面上挂 DOM 可见的状态标记(data-ar-state 等),
而不是只 console.log——这样无头测试和人工都能读。
WebAR 最难的是「改完不知道有没有坏」,只能拿手机手测。 用合成假摄像头可以做到全自动。
--disable-gpuMindAR 的 tfjs kernel 走 WebGL。加 --disable-gpu 会连 SwiftShader
一起关掉,然后在 Detector 构造时崩,报错是极具误导性的:
Cannot read properties of undefined (reading 'get')
这个报错和 GPU 完全看不出关系。正确姿势:
--use-gl=angle --use-angle=swiftshader --enable-unsafe-swiftshader
# 千万不要加 --disable-gpu
同时在页面里加 WebGL 可用性预检,给用户可读的报错, 而不是让上面那句崩在 catch 不到的地方。
--use-file-for-fake-video-capture=/path/to/card_view.y4m
Y4M(YUV4MPEG2)需要手写,格式要点:
YUV4MPEG2 W480 H640 F30:1 Ip A1:1 C420mpeg2
FRAME
<Y 平面 W*H 字节><U 平面 (W/2)*(H/2)><V 平面 (W/2)*(H/2)>
FRAME
...
FRAME\n,第一行是文件头。起本地 http.server,额外加一个 /__state POST 端点,
把页面打点的阶段追加写入 JSONL。测试脚本再去读,
就能断言「有没有走到 arReady」「锁到的是哪张卡」。
这样一条命令就能验证:页面启动 AR → 拿到视频 → 模型解析成功 → 锁定卡牌 → 认出是哪张。
WebAR 必须 https(getUserMedia 的安全上下文要求)。
本地 http://localhost 可以,手机访问必须走公网 https。
静态站点直接部署,注意把 vendor/(A-Frame、MindAR)本地化——
不要依赖 CDN,国内访问不稳且会被墙,页面会白屏。
importData() 走运行时路径校验:目标数、tracking points--use-file-for-fake-video-capture + 状态回报端点,
断言 arReady 与 targetFoundmaxTrack 是否够多卡、missTolerance 是否够宽容