ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

IoT-For-Beginners 实战:基于 Wio Terminal 的库存计数 —— 用边界框重叠过滤实现对货架商品的精准统计

2026/9/14 6:40:38 拓冰建站 浏览量
IoT-For-Beginners 实战:基于 Wio Terminal 的库存计数 —— 用边界框重叠过滤实现对货架商品的精准统计 IoT-For-Beginners 实战基于 Wio Terminal 的库存计数 —— 用边界框重叠过滤实现对货架商品的精准统计【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners本指南是微软 IoT-For-Beginners 开源课程12 周 / 24 课时零售项目第 2 课的收尾任务讲解如何在 Wio Terminal 上把上一课训练好的 Custom Vision 目标检测模型返回的边界框bounding box转化为货架上有几件商品的明确答案。读完本文你将掌握边界框的 0-1 归一化坐标体系、矩形相交面积计算、基于重叠阈值的去重过滤算法以及如何在受限的嵌入式环境中用 ArduinoJson 和 C 实现可运行的库存计数程序。为什么需要计数而不只是检测在零售场景中目标检测模型能够告诉设备图片里有番茄酱罐头但无法直接回答货架上还剩几罐。当摄像头对准可容纳 8 罐的货架时若检测结果只有 7 罐就意味着缺货需要补货如果检测到放错位置的异类商品例如婴儿玉米罐头混入番茄酱货架也可以及时提醒店员或补货机器人。目标检测返回的每次预测prediction包含三部分关键信息tagName检测到的对象标签如tomato pasteprobability置信度概率0-1 之间的小数boundingBox边界框对象内含left、top、width、height四个字段。计数的难点在于边界框可能重叠。例如同一种商品被多次检测、或者一个罐头被另一个罐头遮挡时模型可能为同一个物理对象输出两个高度重叠的边界框。如果直接按预测条数计数就会把一件商品数成两件。上图中各边界框只有轻微重叠可以直接按 4 个框计数但如果重叠明显加大就需要过滤掉那些指示同一对象的重复框。边界框的 0-1 归一化坐标系在设计过滤算法之前先理解 Custom Vision 返回的边界框格式。根据课程 README 中Bounding boxes章节的说明边界框用 4 个值定义top、left、height、width所有值都是0-1 之间的比例代表相对于图像尺寸的百分比位置坐标系原点(0,0)位于图像左上角bottom top heightright left width。坐标数值Top0.4Left0.4Height0.3Width0.2例如一张 600×800 像素的图片中left 0.4表示框左缘距图像左边界600 × 0.4 240像素。使用归一化坐标的好处是无论图像如何缩放边界框都精确地定位在图像长度的 0.4、高度的 0.4 处。任务忽略重叠的库存计数下面进入本课的核心编程任务。假设你已经完成了前一课从 IoT 设备调用目标检测器详见 wio-terminal-object-detector.md工程stock-counter已具备拍照、调用 Custom Vision、解析predictions数组的能力。本任务只改造预测处理逻辑完整代码可在 code-count/wio-terminal 目录中查阅。第一步定义重叠阈值在processPredictions函数上方添加一行const float overlap_threshold 0.20f;该常量定义两个边界框被视为同一对象之前允许的重叠百分比。0.20f即 20% 重叠。注意这是浮点字面量f后缀确保在嵌入式编译环境中按单精度处理避免与整数运算产生类型转换开销。第二步定义矩形结构与面积计算函数在阈值下方、processPredictions函数之上添加计算两个矩形重叠面积的辅助代码struct Point { float x, y; }; struct Rect { Point topLeft, bottomRight; }; float area(Rect rect) { return abs(rect.bottomRight.x - rect.topLeft.x) * abs(rect.bottomRight.y - rect.topLeft.y); } float overlappingArea(Rect rect1, Rect rect2) { float left max(rect1.topLeft.x, rect2.topLeft.x); float right min(rect1.bottomRight.x, rect2.bottomRight.x); float top max(rect1.topLeft.y, rect2.topLeft.y); float bottom min(rect1.bottomRight.y, rect2.bottomRight.y); if ( right left bottom top ) { return (right-left)*(bottom-top); } return 0.0f; }这段代码定义了三个要素Point结构保存图像上的一个点坐标(x, y)Rect结构用左上角topLeft与右下角bottomRight两个点定义矩形area函数由对角线两点计算矩形面积宽度 × 高度overlappingArea函数计算两个矩形的交集面积。其原理是取两个矩形左边缘的较大值、右边缘的较小值、上边缘的较大值、下边缘的较小值若right left bottom top说明存在实际相交区域返回(right-left)*(bottom-top)否则返回 0。该算法就是经典的轴对齐矩形求交axis-aligned rectangle intersection时间复杂度 O(1)非常适合资源有限的单片机。第三步把边界框转换成矩形在overlappingArea函数下方添加将 Custom Vision 的boundingBoxJSON 对象转换为Rect的函数Rect rectFromBoundingBox(JsonVariant prediction) { JsonObject bounding_box prediction[boundingBox].asJsonObject(); float left bounding_box[left].asfloat(); float top bounding_box[top].asfloat(); float width bounding_box[width].asfloat(); float height bounding_box[height].asfloat(); Point topLeft {left, top}; Point bottomRight {left width, top height}; return {topLeft, bottomRight}; }这里使用了ArduinoJson库的JsonVariant/JsonObject接口课程固定依赖bblanchon/ArduinoJson 6.17.3见 platformio.ini。函数从预测中提取boundingBox对象读取归一化坐标右上角right left width右下角bottom top height。这也是上一节坐标系说明在代码中的直接落地。第四步两两比较预测并过滤重叠现在改写processPredictions函数的主体。首先清空函数原有内容然后加入如下代码std::vectorJsonVariant passed_predictions; for (int i 0; i predictions.size(); i) { Rect prediction_1_rect rectFromBoundingBox(predictions[i]); float prediction_1_area area(prediction_1_rect); bool passed true; for (int j i 1; j predictions.size(); j) { Rect prediction_2_rect rectFromBoundingBox(predictions[j]); float prediction_2_area area(prediction_2_rect); float overlap overlappingArea(prediction_1_rect, prediction_2_rect); float smallest_area min(prediction_1_area, prediction_2_area); if (overlap (overlap_threshold * smallest_area)) { passed false; break; } } if (passed) { passed_predictions.push_back(predictions[i]); } }理解这段代码需要抓住三个关键设计成对比较且不重复外层循环从当前预测开始内层循环从j i 1开始。一旦比较过预测 1 与预测 2就不需要再比较 2 与 1只需继续与 3、4……比较。这样把比较次数从 O(n²) 降为 O(n²/2)。阈值必须乘以较小边界框的面积而非整图面积overlap_threshold是百分比直接与重叠面积比较没有意义。正确的语义是重叠面积不得超过较小边界框面积的 20%。因此代码先取min(prediction_1_area, prediction_2_area)再用overlap overlap_threshold * smallest_area判定。这与课程 README 中the overlap exceeds the given percentage of the bounding box, not the given percentage of the whole image的说明完全一致。命中即跳出一旦当前预测与任一其他预测的重叠超过阈值就标记passed false并break不再与该预测后面的对象比较。所有通过检查的预测被放入passed_predictions向量供后续输出使用。 这是一个非常简化的重叠移除方法只移除重叠对中的第一个。对于生产代码你可能需要加入更多逻辑例如考虑多个对象之间的重叠关系或者判断一个边界框是否完全包含另一个边界框。第五步向串口监视器输出通过的预测在过滤代码之后添加如下循环将过滤后每个预测的标签、置信度与边界框打印到串口for(JsonVariant prediction : passed_predictions) { String boundingBox prediction[boundingBox].asString(); String tag prediction[tagName].asString(); float probability prediction[probability].asfloat(); char buff[32]; sprintf(buff, %s:\t%.2f%%\t%s, tag.c_str(), probability * 100.0, boundingBox.c_str()); Serial.println(buff); }这里把probability0-1乘以 100 转换为百分比并保留两位小数%.2f%%标签与边界框 JSON 字符串以制表符\t分隔便于在串口监视器中对齐阅读。第六步输出最终计数在串口输出之后添加计数打印Serial.print(Counted ); Serial.print(passed_predictions.size()); Serial.println( stock items.);passed_predictions.size()即去重后实际检测到的商品数量。这个数字可以进一步发送到 IoT 服务例如 Azure IoT Hub用于在库存水平过低时触发告警。上传运行与结果验证将代码编译上传到 Wio Terminal把摄像头对准货架上的商品按下C 按钮Wio Terminal 上的物理按键即可在串口监视器看到如下输出Connecting to WiFi.. Connected! Image captured Image read to buffer with length 17416 tomato paste: 35.84% {left:0.395631,top:0.215897,width:0.180768,height:0.359364} tomato paste: 35.87% {left:0.378554,top:0.583012,width:0.14824,height:0.359382} tomato paste: 34.11% {left:0.699024,top:0.592617,width:0.124411,height:0.350456} tomato paste: 35.16% {left:0.513006,top:0.647853,width:0.187472,height:0.325817} Counted 4 stock items.可以看到 4 个边界框互不重叠或重叠很小全部通过过滤最终输出Counted 4 stock items.。建议动手调节overlap_threshold的值例如 0.1、0.3、0.5观察哪些预测被忽略从而理解阈值对计数结果的直接影响阈值越小过滤越严格越容易丢弃合法对象阈值越大过滤越宽松越容易把重复检测计入。从源码印证完整调用链本任务的最终实现对应仓库中的 main.cpp。从源码结构看完整的库存计数流程为setup()初始化串口9600 波特率、连接 WiFiconnectWiFi、初始化摄像头setupCamera、配置 C 按钮为输入上拉pinMode(WIO_KEY_C, INPUT_PULLUP)loop()轮询检测 C 按钮按下digitalRead(WIO_KEY_C) LOW触发buttonPressed()buttonPressed()调用camera.startCapture()等待拍照完成再通过camera.readImageToBuffer把 JPEG 读入内存缓冲区detectStock()以application/octet-stream内容类型和Prediction-Key请求头调用 Custom Vision 预测端点PREDICTION_URL/PREDICTION_KEY在 config.h 中配置响应体用 ArduinoJson 反序列化后取出predictions数组先用threshold概率阈值初筛再交给processPredictions()processPredictions()执行本文讲解的重叠过滤与计数输出。摄像头封装见 camera.h它基于 ArduCAM OV2640 传感器通过 SPI 读取 FIFO 中的 JPEG 数据。工程运行环境由 platformio.ini 定义PlatformIO 平台atmelsam、开发板seeed_wio_terminal、框架arduino依赖Seeed Arduino rpcWiFi、ArduinoJson 6.17.3等库并通过-DARDUCAM_SHIELD_V2 -DOV2640_CAM编译标志启用 ArduCAM 屏蔽板与 OV2640 传感器支持。需要特别注意的是config.h 中的SSID、PASSWORD、PREDICTION_URL、PREDICTION_KEY均为占位符运行前必须替换为你的 WiFi 凭据与上一课发布的 Custom Vision 迭代对应的预测 URL 和密钥发布方法见课程 README 的Publish an iteration of your object detector小节文件末尾还内嵌了 Microsoft Azure DigiCert Global Root G2 根证书用于与预测端点建立 TLS 安全连接。扩展思考判断放错位置的商品结合多个标签与边界框的位置约束例如定义每层货架允许出现的商品与区域可以让设备识别异物混入并告警正如课程 README 中rogue can示例所展示的场景。更精细的去重逻辑当前算法只移除重叠对中的第一个属于启发式简化生产环境可改用 NMS非极大值抑制思路结合置信度高低决定保留哪个框或处理一个框完全包含另一个框的嵌套情形。云端联动把passed_predictions.size()上报到 IoT 服务即可构建库存不足自动通知的完整闭环本课作业 assignment.md 则进一步要求把目标检测模型导出为紧凑模型并部署到边缘设备上运行。至此你的 Wio Terminal 库存计数程序已大功告成从拍照、调用云端检测器到边界框重叠过滤、输出最终计数一条完整的零售智能库存链路已经打通。【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考