将库存目标检测器部署到边缘:从 Custom Vision 紧凑模型到 IoT 设备端库存计数

发布时间:2026/9/16 13:46:22
将库存目标检测器部署到边缘:从 Custom Vision 紧凑模型到 IoT 设备端库存计数 将库存目标检测器部署到边缘从 Custom Vision 紧凑模型到 IoT 设备端库存计数【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners本篇技术指南基于 IoT-For-Beginners 仓库零售项目5-retail第 2 课《Check stock from an IoT device》的课后作业展开。该作业要求你将此前在云端训练好的库存目标检测器像上一个项目处理图像分类器那样以紧凑模型compact domain形式导出并在边缘设备上运行然后让 Wio Terminal、Raspberry Pi 或虚拟 IoT 设备直接调用边缘版本完成货架库存检测与计数。读完本文你将掌握从 Custom Vision 导出边缘可部署模型、通过 Azure IoT Edge 部署容器模块、从 IoT 设备以 REST/SDK 方式调用边缘推理接口以及结合边界框bounding box与重叠消除算法统计库存数量的完整实战路径。1. 作业定位把分类器的边缘部署经验迁移到目标检测器作业原文translations/cs/5-retail/lessons/2-check-stock-device/assignment.md给出的任务非常明确在上一项目中你已经把自己的图像分类器部署到了边缘现在要对目标检测器做同样的事情——将其导出为紧凑模型并在边缘运行且边缘版本必须能被你的 IoT 设备访问。这并非从零开始的边缘部署课程而是建立在两条既有知识链上的综合实践边缘部署链路来自 4-manufacturing 项目的第 3 课 Run your fruit detector on the edge其中完整讲述了边缘计算概念、Azure IoT Edge 设备注册、IoT Edge runtime 安装、模型导出、容器化部署以及从 IoT 设备调用边缘模型的流程目标检测器链路来自零售项目第 1 课 Train a stock detector其中完成了 Custom Vision 目标检测器的训练以及第 2 课 Check stock from an IoT device 中从 IoT 设备调用检测器、解析边界框、统计库存的代码实践。作业的考核点恰好落在两条链路的交叉处能够正确使用紧凑型域、能够导出目标检测器、能够在边缘运行并被 IoT 设备访问。下面我们逐层拆解。2. 为什么是目标检测器而不是图像分类器在动手部署之前先明确两者在推理结果上的本质差异这决定了边缘调用代码的结构。图像分类器对整张图片输出每个标签的概率每个标签只有一个置信度结果目标检测器在图片中找出一个或多个对象每个检测结果都附带标签、概率和边界框bounding box即对象在图片中的位置范围。如上图所示检测器在货架图片上圈出 4 个番茄酱罐头并分别给出 35.8%、33.5%、25.7%、16.6% 的概率。正因为返回的是多个对象 各自位置你才能进一步做库存计数——这是分类器做不到的。这个差异直接影响调用代码分类器调用classify_image而检测器必须调用detect_image并且由于检测器会对同一标签返回多个候选结果必须用阈值过滤低概率项。仓库中的完整可运行版本见 code-detect/pi/stock-counter/app.py。3. 边缘部署的目标把推理从云端移到本地网络边缘计算的核心思路是把数据处理和 AI 推理放到离数据产生地尽可能近的位置——也就是你自己的内部网络而不是跨互联网调用云端。在 4-manufacturing/lessons/3-run-fruit-detector-edge/README.md 中边缘部署被总结为以下几点收益这也正是本次作业的动机速度推理发生在同一内部网络数据不再跨洋往返网络拥塞风险更低弱网可用在连接不稳定或持续联网成本过高的场景下仍可工作降低成本数据采集、存储、分析与触发动作在边缘完成减少云端服务用量隐私与安全图像数据停留在本地网络分析完成后不必上云留存降低泄露风险。结合零售场景这意味着门店内摄像头拍摄的货架画面可以在本店边缘设备上直接完成推理只有库存不足出现异常商品这类结论或少量分析数据才需要上报。当货架可放 8 罐番茄酱而检测器只数出 7 罐时IoT 设备即可向店员或补货机器人发出补货通知甚至结合边界框位置信息定位空缺处——这正是本课 README 所描述的核心用例。当然边缘也有短板扩容需要人工增加设备、冗余与灾备配置成本高、系统维护需要自己负责。生产系统中通常按需在云端与边缘之间做混合部署。4. 前置条件使用紧凑型域compact domain训练检测器作业评测标准的第一条就是能够使用正确的紧凑型域。这是 Custom Vision 导出边缘模型的硬性前提只有使用紧凑型域compact domain训练的迭代才支持导出为可在本地运行的模型文件或容器镜像。回顾训练过程在零售项目第 1 课中创建stock-detector项目时项目类型选择Object Detection域选择Products on Shelves针对货架商品检测优化。如果你的训练项目使用的域不支持导出则需要重新创建一个使用紧凑型域的项目并重新训练——这正是作业中使用正确的紧凑型域这一考核点的意义所在。5. 导出紧凑模型并容器化模型导出在 Custom Vision 门户完成流程与上一项目导出分类器一致打开stock-detector项目进入顶部Performance性能标签页在侧边 Iterations 列表中选择最新迭代点击该迭代的Export导出按钮按目标平台选择导出格式如 Dockerfile / TensorFlow 等将导出的工件通常以容器镜像形式推送到自己的容器仓库供边缘设备拉取。提示如果你在 Raspberry Pi 这类 ARM 架构设备上运行务必选择与 CPU 架构匹配的镜像标签否则容器无法启动。容器化部署的具体操作构建镜像、推送到 Azure Container Registry、通过 IoT Edge 模块下发到设备可回看 4-manufacturing/lessons/3-run-fruit-detector-edge/README.md 中的 Export your model 与 Prepare your container for deployment 章节。Custom Vision 导出的检测模型容器对外暴露的 REST API 与云端版本一致区别仅在 endpoint 指向边缘设备而非 Azure 认知服务地址。6. 注册边缘设备并部署运行在边缘设备上跑模型前需要先在 Azure IoT Hub 中注册一台 IoT Edge 设备。命令与注册普通设备类似多一个--edge-enabled标志az iot hub device-identity create --edge-enabled \ --device-id stock-detector-edge \ --hub-name hub_name随后取得该设备的连接字符串在边缘设备上安装 IoT Edge runtime 并配置连接字符串。runtime 就绪后即可把第 5 步的检测模型容器作为 IoT Edge 模块下发部署。部署完成后边缘设备上就运行了一个本地版 Custom Vision 推理服务。之后 IoT 设备只需把推理 endpoint 从云端的https://location.api.cognitive.microsoft.com/customvision/...换成边缘设备的地址其余调用方式不变。这样既保留了 Custom Vision 训练流程又获得了边缘推理的速度、成本与隐私收益。7. 从 IoT 设备调用边缘版目标检测器完成边缘部署后IoT 设备侧调用代码与调用云端检测器基本相同。下面分别给出 Python 与嵌入式两条路径。7.1 Raspberry Pi / 虚拟设备Python SDK在 single-board-computer-object-detector.md 中核心改动是把classify_image换成detect_image并引入概率阈值过滤results predictor.detect_image(project_id, iteration_name, image) threshold 0.3 predictions list(prediction for prediction in results.predictions if prediction.probability threshold) for prediction in predictions: print(f{prediction.tag_name}:\t{prediction.probability * 100:.2f}%)这段代码调用detect_image运行目标检测把所有概率高于阈值的预测收集起来打印。与分类器每标签只返回一个结果不同检测器会返回大量候选框低概率项必须过滤。完整可运行代码见 code-detect/pi/stock-counter/app.py其中还展示了如何从预测 URL 中解析 endpoint、project_id 与 iteration_nameparts prediction_url.split(/) endpoint https:// parts[2] project_id parts[6] iteration_name parts[9]在虚拟设备CounterFit场景下该代码对应 code-detect/virtual-iot-device 目录。典型输出piraspberrypi:~/stock-counter $ python3 app.py tomato paste: 34.13% tomato paste: 33.95% tomato paste: 35.05% tomato paste: 32.80%提示threshold默认 0.3需要根据实际图片和模型表现调整过低会混入误检过高会漏检。7.2 Wio TerminalArduino/C在 wio-terminal-object-detector.md 中检测逻辑通过 HTTP POST 直接调用预测接口实现。仓库中的完整代码见 code-count/wio-terminal/stock-counter/src/main.cppvoid 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(); }要点包括通过Prediction-Key请求头传递访问密钥、以application/octet-stream上传 JPEG 字节流、用 ArduinoJson 解析返回的predictions数组并按阈值过滤。阈值常量声明在 main.cppconst float threshold 0.0f; const float overlap_threshold 0.20f;8. 进阶实战用边界框完成货架库存计数作业的隐含目标是让边缘检测结果真正可用而边界框正是把检测升级为计数的关键。8.1 边界框的四个值边界框由四个 0–1 之间的比例值定义top、left、height、width。坐标系原点在图片左上角数值表示占图片尺寸的比例因此无论图片缩放成什么分辨率比例值都不会失真。例如某张 800 像素高的图片中边界框距顶部 320 像素则 top 0.4。结合概率与边界框还可以做更聪明的判断当两个边界框高度重叠例如一个框完全套在另一个框里时物理上一个罐头不可能包含另一个罐头代码可以据此忽略低概率的重复检测。判断规则在本课 README 的 Bounding boxes 一节有详细讲解。8.2 Python打印、绘制与重叠消除先在打印语句中带上边界框print(f{prediction.tag_name}:\t{prediction.probability * 100:.2f}%\t{prediction.bounding_box})再用 Pillow 把边界框画回图片上便于调试见 single-board-computer-count-stock.mdwith Image.open(image.jpg) as im: draw ImageDraw.Draw(im) for prediction in predictions: scale_left prediction.bounding_box.left scale_top prediction.bounding_box.top scale_right prediction.bounding_box.left prediction.bounding_box.width scale_bottom prediction.bounding_box.top prediction.bounding_box.height left scale_left * im.width top scale_top * im.height right scale_right * im.width bottom scale_bottom * im.height draw.rectangle([left, top, right, bottom], outlineImageColor.getrgb(red), width2) im.save(image.jpg)比例坐标乘以图片实际宽高即得像素坐标绘制结果如下随后用 Shapely 计算两两边界框的交叠面积并删除重叠超限的重复检测。核心逻辑见 code-count/pi/stock-counter/app.pyoverlap_threshold 0.002 def create_polygon(prediction): scale_left prediction.bounding_box.left scale_top prediction.bounding_box.top scale_right prediction.bounding_box.left prediction.bounding_box.width scale_bottom prediction.bounding_box.top prediction.bounding_box.height return Polygon([(scale_left, scale_top), (scale_right, scale_top), (scale_right, scale_bottom), (scale_left, scale_bottom)]) to_delete [] for i in range(0, len(predictions)): polygon_1 create_polygon(predictions[i]) for j in range(i1, len(predictions)): polygon_2 create_polygon(predictions[j]) overlap polygon_1.intersection(polygon_2).area smallest_area min(polygon_1.area, polygon_2.area) if overlap (overlap_threshold * smallest_area): to_delete.append(predictions[i]) break for d in to_delete: predictions.remove(d) print(fCounted {len(predictions)} stock items)注意重叠阈值不是绝对面积而是以两个框中较小者的面积为分母的比例overlap_threshold * smallest_area这样判断的是小框被覆盖了多少比例与图片绝对尺寸无关。教程版阈值为 0.2020%仓库代码中 code-count/pi/stock-counter/app.py 使用的overlap_threshold 0.002是面向该示例图片调优后的更严格取值你需要针对自己的摄像头画面调整。8.3 Wio Terminal纯 C 的矩形交叠计算嵌入式端没有 Shapely仓库用纯 C 实现了同样的思路见 wio-terminal-count-stock.md 与 main.cppstruct 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; }rectFromBoundingBox从 JSON 的boundingBox对象取出 left/top/width/height 并构造成矩形右下角坐标由 leftwidth、topheight 计算得到。processPredictions中采用两两比较第 i 个框只与第 i1 个及其后的框比较避免重复比较重叠超过overlap_threshold * smallest_area即判定为同一对象并剔除最后打印存活数量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.教程也明确指出这种遇到重叠就删掉先出现者的做法非常简化生产环境应加入更多逻辑例如处理多对象之间的复杂重叠关系、或判断一个框是否完全被另一个框包含。计数结果随后可以上报 IoT 服务在库存低于阈值时触发补货告警。9. 用边缘采集的真实数据重训模型与图像分类器一样用 IoT 设备在真实光照、角度、货架条件下采集的图像可以重训目标检测器使其在边缘场景下表现更稳。差异在于目标检测器重训时不能只给图片打标签而必须逐一核对模型输出的每个边界框——框住了错误对象就删除、位置不准就调整、漏框则补充全部修正后再点击Train重训。重训并发布新迭代后导出新的边缘镜像并重新部署到边缘设备即完成一轮边缘数据 → 云端重训 → 边缘再部署的闭环。10. 作业评测标准作业的考核量表原文见 assignment.md如下标准优秀合格待改进将目标检测器部署到边缘能够使用正确的紧凑型域导出目标检测器并在边缘运行能够使用正确的紧凑型域并导出目标检测器但无法在边缘运行无法使用正确的紧凑型域无法导出目标检测器也无法在边缘运行对照该表自查即可确认紧凑型域训练第 4 节→ 模型导出第 5 节→ 边缘运行与 IoT 设备访问第 6、7 节三个环节缺一不可。11. 收尾别忘了清理云资源这是零售项目中的最后一课。按照本课 README 的提醒完成作业前你需要保留云服务因此务必先完成部署与验证然后再按 clean-up.md 的指引清理 Custom Vision、IoT Hub 等云资源避免产生持续费用。12. 挑战延伸完成基础作业后教程还给出了两条延伸挑战错误商品检测在模型上训练多种对象当检测到不属于该货架的商品例如番茄酱货架上的玉米罐头时发出告警错位检测对同一货架上并排摆放的多种商品通过给边界框设定位置范围限制判断商品是否被放错位置。这两条挑战可以进一步把第 8 节的边界框运算能力位置、重叠、坐标比例与第 5 节起的边缘部署链路组合起来形成一套完整的门店智能补货与理货方案。【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考