IoT-For-Beginners 零售项目实战:在 Wio Terminal 上调用 Custom Vision 对象检测器完成货架库存识别
2026/9/15 18:54:59 网站建设 项目流程

IoT-For-Beginners 零售项目实战:在 Wio Terminal 上调用 Custom Vision 对象检测器完成货架库存识别

【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners

本篇技术指南基于 IoT-For-Beginners 仓库5-retail(零售)项目的第 2 课「从 IoT 设备检查库存」,聚焦 Wio Terminal(Arduino 平台)开发板:如何把上一课训练好的 Custom Vision 对象检测器(object detector)从云端"搬到"嵌入式设备上,将原有的图像分类(classify)代码改造为对象检测(detect)代码,并通过置信度阈值过滤低概率检测结果。完成本指南后,你将掌握detectStock函数的完整实现思路、ArduinoJson 解析预测结果、串口输出检测报告的全套实战能力,为后续的库存计数(stock counting)打下基础。

一、课程背景:为什么需要把对象检测器接入 IoT 设备

5-retail项目的前一课中,你已经使用 Custom Vision 训练了一个能够识别货架上番茄酱罐头(tomato paste)的对象检测器,并把它发布为可调用的预测服务。本课的核心任务,就是让部署在货架旁的 Wio Terminal 拍摄照片、把图片字节流发送到该服务,再把返回的多组"标签 + 概率 + 边界框"结果在串口监视器中展示出来。

与图像分类器(image classifier)不同,对象检测器会返回一个或多个检测结果(同一张图片中可能同时识别出多个罐头),因此不能简单地把分类代码照搬过来——你需要在代码中引入阈值过滤结果批量处理逻辑。本课英文原文档位于 5-retail/lessons/2-check-stock-device/wio-terminal-object-detector.md,对应的完整可运行代码位于 code-detect/wio-terminal。

前置要求:请先完成课程 5-retail/lessons/2-check-stock-device/README.md 中「发布对象检测器迭代」的任务,从 Custom Vision 门户获取Prediction URLPrediction-Key。发布后的检测端点 URL 形如https://<location>.api.cognitive.microsoft.com/customvision/v3.0/Prediction/<id>/detect/iterations/Iteration2/image

二、复制图像分类器项目:从零搭建stock-counter

对象检测器的绝大部分代码与制造项目(4-manufacturing)中创建的图像分类器相同,因此不必从零写起,而是复制并改造

任务:复制图像分类器项目

  1. 连接 ArduCam 摄像头:按照制造项目第 2 课的步骤把 ArduCam 接到 Wio Terminal,参见 4-manufacturing/lessons/2-check-fruit-from-device/wio-terminal-camera.md 中的"连接摄像头"任务。为了让检测结果稳定,建议把摄像头固定在一个固定位置——例如把排线挂在纸箱或罐头上,或用双面胶将摄像头固定在纸箱上,模拟真实货架监控场景。

  2. 创建 PlatformIO 工程:使用 PlatformIO 新建一个 Wio Terminal 工程,命名为stock-counter

  3. 复现拍照流程:按照制造项目第 2 课 README.md 中"使用 IoT 设备拍摄图像"任务的步骤,实现从摄像头拍照并读取字节缓冲区的逻辑。

  4. 复现分类调用流程:按照制造项目第 2 课 README.md 中"从 IoT 设备调用图像分类器"任务的步骤实现网络请求。这部分代码绝大部分可以原样复用到对象检测中。

三、核心改造:从classifyImagedetectStock

分类代码与检测代码高度相似,主要差异有两点:调用的 URL 不同(检测端点而非分类端点),以及返回结果的形态不同(单个结果 vs 多个结果)。下面按改造步骤逐项展开,每个步骤均可在 code-detect/wio-terminal/stock-counter/src/main.cpp 中找到最终成品。

步骤 1:添加<vector>头文件

main.cpp顶部加入以下 include:

#include <vector>

对象检测会返回多个预测结果,后续需要用std::vector容器来收集"通过阈值"的预测项,因此必须引入该标准库头。

步骤 2:重命名函数为detectStock

classifyImage函数改名为detectStock,同时把buttonPressed函数中的调用处一并改名。仓库成品代码中,该函数的签名是:

void detectStock(byte *buffer, uint32_t length)

它接收拍照得到的 JPEG 字节缓冲区指针与长度,负责完成 HTTP 请求与结果解析(详见第五节源码剖析)。

步骤 3:声明置信度阈值threshold

detectStock函数上方声明一个常量,用于过滤低概率检测:

const float threshold = 0.3f;

为什么要过滤?图像分类器每个标签只返回一个结果;而对象检测器会为图片中每个疑似目标都返回一个结果,其中混有大量低置信度的误检。threshold = 0.3f表示只保留概率大于 30% 的检测。仓库成品代码code-count版本中该值被设为0.0f(code-count/wio-terminal/stock-counter/src/main.cpp 第 61 行),说明阈值需要根据实际图片与模型表现灵活调整。

步骤 4:新增processPredictions处理函数

detectStock上方声明一个处理预测列表的函数:

void processPredictions(std::vector<JsonVariant> &predictions) { for(JsonVariant prediction : predictions) { String tag = prediction["tagName"].as<String>(); float probability = prediction["probability"].as<float>(); char buff[32]; sprintf(buff, "%s:\t%.2f%%", tag.c_str(), probability * 100.0); Serial.println(buff); } }

该函数接收一个JsonVariant预测列表,逐条取出tagName(标签名)与probability(概率,0~1 之间的小数),乘以 100 转成百分比后通过sprintf格式化为标签:概率形式打印到串口监视器。

步骤 5:用阈值过滤逻辑替换原有 for 循环

detectStock函数内部,把原来遍历预测结果的for循环体替换为以下代码:

std::vector<JsonVariant> passed_predictions; for(JsonVariant prediction : predictions) { float probability = prediction["probability"].as<float>(); if (probability > threshold) { passed_predictions.push_back(prediction); } } processPredictions(passed_predictions);

这段代码的作用是:遍历所有预测,把概率高于threshold的预测收集进passed_predictions(一个std::vector<JsonVariant>),最后统一交给processPredictions打印。这样,误检噪声被剔除,串口输出只保留可信的检测结果。

四、上传运行与结果解读

完成上述改造后:

  1. 通过 PlatformIO 把代码上传到 Wio Terminal;
  2. 将摄像头对准货架上的物品;
  3. 按下C 按钮WIO_KEY_C)触发拍照与检测;
  4. 打开串口监视器查看输出。

课程文档给出的典型输出如下:

Connecting to WiFi.. Connected! Image captured Image read to buffer with length 17416 tomato paste: 35.84% tomato paste: 35.87% tomato paste: 34.11% tomato paste: 35.16%

可以看到:设备先完成 WiFi 连接(Connecting to WiFi..Connected!),随后成功捕获图像(Image captured),并从 ArduCam FIFO 读取出 17416 字节的 JPEG 数据,最后对象检测器在货架图片中识别出 4 个番茄酱罐头,置信度均在 34%~36% 之间。

💁调参提示:如果出现大量无关输出或漏检,可能需要针对你的图片把threshold调整到合适值。你拍摄的原始图片与这些数值都可以在 Custom Vision 门户的Predictions标签页中查看,便于对照验证模型表现。

下面这张图展示了课程中同一场景的检测结果:货架上 4 罐番茄酱被全部框出,4 个检测的置信度分别为 35.8%、33.5%、25.7% 和 16.6%:

五、源码级剖析:detectStock的完整调用链

光看改造步骤还不够,下面结合仓库源码把detectStock背后的完整调用链拆解清楚,便于你真正理解"嵌入式设备如何调用云端对象检测器"。

5.1 完整的detectStock实现

仓库成品代码(code-detect/wio-terminal/stock-counter/src/main.cpp 第 77-111 行)中,detectStock的完整实现为:

void detectStock(byte *buffer, uint32_t length) { HTTPClient httpClient; httpClient.begin(client, PREDICTION_URL); httpClient.addHeader("Content-Type", "application/octet-stream"); httpClient.addHeader("Prediction-Key", PREDICTION_KEY); int httpResponseCode = httpClient.POST(buffer, length); if (httpResponseCode == 200) { String result = httpClient.getString(); DynamicJsonDocument doc(1024); deserializeJson(doc, result.c_str()); JsonObject obj = doc.as<JsonObject>(); JsonArray predictions = obj["predictions"].as<JsonArray>(); std::vector<JsonVariant> passed_predictions; for(JsonVariant prediction : predictions) { float probability = prediction["probability"].as<float>(); if (probability > threshold) { passed_predictions.push_back(prediction); } } processPredictions(passed_predictions); } httpClient.end(); }

关键点逐条说明:

  • HTTPS 请求httpClient.begin(client, PREDICTION_URL)使用全局的WiFiClientSecure client发起 HTTPS 请求,client的 CA 证书在connectWiFi()中通过client.setCACert(CERTIFICATE)设置(见 config.h 中的 DigiCert Global Root G2 根证书)。
  • 两个请求头Content-Type: application/octet-stream表示请求体是原始图片字节流;Prediction-Key携带你在 Custom Vision 门户获取的密钥——只有携带合法密钥的应用才被允许调用模型,其他请求一律被拒绝。
  • 二进制上传httpClient.POST(buffer, length)直接把 JPEG 字节缓冲区和长度作为请求体 POST 出去,无需 base64 编码。
  • 响应解析:响应码为 200 时,用ArduinoJsonDynamicJsonDocument doc(1024)解析 JSON,取出顶层predictions数组,数组中每个元素包含tagNameprobability以及boundingBox等字段。
  • 阈值过滤:对predictions数组逐条判断probability > threshold,通过的放入passed_predictions
  • 资源释放httpClient.end()关闭连接。

5.2 拍照与触发链路:loopbuttonPressed

main.cpp的主循环(第 136-145 行)持续轮询 C 按钮:

void loop() { if (digitalRead(WIO_KEY_C) == LOW) { buttonPressed(); delay(2000); } delay(200); }

setup()中通过pinMode(WIO_KEY_C, INPUT_PULLUP)将 WIO Terminal 的 C 按钮配置为内部上拉输入(main.cpp 第 59 行),按下时读到LOW即触发buttonPressed(),并在两次触发之间加入 2 秒防抖延迟。

buttonPressed完成"拍照 → 读缓冲 → 检测 → 释放内存"的完整流程:

void buttonPressed() { camera.startCapture(); while (!camera.captureReady()) delay(100); Serial.println("Image captured"); byte *buffer; uint32_t length; if (camera.readImageToBuffer(&buffer, length)) { Serial.print("Image read to buffer with length "); Serial.println(length); detectStock(buffer, length); delete (buffer); } }

摄像头封装类Camera(见 camera.h)基于 ArduCAM 库实现:init()负责 SPI 总线自检、OV2640 传感器芯片识别与 JPEG 格式初始化;startCapture()冲刷 FIFO 并触发一次拍摄;captureReady()通过CAP_DONE_MASK位轮询拍摄完成标志;readImageToBuffer()从 FIFO 突发读出 JPEG 数据并以new byte[length]动态分配缓冲区,读到 JPEG 结束标记0xFF 0xD9时停止。每次检测完成后delete(buffer)释放堆内存,防止内存泄漏。

5.3 配置项与工程依赖

所有需要你填写的配置都集中在 config.h:

配置项说明填写示例
SSIDWiFi 网络名称你的路由器 SSID
PASSWORDWiFi 密码你的 WiFi 密码
PREDICTION_URLCustom Vision 检测端点 URL第五节开头提到的 detect 端点
PREDICTION_KEY预测密钥Custom Vision 门户中获取的 Prediction-Key
CERTIFICATEAzure TLS 根证书仓库已内置 DigiCert Global Root G2,一般无需改动

工程依赖由 platformio.ini 声明:目标板为seeed_wio_terminal(Atmel SAM 平台 + Arduino 框架),依赖seeed-studio系列的 rpcWiFi、FS、SFUD、rpcUnified、mbedtls、RTC 库以及bblanchon/ArduinoJson @ 6.17.3build_flags中定义了-DARDUCAM_SHIELD_V2-DOV2640_CAM,用于启用 ArduCAM 扩展板与 OV2640 摄像头支持——创建新工程时务必保留这两个宏,否则摄像头驱动无法编译。

六、下一步:从"检测"走向"计数"

本课完成的是检测,而课程系列的目标是计数:结合检测返回的boundingBox(边界框,由lefttopwidthheight四个 0~1 归一化坐标定义),可以判断两个检测框是否高度重叠、是否为同一个物体,进而统计货架上的实际库存数量。这部分进阶内容见本课下一篇指南 wio-terminal-count-stock.md,对应成品代码位于 code-count/wio-terminal。

本课配套的完整课程说明(含发布检测器迭代、边界框原理、模型重训练等内容)请参阅 5-retail/lessons/2-check-stock-device/README.md。

结语

至此,你的 Wio Terminal 已经成功从"图像分类设备"升级为"对象检测设备":按下 C 按钮即可拍摄货架照片、调用 Custom Vision 对象检测服务、按 30% 置信度阈值过滤并打印所有可信检测结果。整个改造过程只涉及少量代码差异——新增<vector>头、重命名函数、声明阈值、新增处理函数、替换遍历逻辑——印证了对象检测与图像分类在客户端调用层面的高度相似性。接下来,就可以基于边界框数据实现真正的库存计数与低库存告警了。

【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询