IoT-For-Beginners 实战:Wio Terminal 调用 Custom Vision REST API 实现水果图像分类

发布时间:2026/9/17 1:23:23
IoT-For-Beginners 实战:Wio Terminal 调用 Custom Vision REST API 实现水果图像分类
IoT-For-Beginners 实战Wio Terminal 调用 Custom Vision REST API 实现水果图像分类【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners导读本文对应 IoT-For-Beginners 课程第 4 单元「制造」第 2 课《从 IoT 设备检查水果质量》的 Wio Terminal 环节完整讲解如何在基于 ArduinoSAMD51的 Wio Terminal 上通过 HTTPS 安全连接将摄像头捕获的 JPEG 图片发送到 Azure Custom Vision 图像分类服务并解析返回的 JSON 预测结果。读完本文你将掌握在微控制器上手工配置 SSL 根证书、使用WiFiClientSecureHTTPClient发起 HTTPS POST 上传二进制图片、借助 ArduinoJson 解析predictions数组并将分类概率实时输出到串口监视器的完整可运行方案。一、整体背景从「拍照」到「分类」的闭环在上一环节wio-terminal-camera.md中你已经完成了 Wio Terminal 的硬件搭建通过 SPI 总线连接 ArduCAM Mini 2MP PlusOV2640 传感器并用 C 按钮WIO_KEY_C触发拍照把 JPEG 数据读入内存缓冲区。本文要解决的是闭环的后半段把这些图片数据交给云端训练好的分类模型得到「ripe成熟/ unripe未成熟」之类的概率结果。从课程总览 4-manufacturing/lessons/2-check-fruit-from-device/README.md 可以看到调用分类器之前还需要在 Custom Vision 门户中完成两件事发布模型迭代Publish an iteration在 Custom Vision 项目的Performance页签选中最新迭代点击Publish得到形如https://location.api.cognitive.microsoft.com/customvision/v3.0/Prediction/id/classify/iterations/Iteration2/image的预测 URL 和Prediction-Key密钥从 IoT 设备调用分类器把 URL 与密钥写入固件通过 REST API 完成图片分类。本文即聚焦第 2 步在 Wio Terminal 上的完整实现。二、HTTPS 与微控制器的特殊之处必须手工植入根证书Custom Vision 的 REST API 运行在 HTTPS安全 HTTP之上。与浏览器自动完成 TLS 握手不同微控制器不会自动获取并校验服务器的公钥证书必须在客户端代码中手工指定可信的 CA 根证书用它加密与服务器之间的通信流量。关于证书有两点重要事实这类证书包含的是公开密钥不需要保密可以安全地硬编码在源代码中甚至公开发布在代码托管平台上证书内容基本不变因此「一次配置、长期使用」完全可行。原文档给出的验证方法是在 macOS/LinuxWindows 可通过 WSL执行以下命令检查api.cognitive.microsoft.com:443端点实际下发的证书链中是否包含 DigiCert Global Root G2openssl s_client -showcerts -verify 5 -connect api.cognitive.microsoft.com:443从 code-classify/wio-terminal/fruit-quality-detector/src/config.h 的完整源码可以看到仓库中已内置这份Microsoft Azure DigiCert Global Root G2 根证书它被全球众多 Azure 服务使用可直接以const char *CERTIFICATE的形式写入config.hconst char *CERTIFICATE -----BEGIN CERTIFICATE-----\r\n MIIF8zCCBNugAwIBAgIQAueRcfuAIek/4tmDg0xQwDANBgkqhkiG9w0BAQwFADBh\r\n ...完整 PEM 内容见仓库 config.h... -----END CERTIFICATE-----\r\n;证书以\r\n拼接成多行字符串是为了保持 PEM 文件的标准换行格式满足底层 mbedTLS 的解析要求。三、任务一配置 SSL 客户端WiFiClientSecure本环节需要修改fruit-quality-detector项目若从零开始可参照 wio-terminal-camera.md 先完成拍照功能。仓库完整成品位于 code-classify/wio-terminal。3.1 在config.h中加入根证书打开config.h把上述CERTIFICATE常量追加进去。它在仓库中的完整位置见 config.h。3.2 引入安全客户端头文件并创建实例在main.cpp顶部 include 区加入#include WiFiClientSecure.h并在所有 include 语句之后声明全局实例WiFiClientSecure client;WiFiClientSecure是 Arduino 生态中负责通过 HTTPS 与 Web 端点通信的安全客户端类——普通的WiFiClient只能建立明文 TCP/HTTP 连接而安全客户端会在握手阶段使用你指定的 CA 证书完成服务器身份验证并加密后续流量。3.3 在connectWiFi中绑定 CA 证书在 WiFi 连接成功后调用setCACert把根证书注入安全客户端void connectWiFi() { while (WiFi.status() ! WL_CONNECTED) { Serial.println(Connecting to WiFi..); WiFi.begin(SSID, PASSWORD); delay(500); } client.setCACert(CERTIFICATE); Serial.println(Connected!); }对应源码见 main.cpp 的 connectWiFi 函数。SSID/PASSWORD同样定义在config.h中需要替换为你的真实 WiFi 凭据。注意setCACert必须在发起任何 HTTPS 请求之前调用且必须在 WiFi 连接建立后执行——这也是把它放在connectWiFi()末尾的原因。四、任务二编写classifyImage函数调用分类服务4.1 添加 ArduinoJson 依赖Custom Vision 的 REST API 返回 JSON 文档Wio Terminal 这类内存受限的微控制器没有内置 JSON 解析器需要使用第三方库。在 platformio.ini 的lib_deps列表中追加一行bblanchon/ArduinoJson 6.17.3当前仓库的platformio.ini中lib_deps还包含seeed-studio/Seeed Arduino rpcWiFi 1.0.5、Seeed Arduino FS 2.1.1、Seeed_Arduino_mbedtls 3.0.1等依赖其中mbedtls正是 Wio Terminal 平台上 TLS 的底层实现并设置了两个编译标志build_flags -w -DARDUCAM_SHIELD_V2 -DOV2640_CAM4.2 在config.h中登记预测 URL 与密钥const char *PREDICTION_URL PREDICTION_URL; const char *PREDICTION_KEY PREDICTION_KEY;PREDICTION_URL替换为 Custom Vision 门户Prediction URL对话框中If you have an image file部分给出的 URL即发布迭代时获取的.../classify/iterations/Iteration2/image端点PREDICTION_KEY替换为门户中显示的Prediction-Key值它是调用模型的访问凭据只有携带正确密钥的应用才被允许使用模型。4.3 引入 ArduinoJson 头文件在main.cpp顶部加入#include ArduinoJSON.h4.4 实现classifyImage函数在buttonPressed函数上方新增以下函数完整实现见 main.cpp 的 classifyImage 函数void classifyImage(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(); 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); } } httpClient.end(); }逐段拆解其工作原理声明 HTTPClientHTTPClient是 Arduino 生态中封装 REST API 交互的客户端类begin(client, PREDICTION_URL)将刚才配好根证书的WiFiClientSecure实例与预测端点绑定从而走 HTTPS。设置请求头Content-Type: application/octet-stream——声明请求体是原始二进制数据JPEG 图片Prediction-Key: 密钥——携带 Custom Vision 预测密钥完成身份认证。发起 POST 请求httpClient.POST(buffer, length)把从摄像头读出的 JPEG 字节数组作为请求体上传。POST 语义是「发送数据并获取响应」区别于浏览器加载网页用的 GET仅获取数据。检查状态码HTTP 状态码是标准定义的值200表示OK即分类成功。非 200如 401 密钥错误、404 端点错误则跳过解析分支。读取并解析响应成功后getString()取回 JSON 文本DynamicJsonDocument doc(1024)分配 1 KB 动态内存deserializeJson完成解析。遍历 predictions 数组对每个标签取出tagName与probability用sprintf格式化为标签: xx.xx%输出到串口。4.5 在按钮处理函数中接入分类调用在buttonPressed中把原来保存到 SD 卡的代码替换为classifyImage调用或保留存卡但在删除缓冲区之前追加调用if (camera.readImageToBuffer(buffer, length)) { Serial.print(Image read to buffer with length ); Serial.println(length); classifyImage(buffer, length); delete (buffer); }若选择替换掉 SD 卡保存逻辑可顺带删除setupSDCard与saveToSDCard函数让工程更精简。从源码结构看code-classify/wio-terminal 的成品正是这样清理过的版本——它不再包含 SD 卡相关 include只保留相机与分类链路。五、理解响应 JSONpredictions数组是核心Custom Vision 分类接口返回的 JSON 文档结构如下示例响应{ id:45d614d3-7d6f-47e9-8fa2-04f237366a16, project:135607e5-efac-4855-8afb-c93af3380531, iteration:04f1c1fa-11ec-4e59-bb23-4c7aca353665, created:2021-06-10T17:58:58.959Z, predictions:[ { probability:0.5582016, tagId:05a432ea-9718-4098-b14f-5f0688149d64, tagName:ripe }, { probability:0.44179836, tagId:bb091037-16e5-418e-a9ea-31c6a2920f17, tagName:unripe } ] }要点顶层字段id、project、iteration、created为请求元数据代码中未使用predictions数组才是关键每个标签一个条目包含tagName标签名与probability概率概率是 0–1 的浮点数0表示 0% 匹配1表示 100% 匹配。代码中probability * 100.0即转换为百分比图像分类器会为所有已训练标签返回概率各标签概率之和通常为 1softmax 归一化结果。六、烧录运行与结果验证使用 PlatformIO 构建并上传固件到 Wio Terminalenv:seeed_wio_terminal见 platformio.ini。打开串口监视器波特率 9600见main.cpp中Serial.begin(9600)。将摄像头对准水果按下顶部靠近电源开关的C 按钮WIO_KEY_CINPUT_PULLUP模式按下为低电平触发。预期输出类似Connecting to WiFi.. Connected! Image captured Image read to buffer with length 8200 ripe: 56.84% unripe: 43.16%其中Image read to buffer with length 8200说明本次 JPEG 图片约 8200 字节图片内容不同压缩后大小不同随后是各标签的预测百分比。关于按键触发loop()中检测到WIO_KEY_C为低电平即调用buttonPressed()随后delay(2000)防止长按重复触发。buttonPressed内部先startCapture()再轮询captureReady()等待相机把图像压缩为 JPEG 存入片内 FIFO最后readImageToBuffer读出字节数组细节见 camera.h。打开 Custom Vision 门户的Predictions页签还能看到本次上传的图片及与串口一致的预测值。以下是香蕉测试的典型结果预测为成熟ripe56.8%、未成熟unripe43.1%。七、性能与准确性提升建议课程 README.md 指出IoT 设备拍摄的照片与训练集图片差异越大预测越不准。几点实操建议分辨率匹配模型输入仅为 227×227因此 camera.h 中Camera camera Camera(JPEG, OV2640_640x480)选用 640×480 已足够无需传输更大图片迭代重训在 Custom VisionPredictions页签用设备实拍图重训模型并重新发布迭代然后更新PREDICTION_URL中的迭代名如Iteration3光照一致性保持拍摄环境光照稳定必要时在setup中先拍摄几张丢弃等待摄像头自动白平衡收敛。八、小结本文在 Wio Terminal 上完成了从「拍摄 JPEG 图片」到「HTTPS 上传 Custom Vision」再到「解析 JSON 概率」的完整链路。三个关键技术点值得记住微控制器不自动信任服务器证书必须通过client.setCACert(CERTIFICATE)手工注入 DigiCert Global Root G2 根证书二进制图片上传通过HTTPClient.POST(buffer, length)完成配合Content-Type: application/octet-stream与Prediction-Key两个请求头响应解析依赖 ArduinoJson仓库锁定版本6.17.3核心是遍历predictions数组并读取tagName与probability。完整可运行的参考代码位于仓库 code-classify/wio-terminal/fruit-quality-detector源码 main.cpp、config.h、camera.h、platformio.ini你可以在保留现有拍照工程的基础上按本文步骤增量接入分类能力。【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考