Luwu-OS 的视觉、语音、情绪识别都靠 ONNX 模型。本章先给出现有模型清单与“哪个应用用哪个模型”的对照表,再讲通用推理接口与替换步骤,最后附排查表——让你能安全地换上自己的模型并确认它生效。
本章包含:
小节 | 内容 | 难度 |
|---|---|---|
6.1 ONNX 模型列表 | 现有模型清单 + 模型→应用对照表(含 6.1.1) | ★☆☆ |
6.2 通用推理接口 | onnxruntime / | ★★★ |
6.3 替换人脸检测模型 | 换模型四步 + 重启 | ★★☆ |
6.4 替换手势识别模型 | 换模型 + 适配包装类张量签名 | ★★★ |
6.5 替换后常见问题 | 张量不匹配、检不出、帧率下降等排查表 | ★★☆ |
6.6 常用查询命令 | 打印模型输入/输出签名、查看占用 | ★☆☆ |
🧭 阅读路径:只想“换个现成模型”,看 6.3/6.4 即可;要在模型层面做二次开发,先看 6.1.1 对照表和 6.2 推理接口。建议任何替换前先跑一遍 6.6 的命令保存原始签名。
前置条件:
完成第一章,已能 SSH 登录机器狗;
有一个待替换的 ONNX 模型文件(保持输入尺寸/预处理与现有模型接近,最省事);
知道要替换的是哪个应用的哪个模型(见 6.1.1)。
6.1 ONNX 模型列表
/opt/luwu-os/model/ 目录中的模型文件(不同固件版本可能增减,文件名为常见/公开命名,实际以机器狗上该目录为准):
模型文件 | 用途 | 来源 |
|---|---|---|
| 人脸检测 | YuNet (OpenCV) |
| 手掌检测 | MediaPipe |
| 手部 21 关键点 | MediaPipe |
| 人体检测 | MediaPipe |
| 人体姿态估计(33 关键点) | MediaPipe |
| 教育 / 视觉库中的情绪识别( | 自定义训练 |
| 性别 / 年龄估计 | 自定义训练 |
| 通用目标检测(COCO 80 类) | YOLO |
| 语音 / 音频嵌入与频谱特征(命名因版本而异) | 自定义训练 |
唤醒词模型( | 唤醒词检测(英文“Hi Luka” / 中文“小陆同学”等,按系统语言选定) | 自定义训练 |
| 中文字体(UI 渲染,非模型) | 微软雅黑 |
| MediaPipe 模型的 Python 包装类(非模型,含 | 项目自带 |
⚠️ 上表中自定义训练的模型(情绪/性别年龄/语音嵌入等)文件名会随版本变化;
ls /opt/luwu-os/model/才是权威清单。替换逻辑不变,只是文件名要按实际目录写。
6.1.1 模型 → 应用对应关系
各个 ONNX 模型并非集中加载,而是由具体的应用在启动时按需加载。替换模型前,先确认它被哪个应用引用(表中的变量名与路径以实际源码为准,不同版本可能改名):
模型 | 使用它的应用 | 加载位置 |
|---|---|---|
| 人脸跟随 |
|
| 手势命令 |
|
| 人体跟随 |
|
| 姿态识别 | 相关演示应用 |
| 教育 / 视觉库中的情绪识别(并非 AI 对话的表情引擎,后者由服务器返回的 emotion 名驱动) |
|
| 通用目标识别 / 示例程序 | 相关应用 |
语音 / 音频嵌入与频谱模型 | 唤醒词 / 自定义 ASR 相关 |
|
唤醒词模型 | 唤醒词激活(“Hi Luka” / “小陆同学”) |
|
| 特征展示 | 相关演示应用 |
💡 所有 ONNX 模型统一通过
onnxruntime推理;MediaPipe 系列模型(手掌/手部/人体/姿态)额外被mp_*.py包装类封装,内部做输入预处理与输出后处理。包装类通过sys.path.insert(0, model目录)后from mp_palmdet import MPPalmDet导入(见apps/gesture/main.py)。
怎么快速定位某模型被哪个应用引用:不用一个个翻源码,直接在 /opt/luwu-os 下 grep 文件名(限制只搜 .py):
grep -rl "face_detection_yunet" /opt/luwu-os/apps --include="*.py"
grep -rl "palm_detection" /opt/luwu-os/apps --include="*.py"
💡 结果列出含该文件名 / 变量的应用路径,即它的加载位置;多数应用还会定义模型路径变量(如
FACE_MODEL_PATH),再grep那个变量名就能定位到具体赋值的行。
6.2 通用推理接口(onnxruntime 封装)
所有应用加载 ONNX 模型的写法基本一致:先用 onnxruntime.InferenceSession 建会话,再把图像帧预处理成模型输入,最后取输出。以人脸检测为例(真实代码中的 apps/face_follow/main.py 使用 OpenCV 的 FaceDetectorYN):
import cv2
# 加载 YuNet 人脸检测器
face_detector = cv2.FaceDetectorYN.create(
model_path.replace("model/", "/opt/luwu-os/model/"), # 绝对路径
"", # config(YuNet 无配置文件)
(320, 240), # 输入尺寸 (width, height)
scoreThreshold=0.7, # 置信度阈值
nmsThreshold=0.3, # NMS 阈值
topK=5000, # 最多返回人脸数
)
# 每帧推理:先把 BGR 帧转为 RGB(YuNet 需要 RGB)
rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# setInputSize 允许逐帧指定输入尺寸;输入尺寸会自动等比缩放
face_detector.setInputSize((frame.shape[1], frame.shape[0]))
ok, faces = face_detector.detect(rgb)
# faces: Nx15 数组;前 4 列为框,其后 10 列为右眼/左眼/鼻尖/右嘴角/左嘴角 5 个关键点的坐标(每点 2 列),最后 1 列为置信度
而 MediaPipe 软件件走包装类,内部同样用 onnxruntime 推理(见 apps/gesture/main.py):
import sys
sys.path.insert(0, "/opt/luwu-os/model") # 包装类在 model/ 目录下
from mp_palmdet import MPPalmDet
from mp_handpose import MPHandPose
palm = MPPalmDet() # 内部读取 model/palm_detection_mediapipe_2023feb.onnx
hand = MPHandPose() # 内部读取 model/handpose_estimation_mediapipe_2023feb.onnx
# 每帧推理:先预处理(缩放/归一化),再推理,最后后处理(解码关键点/框)
palm_out = palm.detect(frame)
hand_pts = hand.estimate(frame, palm_out) # 输出 21 个关键点
💡 上面
palm.detect(...)/hand.estimate(...)是示意调用,包装类的方法名、参数与签名以实际mp_*.py定义为准;不确定时先cat /opt/luwu-os/model/mp_handpose.py查看真实接口。手工调用 onnxruntime 的一般流程(替换非 OpenCV/非 MediaPipe 包装的自定义模型时参考): ```python import onnxruntime as ort, numpy as np sess = ort.InferenceSession("/opt/luwu-os/model/your_model.onnx") inp = sess.get_inputs()[0] # 取输入签名,名字/形状/类型
图像预处理:resize + 归一化 + CHW → NCHW + 转 float32
x = preprocess(frame) # shape=(1, C, H, W) out = sess.run(None, {inp.name: x})[0] # 取第一个输出 out = postprocess(out) # 按模型输出定义解码 ```
6.3 替换人脸检测模型
放置模型:将新 ONNX 文件放到
/opt/luwu-os/model/目录改路径:修改
apps/face_follow/main.py中的FACE_MODEL_PATH变量,指向新文件(目前指向model/face_detection_yunet_2023mar.onnx)适配参数:除非新模型输出与 YuNet 完全一致,否则要同步调整
cv2.FaceDetectorYN.create()的参数——输入尺寸(320, 240)、scoreThreshold、nmsThreshold、topK都要按新模型重新调。若换了非 YuNet 结构(如另一个检测网络),需改用 6.2 节的 onnxruntime 通用推理方式重启应用:人脸跟随是常驻进程,改完需重启才会重新加载模型
# 重启人脸跟随应用(由 launcher 完整启动)
echo "apps/face_follow/main.py" > /tmp/luwu_run.fifo
⚠️ 验证时可临时改为日志/终端打印检测结果的数量,确认能正常检出人脸,再逐步调阈值。
6.4 替换手势识别模型
替换文件:替换
/opt/luwu-os/model/下的palm_detection_*.onnx和handpose_estimation_*.onnx改路径:更新
apps/gesture/main.py中的PALM_MODEL/HAND_MODEL路径变量适配包装类:
MPPalmDet/MPHandPose会按固定的输入/输出张量签名做预处理与后处理。若新模型的输入形状、归一化方式或输出张量名不一致,需同步修改包装类(mp_palmdet.py/mp_handpose.py),否则会报张量名不存在或结果解码错乱重启应用:同样用
echo "apps/gesture/main.py" > /tmp/luwu_run.fifo重启
6.5 替换后常见问题
替换模型后若检测异常,按下面顺序排查(多为"张量签名不匹配"):
现象 | 可能原因 | 处理 |
|---|---|---|
启动即报 | 模型输入/输出张量名与包装类硬编码不一致 | 打开包装类,用 |
能加载但一直检不出目标 | 输入预处理不对(尺寸 / 归一化 / BGR↔RGB / 通道顺序) | 对照原模型的预处理逻辑逐个核对 |
检测到但框位置偏 | 输出后处理解码公式与模型不匹配 | 按新模型输出定义重写解码(缩放回原尺寸) |
帧率明显下降 | 模型过大 / 输入尺寸过大 / 未走 | 降低输入分辨率、换小模型、或预热 session |
只对特定目标有效 | 换成了不同类别的模型 | 确认应用逻辑与模型输出类别一致(如 |
性能提示:机器狗主控为 Raspberry Pi 5(CM5),建议用量化版 ONNX(如 int8)或较小的模型;推理次数应控制在每帧 1~2 次以内,否则会拖慢主界面帧率。也可把重复加载的 session 缓存为全局变量,避免每帧重建。
6.6 常用查询命令
# 查看某 ONNX 模型的输入/输出签名(执行前把路径换成实际的模型路径)
python3 - <<'EOS'
import onnxruntime as ort, glob, os
for p in glob.glob('/opt/luwu-os/model/*.onnx'):
s = ort.InferenceSession(p)
ins = [(i.name, i.shape, i.type) for i in s.get_inputs()]
outs = [(o.name, o.shape) for o in s.get_outputs()]
print(os.path.basename(p), ins, outs)
EOS
# 查看 model 目录占用与文件
du -sh /opt/luwu-os/model && ls -la /opt/luwu-os/model
6.7 替换前后检查清单
换模型前先跑一遍 6.6 存好基线,换的过程对照下面逐项打勾:
阶段 | 检查项 |
|---|---|
替换前 | ① 记下原模型输入/输出签名(6.6);② 备份原文件( |
替换后 | ① 应用能启动不报错;② 能稳定检出目标;③ 帧率可接受;④ 结果定位正确 |
出问题时 | 依次看 6.5 排查表;先怀疑张量签名 → 预处理 → 解码 → 性能 |
📌 若只是想体验自定义模型而暂不进源码,可先在
model/放一个同输入输出签名的 ONNX,只改路径不写代码,先用最小改动验证整条链路。
