跳转到内容

$yolo 模型调用

更新: 2026/3/2 字数: 0 字 时长: 0 分钟

$yolo 模块提供了基于 YOLO 模型的实时目标检测、实例分割、姿态估计、分类和旋转框检测等功能。支持 ONNXNCNN 两种模型格式,适用于多种计算机视觉任务。

$yolo.load(options[, callback])

  • options {Object} 模型配置选项
    • binPath {string} NCNN 模型的权重文件路径(.bin 文件)
    • paramPath {string} NCNN 模型的结构文件路径(.param 文件)
    • onnxPath {string} ONNX 模型文件路径(.onnx 文件)
    • classesPath {string} 类别标签文件路径(.txt、.yaml 文件)
    • classes {Array<string>} 直接传入类别数组
    • version {number} YOLO 输入输出层解析版本:
      • 通常情况下,对应模型的 YOLO 实际版本号,例如:0, 5, 6, 7, 8, 9, 10, 11, 12, 13, 26(0 代表 YOLOX)
      • 由于不同 YOLO 版本在不同导出方式下导出为 NCNN 模型时,输入层和输出层的名称、数量及结构可能不同,因此 version 实际取决于模型的输入输出层结构,不完全等同于训练版本。
      • 请参考 NCNN 输入输出层名称 选择与你模型匹配的版本。
      • 如果选择错误,可能导致推理闪退始终无法检测到目标,具体可参阅常见问题
    • task {number} 任务类型:0-检测, 1-分割, 2-分类, 3-姿态, 4-旋转框, 5-跟踪
    • device {number} 推理设备:0-CPU, 1-GPU, 2-Turnip
  • callback {Object} 加载回调(可选)
    • onload(yolo, success) 模型加载完成时回调
  • 返回 Yolo 对象,失败返回 null

加载 YOLO 模型并创建推理实例。

模型格式说明
  • 使用 NCNN 模型时:必须同时传入 binPath + paramPath
  • 使用 ONNX 模型时:只需要传入 onnxPath
  • 两种格式不能同时传入
类别配置方式(二选一)
  • 使用 classesPath 从文件加载类别
  • 或使用 classes 直接传入数组

注意事项

  • 必须在不需要时调用 Yolo.release() 释放模型资源,否则会导致内存泄漏
js
let yolo = $yolo.load({
    binPath: "/sdcard/yolo/yolo26n.ncnn.bin",
    paramPath: "/sdcard/yolo/yolo26n.ncnn.param",

    // 类别来源(二选一)
    classesPath: "/sdcard/yolo/det.txt",
    // classes: ["人","自行车","汽车","摩托车","飞机","公交车","火车","卡车","船","红绿灯","消防栓","停止标志","停车计时器","长椅","鸟","猫","狗","马","羊","牛","大象","熊","斑马","长颈鹿","背包","雨伞","手提包","领带","行李箱","飞盘","滑雪板","单板滑雪板","球","风筝","棒球棒","棒球手套","滑板","冲浪板","网球拍","瓶子","酒杯","杯子","叉子","刀","勺子","碗","香蕉","苹果","三明治","橙子","西兰花","胡萝卜","热狗","披萨","甜甜圈","蛋糕","椅子","沙发","盆栽","床","餐桌","马桶","电视","笔记本电脑","鼠标","遥控器","键盘","手机","微波炉","烤箱","烤面包机","水槽","冰箱","书","时钟","花瓶","剪刀","泰迪熊","吹风机","牙刷"],

    version: 26, //YOLO26版本
    task: 0,   // 目标检测
    device: 0  // CPU
}, {
    onload: function(yolo, success) {
        console.log(success ? "NCNN 模型加载成功" : "NCNN 模型加载失败");
    }
});

if (!yolo) {
    console.error("加载失败");
    exit();
}
// 当脚本正常或者异常退出时会触发该事件
events.on("exit", function () {
  try {
    if (yolo) {
      yolo.release();
    }
  } catch (error) {
    console.error(error);
  }
});


//在不需要时记得释放模型资源
yolo.release();
js
let yolo = $yolo.load({
    onnxPath: "/sdcard/yolo/yolo11n.onnx",
    classesPath: "/sdcard/yolo/det.txt",
    version: 11,
    task: 0,   // 目标检测
    device: 0  // CPU
}, {
    onload: function(yolo, success) {
        console.log(success ? "ONNX 模型加载成功" : "ONNX 模型加载失败");
    }
});

if (!yolo) {
    console.error("加载失败");
    exit();
}
// 当脚本正常或者异常退出时会触发该事件
events.on("exit", function () {
  try {
    if (yolo) {
      yolo.release();
    }
  } catch (error) {
    console.error(error);
  }
});


//在不需要时记得释放模型资源
yolo.release();

$yolo.load 支持使用 相对路径 来加载模型文件,相对路径是相对于当前工作目录或应用安装目录的路径。在 Bot.js 项目中,尤其是在打包应用后,使用相对路径可以提高项目的可移植性,确保在不同设备或环境下都能正常工作。

项目目录结构

├─📁 MyProject/               # 项目文件夹
│  ├─📄 main.js               # 主脚本文件
│  ├─📁 assets/               # 存储模型文件的文件夹
│  │  ├─📄 yolo26n.ncnn.bin   # NCNN 格式权重文件
│  │  ├─📄 yolo26n.ncnn.param # NCNN 格式结构文件
│  │  └─📄 det.txt            # 类别标签文件
│  └─📄 project.json          # 项目配置文件

加载相对路径示例

js
// 使用相对路径加载 NCNN 格式的 YOLO 模型
let yolo = $yolo.load({
    binPath: "./assets/yolo26n.ncnn.bin",  // 相对路径
    paramPath: "./assets/yolo26n.ncnn.param", // 相对路径
    classesPath: "./assets/det.txt",  // 相对路径
    version: 26, // YOLO26版本
    task: 0,   // 目标检测
    device: 0  // CPU
}, {
    onload: function(yolo, success) {
        console.log(success ? "NCNN 模型加载成功" : "NCNN 模型加载失败");
    }
});

Yolo

$yolo.load 返回的 YOLO 推理对象。

Yolo.getInfo()

  • 返回 {Object | null} 模型信息对象 获取模型信息,仅仅 ONNX 格式模型可以获取到模型信息,NCNN 格式的模型是获取不到的。
js
var info = yolo.getInfo();
if (info) {
    console.log("模型信息:", JSON.stringify(info));
}

Yolo.setConfig(modelOptions[, drawOptions])

  • modelOptions {Object} 推理选项
    • width {number} 输入图像宽度,默认 640
    • height {number} 输入图像高度,默认 640
    • meanVals {Array<number>} 均值归一化值,默认 [0, 0, 0]
    • normVals {Array<number>} 方差归一化值,默认 [1/255, 1/255, 1/255]
    • conf {number} 置信度阈值,默认 0.25
    • iou {number} IOU 阈值,默认 0.7
    • sort {number} 排序方式,默认 0,具体参阅识别结果排序规则
    • show {boolean} 是否显示结果,默认 false
    • save {boolean} 是否保存结果,默认 false
    • saveTxt {boolean} 是否保存标签文件,默认 false
    • saveConf {boolean} 是否保存置信度,默认 false
    • verbose {boolean} 是否输出详细信息,默认 false
    • path {string} 输出路径,默认 null
    • project {string} 项目名称,默认 null
    • name {string} 结果名称,默认 null
    • vidStride {number} 视频帧采样间隔或帧跳过因子,默认 1,在 YOLO 视频推理中,它控制视频处理时跳过的帧数
  • drawOptions {Object} 绘制选项(可选)
    • lineWidth {number} 边框线宽,默认 0
    • fontSize {number} 字体大小,默认 0
    • kptRadius {number} 关键点半径,默认 5
    • showBoxes {boolean} 是否显示边界框,默认 true
    • showLabels {boolean} 是否显示标签,默认 true
    • showConf {boolean} 是否显示置信度,默认 true
    • showMasks {boolean} 是否显示掩码,默认 true
    • showKptLine {boolean} 是否显示骨骼连线,默认 true
    • classPalette {Array<Array<number>> | Array<number|string>} 类别颜色调色板,可以是二维数组的ARGB、RGB 或是一维数组的颜色值
    • posePalette {Array<Array<number>> | Array<number|string>} 姿态颜色调色板,可以是二维数组的ARGB、RGB 或是一维数组的颜色值
    • skeleton {Array<Array<number>>} 骨架连接关系
    • limbColor {Array<number>} 骨骼颜色索引
    • kptColor {Array<number>} 关键点颜色索引
  • 返回 {boolean} 是否设置成功

设置全局推理和绘制选项参数。

js
yolo.setConfig({
    width: 640,
    height: 640,
    meanVals: [0, 0, 0],
    normVals: [1/255, 1/255, 1/255],
    conf: 0.25,
    iou: 0.7,
    sort: 0,
    show: false,
    save: false,
    saveTxt: false,
    saveConf: false,
    verbose: true
}, {
    lineWidth: 2,
    fontSize: 14,
    kptRadius: 5,
    showBoxes: true,
    showLabels: true,
    showConf: true,
    showMasks: true,
    showKptLine: true,
    classPalette: [
            //[A, R, G, B] 或者[R, G, B] 或者 colors.rgb(4, 42, 255) 或者 colors.argb(255, 4, 42, 255) 或者 #FF042AFF
            [255, 4, 42, 255],
            [255, 11, 219, 235],
            [255, 243, 243, 243],
            [255, 0, 223, 183],
            [255, 17, 31, 104],
            [255, 255, 111, 221],
            [255, 255, 68, 79],
            [255, 204, 237, 0],
            [255, 0, 243, 68],
            [255, 189, 0, 255],
            [255, 0, 180, 255],
            [255, 221, 0, 186],
            [255, 0, 255, 255],
            [255, 38, 192, 0],
            [255, 1, 255, 179],
            [255, 125, 36, 255],
            [255, 123, 0, 104],
            [255, 255, 27, 108],
            [255, 252, 109, 47],
            [255, 162, 255, 11]
            // ... 更多颜色
    ],
    posePalette: [
            //[A, R, G, B] 或者[R, G, B] 或者 colors.rgb(4, 42, 255) 或者 colors.argb(255, 4, 42, 255) 或者 #FF042AFF
            [255, 128, 0],
            [255, 153, 51],
            [255, 178, 102],
            [230, 230, 0],
            [255, 153, 255],
            [153, 204, 255],
            [255, 102, 255],
            [255, 51, 255],
            [102, 178, 255],
            [51, 153, 255],
            [255, 153, 153],
            [255, 102, 102],
            [255, 51, 51],
            [153, 255, 153],
            [102, 255, 102],
            [51, 255, 51],
            [0, 255, 0],
            [0, 0, 255],
            [255, 0, 0],
            [255, 255, 255]
            // ... 更多颜色
    ],
    skeleton: [
      // 下半身连接(注意:这里的索引从1开始,与COCO标准0-16不同)
      [16, 14], // 右踝(16) -> 右膝(14):右小腿
      [14, 12], // 右膝(14) -> 右髋(12):右大腿
      [17, 15], // 左踝(17) -> 左膝(15):左小腿
      [15, 13], // 左膝(15) -> 左髋(13):左大腿
      [12, 13], // 右髋(12) -> 左髋(13):连接髋部,形成腰线

      // 躯干连接
      [6, 12], // 右肩(6) -> 右髋(12):身体右侧线
      [7, 13], // 左肩(7) -> 左髋(13):身体左侧线
      [6, 7], // 右肩(6) -> 左肩(7):连接双肩,形成肩线

      // 右臂连接
      [6, 8], // 右肩(6) -> 右肘(8):右上臂
      [8, 10], // 右肘(8) -> 右腕(10):右前臂
      [10, 11], // 右腕(10) -> 右手(11):右手(如果有手部关键点)

      // 左臂连接
      [7, 9], // 左肩(7) -> 左肘(9):左上臂
      [9, 11], // 左肘(9) -> 左腕(11):左前臂
      // [11, 12] // 注释掉的:可能之前尝试过连接左手到某处

      // 面部连接
      [2, 3], // 右眼(2) -> 鼻子(3)?
      [1, 2], // 左眼(1) -> 右眼(2):连接双眼
      [1, 3], // 左眼(1) -> 鼻子(3)?
      [2, 4], // 右眼(2) -> 右耳(4)
      [3, 5], // 鼻子(3) -> 左耳(5)?
      [4, 6], // 右耳(4) -> 右肩(6):头部到肩膀的斜方肌
      [5, 7], // 左耳(5) -> 左肩(7):头部到肩膀的斜方肌
      // ... 更多骨骼连接
    ],
    limbColor: [9, 9, 9, 9, 7, 7, 7, 0, 0, 0, 0, 0, 16, 16, 16, 16, 16, 16, 16],
    kptColor: [16, 16, 16, 16, 16, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9]
});

Yolo.getConfig()

  • 返回 {object} 配置对象,包含推理选项和绘制选项

获取当前配置参数。

js
var config = yolo.getConfig();
console.log(config);

Yolo.predict(image[, options])

  • image {Image | Bitmap | File | string} 输入源:
    • Image / Bitmap: 图像对象
    • File: 图像文件
    • string: 图像文件路径或URL
  • options {Object} 推理选项,可选,参见推理选项
  • 返回 YOLOResults 对象

执行图像推理预测。

注意事项

  • 除了images.captureScreen()截屏得到的 Image 对象除外(自动回收),其他必须在不需要时调用 YOLOResults.recycleOrig() 回收源图 Image 对象资源,否则会导致内存泄漏
  • 当推理选项中 show: truesave: true 时,方法内部会生成带有绘制结果的Image对象资源
js

// 请求截图
if(!requestScreenCapture()){
    toastLog("请求截图失败");
    exit();
}

// 延迟 5000 毫秒
sleep(5000)
// 截屏 返回Image对象 会自动回收,不要手动回收 否则可能导致captureScreen()返回的是 null
var img = captureScreen(); 
var yoloResults = yolo.predict(img)
console.log(yoloResults);
// 回收当前结果绘制的Image对象资源
yoloResults.recycle();
js
var img = images.read("/sdcard/test.jpg");
var yoloResults = yolo.predict(img, {
    width: 320,
    height: 320,
    conf: 0.3,
    save: true
});
console.log(yoloResults);
// 回收源图的Image对象资源
img.recycle();
// 回收当前结果绘制的Image对象资源
yoloResults.recycle();
js

// var yoloResults = yolo.predict(new java.io.File("/sdcard/test.jpg"), {
//     width: 320,
//     height: 320
// });
var yoloResults = yolo.predict("/sdcard/test.jpg", {
    width: 320,
    height: 320
});
console.log(yoloResults);
// 回收源帧的Image对象资源
yoloResults.recycleOrig();
// 回收当前结果绘制的Image对象资源
yoloResults.recycle();
js
var yoloResults = yolo.predict("https://docs.botjs.org/yolo/bus.jpg");
console.log(yoloResults);
// 回收源帧的Image对象资源
yoloResults.recycleOrig();
// 回收当前结果绘制的Image对象资源
yoloResults.recycle();

Yolo.predict(video[, options])

  • video {File | string} 输入源:
    • File: 视频文件
    • string: 视频文件路径或URL
  • options {Object} 推理选项,可选,参见推理选项
  • 返回 {Array<YOLOResults>}

执行视频推理预测,如果视频文件偏大,源帧和绘制帧没有释放可能导致内存泄漏,推荐使用下面回调函数的 API。

注意事项

  • 必须在不需要时调用 YOLOResults.recycleOrig() 回收源图 Image 对象资源,否则会导致内存泄漏
  • 当推理选项中 show: truesave: true 时,方法内部会生成带有绘制结果的Image对象资源
  • 输入为网络URL时,会先下载视频到临时目录再进行推理
js
var yoloResultsList = yolo.predict("/storage/emulated/0/assets/videos/04滑冰.mp4", {
  width: 320,
  height: 320,
  save: true,
  show: true,
  saveTxt: true,
  saveConf: false,
  verbose: true,
  vidStride: 10,
  project: "runs",
  name: "video_predict",
});

// 检查是否成功获取结果
if (!yoloResultsList) {
  console.error("视频推理失败或返回空结果");
  exit();
}

// 遍历数组并处理每个结果
for (var i = 0; i < yoloResultsList.length; i++) {
  var yoloResults = yoloResultsList[i];
  console.log("第", i + 1, "帧结果:", yoloResults);

  // 可以进一步处理结果
  var summary = yoloResults.summary();
  console.log("检测到", yoloResults.size(), "个目标");
  console.log("摘要信息:", summary);

  console.log(yoloResults.orig());

  console.log(yoloResults.plot());
  // 回收源帧的Image对象资源
  yoloResults.recycleOrig();
  // 回收当前结果绘制的Image对象资源
  yoloResults.recycle();
  console.log("已释放第", i + 1, "帧资源");
}

console.log("视频处理完成,共处理", yoloResultsList.length, "帧");

Yolo.predict(video, options, callback)

  • video {File | string} 输入源:

    • File: 视频文件
    • string: 视频文件路径或URL
  • options {Object} 推理选项,可选,参见推理选项

  • callback {Object} 视频推理回调,可选,用于监听视频解码、逐帧推理进度、保存状态等生命周期事件:

    • onReady(width, height, fps, durationUs, totalFrames)
      视频初始化完成时触发。

      • width {number} 视频宽度
      • height {number} 视频高度
      • fps {number} 帧率
      • durationUs {number} 视频总时长(微秒)
      • totalFrames {number} 总帧数
    • onFrame(results, ptsUs, index)
      每一帧推理完成时触发。

      • results {YOLOResults} 当前帧检测结果
      • ptsUs {number} 当前帧时间戳(微秒)
      • index {number} 当前帧索引
    • onProgress(percent, index, total)
      推理进度回调。

      • percent {float} 当前进度(0~100)
      • index {number} 当前处理帧
      • total {number} 总帧数
    • onComplete(frameCount, costMs)
      全部视频帧推理完成时触发。

      • frameCount {number} 实际处理帧数
      • costMs {number} 总耗时(毫秒)
    • onError(error)
      推理或解码过程中发生错误时触发。

      • error {Error} 错误信息
    • onSaveStart()
      开始保存推理结果(视频或图片)时触发。

    • onSaveProgress(percent, index, total)
      保存进度回调。

      • percent {float} 保存进度(0~100)
      • index {number} 当前保存帧
      • total {number} 总保存项帧
    • onSaveCompleted()
      所有结果保存完成时触发。

  • 返回 VideoPredict 对象

执行视频推理预测。

注意事项

  • 必须在不需要时调用 YOLOResults.recycleOrig() 回收源图 Image 对象资源,否则会导致内存泄漏
  • 当推理选项中 show: truesave: true 时,方法内部会生成带有绘制结果的Image对象资源
  • 输入为网络URL时,会先下载视频到临时目录再进行推理
js
var videoPredict = yolo.predict(
  "/storage/emulated/0/assets/videos/04滑冰.mp4",
  {
    width: 640,
    height: 640,
    save: true,
    saveTxt: true,
    saveConf: false,
    verbose: true,
    vidStride: 10,
    project: "runs",
    name: "video_predict",
  },
  {
    onReady: function (width, height, fps, durationUs, totalFrames) {
      console.log(
        "视频准备完成: width=" +
          width +
          ", height=" +
          height +
          ", fps=" +
          fps +
          ", durationUs=" +
          durationUs +
          ", totalFrames=" +
          totalFrames
      );
    },
    onFrame: function (yoloResults, ptsUs, index) {
      console.log("处理帧 index=" + index + ", ptsUs=" + ptsUs);
      console.log("推理结果: " + yoloResults);

      // 回收源帧 Image 对象资源,防止内存泄漏
      yoloResults.recycleOrig();

      // 回收当前结果绘制的 Image 对象资源
      yoloResults.recycle();
    },
    onProgress: function (percent, index, total) {
      console.log("推理进度: " + percent + "%, index=" + index + ", totalFrames=" + total);
    },
    onComplete: function (frameCount, costMs) {
      console.log("推理完成: 总帧数=" + frameCount + ", 总耗时=" + costMs + "ms");
    },
    onError: function (error) {
      console.error("推理错误: " + error);
    },
    onSaveStart: function () {
      console.log("视频保存开始");
    },
    onSaveProgress: function (percent, index, total) {
      console.log("保存进度: " + percent + "%, index=" + index + ", total=" + total);
    },
    onSaveCompleted: function () {
      console.log("视频保存完成");
    },
  }
);

// -------------------- 执行视频推理 --------------------

// 开始解码视频并对每帧进行推理
console.log("开始解码并推理视频");
videoPredict.start();

// 等待解码与推理完成
console.log("等待解码/推理完成...");
while (videoPredict.isProcessing()) {
  sleep(100); // 轮询等待,每 100ms 检查一次
}

// 等待视频帧合并完成(保存视频)
console.log("等待帧合并完成...");
while (videoPredict.isMerging()) {
  sleep(100);
}

// 停止解码器
console.log("停止视频解码器");
videoPredict.stop();

// 释放解码器资源,防止内存泄漏
console.log("释放解码器资源");
videoPredict.release();

console.log("视频处理全流程完成");

Yolo.predict(facing[, options])

  • facing {number} 相机方向:
    • 0: 后置摄像头
    • 1: 前置摄像头
  • options {Object} 推理选项,可选,参见推理选项
  • 返回 {FloatCamera}类型的对象,提供相机推理控制

打开摄像头推理预测,打开摄像头进行推理预测需要悬浮窗权限,并且必须在应用处于前台时调用,否则无法正常显示摄像头悬浮窗。

js
var floatCamera = yolo.predict(0, {
  width: 320,
  height: 320,
  conf: 0.55,
  iou: 0.5,
  verbose: true,
  saveTxt: false,
});

// 启动相机预览
floatCamera.show();

// 当脚本退出时,确保关闭悬浮摄像头
events.on("exit", function () {
  try {
    if (floatCamera) {
      floatCamera.close(); // 安全关闭悬浮摄像头
    }
  } catch (error) {
    console.error(error); // 捕获异常并输出
  }
});

// 等待 20 秒后手动关闭悬浮摄像头(可选)
sleep(20000);
floatCamera.close();
//在不需要时记得释放模型资源
yolo.release();

Yolo.predictCamera(facing, options, callback)

  • facing {number} 相机方向:

    • 0:后置摄像头
    • 1:前置摄像头
  • options {Object} 推理选项,可选,参见 推理选项

  • callback {Object} 相机推理回调:

    • needImage() {Function} 返回是否需要生成 Image 对象(源帧和绘制后的帧)。

      • 默认返回 false,此时不会生成 Image,对性能和内存更友好。
      • 如果设置为 true,推理过程中会生成源帧和绘制后的 Image 对象。使用完必须在 onResults 回调里YOLOResults.recycleOrig()YOLOResults.recycle() 回收,否则会造成内存泄漏。
    • onResults(yoloResults) {Function} 推理结果回调函数,可在这里处理检测到的目标。

      • yoloResults {YOLOResults} 推理结果
      • 如果 needImage = true,可在此回调中访问和处理源帧或绘制后的帧,并调用回收方法释放资源。
  • 返回 {FloatCamera} 类型对象,提供悬浮摄像头控制功能。

打开摄像头推理预测,打开摄像头进行推理预测需要悬浮窗权限,并且必须在应用处于前台时调用,否则无法正常显示摄像头悬浮窗。

用途说明

如果你需要保存源帧或绘制后的结果,或在推理过程中对图像做额外处理,可以使用带回调函数的 predictCamera

但是一定要记住:生成的 Image 对象必须在 onResults 中回收,否则会造成内存泄漏。

如果不需要处理 Image 或保存帧,可以设置 needImage = false,这样性能更高,内存压力更低。

js
var floatCamera = yolo.predictCamera(
  1,
  {
    width: 320,
    height: 320,
    verbose: true,
    saveTxt: false,
  },
  {
    needImage: function () {
      return false; // 是否需要返回 Image 对象;false 表示不生成 Image,可提高推理效率并减少内存占用
    },
    onResults: function (yoloResults) {
      console.log("检测到目标数:", yoloResults.size());

      // 如果 needImage 设置为 true:
      // 推理过程中会生成原始帧 Image 对象和绘制后的 Image 对象。
      // 使用完毕后必须手动释放或回收,否则可能导致内存泄漏。
      // var origImg = yoloResults.orig();
      // var plotImg = yoloResults.plot();
      // if (plotImg) {
      //   plotImg.saveTo("/sdcard/plotImg.png");
      // }
      // yoloResults.recycleOrig();
      // yoloResults.recycle();
    },
  }
);

// 启动相机预览
floatCamera.setTitle("我的相机推理"); //设置标题
floatCamera.show(); //显示悬浮
floatCamera.setPosition(100, 100);
floatCamera.setSize(device.width * 0.8, device.height * 0.8);

// 当脚本退出时,确保关闭悬浮摄像头
events.on("exit", function () {
  try {
    if (floatCamera) {
      floatCamera.close(); // 安全关闭悬浮摄像头
    }
  } catch (error) {
    console.error(error); // 捕获异常并输出
  }
  try {
    if (yolo) {
      // 脚本退出时执行模型资源清理
      // 用于处理异常退出(报错、中断、强制停止等)场景,避免资源泄漏
      // 注意:业务逻辑中应主动调用 release(),此处仅作为兜底机制
      yolo.release();
    }
  } catch (error) {
    console.error(error);
  }
});

// 等待 20 秒后手动关闭悬浮摄像头(可选)
sleep(20000);
floatCamera.close();
//在不需要时记得释放模型资源
yolo.release();

Yolo.release()

释放模型及其相关资源。

在模型不再使用时,应主动调用 Yolo.release() 以释放内存资源,避免长期占用内存或导致性能下降。

js
yolo.release();

示例:脚本退出时的兜底释放

在实际开发中,建议在脚本退出时增加资源清理逻辑,用于处理异常退出(报错、中断、强制停止等)场景,防止模型资源未正确释放。

⚠ 注意:此方式属于兜底机制,正常业务流程中仍应主动调用 Yolo.release()

js
events.on("exit", function () {
  try {
    if (yolo) {
      // 脚本退出时执行模型资源清理
      // 处理异常退出场景,避免资源泄漏
      // 正常使用中应主动调用 release()
      yolo.release();
    }
  } catch (error) {
    console.error(error);
  }
});

VideoPredict

VideoPredict 表示一个视频解码与推理控制对象,用于解码视频文件、执行逐帧推理以及控制保存和资源释放。

通过 Yolo.predict(video, options, callback) 获取实例。


VideoPredict.start()

开始解码视频并执行推理。

如果未调用,视频不会开始解码和推理。


VideoPredict.isProcessing()

  • 返回 {boolean}
    • true:正在解码或推理中
    • false:未开始或已完成

返回是否正在解码并推理视频帧。


VideoPredict.isMerging()

  • 返回 {boolean}
    • true:正在合并视频
    • false:未合并或已完成

返回是否正在合并视频帧(生成保存的视频文件)。

仅在 options.save = true 时有效。


VideoPredict.stop()

停止视频解码和推理。

停止后当前处理将终止,不会继续推理剩余帧。


VideoPredict.release()

释放解码器相关资源。

释放后对象不可再次使用,否则可能抛出异常。

建议在视频处理完成后调用,以防止内存泄漏。

FloatCamera

FloatCamera 表示一个悬浮摄像头窗口对象,用于显示摄像头预览并执行实时推理。

通过 Yolo.predictCamera(facing, options, callback) 获取实例。


FloatCamera.show()

显示悬浮摄像头窗口。如果未调用,摄像头不会显示。


FloatCamera.setTitle(title)

  • title {string} 标题文本

设置悬浮窗标题。


FloatCamera.setPosition(x, y)

  • x {number} X 坐标(像素)
  • y {number} Y 坐标(像素)

设置悬浮窗位置。基于屏幕左上角坐标。


FloatCamera.setSize(width, height)

  • width {number} 宽度(像素)
  • height {number} 高度(像素)

设置悬浮窗大小。


FloatCamera.close()

关闭悬浮摄像头并释放相关资源。关闭后不可再次使用。

推理选项

options {object} 包括:

  • width {number} 输入图像宽度,默认 640
  • height {number} 输入图像高度,默认 640
  • meanVals {Array<number>} 均值归一化值,默认 [0, 0, 0]
  • normVals {Array<number>} 方差归一化值,默认 [1/255, 1/255, 1/255]
  • conf {number} 置信度阈值,默认 0.25
  • iou {number} IOU 阈值,默认 0.7
  • sort {number} 排序方式,默认 0,具体参阅识别结果排序规则
  • show {boolean} 是否显示结果,默认 false
  • save {boolean} 是否保存结果,默认 false
  • saveTxt {boolean} 是否保存标签文件,默认 false
  • saveConf {boolean} 是否保存置信度,默认 false
  • verbose {boolean} 是否输出详细信息,默认 false
  • path {string} 输出路径,默认 null
  • project {string} 项目名称,默认 null
  • name {string} 结果名称,默认 null
  • vidStride {number} 视频帧采样间隔或帧跳过因子,默认 1,在 YOLO 视频推理中,它控制视频处理时跳过的帧数

绘制选项

用于控制识别结果的绘制样式。

所有参数的默认值均可通过 Yolo.getConfig() 获取。

options {object} 包括:

  • lineWidth {number} 边框线宽,默认 0
  • fontSize {number} 字体大小,默认 0
  • kptRadius {number} 关键点半径,默认 5
  • showBoxes {boolean} 是否显示边界框,默认 true
  • showLabels {boolean} 是否显示标签,默认 true
  • showConf {boolean} 是否显示置信度,默认 true
  • showMasks {boolean} 是否显示掩码,默认 true
  • showKptLine {boolean} 是否显示骨骼连线,默认 true
  • classPalette {Array<Array<number>> | Array<number|string>} 类别颜色调色板,可以是二维数组的ARGB、RGB 或是一维数组的颜色值
  • posePalette {Array<Array<number>> | Array<number|string>} 姿态颜色调色板,可以是二维数组的ARGB、RGB 或是一维数组的颜色值
  • skeleton {Array<Array<number>>} 骨架连接关系
  • limbColor {Array<number>} 骨骼颜色索引
  • kptColor {Array<number>} 关键点颜色索引

识别结果排序规则

用于控制识别结果集合的返回顺序。

默认值为 0,表示不进行排序。

排序方式描述
0(默认)不排序不对识别结果进行排序,保持模型原始输出顺序(性能最佳)
1从左到右按边界框左上角 X 坐标升序排列(X 越小,越靠左)
2从右到左按边界框左上角 X 坐标降序排列(X 越大,越靠右)
3从上到下按边界框左上角 Y 坐标升序排列(Y 越小,越靠上)
4从下到上按边界框左上角 Y 坐标降序排列(Y 越大,越靠下)
5从左上到右下按 (X + Y) 升序排列(越靠左上越优先)
6从右上到左下按 (X − Y) 降序排列(越靠右上越优先)
7从左下到右上按 (X − Y) 升序排列(越靠左下越优先)
8从右下到左上按 (X + Y) 降序排列(越靠右下越优先)
9从左到右,从上到下优先按 X 升序,X 相同时按 Y 升序
10从右到左,从上到下优先按 X 降序,X 相同时按 Y 升序
11从左到右,从下到上优先按 X 升序,X 相同时按 Y 降序
12从右到左,从下到上优先按 X 降序,X 相同时按 Y 降序
13按面积从大到小按边界框面积降序排列(面积越大越优先)
14按面积从小到大按边界框面积升序排列(面积越小越优先)

推理速度

YOLO 版本选择

不同版本的 YOLO 模型在性能和精度上表现差异很大:

  • 性能不一定随版本升高而变快,高版本模型通常更复杂,推理耗时可能更长;低版本模型可能精度不够。

  • 推荐做法

    1. 在训练阶段尝试多版本模型,例如 YOLO26、YOLO11、YOLOv8,具体参阅批量训练不同版本
    2. 在目标手机上进行 推理测试,评估速度和精度综合表现。
    3. 最终选择 最适合该设备的 YOLO 版本

💡 注意:手机 CPU/GPU 性能差异会显著影响模型推理速度,选择模型时务必考虑目标设备。


模型导出与轻量化优化

  • 从 PyTorch 导出 ONNX 或 NCNN 时,可以进行 轻量化优化

    • 剔除冗余节点
    • 量化(FP16、INT8)
    • 使用 NCNN 优化工具链进行优化
  • 优化效果

    • 推理速度可提升 10% ~ 30%,具体提升与模型结构、输入尺寸和设备性能有关。
    • 优化后的模型在精度上通常影响较小,但仍需验证。

详细说明参阅:模型优化


模型输入尺寸优化

  • 输入宽高必须是 32 的倍数

  • 尺寸影响推理速度与精度

    • 尺寸越大 → 精度越高,但推理速度下降
    • 尺寸越小 → 推理速度加快,但精度下降
  • 选择输入尺寸的原则

    1. 根据推理图片区域比例选择输入尺寸:

      • 如果仅需要手机屏幕的一部分区域 → 按区域比例训练模型
      • 如果需要全屏推理 → 按屏幕纵横比例选择最佳输入尺寸
    2. 常见屏幕比例参考:

      • 竖屏:高宽比约 2:1 → 推荐输入尺寸 640×320
      • 横屏:高宽比约 1:2 → 推荐输入尺寸 320×640
    3. 输入尺寸参考训练参数 imgsz

💡 建议在训练阶段结合目标屏幕比例进行尺寸选择,以兼顾精度和速度。


后台推理限制

Android 系统会对后台应用进行 CPU/GPU 限频、线程调度降级和 Doze 节流,导致后台推理速度比前台慢 5~10 倍。

  • 优化方法

    1. 将 App 添加到 电池优化白名单,或请求用户忽略电池优化。

    2. 开启 前台服务

  • 效果:可让后台推理速度接近前台,同时保证线程安全和资源稳定。

常见问题

⚠ 重要说明 绝大多数模型加载或推理异常,通常由以下原因导致:

  1. 模型导出方式或工具使用不正确
  2. 导出工具版本不同,导致生成的输入输出层名称或结构发生变化
  3. 加载时指定的 version 与实际模型结构不匹配

需要特别注意:

  • 即使是同一 YOLO 版本
  • 即使使用的是同一个导出工具

只要导出方式或导出工具版本不同,生成的 NCNN 模型输入输出层结构都可能发生变化。

因此:YOLO 版本 ≠ 模型实际输出层结构

加载模型时,必须根据实际输入输出层名称选择匹配的 version,否则可能出现:

  • 加载闪退
  • 加载报错
  • 推理闪退
  • 无检测结果
  • 检测框异常或数量异常

建议使用 Netron 查看模型结构,并结合「输入输出层对照表」确认后再选择合适的 version

例如:YOLOv8 使用新版 pnnx 导出后,通常需使用 version: 13version: 26 进行加载。


加载模型问题

1️⃣ 加载模型闪退

请优先检查:

  • 输入输出层名称是否匹配
  • version 是否正确

参考:
👉 输入输出层对照表

若以上排查后仍无法解决,可能是模型导出方式或导出工具版本选择不当,建议重新确认导出流程。


2️⃣ 加载模型报错

请根据报错检查 $yolo.load 的各项参数是否正确,比如模型文件路径(bin、param 或 onnx)、类别文件或数组、任务类型、版本号和推理设备,并确保相关文件存在且完整。


3️⃣ 系统架构不支持

主流手机架构均支持:

  • arm64-v8a
  • armeabi-v7a
  • x86_64
  • x86

部分虚拟机或非主流设备可能存在 ABI 不匹配问题。
若设备架构与 SDK 编译架构不一致,可能导致模型加载失败或程序崩溃。


推理问题

1️⃣ 推理闪退

常见原因

模型导出方式或导出工具版本不同,可能导致输入输出层名称或结构发生变化。

当加载时指定的 version 与模型实际结构不匹配时, 可能在推理阶段直接崩溃。

排查步骤

  1. 使用 Netron 查看模型输入输出层名称
  2. 对照「输入输出层对照表」确认结构
  3. 重新选择匹配的 version 进行加载

若以上排查后仍无法解决,可能是模型导出方式或导出工具版本选择不当,建议重新确认导出流程。


2️⃣ 多实例 GPU 推理闪退

原因: 部分设备 GPU 不支持同时加载多个模型实例进行推理。 在多实例场景下可能导致程序崩溃。

解决方案: 如需同时加载多个模型,请使用 CPU 推理模式:

js
$yolo.load({
    modelPath: "...",
    paramPath: "...",
    classPath: "...",
    task: 0, 
    version: 13,
    device: 0,  // 用CPU,别用GPU
});

3️⃣ 始终没有推理结果

请优先确认:

  • version 是否与模型实际结构匹配
  • 输入输出层名称是否正确
  • 导出方式是否正确

这是最常见原因。

参考: 👉 输入输出层对照表


4️⃣ 推理结果异常或检测框过多

可能原因包括:

  • 输入尺寸设置不正确(参考 输入尺寸说明
  • 置信度阈值设置过高或过低
  • 加载时指定的 version 与模型实际结构不匹配
  • 模型导出方式或导出工具版本不同,导致输出层结构发生变化

建议:

  1. 使用 Netron 查看模型输入输出层名称
  2. 对照「输入输出层对照表」确认结构
  3. 重新选择匹配的 version 进行加载

推理一段时间后闪退

在推理过程中,未及时释放占用内存的对象可能导致程序崩溃,主要包括以下两类:

图片对象未释放

在推理过程中,会生成大量 Image 对象:

  • 原图:屏幕截图、本地图片、网络图片、视频
  • 绘制图:推理过程中生成的标注或绘制结果

回收方法:

YOLO 模型对象未释放

当不再使用 YOLO 对象时,如果未及时释放模型和相关资源,也可能导致内存泄漏和性能下降。

建议:

  • 在长时间或循环推理中,务必对所有生成的图片对象及时回收。
  • 使用完 YOLO 对象后,及时调用 Yolo.release() 释放资源,避免内存泄漏和性能下降。

示例

屏幕实时目标检测

js
// 加载模型
var yolo = $yolo.load({
  binPath: "/sdcard/yolov8n.ncnn.bin",
  paramPath: "/sdcard/yolov8n.ncnn.param",
  classes: ["人","自行车","汽车","摩托车","飞机","公交车","火车","卡车","船","红绿灯","消防栓","停止标志","停车计时器","长椅","鸟","猫","狗","马","羊","牛","大象","熊","斑马","长颈鹿","背包","雨伞","手提包","领带","行李箱","飞盘","滑雪板","单板滑雪板","球","风筝","棒球棒","棒球手套","滑板","冲浪板","网球拍","瓶子","酒杯","杯子","叉子","刀","勺子","碗","香蕉","苹果","三明治","橙子","西兰花","胡萝卜","热狗","披萨","甜甜圈","蛋糕","椅子","沙发","盆栽","床","餐桌","马桶","电视","笔记本电脑","鼠标","遥控器","键盘","手机","微波炉","烤箱","烤面包机","水槽","冰箱","书","时钟","花瓶","剪刀","泰迪熊","吹风机","牙刷"],
  version: 8,
  task: 0,
  device: 0,
});

if (!yolo) {
  console.error("模型加载失败");
  exit();
}

// 当脚本正常或者异常退出时会触发该事件
events.on("exit", function () {
  try {
    if (yolo) {
      yolo.release();
    }
  } catch (error) {
    console.error(error);
  }
});

// 配置参数
yolo.setConfig({
  width: 320,
  height: 320,
  conf: 0.25,
  iou: 0.7,
  verbose: true,
});

// 请求屏幕截图权限(首次使用需要授权)
requestScreenCapture();

// 循环执行 100 次检测
for (var i = 0; i < 100; i++) {
  console.log("第", i + 1, "次检测");

  // 获取当前屏幕图像
  var img = captureScreen();

  // 推理
  var yoloResults = yolo.predict(img);

  // 输出结果
  console.log("检测到", yoloResults.size(), "个目标");

  // 获取摘要
  var summary = yoloResults.summary();
  for (var i = 0; i < summary.length; i++) {
    var obj = summary[i];
    console.log(
      obj.name +
        " 置信度:" +
        obj.confidence +
        " 左上:(" +
        obj.box.x1 +
        "," +
        obj.box.y1 +
        ")" +
        " 右下:(" +
        obj.box.x2 +
        "," +
        obj.box.y2 +
        ")"
    );
  }

  // 绘制检测框到屏幕
  $draw.yolo(yoloResults);
  sleep(1000);
  // 清除绘制内容
  $draw.clearSync();
  // 释放本次推理结果对象(重要)
  yoloResults.recycle();
}

// 主动释放模型资源
yolo.release();

相机检测物体

本功能可在检测到目标后,将图片保存到本地,也可根据需要发送邮件通知。
例如,可用于检测异常事件或安全相关场景,如火焰、烟雾、人员进入指定区域等。

⚠ 注意事项:

  1. 前台运行

    • 需在前台运行,否则悬浮相机弹窗可能无法正常显示。
  2. 权限要求

    • 必须授予相机权限,否则无法打开相机。
    • 如果保存到本地,还需授予存储权限。
js
var yolo = $yolo.load(
  {
    binPath: "/sdcard/yolo11s.ncnn.bin",
    paramPath: "/sdcard/yolo11s.ncnn.param",
    task: 0,
    classes:  ["人","自行车","汽车","摩托车","飞机","公交车","火车","卡车","船","红绿灯","消防栓","停止标志","停车计时器","长椅","鸟","猫","狗","马","羊","牛","大象","熊","斑马","长颈鹿","背包","雨伞","手提包","领带","行李箱","飞盘","滑雪板","单板滑雪板","球","风筝","棒球棒","棒球手套","滑板","冲浪板","网球拍","瓶子","酒杯","杯子","叉子","刀","勺子","碗","香蕉","苹果","三明治","橙子","西兰花","胡萝卜","热狗","披萨","甜甜圈","蛋糕","椅子","沙发","盆栽","床","餐桌","马桶","电视","笔记本电脑","鼠标","遥控器","键盘","手机","微波炉","烤箱","烤面包机","水槽","冰箱","书","时钟","花瓶","剪刀","泰迪熊","吹风机","牙刷"],
    version: 11,
    device: 0,
  },
  {
    onload: function (yolo, success) {
      console.log(success ? "NCNN 模型加载成功" : "NCNN 模型加载失败");
    },
  }
);

if (!yolo) {
  console.error("加载失败");
  exit();
}

var floatCamera = yolo.predictCamera(
  0,
  {
    conf: 0.25,
    iou: 0.7,
    width: 320,
    height: 320,
    // verbose: true,
    saveTxt: false,
    saveConf: false,
  },
  {
    needImage: function () {
      return true;
    },
    onResults: function (yoloResults) {
      if (yoloResults.size() > 0) {
        // console.log("检测到目标数:", yoloResults.size());
        var yoloObjects = yoloResults.getYoloObjects();
        var hasPerson = yoloObjects.some(function (obj) {
          return obj.cls === 0; //判断是否检测到“人”(cls = 0)
        });

        if (hasPerson) {
          console.log("检测到人员,保存图片");

          var plotImg = yoloResults.plot();
          if (plotImg) {
            // 确保目录存在
            var saveDir = "/sdcard/cloud/yolo/";
            files.ensureDir(saveDir);

            var filePath = saveDir + generateFileName();
            plotImg.saveTo(filePath);
          }
          // var origImg = yoloResults.orig();
          // if (origImg) {
          //   var filePath = "/sdcard/cloud/yolo/orig" + generateFileName();
          //   origImg.saveTo(filePath);
          // }
        }
      }
      // 回收原图
      yoloResults.recycleOrig();
      // 回收绘制图
      yoloResults.recycle();
    },
  }
);

floatCamera.setTitle("我的相机推理");
floatCamera.show(); //显示悬浮

// 当脚本退出时,确保关闭悬浮摄像头和释放模型资源
events.on("exit", function () {
  try {
    if (floatCamera) {
      floatCamera.close(); // 安全关闭悬浮摄像头
    }
  } catch (error) {
    console.error(error); // 捕获异常并输出
  }
  try {
    if (yolo) {
      yolo.release();
    }
  } catch (error) {
    console.error(error);
  }
});

/**
 * 生成带日期时间和毫秒的图片文件名
 *
 * 格式示例: IMG_20260228_154523_037.png
 * 符合手机相册常用命名规范,便于排序和识别拍摄时间
 *
 * @returns {string} 带日期时间和毫秒的图片文件名
 */
function generateFileName() {
  var now = new Date();

  var yyyy = now.getFullYear();
  var MM = pad(now.getMonth() + 1);
  var dd = pad(now.getDate());

  var HH = pad(now.getHours());
  var mm = pad(now.getMinutes());
  var ss = pad(now.getSeconds());
  var SSS = padMs(now.getMilliseconds());

  return "IMG_" + yyyy + MM + dd + "_" + HH + mm + ss + "_" + SSS + ".png";
}

// 内部工具函数:补零到两位数
function pad(num) {
  return num < 10 ? "0" + num : num;
}

// 内部工具函数:补零到三位数
function padMs(num) {
  if (num < 10) return "00" + num;
  if (num < 100) return "0" + num;
  return num;
}
// 放在脚本结尾,保持脚本运行
setInterval(() => {}, 1000);