news 2026/9/17 6:14:26

IoT 设备调用 Azure Custom Vision Python SDK 实现水果品质图像分类(IoT-For-Beginners 实操)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
IoT 设备调用 Azure Custom Vision Python SDK 实现水果品质图像分类(IoT-For-Beginners 实操)

IoT 设备调用 Azure Custom Vision Python SDK 实现水果品质图像分类(IoT-For-Beginners 实操)

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

在 IoT-For-Beginners 的「制造业:从设备端检查水果品质」一课中,你已经在上一课用 Custom Vision 训练出了能够区分熟果与生果的图像分类器。本篇文章基于该课程文档,完整讲解如何在你自己的 IoT 设备(Raspberry Pi 或 CounterFit 虚拟设备)上拍摄图像,并借助azure-cognitiveservices-vision-customvisionPython SDK 将图像发送到云端进行分类,最终在终端输出ripe/unripe的预测概率。读完本文,你将掌握 Custom Vision Prediction SDK 的完整调用链路、预测 URL 的拆分原理,以及如何用 IoT 设备采集的真实图像持续迭代模型。

前置条件:发布模型迭代并获取预测凭据

在编写任何分类代码之前,你需要先完成两件事:把训练好的模型发布为一个可被外部应用调用的迭代版本,并拿到预测 API 的 URL 与密钥。这两步都发生在 Custom Vision 门户中,详细步骤见 本课 README 的 "Publish your image classifier" 一节:

  1. 登录 Custom Vision 门户,打开你的fruit-quality-detector项目;
  2. 进入Performance(性能)标签页,从侧边Iterations(迭代)列表中选择最新一次训练迭代;
  3. 点击该迭代的Publish(发布)按钮,在弹出的Publish Model对话框中,把Prediction resource选为上一课创建的fruit-quality-detector-prediction资源,名称保持Iteration2并确认发布;
  4. 点击Prediction URL按钮,复制下半部分If you have an image file对应的 URL 与Prediction-Key值。

发布的预测 URL 形如:

https://<location>.api.cognitive.microsoft.com/customvision/v3.0/Prediction/<id>/classify/iterations/Iteration2/image

其中<location>是创建 Custom Vision 资源时选择的区域,<id>是一长串由字母和数字组成的项目标识。Prediction-Key是调用模型时必须携带的安全密钥——只有携带该密钥的应用才被允许调用模型,其他请求都会被拒绝。

✅ 思考题:每次发布新迭代都会得到不同的名称,当 IoT 设备切换使用的模型版本时,需要改动代码中的哪一部分?

环境准备与 SDK 安装

分类代码运行在与摄像头程序相同的fruit-quality-detector项目中(Raspberry Pi 版或虚拟设备版)。先用 VS Code 打开该文件夹;如果使用的是虚拟 IoT 设备,请确保终端中已激活虚拟环境。

Custom Vision 提供了现成的 Python SDK,以 Pip 包形式分发,安装命令如下:

pip3 install azure-cognitiveservices-vision-customvision

安装完成后即可在app.py中导入相关模块。

逐步编写图像分类代码

整个分类流程可以拆成五个环节:导入 SDK 模块 → 配置预测 URL 与密钥 → 拆分 URL 得到端点/项目 ID/迭代名 → 创建预测客户端 → 发送图像并解析结果。

第一步:导入 SDK 依赖

app.py文件顶部追加两行导入语句:

from msrest.authentication import ApiKeyCredentials from azure.cognitiveservices.vision.customvision.prediction import CustomVisionPredictionClient

这两行分别引入了两个职责不同的模块:ApiKeyCredentials负责用 Prediction Key 完成请求身份认证;CustomVisionPredictionClient则提供一个可以调用 Custom Vision 的预测客户端类。如果你同时使用 Raspberry Pi 摄像头,app.py顶部还会包含picameraiotime等标准库导入(详见下文完整代码)。

第二步:配置预测 URL 与密钥

在文件末尾加入如下代码,并替换占位符:

prediction_url = '<prediction_url>' prediction_key = '<prediction key>'

<prediction_url>替换为你在Prediction URL对话框中复制的完整 URL;<prediction key>替换为同一对话框复制的密钥。

第三步:拆分预测 URL

Prediction URL对话框给出的 URL 是为直接调用 REST 端点设计的,而 Python SDK 会在不同位置分别使用 URL 的不同片段。因此需要用split('/')把它拆成需要的三部分:

parts = prediction_url.split('/') endpoint = 'https://' + parts[2] project_id = parts[6] iteration_name = parts[9]

对照上文的标准 URL 格式可以看清每个下标对应的语义:

下标URL 片段提取结果说明
parts[2]<location>.api.cognitive.microsoft.comendpoint拼接上https://前缀后即 REST 端点地址
parts[6]长串字母数字 IDproject_idCustom Vision 项目 ID
parts[9]Iteration2iteration_name已发布迭代的名称

注意这里的下标(2、6、9)是依据上述固定 URL 路径结构推导的,所以只要使用Prediction URL对话框复制的标准格式 URL,拆分结果就是稳定的。

第四步:创建 Predictor 预测对象

用密钥构造凭据对象,再以端点和凭据创建预测客户端:

prediction_credentials = ApiKeyCredentials(in_headers={"Prediction-key": prediction_key}) predictor = CustomVisionPredictionClient(endpoint, prediction_credentials)

prediction_credentials把 Prediction Key 封装进 HTTP 请求头(Header 键名为Prediction-key);predictor则指向拆分出的端点,后续所有预测请求都由它发出。

第五步:发送图像并展示结果

前面摄像头程序把图像保存在内存中的io.BytesIO对象里,发送前需要先把读写位置回卷到起点,再交给预测客户端:

image.seek(0) results = predictor.classify_image(project_id, iteration_name, image)

image.seek(0)BytesIO内部的位置指针移回数据起始处,确保上传的是完整的 JPEG 数据(摄像头程序在捕获后指针已停在末尾)。classify_image会同步返回预测结果对象。

最后遍历结果,把每个标签的置信度打印到终端:

for prediction in results.predictions: print(f'{prediction.tag_name}:\t{prediction.probability * 100:.2f}%')

返回的概率是 0~1 之间的浮点数:0 表示与某个标签 0% 匹配,1 表示 100% 匹配。乘以 100 并保留两位小数后更适合阅读。

💁 图像分类器会返回所有已使用标签的百分比——每个标签都对应一个"图像属于该标签"的概率,而非只给出一个"最佳答案"。

第六步:运行并解读输出

保持摄像头对准水果(真实硬件对准实物;虚拟设备则在 CounterFit 中配置图像文件或 WebCam 源),执行:

python app.py

终端输出示例:

(.venv) ➜ fruit-quality-detector python app.py ripe: 56.84% unripe: 43.16%

同时在 Custom Vision 门户的Predictions(预测)标签页中,可以看到刚上传的图像以及完全一致的ripe 56.8% / unripe 43.1%数值,便于对照校验。

仓库源码佐证:完整可运行实现

仓库中提供了两套与本步骤对应的完整实现,可直接对照或运行:

  • Raspberry Pi 版:code-classify/pi/fruit-quality-detector/app.py
  • 虚拟 IoT 设备版:code-classify/virtual-iot-device/fruit-quality-detector/app.py

两版代码的主体逻辑完全一致,唯一差异是导入来源:真实 Pi 使用from picamera import PiCamera,虚拟设备使用from counterfit_shims_picamera import PiCamera并额外调用CounterFitConnection.init('127.0.0.1', 5000)连接 CounterFit 服务。

以 Raspberry Pi 版为例,完整源码如下:

import io import time from picamera import PiCamera from azure.cognitiveservices.vision.customvision.prediction import CustomVisionPredictionClient from msrest.authentication import ApiKeyCredentials camera = PiCamera() camera.resolution = (640, 480) camera.rotation = 0 time.sleep(2) image = io.BytesIO() camera.capture(image, 'jpeg') image.seek(0) with open('image.jpg', 'wb') as image_file: image_file.write(image.read()) prediction_url = '<prediction_url>' prediction_key = '<prediction key>' parts = prediction_url.split('/') endpoint = 'https://' + parts[2] project_id = parts[6] iteration_name = parts[9] prediction_credentials = ApiKeyCredentials(in_headers={"Prediction-key": prediction_key}) predictor = CustomVisionPredictionClient(endpoint, prediction_credentials) image.seek(0) results = predictor.classify_image(project_id, iteration_name, image) for prediction in results.predictions: print(f'{prediction.tag_name}:\t{prediction.probability * 100:.2f}%')

从源码结构可以看出几个值得注意的工程细节:

  • 分辨率设定为(640, 480):虽然 PiCamera 支持最高 3280×2464 的分辨率,但图像分类器实际工作在很小的输入上(Custom Vision 内部使用 227×227),捕获并上传更大的图片只会徒增带宽与延迟;
  • time.sleep(2):摄像头硬件启动需要数秒,脚本先给相机预热时间再执行捕获(虚拟设备版无此步骤);
  • camera.rotation = 0:设置图像旋转角度。若摄像头排线被悬挂导致画面颠倒,可改为camera.rotation = 180之类按需调整;
  • BytesIO而非直接写文件:摄像头先把 JPEG 写入内存缓冲区,既便于之后seek(0)上传,也顺带用open('image.jpg', 'wb')落盘了一份供人工检查;
  • seek(0)classify_image:同一个BytesIO对象先被camera.capture写入、后被open(...).write(image.read())读走,位置指针位于末尾,因此分类前必须重新回卷到开头。

摄像头采集部分的完整代码位于 code-camera/pi/fruit-quality-detector/app.py 与 code-camera/virtual-iot-device/fruit-quality-detector/app.py。

💁 关于真实 Raspberry Pi,需要注意:自 Raspberry Pi OS Bullseye 起,系统默认相机软件已变更,picamera默认不可用。需要先在终端执行以下命令切换到 legacy camera 模式并重启(该操作会自动启用相机接口):

sudo raspi-config nonint do_legacy 0 sudo reboot

为什么预测结果可能与预期不符:用设备图像迭代模型

你可能会发现,用设备摄像头拍出的图像预测准确率,往往不如直接上传到门户的图片。这并非代码问题,而是训练数据与预测数据分布不一致导致的:模型是用你手机/门户上传的图片训练的,而 IoT 设备摄像头的画质、锐度、色彩都与训练数据有肉眼可见的差异。

上图中左侧香蕉由 Raspberry Pi Camera 拍摄,右侧是同一根香蕉、同一位置由 iPhone 拍摄的,明显更清晰、色彩更亮、对比度更高。要让分类器在实际部署中表现稳定,最直接的方法是用设备真实采集的图像重新训练模型,具体步骤见 本课 README 的 "Improve the model" 一节:

  1. 用 IoT 设备分别拍摄多张熟果与生果图像;
  2. 在 Custom Vision 门户的Predictions标签页中把这些设备图加入训练集并重新训练(细节可参考第一课的重训说明);
  3. 若设备图像与原训练图差异过大,可在Training Images标签页勾选删除原始图片;
  4. 训练出新的迭代并重新发布;
  5. 更新代码中的预测 URL(主要是迭代名部分),重新运行应用;
  6. 重复上述流程,直到预测结果满意为止。

✅ 思考题:除了图像质量,IoT 设备的部署环境还可能从哪些方面影响预测准确性?例如光照方向、背景杂乱程度、拍摄角度、运动模糊等。

扩展挑战:让设备对分类结果做出响应

分类得到的不只是终端上的一串数字——设备完全可以依据概率值采取行动。本课作业 assignment.md 要求你为设备添加"响应逻辑",可选方向包括:

  • 将分类结果发送到 IoT Hub,交给其他系统进一步处理;
  • 直接控制执行器,例如当水果被判定为unripe时点亮 LED;
  • 两者结合:数据上云后由 Serverless 代码判断成熟度,再下发命令控制执行器。

评估标准也给出了分层要求:优秀实现是"响应能稳定地随预测值变化",一般实现是"响应与预测值无关(如仅裸传数据)"。建议在完成本文分类链路后,顺势完成这一作业,把"感知—推理—行动"闭环跑通。

小结

本文基于 IoT-For-Beginners 制造业课程的 single-board-computer-classify-image.md,完整覆盖了从安装azure-cognitiveservices-vision-customvision、解析预测 URL、构造CustomVisionPredictionClient、到调用classify_image并输出概率的全过程。配合仓库中 code-classify 的完整示例代码,你已经可以在 Raspberry Pi 或 CounterFit 虚拟设备上,把摄像头捕获的图像实时送入云端图像分类器,并据此搭建完整的水果品质检测 IoT 应用。

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

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 6:12:06

10MB的Postman替代品,秒开轻量级API调试工具实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/17 6:11:08

开源办公套件LibreOffice:免费搞定PDF转Excel与表格处理

前一阵有朋友发消息问我&#xff1a;PDF里有个表格&#xff0c;想抠出来做成Excel&#xff0c;WPS提示要开会员&#xff0c;网上那些“免费转换工具”不是要上传云端就是带水印&#xff0c;有没有靠谱办法&#xff1f;我回了一句&#xff1a;换LibreOffice试试&#xff0c;以后…

作者头像 李华
网站建设 2026/9/17 6:10:09

DDR3到DDR4迁移选型避坑指南:电源、拓扑与训练全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/17 6:09:30

Sublime Text4 配置 Python 运行环境:构建系统与插件实战

1. 为什么我最后还是回到 Sublime Text4 搭 Python 环境Sublime Text4 搭 Python 运行环境这件事&#xff0c;我从 Sublime Text3 时代一直折腾到现在&#xff0c;中间来来回回换过 PyCharm、VS Code&#xff0c;甚至尝试过在终端里纯靠 vim 加插件硬扛&#xff0c;最后还是回到…

作者头像 李华
网站建设 2026/9/17 6:09:03

Python爬取Boss直聘大数据岗位并进行数据清洗与可视化分析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华