跳转到内容

$yolo 模型调用 ​

更新: 2026/9/29 字数: 0 字 时长: 0 分钟

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

$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: true 或 save: 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: true 或 save: 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: true 或 save: 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: 13 或 version: 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);