13浏览
查看: 13|回复: 0

[M10项目] 桌面看守器:基于行空板K10的小智AI视觉物品守护装置

[复制链接]
本帖最后由 云天 于 2026-8-10 15:47 编辑

        这是一个将AI视觉与语音完美融合的创客项目,使用行空板K10打造了一个能听懂人话、会自己“看”东西的桌面物品看守器,专治办公室里的“笔失踪”难题。
【一、项目背景】办公室的“笔去哪儿了”
        在办公室,桌面上的笔似乎总有一种魔力——别人想写个字、签个名,会随手拿起,用完后却忘了归还,直接带走了。等你需要时,桌上只剩一个空荡荡的笔筒。
“我的笔呢?”成了办公室里每天重复最多的问题。
        于是,“桌面看守器” 的想法诞生了——用行空板K10和小智AI,做一个能听懂人话、会自己“看”东西的桌面管家。你对它说一句:“帮我看好这支笔,每隔10秒检查一次”,它就会盯着你的笔,如果笔不见了,它会立刻语音提醒:“注意!你的笔可能被人拿走了!”,同时24灯珠LED灯点亮。
桌面看守器:基于行空板K10的小智AI视觉物品守护装置图8

【二、硬件准备】

硬件
说明
行空板K10核心主控,集成了ESP32-S3、2.8寸彩色屏幕、200万像素摄像头、麦克风、扬声器等
Type-C数据线用于供电和烧录程序
被看守的物品一支笔(或其他容易丢失的小物件)

        行空板K10的核心参数:
  • 主控:ESP32-S3 双核Xtensa LX7 @240MHz
  • 内存:512KB SRAM + 8MB PSRAM
  • 存储:16MB Flash
  • 屏幕:2.8英寸TFT LCD,320×240分辨率,ILI9341驱动
  • 摄像头:200万像素,GC2145传感器
  • 无线:Wi-Fi 4 + Bluetooth 5

桌面看守器:基于行空板K10的小智AI视觉物品守护装置图5


桌面看守器:基于行空板K10的小智AI视觉物品守护装置图7


桌面看守器:基于行空板K10的小智AI视觉物品守护装置图6


【三、软件准备】
        3.1 开发环境
工具
版本要求
ESP-IDFv6.0.2(或更高版本,本文基于 v6.0.2 测试)
xiaozhi-esp32最新源码(2026年8月)
Python3.x
Git最新版

        3.2 下载代码
git clone https://github.com/78/xiaozhi-esp32.git
cd xiaozhi-esp32

        3.3 设置ESP-IDF环境
确保你已经安装并配置好ESP-IDF v6.0.2环境:
# 假设ESP-IDF安装在 D:\esp-idf-v6.0.2
cd D:\esp-idf-v6.0.2
export.bat   # Windows
# 或 source ./export.sh  (Linux/macOS)

桌面看守器:基于行空板K10的小智AI视觉物品守护装置图3

        然后进入项目目录进行编译。
【四、核心技术实现】
        4.1 设置目标芯片与配置板型
        行空板K10使用ESP32-S3芯片,需要先设置目标芯片:
idf.py set-target esp32s3

        然后进入配置菜单,选择对应的板型:
idf.py menuconfig

        在 Xiaozhi Assistant -> Board Type 中选择 DFRobot 行空板 K10。
Xiaozhi Assistant -> Board Type -> DFRobot 行空板 K10

桌面看守器:基于行空板K10的小智AI视觉物品守护装置图1
        摄像头配置要点(重要):
  • 传感器型号:GC2145(Component config -> Espressif Camera Sensors Configurations -> Camera Sensor Configuration -> Select and Set Camera Sensor -> GC2145 ->  Auto detect GC2145)
  • 输出格式:RGB565
    800x600 20fps, DVP 8-bit, 20M input(Component config -> Espressif Camera Sensors Configurations -> Camera Sensor Configuration -> Select and Set Camera Sensor -> GC2145 ->  Select default output format for DVP interface (RGB565 800x600 20fps, DVP 8-bit, 20M input) -> RGB565 800x600 20fps, DVP 8-bit, 20M input)
  • 字节序交换:启用 Enable software camera buffer endianness swapping(Xiaozhi Assistant -> Camera Configuration -> Enable software camera buffer endianness swapping
  • PSRAM模式:根据板子选 Octal Mode PSRAM 或 Quad Mode PSRAM(Component config -> ESP PSRAM -> SPI RAM config -> Mode (QUAD/OCT) -> Octal Mode PSRAM

桌面看守器:基于行空板K10的小智AI视觉物品守护装置图2


        4.2 解决摄像头驱动问题(关键步骤)
        在实际编译过程中,我发现摄像头无法初始化,日志报错:
  1. E (928) EspVideo: no supported pixel format found
复制代码
        问题原因:GC2145传感器输出的是大端RGB565格式(FOURCC = 'RGBR' 或 'RBGR'),但 EspVideo 驱动中没有为这种格式分配优先级,导致驱动认为“不支持”。
        解决方法:修改 common/esp_video.cc,在 get_rank 函数中添加对大端RGB565格式的识别。
        找到类似如下的 get_rank lambda 定义,在 switch 中添加:
  1. // 在 esp_video.cc 的构造函数中,找到 get_rank lambda
  2. auto get_rank = [](uint32_t fmt) -> int {
  3.     switch (fmt) {
  4.         case V4L2_PIX_FMT_YUYV:
  5.         case V4L2_PIX_FMT_UYVY:
  6.         case V4L2_PIX_FMT_YUV422P:
  7.             return 10;
  8.         case V4L2_PIX_FMT_RGB565:
  9.             return 11;
  10.         // ★★★ 新增对大端 RGB565 的支持 ★★★
  11. #ifdef V4L2_PIX_FMT_RGB565X
  12.         case V4L2_PIX_FMT_RGB565X:
  13. #else
  14.         // 如果系统中没有定义 V4L2_PIX_FMT_RGB565X,则直接使用实际检测到的值
  15.         case 0x52424752:   // 根据日志,检测到的是 0x52424752('RBGR')
  16. #endif
  17.             return 11;   // 与大端 RGB565 同等优先级
  18.         // ... 其他 case ...
  19.         default:
  20.             return 1 << 29;  // unsupported
  21.     }
  22. };
复制代码
       4.3 注册MCP监控工具
        小智AI通过MCP(模型上下文协议)来调用工具。我们需要为它注册两个新工具:self.camera.start_monitor(启动监控)和 self.camera.stop_monitor(停止监控)。
        首先在文件顶部包含必要的头文件:
  1. #include "mcp_server.h"
  2. #include "esp_timer.h"
复制代码
        在 df_k10_board.cc 的类定义中添加成员变量:
  1. class Df_K10Board : public WifiBoard {
  2. private:
  3.     esp_timer_handle_t cup_monitor_timer_ = nullptr;
  4.     struct CallbackData {
  5.         std::string target;
  6.         int interval;
复制代码
        在构造函数中调用 InitializeTools():
  1. Df_K10Board() {
  2.     InitializeI2c();
  3.     InitializeIoExpander();
  4.     InitializeSpi();
  5.     InitializeIli9341Display();
  6.     InitializeButtons();
  7.     InitializeIot();
  8.     InitializeCamera();
  9.     InitializeTools();   // 确保摄像头已初始化后再注册工具
  10. }
复制代码
        在 df_k10_board.cc 中实现 InitializeTools():
  1. void Df_K10Board::InitializeTools() {
  2.     auto& mcp = McpServer::GetInstance();
  3.     // 启动监控工具
  4.     mcp.AddTool(
  5.         "self.camera.start_monitor",
  6.         "启动摄像头周期监控,持续监视某个物体(如水杯、遥控器等)。"
  7.         "系统会每隔指定的秒数自动抓拍一张照片,并询问AI该物体是否还在。"
  8.         "如果物体消失,小智会立即语音播报提醒。"
  9.         "当用户说“看着我的水杯”、“帮我盯着遥控器”、“监控杯子是否还在”等类似指令时,请调用此工具。",
  10.         PropertyList({
  11.             Property("target",        kPropertyTypeString),
  12.             Property("interval_sec",  kPropertyTypeInteger, 10, 1, 300)
  13.         }),
  14.         [this](const PropertyList& props) -> ReturnValue {
  15.             // ... 实现启动定时器逻辑
  16.         }
  17.     );
  18.     // 停止监控工具
  19.     mcp.AddTool(
  20.         "self.camera.stop_monitor",
  21.         "停止当前正在进行的摄像头监控。"
  22.         "当用户说“停止监控”、“别看了”、“结束监控”等指令时,调用此工具。",
  23.         PropertyList(),
  24.         [this](const PropertyList&) -> ReturnValue {
  25.             // ... 实现停止定时器逻辑
  26.         }
  27.     );
  28. }
复制代码
        监控回调的核心流程(定时器回调):
  1. // 1. 抓拍
  2. if (!camera->Capture()) {
  3.     ESP_LOGW(TAG, "抓拍失败,本次跳过");
  4.     return;
  5. }
  6. // 2. 调用AI视觉识别
  7. std::string question = "请仔细检查图片中是否有“" + d->target + "”,如果还在只回答“有”,如果不在只回答“没有”。";
  8. std::string answer;
  9. try {
  10.     answer = camera->Explain(question);
  11. } catch (const std::exception& e) {
  12.     ESP_LOGE(TAG, "AI识别异常: %s", e.what());
  13.     return;
  14. }
  15. // 3. 判断结果并播报
  16. bool is_present = (answer.find("有") != std::string::npos);
  17. if (!is_present) {
  18.     auto& app = Application::GetInstance();
  19.     Protocol& proto = app.GetProtocol();
  20.     std::string alert = "注意!" + d->target + " 可能不见了!";
  21.     if (app.GetDeviceState() == kDeviceStateListening ||
  22.         app.GetDeviceState() == kDeviceStateIdle) {
  23.         proto.SendWakeWordDetected(alert);  // 主动播报提醒
  24.     }
  25.     ESP_LOGI(TAG, "警报:%s", alert.c_str());
  26. } else {
  27.     ESP_LOGI(TAG, "%s 还在", d->target.c_str());
  28. }
复制代码
       在 InitializeIot() 中初始化 P0 灯带
  1. void InitializeIot() {
  2.     // 原有的板载 LED(3颗)
  3.     led_strip_ = new CircularStrip(BUILTIN_LED_GPIO, 3);
  4.     new LedStripControl(led_strip_);
  5.     // ★ 新增:初始化 P0 上的 24 颗 WS2818 灯带
  6.     external_led_strip_ = new CircularStrip(GPIO_NUM_0, 24);
  7.     external_led_strip_->SetAllColor({0, 0, 0});   // 默认熄灭
  8. }
复制代码

        4.4 主动播报的实现
        当监控发现物品不见时,需要让设备主动说话提醒用户。这里利用了 SendWakeWordDetected 方法——它模拟了一次“唤醒词检测”,让系统立即打断当前状态并播报提醒内容。
  1. if (app.GetDeviceState() == kDeviceStateListening ||
  2. app.GetDeviceState() == kDeviceStateIdle) {
  3. proto.SendWakeWordDetected(alert);
  4. }
复制代码
        状态说明:
  • kDeviceStateIdle:设备待命,等待唤醒
  • kDeviceStateListening:设备正在聆听用户说话

        在这两种状态下播报不会干扰设备正在进行的其他操作(如播放回复)。
        4.5 修改 application.h 增加 GetProtocol() 方法
        由于我们在定时器回调中需要获取 Protocol 实例来调用 SendWakeWordDetected(),而 Application 类默认没有提供 GetProtocol() 公有接口,因此需要手动添加:
        4.6 编译与烧录
  1. # 编译
  2. idf.py build
  3. # 烧录(替换COMx为实际端口,如COM11)
  4. idf.py -p COMx flash
  5. # 监控日志(调试用)
  6. idf.py -p COMx monitor
复制代码
       如果需要生成合并固件(一个文件)供 flash_download_tool 使用:
  1. esptool --chip esp32s3 merge-bin -o xiaozhi.bin  --flash-mode dio --flash-size 16MB --flash-freq 80m   0x0 build/bootloader/bootloader.bin   0x8000 build/partition_table/partition-table.bin   0xd000 build/ota_data_initial.bin   0x20000 build/xiaozhi.bin   0x800000 build/generated_assets.bin
复制代码
       小智AI行空板k10固件:下载附件xiaozhi.zip

        然后用 flash_download_tool 选择ESP32-S3,将 merge.bin 烧录到地址 0x0 即可。
【五、项目效果与使用演示】

        对话示例
        启动监控:
        用户:“小智,帮我看好这支笔,每隔20秒检查一次。”
        小智:“好的,我来帮你盯着这支笔,如果它不见了我会提醒你的。”
        系统后台:启动周期定时器,每20秒抓拍一次,调用AI视觉判断笔是否还在。

        触发警报:
        用户:(拿走桌上的笔)
        小智(主动播报):“注意!你的笔可能被人拿走了!”
        系统后台:日志显示 警报:注意!笔 可能不见了!

        停止监控:
        用户:“小智,停止监控。”
        小智:“已停止监控。
【六、项目总结与展望】
        6.1 项目亮点
  • 自然语音交互:通过日常语言即可启动/停止监控,无需编程或按键操作。
  • AI视觉理解:不是简单的“画面变化检测”,而是真正“理解”画面内容——AI会判断“笔还在不在”,不会因为光线变化或有人路过就误报。
  • 主动播报提醒:物品不见时,设备会主动“说话”提醒,无需用户主动询问。
  • 周期可调:用户可自定义检查间隔,在“实时性”和“功耗/费用”之间取得平衡。

        6.2 应用场景扩展
  • 桌面物品看守:笔、遥控器、手机、钥匙等易丢失的小物件
  • 办公室共享物品管理:共享文具、计算器、充电器等
  • 儿童玩具监控:提醒孩子收拾好自己的玩具
  • 宠物活动监测:监控宠物是否在指定区域

【附录:完整代码】
        df_k10_board.cc(工具注册完整实现)
  1. void Df_K10Board::InitializeTools() {
  2.     auto& mcp = McpServer::GetInstance();
  3.     // 启动监控工具
  4.     mcp.AddTool(
  5.         "self.camera.start_monitor",
  6.         "启动摄像头周期监控,持续监视某个物体(如水杯、遥控器等)。"
  7.         "系统会每隔指定的秒数自动抓拍一张照片,并询问AI该物体是否还在。"
  8.         "如果物体消失,小智会立即语音播报提醒。"
  9.         "当用户说“看着我的水杯”、“帮我盯着遥控器”、“监控杯子是否还在”等类似指令时,请调用此工具。"
  10.         "参数:\n"
  11.         "  target:要监控的物体名称(中文,例如“水杯”)\n"
  12.         "  interval_sec:检查间隔秒数,默认10秒,范围1~300秒",
  13.         PropertyList({
  14.             Property("target",        kPropertyTypeString),
  15.             Property("interval_sec",  kPropertyTypeInteger, 20, 1, 300)
  16.         }),
  17.         [this](const PropertyList& props) -> ReturnValue {
  18.             std::string target = props["target"].value<std::string>();
  19.             int interval = props["interval_sec"].value<int>();
  20.             // 如果已有监控定时器,先停掉
  21.             if (cup_monitor_timer_) {
  22.                 esp_timer_stop(cup_monitor_timer_);
  23.                 esp_timer_delete(cup_monitor_timer_);
  24.                 cup_monitor_timer_ = nullptr;
  25.             }
  26.             if (cup_monitor_data_) {
  27.                 delete cup_monitor_data_;
  28.                 cup_monitor_data_ = nullptr;
  29.             }
  30.             // ★ 只创建一次,传入三个参数(包含 led_strip 指针)
  31.             cup_monitor_data_ = new Df_K10Board::CallbackData{target, interval, external_led_strip_};
  32.             const esp_timer_create_args_t args = {
  33.                 .callback = [](void* arg) {
  34.                     auto* d = static_cast<Df_K10Board::CallbackData*>(arg);
  35.                     auto& board = Board::GetInstance();
  36.                     auto camera = board.GetCamera();
  37.                     if (!camera) {
  38.                         ESP_LOGE(TAG, "摄像头未初始化");
  39.                         return;
  40.                     }
  41.                     // 1. 抓拍
  42.                     if (!camera->Capture()) {
  43.                         ESP_LOGW(TAG, "抓拍失败,本次跳过");
  44.                         return;
  45.                     }
  46.                     // 2. 构造提问
  47.                     std::string question = "请仔细检查图片中是否有“" + d->target + "”,如果还在只回答“还在”,如果不在只回答“没有”。";
  48.                     std::string answer;
  49.                     try {
  50.                         answer = camera->Explain(question);
  51.                     } catch (const std::exception& e) {
  52.                         ESP_LOGE(TAG, "AI识别异常: %s", e.what());
  53.                         return;
  54.                     }
  55.                     // 3. 判断结果
  56.                     bool is_present = (answer.find("还在") != std::string::npos);
  57.                     if (!is_present) {
  58.                         auto& app = Application::GetInstance();
  59.                         Protocol& proto = app.GetProtocol();
  60.                         std::string alert = "注意!" + d->target + " 可能不见了!";
  61.                         if (app.GetDeviceState() == kDeviceStateListening ||
  62.                             app.GetDeviceState() == kDeviceStateIdle ||  
  63.                             app.GetDeviceState() == kDeviceStateSpeaking) {
  64.                             proto.SendWakeWordDetected(alert);
  65.                             // ★ 通过 d->led_strip 控制灯带
  66.                             if (d->led_strip) {
  67.                                 d->led_strip->SetAllColor({51, 0, 0});   // 低亮度红色
  68.                             }
  69.                         }
  70.                         ESP_LOGI(TAG, "警报:%s", alert.c_str());
  71.                     } else {
  72.                         // ★ 通过 d->led_strip 熄灭灯带
  73.                         if (d->led_strip) {
  74.                             d->led_strip->SetAllColor({0, 0, 0});
  75.                         }
  76.                         ESP_LOGI(TAG, "%s 还在", d->target.c_str());
  77.                     }
  78.                 },
  79.                 .arg = cup_monitor_data_,
  80.                 .name = "cup_monitor"
  81.             };
  82.             esp_timer_create(&args, &cup_monitor_timer_);
  83.             esp_timer_start_periodic(cup_monitor_timer_, (uint64_t)interval * 1000000ULL);
  84.             ESP_LOGI(TAG, "开始监控 %s,间隔 %d 秒", target.c_str(), interval);
  85.             cJSON* json = cJSON_CreateObject();
  86.             cJSON_AddStringToObject(json, "status", "started");
  87.             cJSON_AddStringToObject(json, "info", ("正在监控 " + target + ",每 " + std::to_string(interval) + " 秒检查一次").c_str());
  88.             return json;
  89.         }
  90.     );
  91.     // 停止监控工具
  92.     mcp.AddTool(
  93.         "self.camera.stop_monitor",
  94.         "停止当前正在进行的摄像头监控。"
  95.         "当用户说“停止监控”、“别看了”、“结束监控”等指令时,调用此工具。",
  96.         PropertyList(),
  97.         [this](const PropertyList&) -> ReturnValue {
  98.             if (cup_monitor_timer_) {
  99.                 esp_timer_stop(cup_monitor_timer_);
  100.                 esp_timer_delete(cup_monitor_timer_);
  101.                 cup_monitor_timer_ = nullptr;
  102.             }
  103.             if (cup_monitor_data_) {
  104.                 delete cup_monitor_data_;
  105.                 cup_monitor_data_ = nullptr;
  106.             }
  107.             // ★ 停止监控时熄灭外部灯带(这里可以直接使用成员变量,因为此 lambda 捕获了 this)
  108.             if (external_led_strip_) {
  109.                 external_led_strip_->SetAllColor({0, 0, 0});
  110.             }
  111.             ESP_LOGI(TAG, "摄像头监控已停止");
  112.             return true;
  113.         }
  114.     );
  115. }
复制代码




您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4 备案 沪公网安备31011502402448

© 2013-2026 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail