1. 项目概述:当嵌入式AI遇见药片识别
最近在捣鼓一个挺有意思的嵌入式AI项目,名字叫“Edge Impulse Pill Identifier”,直译过来就是“基于Edge Impulse的药片识别器”。这玩意儿听起来可能有点专业,但说白了,就是做一个能自己“看”药片、然后告诉你这是啥药的小设备。它不依赖云端,所有计算都在本地一个小巧的微控制器上完成,比如一块树莓派Pico或者一块STM32开发板。这个想法源于一个很实际的需求:家里老人药瓶多,有时候会搞混,或者药片掉出来分不清了,这时候有个小工具能快速扫一眼就识别出来,能避免不少麻烦。
Edge Impulse这个平台,是这几年在嵌入式机器学习(TinyML)圈子里火起来的开发工具。它把传统上非常复杂的机器学习模型训练、优化和部署流程,变得像搭积木一样直观。你不需要是深度学习专家,只要会点基础的编程,就能通过它提供的图形化界面和数据采集工具,构建出能在资源极其有限的边缘设备上运行的AI模型。所以,“Edge Impulse Pill Identifier”这个项目,本质上就是利用Edge Impulse平台,为特定的微控制器训练一个专用于药片图像分类的轻量级模型,并把它部署到硬件上,实现离线识别。
这个项目适合谁呢?首先是对嵌入式开发和AI应用感兴趣的硬件爱好者、创客。其次,它对于物联网(IoT)开发者、产品经理来说,是一个绝佳的TinyML落地案例,展示了如何将AI能力赋予功耗和算力都受限的设备。最后,对于医疗辅助设备、智能家居领域的探索者,这个项目提供了一个完整的技术原型和实现思路。整个过程,你会接触到图像数据采集、模型训练、性能评估、嵌入式部署等一系列核心环节,是一次非常扎实的全栈式AIoT实践。
2. 项目核心思路与技术选型解析
2.1 为什么选择Edge Impulse与图像分类?
做药片识别,首先得确定技术路线。常见的方案有几种:一是用手机APP调用云端API,这需要网络,有延迟和隐私顾虑;二是用本地电脑跑OpenCV做特征匹配,但便携性差;三就是我们选的这条路——在边缘设备上运行轻量级神经网络模型。
选择Edge Impulse平台的核心原因在于其“端到端”的易用性和对边缘设备的深度优化。传统的机器学习工作流,从数据标注、模型训练(用TensorFlow或PyTorch)、到模型压缩(如量化、剪枝)、最后转换成嵌入式框架支持的格式(如TensorFlow Lite for Microcontrollers),每一步都有坑,对新手极不友好。Edge Impulse把这些步骤全部集成在一个Web IDE里,你只需要上传数据、设计模型结构、点击训练,它就能自动输出一个针对你指定硬件平台优化好的、可以直接烧录的固件或库文件。这大大降低了TinyML的应用门槛。
至于为什么用图像分类而不是物体检测?这取决于需求。我们的目标是识别一颗单独的药片是什么,而不是在复杂场景中定位多颗药片。图像分类任务更简单,模型更小,推理速度更快,非常适合资源紧张的微控制器。我们只需要把药片放在一个相对干净的背景(比如白色托盘)上,用摄像头拍下它的“证件照”,模型就能根据颜色、形状、大小、表面的刻痕文字等特征进行分类。当然,如果未来需要同时识别多颗药片并定位,可以升级到物体检测模型,但那对算力和内存的要求会高一个数量级。
2.2 硬件选型背后的考量
硬件是项目的基石。选择硬件时,我们需要在算力、内存、功耗、成本、摄像头支持以及Edge Impulse的兼容性之间做权衡。
微控制器(MCU):这是大脑。我们有几个热门选择:
- Arduino Nano 33 BLE Sense:内置了摄像头接口,但算力相对较弱(Cortex-M4F),适合非常简单的模型或低分辨率图像。
- 树莓派 Pico / Pico W:性价比极高,但本身没有摄像头接口,需要额外搭配OV7670等摄像头模块并通过杜邦线连接,对新手布线有点挑战。
- ESP32-CAM:这是一个All-in-One的方案,集成了ESP32芯片和OV2640摄像头,价格低廉,有Wi-Fi,但可用内存(PSRAM)有限,且Edge Impulse对其官方支持度在持续优化中。
- STM32F746/769 Discovery Kit:性能强劲(Cortex-M7),带LCD屏,能直接显示识别结果,但价格较高,属于“豪华”开发板。
对于入门和大多数应用场景,我推荐从树莓派Pico + OV7675摄像头模块开始。理由如下:Pico的RP2040芯片双核ARM Cortex-M0+,主频133MHz,性能足够;社区支持强大;Edge Impulse对其有良好的支持。OV7675比OV7670像素稍高(30万 vs 10万),且通常带FPC软排线,连接更可靠。这个组合成本可控,能完整跑通流程。
摄像头:药片识别不需要4K高清。OV7675的640x480(VGA)分辨率完全足够。关键在于要能稳定地通过I2C(配置)和DVP或SPI(数据传输)接口与MCU通信。选择带FIFO(先入先出存储器)的摄像头模块会更好,可以缓解MCU读取数据的压力。
其他外围:一个白色LED补光灯(确保光照均匀)、一个轻触开关(控制拍照)、一个OLED小屏幕(用于显示识别结果和置信度)会极大提升项目的完整度和用户体验。电源方面,可以用USB供电,或者接一个3.7V锂电池配合充放电模块实现便携。
注意:在采购硬件前,务必去Edge Impulse的官方文档查看“Supported Devices”列表,确认你选的MCU在官方支持或社区支持范围内,这能避免后期在部署环节遇到无法解决的兼容性问题。
3. 数据采集:模型好坏的基石
3.1 构建你的“药片图库”
AI模型就像学生,训练数据就是教材。教材的质量直接决定学生的水平。对于药片识别,我们需要为每一种需要识别的药片采集足够数量的图片。
采集原则:
- 多样性:同一药片,要从不同角度(正面、侧面、倾斜)、不同光照条件(自然光、室内光、开补光灯)、不同背景(但建议前期统一用纯色背景如白纸)进行拍摄。这能让模型学会关注药片本身的特征,而不是无关的环境信息。
- 数量均衡:每种药片建议至少采集50-100张有效图片。如果某些种类图片太少,模型会偏向于图片多的种类,导致识别不准。
- 高质量:图片要清晰,对焦准确。药片在画面中的占比要相对稳定,比如占据画面中心的1/3到1/2。
实操方法:
- 搭建简易摄影棚:找一个纸盒,内壁贴上白色卡纸,顶部开孔放置摄像头,侧面开孔用于放置药片和补光。这能创造一个简单可控的拍摄环境。
- 使用Edge Impulse数据采集工具:这是最便捷的方式。Edge Impulse提供了桌面数据采集工具(
edge-impulse-data-forwarder)和手机APP。你可以将开发板通过USB连接到电脑,运行数据转发器,然后在Edge Impulse Studio的“数据采集”页面,直接控制开发板拍照并上传到云端项目。用手机APP的话,可以直接用手机摄像头拍摄,但要注意手机摄像头参数与最终部署的摄像头差异可能影响模型泛化能力。 - 手动拍摄后上传:你也可以用任何相机拍好照片,然后通过Edge Impulse Studio的Web界面上传。图片格式支持JPG或PNG。上传时需要为每张图片打上正确的标签(即药片名称)。
我的心得:在采集初期,不要怕麻烦。我通常会为每种药片先拍20个“标准照”(药片平放,居中,光照好),然后手动制造一些“噪声”:比如把药片放在盘子边缘、手指捏着拍一半、稍微有点模糊的照片。这些“不完美”的数据反而能提升模型的鲁棒性。记得建立一个metadata.csv文件记录每张图片对应的信息,虽然Edge Impulse Studio里可以标注,但本地留个备份是好习惯。
3.2 数据标注与增强
上传完图片后,需要在Edge Impulse Studio里进行标注。由于我们做的是图像分类,标注工作非常简单:在“Data acquisition”页面,选择对应的数据集(训练集或测试集),系统会自动按文件夹或标签分组,你只需要检查确认即可。通常,我们会将80%的数据划分为训练集,20%作为测试集,用于最终评估模型在未见过的数据上的表现。
数据增强(Data Augmentation):这是在小数据集上提升模型性能的关键魔法。Edge Impulse在训练管道中内置了增强选项。对于图像分类,我强烈建议开启以下增强:
- 随机裁剪与缩放:模拟药片在画面中位置和大小的变化。
- 随机亮度与对比度调整:模拟不同光照条件。
- 随机旋转(小角度):模拟拍摄角度的微小变化。
- 水平/垂直翻转:对于药片这种中心对称的物体,翻转是安全的增强方式。
注意:增强要合理。比如,药片上的刻字如果翻转了可能会变成镜像,如果刻字是重要特征,就要谨慎使用翻转增强。Edge Impulse的参数可以调节强度,建议从默认值开始,观察增强后的样本预览,确保生成的图片看起来仍然是合理的药片图像。
4. 模型设计与训练:在边缘设备上“炼丹”
4.1 脉冲设计(Impulse Design)
这是Edge Impulse的核心概念,它定义了从原始数据到推理结果的处理流水线。对于我们的图像分类项目,一个标准的脉冲设计如下:
- 输入块(Input Block):选择“图像”类型,设置图像尺寸。这里有一个关键权衡:尺寸越大,保留的细节越多,模型可能更准,但计算量和内存消耗呈平方级增长。对于药片识别,
96x96或128x128像素的彩色图像通常是一个甜点。你可以先尝试96x96,如果效果不佳再尝试增大。 - 处理块(Processing Block):选择“图像”处理器。它的作用是将原始图像像素数据转换为更适合神经网络处理的格式。Edge Impulse的图像处理器通常会进行灰度化(可选)、归一化(将像素值从0-255缩放到0-1或-1到1之间)等操作。
- 学习块(Learning Block):选择“神经网络分类器”。这里需要选择具体的神经网络架构。
4.2 神经网络架构选择与参数调校
Edge Impulse提供了几种预置的神经网络模型,适用于不同的资源约束:
- MobileNetV1/V2:经典高效的移动端模型,在精度和速度之间平衡得很好。通常是我的首选。对于
96x96输入,可以选择MobileNetV2 96x96 0.35(最后的0.35是宽度乘子,越小模型越瘦身)。 - EfficientNet:比MobileNet更先进,同等精度下参数更少,但可能在某些MCU上支持不够好。
- 自定义模型:如果你熟悉Keras,可以自己编写模型架构。但对于大多数应用,预置模型足够。
关键参数设置:
- 训练周期(Epochs):模型遍历整个训练集的次数。太少欠拟合,太多过拟合。从30-50开始,观察损失曲线。
- 学习率(Learning Rate):控制模型参数更新的步长。Edge Impulse通常会自动调整,保持默认即可。
- 验证集划分:Edge Impulse会自动从训练集中留出一部分(如10%)作为验证集,在每个训练周期后评估,用于监控过拟合。
开始训练:点击“开始训练”,平台会在云端进行模型训练。这个过程可能需要几分钟到半小时,取决于数据量、模型复杂度和队列情况。训练完成后,你会看到模型在验证集上的准确率。
4.3 模型性能评估与优化
不要只看验证集准确率就高兴!必须进入“模型测试”页面,使用我们之前预留的测试集(那20%从未参与训练的数据)进行测试。这里的准确率才是模型真实能力的反映。
分析混淆矩阵:这是最重要的诊断工具。矩阵的行是真实标签,列是预测标签。理想情况下,对角线上的数字(正确分类)应该很高。如果发现某两种药片(比如A和B)经常被混淆,说明模型没能很好地区分它们。这时候你需要:
- 回顾数据:是不是A和B长得太像了(大小、颜色、形状都接近)?如果是,可能需要采集更多能突出它们细微差别的图片,比如特写它们表面的独特刻痕。
- 调整模型:可以尝试稍微增加输入图像尺寸(如从
96x96到128x128),让模型看到更多细节。或者尝试更深的模型架构(如从MobileNetV1 0.25切换到0.5)。 - 特征可视化:Edge Impulse提供了“特征可视化”工具(如t-SNE图),可以将模型“眼中”的图片映射到二维空间。如果不同类别的点混杂在一起,说明模型学到的特征区分度不够。
我的调优经验:一次只改变一个变量。比如,先固定96x96输入,比较MobileNetV1和V2的效果。确定架构后,再尝试调整图像尺寸。记录每次实验的测试集准确率、模型大小(RAM/Flash占用)和推理时间(在模拟器中查看)。我们的目标是找到在满足准确率门槛(比如>95%)的前提下,模型最小、推理最快的那一个组合。对于药片识别,如果种类不多(<10种),96x96输入配合一个轻量级MobileNet通常就能达到98%以上的测试准确率。
5. 模型部署与嵌入式端实现
5.1 部署到设备:生成固件与库
模型测试满意后,就可以部署了。Edge Impulse提供了多种部署方式:
- C++库:这是最常用、最灵活的方式。平台会为你生成一个包含模型权重、推理引擎和预处理代码的C++库,以及一个示例项目。你可以将这个库集成到自己的嵌入式IDE(如Arduino IDE, PlatformIO, Mbed Studio)中。
- 预编译固件:对于官方深度支持的开发板(如Nano 33 BLE),可以直接下载一个完整的、可烧录的固件。上电后,设备会作为一个USB串口设备,你可以通过串口工具发送图像数据并接收识别结果。这种方式最快,但定制性差。
- Edge Impulse for Linux:如果你用的是树莓派(完整的Linux系统),可以安装其Linux SDK,通过命令行或Python API调用模型。
对于树莓派Pico,我们选择C++库方式。在部署页面,选择“C++库”,然后选择你的目标设备(Raspberry Pi RP2040)。点击“构建”后,会下载一个ZIP文件。
5.2 硬件连接与电路搭建
以树莓派Pico + OV7675为例:
- 摄像头连接:OV7675模块通常使用I2C(用于配置)和DVP并行数据口。需要将摄像头的SDA、SCL连接到Pico的I2C引脚(如GP4/SDA, GP5/SCL),将摄像头的VSYNC、HREF、PCLK以及8位数据线D0-D7连接到Pico的一组GPIO。具体引脚定义需要查看摄像头模块和Pico的文档。特别注意:Pico的3.3V输出电流有限,如果摄像头供电不足,会导致图像不稳定,最好使用外部3.3V电源单独给摄像头供电。
- OLED屏幕连接:常用的I2C SSD1306 OLED屏,只需连接VCC、GND、SDA、SCL四根线到Pico的另一组I2C接口或与摄像头共用(地址不同即可)。
- 按键与LED:连接一个轻触开关到某个GPIO,用于触发拍照。连接一个LED到PIO,用于指示状态。
5.3 嵌入式端代码解析与集成
解压下载的C++库,你会看到edge-impulse-sdk文件夹和tflite-model文件夹等。关键是将这些文件正确放入你的项目结构中。
以PlatformIO为例,项目结构大致如下:
your_project/ ├── lib/ │ └── edge-impulse-sdk/ (整个库文件夹) ├── src/ │ ├── main.cpp (你的主程序) │ └── ei_image_classification.cpp (从Edge Impulse示例修改而来) ├── include/ │ └── model_variables.h (模型相关头文件) └── platformio.ini主程序逻辑流程:
- 初始化:初始化串口(用于调试输出)、I2C(用于摄像头和OLED)、摄像头模块(配置分辨率、输出格式等)、OLED屏幕。
- 等待触发:循环检测按键是否被按下。
- 捕获图像:当按键按下,控制摄像头捕获一帧图像,将原始数据(通常是RGB565或YUV格式)存储到缓冲区。
- 图像预处理:调用Edge Impulse SDK提供的
signal->get_data函数。这里是关键:你需要编写一个函数,将你的摄像头原始数据转换成SDK期望的格式。通常SDK期望一个指向图像数据(如RGB888像素数组)的指针。你需要根据你的摄像头输出格式进行转换(例如,从RGB565到RGB888)。// 伪代码示例 int raw_image_buffer[WIDTH * HEIGHT * 2]; // RGB565 // ... 从摄像头读取数据到 raw_image_buffer ... int8_t* ei_buffer = (int8_t*)malloc(WIDTH * HEIGHT * 3); // RGB888 convert_rgb565_to_rgb888(raw_image_buffer, ei_buffer, WIDTH, HEIGHT); signal_t signal; signal.total_length = WIDTH * HEIGHT * 3; signal.get_data = &get_image_data_callback; // 回调函数返回ei_buffer - 运行推理:调用
run_classifier(&signal, &result, true)。这个函数会执行神经网络推理。 - 解析结果:
result结构体包含了所有类别的得分(置信度)。找到得分最高的那个类别。ei_impulse_result_t result = {0}; run_classifier(&signal, &result, true); float max_score = 0; uint8_t max_index = 0; for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) { if (result.classification[ix].value > max_score) { max_score = result.classification[ix].value; max_index = ix; } } const char* label = ei_classifier_inferencing_categories[max_index]; - 输出与显示:通过串口打印识别结果和置信度,同时在OLED屏幕上显示出来。
调试心得:最耗时的部分往往是图像预处理。务必确保你传递给SDK的图像数据格式、尺寸和你在Edge Impulse中训练模型时设置的完全一致(RGB vs 灰度,96x96vs128x128)。一个有效的调试方法是:先将一张已知的、标准的药片图片(例如,从训练集中选一张)转换成C语言数组,硬编码在程序里,用这张静态图片进行推理。如果静态图片能正确识别,说明模型和SDK集成没问题,问题就出在摄像头数据采集或转换环节。另外,务必关注Pico的内存(RAM)使用情况,图像缓冲区很大,容易导致内存不足崩溃,可以使用malloc和free动态管理大缓冲区。
6. 项目优化与进阶思考
6.1 性能压榨与功耗管理
当基本功能跑通后,我们可以追求更极致的性能。
- 模型量化:Edge Impulse在部署时默认已经进行了int8量化。这是将模型权重和激活值从浮点数转换为8位整数,能大幅减少模型体积和加速计算,且精度损失很小。这是TinyML的标配操作,我们无需额外处理。
- 利用RP2040双核:Pico有两个核心,可以让一个核心专责摄像头数据采集和预处理(计算密集型),另一个核心负责推理和UI更新(I/O密集型),通过队列(queue)进行核间通信,提升整体吞吐率。
- 降低帧率与动态功耗:药片识别不是视频流处理,不需要高帧率。可以降低摄像头采集频率,或者在等待触发时让MCU进入休眠模式,仅靠按键中断唤醒,这能极大延长电池续航。
- 优化图像传输:如果使用SPI接口的显示屏,刷新整屏图像很慢。可以只更新屏幕上文字变化的区域,而不是全屏刷新。
6.2 功能扩展与产品化思路
一个原型到产品,还有距离:
- 多模态输入:除了图像,是否可以加入其他传感器?例如,用一个简单的颜色传感器获取药片的平均RGB值,作为图像特征之外的辅助信息,提升对颜色极其相似药片的区分度。Edge Impulse支持融合多种传感器数据(如图像+音频+加速度计)进行融合分类。
- 离线数据库与交互:在设备内部Flash或外置SD卡中存储一个简单的药片信息数据库(JSON格式)。识别出药片后,不仅显示名称,还可以滚动显示其用途、剂量、禁忌等关键信息(需要预先录入)。
- 联网与数据同步:如果使用ESP32-CAM或Pico W这类带Wi-Fi的硬件,可以在识别后,通过MQTT将记录(时间、药片名)发送到家庭服务器或云端,用于服药记录追踪。也可以定期从云端更新药片数据库。
- 外壳与工业设计:3D打印一个包含药片槽、摄像头窗口、按钮和显示屏的外壳,让设备看起来更像个产品。药片槽的设计可以标准化拍摄距离和角度,进一步提高识别稳定性。
- 模型在线更新:当需要识别新药片时,能否在不重新烧录固件的情况下更新模型?这需要设计一个OTA(空中下载)更新机制,通过Wi-Fi将新的模型文件(由Edge Impulse生成)下载到设备并替换旧模型。这是一个高级功能,需要对固件进行分区管理。
6.3 常见问题与故障排查实录
在开发过程中,你几乎一定会遇到下面这些问题:
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
编译错误,找不到ei_classifier相关函数 | SDK库未正确链接或头文件路径错误 | 1. 检查platformio.ini中的lib_deps或lib_extra_dirs是否指向正确的edge-impulse-sdk路径。2. 检查 main.cpp是否包含了必要的头文件,如edge-impulse-sdk/classifier/ei_run_classifier.h。 |
| 程序运行崩溃(Hard Fault) | 内存不足(栈或堆溢出) | 1. 检查图像缓冲区大小。一个96x96的RGB888图像需要约27KB,确保在全局区或堆上分配。2. 在PlatformIO中调整 board_build.ldscript,增大堆栈大小。3. 使用 malloc后务必检查返回值是否为NULL。 |
| 推理结果始终是同一个类别,或置信度很低 | 图像数据格式或尺寸错误 | 1.最重要的检查:确认传递给run_classifier的图像数据是否是RGB888(或灰度)格式,且尺寸与训练时完全一致。2. 编写一个测试函数,将一幅已知的、简单的测试图(如全红、全绿)的数据送入推理,看输出是否符合预期。 3. 通过串口将摄像头采集到的原始数据的前几十个字节打印出来,与一张正常图片的二进制数据对比。 |
| 摄像头初始化失败,无图像数据 | I2C通信失败或电源问题 | 1. 用逻辑分析仪或示波器检查I2C的SCL/SDA波形,确认地址是否正确(OV7675通常为0x21)。 2. 测量摄像头模块的供电电压,确保在3.3V左右且稳定。尝试外接电源。 3. 检查所有连接线是否牢固,特别是D0-D7数据线。 |
| 模型推理速度极慢 | 时钟频率未设置或模型过大 | 1. 确认Pico的CPU时钟是否设置为最高频率(set_sys_clock_khz(133000, true))。2. 在Edge Impulse的“部署”页面,查看模型详情,关注其RAM和Flash占用。如果过大,返回去使用更小的输入尺寸或更精简的模型架构(如MobileNet宽度乘子0.1)。 |
| OLED屏幕不显示 | I2C地址错误或初始化序列不对 | 1. 先运行一个简单的OLED测试程序(如显示“Hello”),排除屏幕本身和接线问题。 2. SSD1306的I2C地址通常是0x3C或0x3D,尝试切换。 |
最后的个人体会:做这个项目,最大的收获不是最终做出了一个能识别药片的小盒子,而是完整地走通了一遍TinyML的应用闭环。从问题定义、数据采集这个“脏活累活”,到模型训练调参这个“玄学环节”,再到嵌入式部署调试这个“硬核战场”,每一步都有坑,但每一步填坑的过程都是实打实的经验积累。Edge Impulse极大地平滑了从模型到硬件的路径,但它不是一个黑盒,理解其背后的数据流、模型优化和硬件限制,才能让你在遇到问题时游刃有余。下次如果你想让一个单片机“听懂”声音、“感觉”振动,这套方法论依然适用。