摘要:前阵子我被一篇《用 XIAO ESP32S3 制作桌面陪伴机器人》在创客圈刷屏——一块几十块钱的 ESP32S3 + 一块 0.96 寸 OLED,靠 PC 端 Python 识别你"在听歌/在打字/发呆",让屏幕上的小眼睛变表情 [媒体]。它很可爱,但本质是个状态显示器,没有任何智能。我把它改造成保留"PC 状态识别 + 表情反馈"的灵魂,增加了摄像头人检、本地模型对话这些真本事,完整走了一遍阿加犀 AidLux + AidLite + QNN + MMS 工具链。这是一篇改造实战,不是翻译——凡是原项目的代码我标注 [沿用],凡是犀牛派 X1 新增的能力我标注 [新增]。所有命令整理自阿加犀官方文档与社区教程,动手前请对照你所用 SDK 版本核对。

在这里插入图片描述


目录


一、从 ESP32S3 玩具到犀牛派 X1:为什么改造

1.1 原项目回顾

原项目(下称"玩具版")的构成非常清晰 [媒体]:

  • 硬件:Seeed Studio XIAO ESP32S3 + 0.96 寸 SSD1306 OLED(I2C,128×64,地址 0x3C)
  • 固件:用 FluxGarage_RoboEyes 库在 OLED 上画眼睛,内置 8 种状态(Music / Typing / Browsing / Idle / Gaming / Laughing / Error / Watching)
  • PC 端:Python 脚本用 pycaw 听音频、pynput 数按键、comtypes 读空闲时间,识别 Music/Typing/Idle/Browsing 四种状态
  • 通信:PC 通过 HTTP 把状态 JSON 发给 ESP32 的 /state 接口,ESP32 据此切换眼睛动画

它确实可爱,但它的"智能"是纯预设动画——换一句话概括:它是个被遥控的状态显示器,不知道你长什么样、在不在、想说啥。

1.2 改造动机:状态显示器 vs 边缘智能体

维度ESP32S3 玩具版犀牛派 X1 改造版
算力240MHz 双核,无 AI 加速QCS8550 Hexagon NPU,~48 TOPS
“感知”仅有 PC 上报的状态摄像头实时人检(NPU)
显示0.96 寸单色 OLEDHDMI/MIPI 大屏,彩色动画
是否"懂你"否,纯查表可接本地小模型做对话
数据回路PC→设备单行PC + 摄像头→设备融合
部署复杂度极简中(需搭 AI 工具链)

改造的核心一句话:把"外部告诉它你在干嘛"升级为"它自己用摄像头看见你在干嘛",再把 NPU 的算力留给真正有价值的 AI(人检、对话),而不是浪费在画像素眼睛上。

1.3 规格对照:ESP32S3 → 犀牛派 X1

规格XIAO ESP32S3犀牛派 X1(QCS8550)
SoCESP32S3(Xtensa LX7)高通 QCS8550(Kalama)
AI 加速无Hexagon NPU,HTP V73 [官方]
内存512KB + 8MB PSRAM板载 LPDDR5X,GB 级 [官方]
显示外接 128×64 OLEDHDMI/MIPI,可驱 1080P
摄像头不支持USB/CSI,NPU 实时推理
系统Arduino 裸机Ubuntu 22.04(AidLux),Python 3.10 [社区]
网络2.4G Wi-FiWi-Fi + 以太网
价格量级¥40 级¥数千级 [推算]

结论:玩具版是"能亮",犀牛派 X1 版是"能看、能想、能说"。本文的价值不在炫技,而在把阿加犀这条被低估的端侧 AI 工具链,用一个小而完整的项目串起来——你读懂它,就能套到任何自定义模型上。


二、改造总览:新架构与数据流

2.1 改造后的系统架构

犀牛派 X1 QCS8550

PC 端 Windows 笔记本

HTTP JSON /state

活动识别客户端
pycaw + pynput + comtypes

局域网 Wi-Fi

USB 摄像头

NPU 人检
YOLO26l + AidLite + QNN

HTTP 服务
接收 PC 状态

状态融合决策

陪伴 UI
Pygame 大屏眼睛

本地小模型对话
Qwen via NPU - 进阶

HDMI 显示器

注意两条独立的数据源(PC 活动 + 摄像头人检)在犀牛派 X1 内部融合,而不是像玩具版那样只有一个输入——这是工程上最关键的升级。

2.2 能力升级清单

原版能力改造版对应是否新增 NPU
OLED 眼睛动画Pygame 大屏机械眼否(CPU 足矣)
PC 状态→表情同左 + 融合人检否
—摄像头人检(在/不在、是否看向屏幕)✅ YOLO26l
—本地小模型对话✅ Qwen 量化
—多状态并行 + 记忆✅

2.3 犀牛派 X1 端侧 AI 部署方法论(阿加犀五步法 + MMS 捷径)

阿加犀社区总结过一条端侧 NPU 部署"五步工作法" [社区]:

1. 搭建匹配开发板的 QAIRT SDK 环境
2. 用 torch 训练/导出 ONNX
3. 安装 aarch64 交叉编译工具链
4. qnn 转换 ONNX → 编译为 .so / .ctx.bin
5. AidLite 加载模型推理(可接 ROS 节点)

对本文的捷径:我们要的人检模型(YOLO26l W8A8 for QCS8550)阿加犀模型农场(MMS)已经预转换好 NPU 格式,所以步骤 1~4 直接用 MMS 下载成品,我们只做第 5 步推理封装——这正是阿加犀生态"降低门槛"的价值所在。如果你想换自己的模型,再回退走完整五步法。

一个必须讲清的取舍:用 MMS 预置模型,省了环境搭建与量化编译,但代价是失去对精度与算子的控制权——模型结构、量化方式、输入布局都由农场方定,遇到"检测框偏了、类别不对"你改不了,只能等农场更新或自己走五步法重转。本文选捷径,是因为人检是成熟任务、YOLO26l 是社区验证过的通用检测模型;但凡是业务强相关的自定义模型(比如"只检测某种特定零件"),必须走完整五步法——这是"快"和"可控"的 trade-off,不是白送的午餐。这也解释了为什么阿加犀既提供 MMS 又保留 QAIRT/QNN 工具链:新手走捷径起飞,专家走全链路掌控。


三、硬件 BOM 清单

类别物料规格价格量级备注
主控犀牛派 X1QCS8550,含散热¥数千 [推算]已预装 AidLux
显示HDMI/MIPI 屏5~10 寸,800×480 起¥150~400越大越有"陪伴感"
传感USB 摄像头1080P,UVC 免驱¥50~120人检输入
音频USB 小音箱(进阶)—¥30对话版用
电源配套电源/PD≥ 30W含主板—
线缆HDMI + USB 线—¥20—

对比玩具版(¥40 级),犀牛派 X1 版是"十倍预算换百倍能力"——但全离线、数据不出户是玩具版也给不了的。


四、环境准备:AidLux + AidLite + MMS

4.1 系统与环境确认

犀牛派 X1 出厂即 AidLux(Ubuntu 22.04,内核 5.15,Python 3.10)[社区]。先确认 NPU 加速后端可用:

# 确认 QNN 后端就绪(不同 SDK 路径略有差异)
python3 -c "import qti.aisw.dlc_utils; print('QNN ready')"

# 查看 AidLite 版本
dpkg -l | grep aidlite
# 期望看到:aidlite-sdk 2.3.x 与 aidlite-qnn236 2.3.x

4.2 AidLite SDK 安装与验证

若未预装或需升级,按官方文档安装 [官方]:

# 1. 从文档中心"资源下载中心"获取对应版本安装包,上传到 /home/aidlux
# 2. 解压并安装
tar -xzf aidlite-sdk-ubuntu22.04-qnn236.tar.gz
cd aidlite-sdk
sudo ./install.sh

# 3. 验证(Python 与 C++ 版本号)
python3 -c "import aidlite; print(aidlite.get_library_version())"
python3 -c "import aidlite; print(aidlite.get_py_library_version())"

# 4. 也可通过 aid-pkg 安装后端插件
sudo aid-pkg update
sudo aid-pkg install aidlite-sdk
sudo aid-pkg install aidlite-qnn236

AccelerateType 与 FrameworkType 对应关系 [官方]:

加速类型说明适用
TYPE_CPUCPU 通用调试、轻量模型
TYPE_GPUGPU 通用图像、浮点模型
TYPE_DSP高通 NPU(HTP)量化模型、高性能推理(推荐)

4.3 MMS 模型农场下载 YOLO26l(NPU 模型)

阿加犀模型农场(MMS)已为犀牛派 X1 准备好了 QCS8550 专用 NPU 模型 [社区]:

# 浏览可下模型
mms list
mms list yolo

# 下载 YOLO26l INT8(QNN2.36 / NPU)人检/检测模型
# 文件名以模型农场实际为准,以下为社区教程中的示例
mms download cutoff_yolo26l_qcs8550_w8a8.qnn236.ctx.bin

# 若需 Stable Diffusion 等生成式模型也可走 MMS
# mms get -m Stable-Diffusion-v2.1 -p w8a16 -c qcs8550 -b qnn2.31 -d /home/aidlux/stable_diffusion

模型输出布局 [社区]:输入 640×640,split_xyxy 布局——[1,4,8400](框 xyxy)+ [1,80,8400](类别分数),类别数 80(COCO)。

4.4 验证 NPU 是否真正生效

这是最容易"假跑在 CPU"的环节。验证法:跑推理时另开终端看 CPU 占用。NPU 真正生效时,CPU 占用应明显偏低 [社区]。

# 终端 A:运行推理脚本
python3 camera_test.py

# 终端 B:观察 CPU
htop
# 期望:CPU 单核占用 < 30%,否则大概率回退到 CPU 推理

五、犀牛派 X1 端:AI 陪伴核心

5.1 摄像头人检:YOLO26l 经 AidLite/QNN 推理

这是改造版的"眼睛之外的眼睛"——它自己看见你在不在、看没看屏幕。[新增]

# person_detect.py —— 犀牛派 X1 端,NPU 人检
import cv2
import numpy as np
import aidlite

MODEL_PATH = "./models/QCS8550/W8A8/cutoff_yolo26l_qcs8550_w8a8.qnn236.ctx.bin"
INPUT_SIZE = 640
CONF_TH = 0.5

def load_model():
    # 阿加犀 2.3.x 的 OO API(旧版请用 aidlite.init()/load())
    model = aidlite.Model.create_instance(model_path=MODEL_PATH)
    model.set_model_properties(
        input_shapes=[[1, INPUT_SIZE, INPUT_SIZE, 3]],
        input_data_type=aidlite.DataType.TYPE_FLOAT32,
        output_shapes=[[1, 4, 8400], [1, 80, 8400]],
        output_data_type=aidlite.DataType.TYPE_FLOAT32,
    )
    cfg = aidlite.Config.create_instance()
    cfg.accelerate_type = aidlite.AccelerateType.TYPE_DSP   # 走 NPU
    cfg.framework_type  = aidlite.FrameworkType.TYPE_QNN
    cfg.number_of_threads = 4
    assert model.init(cfg) == 0, "NPU 初始化失败,检查 QNN 后端版本"
    return model

def detect(model, frame):
    blob = cv2.resize(frame, (INPUT_SIZE, INPUT_SIZE)).astype(np.float32) / 255.0
    blob = np.expand_dims(blob, 0)
    out = model.run(blob)          # 按 SDK 版本确认方法名
    boxes, scores = out[0][0], out[1][0]
    # 简易后处理:取 person 类(COCO id=0)高置信框
    persons = []
    for i in range(8400):
        if scores[0, i] > CONF_TH:
            x1, y1, x2, y2 = boxes[:, i]
            persons.append((float(x1), float(y1), float(x2), float(y2)))
    return persons     # 空列表=画面中无人

版本提示:较旧 SDK 用 aidlite.init(); aidlite.load(MODEL_PATH); aidlite.setInput(blob,0); aidlite.invoke(); out=aidlite.getOutput(0)。新 SDK 用上文的 Model.create_instance OO 写法。两代 API 都合法,按你板子上的版本选。

5.2 陪伴 UI:大屏"机械眼"渲染

把 OLED 上那对像素眼睛,升级成 Pygame 大屏机械眼。[改造]

# companion_ui.py —— 犀牛派 X1 端,大屏眼睛动画
import pygame, math, random, time

class RoboEyes:
    def __init__(self, w=800, h=480):
        pygame.init()
        self.screen = pygame.display.set_mode((w, h))
        self.mood = "neutral"      # neutral/curiosity/happy/sleepy
        self.blink_at = time.time() + random.uniform(2, 5)

    def set_mood(self, m): self.mood = m

    def _eye(self, cx, cy, r, look=0.0):
        col = {"neutral":(80,200,255),"curiosity":(120,255,180),
               "happy":(255,220,90),"sleepy":(120,140,200)}[self.mood]
        pygame.draw.circle(self.screen, col, (cx, cy), r, 4)
        pygame.draw.circle(self.screen, col, (cx+look*r*0.4, cy), r//3)

    def render(self):
        self.screen.fill((15,18,25))
        t = time.time()
        if t > self.blink_at:           # 自动眨眼
            self.blink_at = t + random.uniform(2, 5)
        look = math.sin(t*0.7)*0.6
        self._eye(280, 240, 90, look)
        self._eye(520, 240, 90, look)
        pygame.display.flip()

5.3 HTTP 服务:接收 PC 状态

犀牛派 X1 起一个轻量 HTTP 服务,接收 PC 端上报的活动状态。[改造]

# pc_state_server.py —— 犀牛派 X1 端 Flask 服务
from flask import Flask, request, jsonify
import threading

app = Flask(__name__)
STATE = {"pc": "browsing", "ts": 0.0}
_LOCK = threading.Lock()

@app.route("/state", methods=["POST"])
def set_state():
    data = request.get_json(force=True)
    with _LOCK:
        STATE["pc"] = data.get("state", "browsing")
        STATE["ts"] = time.time()
    return jsonify(ok=True)

@app.route("/status")
def status():
    with _LOCK:
        return jsonify(STATE)

# 启动:flask run --host 0.0.0.0 --port 5000(仅绑内网!)

5.4 状态融合:PC 状态 × 人检 → 表情决策

融合两条输入,决定最终表情——这是改造版相对玩具版最本质的升级。[新增]

# fusion.py —— 融合决策
def decide_mood(pc_state: str, persons: list, pc_fresh: bool) -> str:
    present = len(persons) > 0
    if not pc_fresh:
        return "sleepy"                    # PC 久未上报:待机/瞌睡
    if not present:
        return "curiosity"                  # 人不在但 PC 在跑:好奇张望
    # 人也在、PC 也在:按 PC 活动给情绪
    return {"music":"happy","typing":"curiosity",
            "browsing":"neutral","idle":"sleepy"}.get(pc_state, "neutral")

主循环把三者串起来:

# main.py —— 犀牛派 X1 端主循环
import time
from person_detect import load_model, detect
from companion_ui import RoboEyes
from pc_state_server import STATE, _LOCK
from fusion import decide_mood

model, ui = load_model(), RoboEyes()
cap = cv2.VideoCapture(0)
while True:
    ret, frame = cap.read()
    persons = detect(model, frame) if ret else []
    with _LOCK:
        pc, ts = STATE["pc"], STATE["ts"]
    pc_fresh = (time.time() - ts) < 10          # 10s 内算有效
    ui.set_mood(decide_mood(pc, persons, pc_fresh))
    ui.render()
    time.sleep(0.05)                            # ~20fps UI 刷新

5.5 进阶:本地小模型对话(Qwen via NPU)

如果你要"它真的能聊天",可接一个量化小模型(如 Qwen2.5-0.5B/1.5B INT4)经 NPU 推理 [社区]。方案骨架:

# chat_brain.py —— 进阶:本地 LLM(示意,模型需自行量化部署)
# 思路:用 AidLite 加载量化后的 Qwen .ctx.bin,做流式对话
# 注意:端侧 decode 受内存带宽限制(详见作者另一篇端侧部署文),
#       0.5B INT4 在 QCS8550 上可跑出可用对话速度,1.5B 需评估
def chat(prompt: str) -> str:
    # 1. 拼接 system + 最近几轮(记忆,避免上下文膨胀)
    # 2. AidLite 加载的 Qwen 模型推理
    # 3. 返回文本
    ...

这一节是进阶扩展,不是改造必经之路。先把人检 + 表情做稳,再上对话——否则 NPU 算力被对话独占,人检反而卡。


六、PC 端:Windows 活动识别客户端

PC 端逻辑基本沿用原项目,仅做小幅升级(增加"自动重连"与"状态去抖")。[沿用+升级]

6.1 依赖安装

pip install requests pycaw pynput comtypes

6.2 状态判断逻辑

# desktop_companion_client.py —— PC 端活动识别(沿用原项目思路)
import time, requests
from pycaw.pycaw import AudioUtilities
from pynput import keyboard
from ctypes import windll

X1_IP = "192.168.1.100"     # ← 改成犀牛派 X1 的 IP
POLL = 2.0                  # 轮询间隔
last_state, err_cnt = "browsing", 0

def is_music():             # 是否有音频播放
    sessions = AudioUtilities.GetAllSessions()
    return any(s.State == 1 for s in sessions if s.State is not None)

def key_rate():             # 每秒按键数
    # 用 pynput 监听计数(简化:此处返回累计均值)
    return _kps

def idle_sec():             # 空闲秒数(Windows API)
    return windll.user32.GetLastInputInfo() and (time.time()-_last_input)

def judge():
    if is_music():   return "music"
    if key_rate() >= 3: return "typing"
    if idle_sec() > 120: return "idle"
    return "browsing"

6.3 状态上报模块

def report(state):
    global last_state, err_cnt
    if state == last_state:
        return                      # 仅在状态变化时上报,省流量
    try:
        r = requests.post(f"http://{X1_IP}:5000/state",
                          json={"state": state}, timeout=3)
        if r.status_code == 200:
            last_state = state; err_cnt = 0
    except Exception:
        err_cnt += 1
        if err_cnt >= 10:           # 连错 10 次退出,提示检查设备
            raise SystemExit("ESP32/犀牛派 未通电或不在同网段")

if __name__ == "__main__":
    while True:
        report(judge())
        time.sleep(POLL)

七、联调与验证

7.1 联调清单

检查项方法期望
犀牛派 X1 NPU 生效htop 看推理时 CPU单核 < 30%
摄像头开流cv2.VideoCapture(0).read()返回帧非空
PC→X1 网络PC ping X1_IP通
HTTP 上报X1 端 /status看到 pc 状态更新
表情随人检变化遮挡摄像头眼睛变 curiosity
表情随 PC 变化PC 放音乐眼睛变 happy

7.2 实测帧率与延迟

# 人检推理帧率(犀牛派 X1,YOLO26l W8A8,NPU)
python3 benchmark_image.py --loops 100
# 预期:NPU 下可达两位数~数十 FPS [推算],以你板子实测为准

所有性能数字一律 [实测] 口径——本文只给测量方法,不替你编数据。YOLO 类模型在 QCS8550 NPU 上达数十 FPS 是公开社区共识 [社区],但具体值以你的环境与 SDK 版本为准。


八、坑点实录(10 条,按被踩概率排序)

#坑现象解法
1AidLite API 版本错配create_instance 报错旧 SDK 改用 init/load,对照版本
2模型未真正走 NPUCPU 满载、帧率极低htop 验证 + 确认 TYPE_DSP
3QNN 后端版本不符模型加载失败aid-pkg 装对应 aidlite-qnn236
4摄像头权限/UVC 免驱VideoCapture 黑屏换免驱 UVC 摄像头
5PC 与 X1 不同网段上报连错 10 次退出确认同 Wi-Fi/同 VLAN
6Flask 绑 0.0.0.0 暴露公网可访问仅绑内网 + 防火墙
7内存膨胀(对话版)卡顿/重启限制上下文轮数
8输入未归一化检测全错确认 /255.0 + 640 resize
9输出布局理解错框/分数错位对照 split_xyxy [1,4,8400]+[1,80,8400]
10SDK 路径不在 PYTHONPATHimport 失败export PYTHONPATH=/usr/local/lib/aidlux/aidlite

九、成本与性能账

项玩具版犀牛派 X1 改造版
硬件¥40 级¥数千 [推算]
AI 能力无人检 + 可选对话
数据隐私PC 活动出网(HTTP 内网)全本地,摄像头不出户
功耗< 1W数十瓦 [推算]
复用价值玩具可套用到任意自定义模型

结论账:改造版贵十倍,但换来"能看能想"的边缘智能,且全离线——这恰恰是 ESP32S3 玩具和任何云端方案都给不了的。


十、结论与扩展路线图

五条结论:

  1. 改造的本质是给"状态显示器"装上一双真正的眼睛——NPU 人检让陪伴机器人第一次"看见"你;
  2. 阿加犀工具链(AidLux + AidLite + QNN + MMS)的价值在把 NPU 部署从"五步工程"缩成"一行下载";
  3. 融合两路输入(PC 状态 + 摄像头)优于单输入,是工程升级的核心;
  4. 不要一上来就上本地对话——先把人检 + 表情做稳,NPU 算力留给最值钱的能力;
  5. 玩具版是创客玩具,改造版是端侧 AI 落地的微型样板,读懂它就能迁移到工业检测、机器人等真场景。

扩展路线图:

V1 人检+表情
NPU YOLO26l

V2 情绪识别
人脸表情分类

V3 本地对话
Qwen INT4 via NPU

V4 多机协同
多陪伴机器人组网

最后一句话:从一块 ¥40 的 OLED 玩具,到一块能跑 NPU 的犀牛派 X1,中间差的不是钱,是把工具链走通的那套工程方法——希望这篇改造实战,能帮你把这套方法真正装进脑子里。


参考链接


数据说明:[媒体] = 微信原文 / 公开报道;[官方] = 阿加犀官方文档;[社区] = 开源社区 / 论坛教程;[沿用] = 原 ESP32S3 项目代码思路;[新增] = 犀牛派 X1 改造新增模块;[推算] = 基于公开参数的估算;[实测] 仅提供测量方法。AidLite 不同 SDK 版本 API(init/load 与 Model.create_instance)存在差异,请务必对照你板子上的版本核对命令与代码。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐