ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

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

IoT-For-Beginners 零售项目实战:在 Wio Terminal 上调用 Custom Vision 对象检测器完成货架库存识别 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 TerminalArduino 平台开发板如何把上一课训练好的 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 URL与Prediction-Key。发布后的检测端点 URL 形如https://location.api.cognitive.microsoft.com/customvision/v3.0/Prediction/id/detect/iterations/Iteration2/image。二、复制图像分类器项目从零搭建stock-counter对象检测器的绝大部分代码与制造项目4-manufacturing中创建的图像分类器相同因此不必从零写起而是复制并改造。任务复制图像分类器项目连接 ArduCam 摄像头按照制造项目第 2 课的步骤把 ArduCam 接到 Wio Terminal参见 4-manufacturing/lessons/2-check-fruit-from-device/wio-terminal-camera.md 中的连接摄像头任务。为了让检测结果稳定建议把摄像头固定在一个固定位置——例如把排线挂在纸箱或罐头上或用双面胶将摄像头固定在纸箱上模拟真实货架监控场景。创建 PlatformIO 工程使用 PlatformIO 新建一个 Wio Terminal 工程命名为stock-counter。复现拍照流程按照制造项目第 2 课 README.md 中使用 IoT 设备拍摄图像任务的步骤实现从摄像头拍照并读取字节缓冲区的逻辑。复现分类调用流程按照制造项目第 2 课 README.md 中从 IoT 设备调用图像分类器任务的步骤实现网络请求。这部分代码绝大部分可以原样复用到对象检测中。三、核心改造从classifyImage到detectStock分类代码与检测代码高度相似主要差异有两点调用的 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.0fcode-count/wio-terminal/stock-counter/src/main.cpp 第 61 行说明阈值需要根据实际图片与模型表现灵活调整。步骤 4新增processPredictions处理函数在detectStock上方声明一个处理预测列表的函数void processPredictions(std::vectorJsonVariant predictions) { for(JsonVariant prediction : predictions) { String tag prediction[tagName].asString(); float probability prediction[probability].asfloat(); 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::vectorJsonVariant passed_predictions; for(JsonVariant prediction : predictions) { float probability prediction[probability].asfloat(); if (probability threshold) { passed_predictions.push_back(prediction); } } processPredictions(passed_predictions);这段代码的作用是遍历所有预测把概率高于threshold的预测收集进passed_predictions一个std::vectorJsonVariant最后统一交给processPredictions打印。这样误检噪声被剔除串口输出只保留可信的检测结果。四、上传运行与结果解读完成上述改造后通过 PlatformIO 把代码上传到 Wio Terminal将摄像头对准货架上的物品按下C 按钮WIO_KEY_C触发拍照与检测打开串口监视器查看输出。课程文档给出的典型输出如下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.asJsonObject(); JsonArray predictions obj[predictions].asJsonArray(); std::vectorJsonVariant passed_predictions; for(JsonVariant prediction : predictions) { float probability prediction[probability].asfloat(); 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 时用ArduinoJson的DynamicJsonDocument doc(1024)解析 JSON取出顶层predictions数组数组中每个元素包含tagName、probability以及boundingBox等字段。阈值过滤对predictions数组逐条判断probability threshold通过的放入passed_predictions。资源释放httpClient.end()关闭连接。5.2 拍照与触发链路loop→buttonPressedmain.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 网络名称你的路由器 SSIDPASSWORDWiFi 密码你的 WiFi 密码PREDICTION_URLCustom Vision 检测端点 URL第五节开头提到的 detect 端点PREDICTION_KEY预测密钥Custom Vision 门户中获取的 Prediction-KeyCERTIFICATEAzure TLS 根证书仓库已内置 DigiCert Global Root G2一般无需改动工程依赖由 platformio.ini 声明目标板为seeed_wio_terminalAtmel SAM 平台 Arduino 框架依赖seeed-studio系列的 rpcWiFi、FS、SFUD、rpcUnified、mbedtls、RTC 库以及bblanchon/ArduinoJson 6.17.3。build_flags中定义了-DARDUCAM_SHIELD_V2与-DOV2640_CAM用于启用 ArduCAM 扩展板与 OV2640 摄像头支持——创建新工程时务必保留这两个宏否则摄像头驱动无法编译。六、下一步从检测走向计数本课完成的是检测而课程系列的目标是计数结合检测返回的boundingBox边界框由left、top、width、height四个 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),仅供参考
返回列表