
简介本资源是一套面向嵌入式AI初学者与课程设计者的完整项目实践方案聚焦无接触式红外体温监测与多模态身份识别场景解决公共场所防疫测温、人脸核验与口罩佩戴合规性判断等实际需求。压缩包共198个文件含41个C语言源码.c与39个头文件.h覆盖STM32F103底层驱动如usart.c、i2c.c、adc.c、tim.c、OpenMV图像处理逻辑及神经网络模型推理接口另有30个编译中间文件.o/.d、3个Python脚本用于模型训练或数据预处理、2个Word文档含系统说明与硬件连接图及Keil工程文件uvprojx/uvoptx等整体8.03MB结构清晰、模块解耦明确。已有60人学习下载提供可直接烧录的hex与axf镜像、GY906DCI红外测温校准代码、基于Adaboost的轻量口罩检测实现、以及适配OpenMV的TinyML人脸识别tflite模型配套OLE集成接口便于对接上位机系统是嵌入式AI融合开发的典型教学级工程范例。1. 项目概述与核心价值最近在捣鼓一个挺有意思的玩意儿一个集成了无接触测温、人脸识别和口罩检测的嵌入式系统。核心就是一块经典的STM32F103C8T6最小系统板加上一个OpenMV Cam H7摄像头模组再外挂一个GY-906 DCI红外测温模块。这组合听起来是不是有点“复古”搭配“新潮”的感觉STM32F103作为十年前就火遍大江南北的MCU至今在成本敏感、性能要求不极致的场景里依然有顽强的生命力而OpenMV则让复杂的机器视觉开发变得像搭积木一样简单。这个项目的核心目标很明确在特定出入口或通道实现非接触式的快速体温筛查并同步完成身份核验与口罩佩戴检查。想象一下在办公楼大堂、学校宿舍楼入口或者小型诊所的预检分诊台人员走过系统能自动完成“体温正常/异常”、“是谁”、“是否佩戴口罩”这三重判断并将结果实时显示在一块小屏幕上甚至通过串口上报给上位机。这比单纯拿个额温枪一个个测效率要高得多也减少了人员近距离接触的风险。我选择STM32F103就是看中了它的稳定、便宜和资料海量。虽然它的主频只有72MHz内存也只有20KB的SRAM处理复杂的图像算法肯定力不从心但作为整个系统的“大脑”和“调度中心”却绰绰有余。它负责读取GY-906的温度数据、通过串口与OpenMV进行指令交互和数据接收、控制外围设备比如报警蜂鸣器或指示灯、管理OLED显示屏的刷新。而所有“重体力活”——人脸检测与识别、口罩检测——都交给了OpenMV。OpenMV内置的MicroPython解释器和丰富的机器视觉库让我们可以用高级语言快速开发视觉算法其专用的图像传感器和处理器为这些任务做了优化性能远超在STM32上裸跑同类算法。GY-906 DCI模块选用的是MLX90614 ESF-BCC这款传感器它通过I2C接口通信能测量物体表面温度精度在±0.5℃以内在医疗级校准后测量距离大致在2-5厘米到数米不等取决于光学镜头完全满足无接触测温的需求。整个系统的难点和乐趣就在于如何让这三个性格迥异的模块协调工作稳定、准确地完成既定任务。2. 系统整体设计与硬件选型思路2.1 核心架构与通信链路设计整个系统的架构可以看作一个主从式结构。STM32F103作为主机Master承担系统控制核心的角色OpenMV和GY-906作为从设备Slave。它们之间的通信链路是系统的血管设计好坏直接决定系统的稳定性和响应速度。1. STM32F103 与 OpenMV 的通信这是数据流的核心。我选择了异步串口UART作为通信方式。为什么不选SPI或I2C主要基于以下几点考虑协议简单容错性好UART是异步通信不需要时钟线连接简单仅需TX、RX、GND三线。在OpenMV端我们可以直接用pyb.UART类来读写在STM32端使用HAL库的HAL_UART_Transmit和HAL_UART_Receive函数即可。协议可以自定义例如STM32发送一个字节的命令码如0xA1代表请求人脸识别OpenMV收到后执行相应任务并将结果如识别到的ID号、置信度打包成特定格式的数据帧回传。带宽足够对于传输识别结果、坐标、温度值等小数据包115200bps甚至9600bps的波特率都绰绰有余远高于实际需求。开发调试方便双方都可以方便地通过串口打印调试信息到PC便于联调。在实际连接时需要特别注意电平匹配。STM32F103的IO口是3.3V电平OpenMV Cam H7的UART引脚也是3.3V因此可以直接相连。连接方式是STM32的USART1_TX (PA9) 接 OpenMV的RX引脚STM32的USART1_RX (PA10) 接 OpenMV的TX引脚。务必交叉连接并共地。2. STM32F103 与 GY-906 的通信GY-906MLX90614模块使用I2C接口。这是一个标准的、低速的、半双工的同步串行总线。STM32F103的硬件I2C如I2C1配置起来相对直接使用HAL库的HAL_I2C_Mem_Read函数可以很方便地读取传感器内部RAM中的温度数据。引脚连接STM32的I2C1_SCL (PB6) 接 GY-906的SCLSTM32的I2C1_SDA (PB7) 接 GY-906的SDA。同样需要上拉电阻通常模块板上已集成并共地。地址问题MLX90614的默认7位I2C地址是0x5A写地址0xB4读地址0xB5。如果总线上只有一个传感器直接使用这个地址即可。3. 显示与交互选用一块0.96寸或1.3寸的OLED显示屏SSD1306驱动通过I2C或SPI与STM32连接。I2C方式更省IO口仅需2根数据线适合本项目。显示屏用于实时显示测温结果、识别出的姓名、口罩佩戴状态以及系统状态提示。2.2 关键硬件模块深度解析STM32F103C8T6最小系统板这是项目的“老黄牛”。它的资源规划是关键USART1 (PA9/PA10):用于与OpenMV通信。这是全双工串口稳定性好。I2C1 (PB6/PB7):一拖二。同时连接GY-906测温模块和OLED显示屏。这里需要注意I2C总线的负载和上拉电阻强度。两个设备地址不同GY-906: 0x5A, SSD1306: 0x3C不会冲突。GPIO口分配几个IO口用于控制LED状态灯、蜂鸣器用于体温超标报警或按键输入。时钟与电源确保使用外部8MHz晶振并通过PLL倍频到72MHz以获得稳定性能。整个系统的供电需要稳定建议使用AMS1117-3.3V等LDO为STM32、OpenMV其内部有DCDC、OLED和GY-906提供干净的3.3V电源。注意总电流需求OpenMV在峰值运行时耗电可能超过250mA。OpenMV Cam H7这是项目的“智慧之眼”。OpenMV H7相比前代M7核心升级为STM32H7系列MCU主频更高内置的硬件JPEG编码器和专用图像处理外设使其处理图像流更加流畅。我们需要在其上运行两个核心任务人脸识别使用OpenMV IDE自带的Face Recognition库。这个库底层基于LBP局部二值模式特征和级联分类器虽然不如深度学习模型准确但在嵌入式端速度极快。你需要先采集用户的人脸图像作为样本然后训练一个识别模型。训练好的模型文件例如person.pgm会存储在OpenMV的Flash中。口罩检测这是本项目的一个亮点。OpenMV官方库可能不直接提供口罩检测模型。一种经典方法是使用Haar-like特征和Adaboost算法训练一个级联分类器。你可以使用PC上的工具如OpenCV收集大量戴口罩和不戴口罩的人脸正负样本训练生成一个.xml格式的级联分类器文件然后将其加载到OpenMV中使用image.find_features()函数进行检测。另一种更现代、可能更准确的方法是使用TensorFlow Lite Micro将一个小型的、针对口罩检测优化的神经网络模型如MobileNetV2-SSD转换成C数组集成到OpenMV的固件中。但这需要一定的模型转换和固件编译能力。GY-906 DCI红外测温模块关键点在于理解其工作原理和校准。MLX90614内部包含一个红外热电堆传感器和一个用于测量环境温度的PTAT传感器。它通过测量物体发射的红外能量和环境温度运用内置的DSP和算法计算出物体表面温度。DCI与非DCIDCI代表“数字控制接口”通常指带有一个简单的光学镜头能提供较小的视场角如约10°实现更精确的定点测量适合本项目这种需要对特定区域如额头测温的场景。精度与校准出厂校准是针对特定距离和发射率的通常默认发射率为0.95适用于大多数人体皮肤。对于高精度医疗应用可能需要在一个已知温度的黑体辐射源前进行二次校准以修正传感器之间的个体差异和环境漂移。在代码中我们读取的是传感器RAM中的TOBJ1物体1温度和TA环境温度寄存器值。3. 软件实现与核心代码剖析3.1 STM32端主控程序框架STM32的程序使用STM32CubeIDE开发基于HAL库整体采用前后台超级循环架构结合中断处理异步事件。// 主循环伪代码框架 int main(void) { // HAL初始化、时钟配置、外设初始化UART, I2C, GPIO等 System_Init(); OLED_Init(); OLED_ShowString(0, 0, System Booting...); // 初始化状态变量 float body_temp 0.0; uint8_t face_id 0xFF; // 0xFF表示未识别 uint8_t mask_status 0; // 0:未知1:佩戴2:未佩戴 uint32_t last_check_time 0; while (1) { // 1. 定时读取温度例如每秒一次 if (HAL_GetTick() - last_check_time 1000) { if (MLX90614_ReadTemp(body_temp) HAL_OK) { OLED_ShowTemp(body_temp); if (body_temp TEMP_THRESHOLD) { // 例如37.3℃ Buzzer_Alert(ON); } else { Buzzer_Alert(OFF); } } last_check_time HAL_GetTick(); } // 2. 向OpenMV发送指令请求执行视觉任务 // 可以设计为轮询或由外部事件触发如人体传感器 UART_SendCmd(CMD_FACE_AND_MASK_DETECT); // 3. 接收并解析OpenMV返回的数据 if (UART_RxFlag) { // 串口接收完成中断置位标志 Parse_OpenMV_Data(rx_buffer, face_id, mask_status); UART_RxFlag 0; // 更新显示 OLED_ShowFaceID(face_id); OLED_ShowMaskStatus(mask_status); } // 4. 其他任务如按键扫描、状态灯控制等 Key_Scan(); // ... HAL_Delay(10); // 防止CPU空转过快 } }串口通信协议设计示例为了保证数据可靠传输需要设计一个简单的帧结构。STM32 - OpenMV命令帧帧头0xAA 命令字1字节 帧尾0x55命令字定义0xA1 - 执行人脸识别0xA2 - 执行口罩检测0xA3 - 执行人脸口罩检测。OpenMV - STM32数据帧帧头0xBB 数据长度1字节 人脸ID1字节 口罩状态1字节 置信度1字节可选 校验和1字节 帧尾0xCC校验和可以是前面所有字节的简单累加和取低8位。3.2 OpenMV端视觉算法实现OpenMV端的代码使用MicroPython编写在OpenMV IDE中开发。核心是一个状态机等待串口命令执行相应任务并返回结果。# OpenMV 主程序框架 (main.py) import sensor, image, time, pyb from pyb import UART # 初始化摄像头 sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QVGA) # 320x240平衡速度与精度 sensor.skip_frames(time 2000) # 初始化串口 uart UART(3, 115200) # 使用UART3根据实际接线调整 uart.init(115200, bits8, parityNone, stop1) # 加载人脸识别模型和口罩检测分类器 face_cascade image.HaarCascade(frontalface, stages25) # 人脸检测 # 假设我们已经训练好了人脸识别模型 face_recog_model image.load_model(person.pgm) # 加载口罩检测的Haar Cascade或初始化神经网络模型 mask_cascade image.HaarCascade(mask_cascade.xml) # 需要自己训练 def find_face_and_mask(): img sensor.snapshot() faces img.find_features(face_cascade, threshold0.5, scale1.25) result {face_id: 0xFF, mask: 0} if faces: for face in faces: # 在每个人脸区域进行识别和口罩检测 # 1. 人脸识别 face_img img.copy(roiface).compress(quality90) # 裁剪人脸区域 # 这里需要将face_img转换为模型接受的格式具体取决于模型 # face_id, confidence face_recog_model.recognize(face_img) # 简化示例假设识别结果 face_id 1 confidence 85 # 2. 口罩检测 (在脸部的下半部分区域进行) # 假设口罩区域在人脸下半部分 mouth_roi (face[0], face[1] face[3]//2, face[2], face[3]//2) masks img.find_features(mask_cascade, roimouth_roi, threshold0.4) mask_status 1 if masks else 2 # 1:有口罩特征2:无 result[face_id] face_id result[mask] mask_status # 画框和标签用于本地调试预览 img.draw_rectangle(face, color(0, 255, 0)) img.draw_string(face[0], face[1]-10, ID:%d M:%d % (face_id, mask_status), color(255, 0, 0)) break # 假设只处理检测到的第一个人脸 return result while(True): if uart.any(): cmd uart.read(1) if cmd b\xa3: # 接收到人脸口罩检测命令 data find_face_and_mask() # 打包数据帧 frame bytearray([0xBB, 0x03, data[face_id], data[mask], 0x00]) # 长度3字节 checksum sum(frame[1:-1]) 0xFF # 计算校验和排除帧头 frame.append(checksum) frame.append(0xCC) uart.write(frame)注意上面的口罩检测部分使用了Haar Cascade作为示例实际效果受训练数据质量影响极大。对于更可靠的效果强烈建议探索集成TensorFlow Lite Micro模型。OpenMV官方有相关教程需要将.tflite模型转换为C数组并编写对应的推理代码。3.3 温度读取与数据处理STM32端读取GY-906温度的代码相对标准。#define MLX90614_ADDR (0x5A 1) // HAL库I2C地址是7位地址左移1位 HAL_StatusTypeDef MLX90614_ReadTemp(float *temp) { uint8_t data[3] {0}; uint16_t temp_raw; float temp_celsius; // 读取物体温度寄存器 TOBJ1 (地址 0x07) if (HAL_I2C_Mem_Read(hi2c1, MLX90614_ADDR, 0x07, I2C_MEMADD_SIZE_8BIT, data, 3, HAL_MAX_DELAY) ! HAL_OK) { return HAL_ERROR; } // 数据格式低8位、高8位、PEC校验本项目暂忽略PEC temp_raw (data[1] 8) | data[0]; // 原始数据是Kelvin * 0.02 转换为摄氏度 temp_celsius (float)temp_raw * 0.02 - 273.15; *temp temp_celsius; return HAL_OK; }温度滤波红外测温容易受到环境气流、被测物体表面反射率发射率波动的影响读数可能会有跳动。可以在STM32端加入简单的软件滤波例如滑动平均滤波或一阶低通滤波。#define FILTER_ALPHA 0.2f // 滤波系数越小越平滑响应也越慢 float filtered_temp 0.0; void LowPass_Filter(float new_sample, float *old_value) { *old_value FILTER_ALPHA * new_sample (1 - FILTER_ALPHA) * (*old_value); } // 在主循环中调用 LowPass_Filter(body_temp, filtered_temp); OLED_ShowTemp(filtered_temp); // 显示滤波后的温度4. 系统集成、调试与性能优化实战4.1 多模块联调与问题定位当所有硬件连接好代码分别烧录后真正的挑战才开始。联调时最常见的问题通信不通现象OLED不显示OpenMV无反应温度读数为0。排查电源第一用万用表测量各模块的VCC和GND引脚电压是否为稳定的3.3V。特别是OpenMV启动电流大劣质USB线或供电不足会导致反复重启。接线复查这是最常出错的地方。务必确认UART的TX-RX是交叉连接I2C的SDA/SCL线是否接反所有GND是否共地。逻辑分析仪/示波器是神器接上逻辑分析仪看UART/I2C总线上是否有波形。如果没有波形检查STM32代码中对应外设的初始化、时钟使能。如果有波形但乱码检查波特率、数据位、停止位设置是否两端一致。OpenMV识别不稳定现象人脸时而被识别时而不能或者识别ID跳变。解决光照是魔鬼机器视觉极度依赖光照。确保测试环境光线均匀避免强逆光、侧光造成的面部阴影或过曝。可以考虑在OpenMV摄像头周围加一圈柔光LED补光灯。调整检测阈值img.find_features()中的threshold参数和scale参数需要根据实际场景微调。阈值越低检测越敏感但也更容易误检scale因子越小检测越细致但速度越慢。训练数据质量为人脸识别模型采集训练样本时要包含同一个人不同角度、不同表情、戴眼镜/不戴眼镜的图片提高模型泛化能力。样本数量建议每人20-30张。口罩检测误报率高现象没戴口罩被判为戴了或者手、衣领等物体被误判为口罩。解决优化检测区域ROI不要在全图搜索口罩。先检测到人脸然后只在下半部分人脸区域鼻子到下巴进行口罩检测这能极大减少误检。使用更先进的模型Haar Cascade方法在复杂场景下局限性明显。如果OpenMV的Flash和RAM空间允许移植一个轻量级的神经网络模型如针对口罩检测优化的Tiny-YOLO或MobileNet-SSD会获得质的提升。这需要用到OpenMV的TensorFlow Lite Micro支持。4.2 性能瓶颈分析与优化策略这个系统的实时性要求是“准实时”不需要毫秒级响应但最好在1-2秒内完成一次完整的“测温-识别-显示”循环。STM32与OpenMV的通信延迟问题串口通信和OpenMV处理图像都需要时间如果STM32发送命令太频繁OpenMV可能来不及处理导致命令堆积或丢失。优化采用请求-响应模式而非持续轮询。STM32发送一个命令后等待并解析OpenMV的回复然后再发送下一个命令。可以在OpenMV端处理完一帧图像并返回结果后才去检查串口缓冲区是否有新命令。OpenMV图像处理速度问题在QVGA分辨率下进行人脸检测、识别和口罩检测帧率可能只有5-10fps影响系统响应速度。优化降低分辨率尝试使用QQVGA (160x120)或QQQVGA (80x60)。对于近距离人脸识别较低分辨率可能已足够。跳帧处理不需要处理每一帧。可以每2帧或3帧处理一次。优化算法顺序先进行轻量级的人脸检测只有检测到人脸后才对该区域进行更耗时的人脸识别和口罩检测。使用灰度图如果颜色信息不重要可以设置sensor.set_pixformat(sensor.GRAYSCALE)能减少数据量提升处理速度。STM32端的任务调度问题如果所有任务都塞在while(1)循环里可能会因为某个任务阻塞如等待串口超时而影响其他任务如温度定时读取。优化引入更简单的时间片或状态机思想。为每个任务测温、通信、显示刷新设置独立的时间戳利用HAL_GetTick()判断是否到执行时间非阻塞地执行。// 简单的时间片调度示例 typedef struct { uint32_t last_run; uint32_t interval; void (*task)(void); } sTask; sTask task_list[] { {0, 1000, Task_ReadTemperature}, // 每秒读一次温度 {0, 200, Task_UpdateDisplay}, // 每200ms刷新一次显示 {0, 50, Task_CheckUART}, // 每50ms检查串口 }; void Scheduler_Run(void) { uint32_t now HAL_GetTick(); for(int i0; isizeof(task_list)/sizeof(task_list[0]); i) { if(now - task_list[i].last_run task_list[i].interval) { task_list[i].task(); task_list[i].last_run now; } } } // 在主循环中调用 Scheduler_Run();5. 常见问题排查与经验心得在实际部署和调试这个系统的过程中我踩过不少坑也积累了一些“教科书上不会写”的经验。问题一温度读数偶尔出现极大值如85℃或负值。原因这通常是I2C通信受到干扰数据读取错误导致的。MLX90614的数据是16位的如果某个bit在传输中翻转就会解码成一个完全错误的温度值。排查与解决硬件上检查I2C走线是否过长建议20cm是否靠近电机、继电器等大电流干扰源。确保SDA和SCL线上有足够强的上拉电阻通常4.7kΩ-10kΩ。可以在STM32和传感器之间的I2C线上串联一个几十欧姆的小电阻有助于抑制信号振铃。软件上在MLX90614_ReadTemp函数中增加数据有效性检查。物体温度正常范围通常在20-45℃之间对于人体如果读出的值超出这个范围例如50℃或15℃可以丢弃该次读数并重试一次或者使用上一次的有效值。加入CRC/PEC校验MLX90614传输的第三个字节是PEC包错误校验字节。实现PEC校验算法可以有效过滤掉传输错误的数据。虽然HAL库的Mem_Read不自动处理这个但我们可以读取3个字节后自己计算并比对PEC。问题二人脸识别在室外强光或室内暗光下失效。原因OpenMV的传感器动态范围有限过亮或过暗都会导致图像细节丢失人脸特征无法提取。解决启用自动曝光控制AECsensor.set_auto_exposure(True)。但有时自动曝光也不够。手动设置曝光和增益针对固定场景可以手动调整到最佳值。使用OpenMV IDE的“镜头”工具在真实环境下调整sensor.set_auto_exposure(False)然后手动设置sensor.set_exposure_us()和sensor.set_gain_db()直到人脸区域清晰可见。增加物理遮光或补光这是最有效的方法。为摄像头做一个遮光罩避免阳光直射镜头。在光线不足的室内增加补光灯最好是漫射光源避免在人脸上产生高光点。问题三系统运行一段时间后死机或重启。原因可能是电源问题、堆栈溢出或看门狗未处理。排查电源负载用示波器观察系统3.3V电源轨的波形在OpenMV执行图像捕捉和处理的瞬间是否有大幅压降跌落到3.0V以下。如果有说明电源带载能力不足需要更换输出电流更大的LDO或直流电源模块并在电源入口加大电容如100uF电解并联0.1uF陶瓷电容储能。内存泄漏OpenMV端在MicroPython中如果在循环里不断创建新的对象如image.Image()而不释放可能导致内存耗尽。确保在函数内部创建的临时对象会随着函数结束而被垃圾回收或者显式地使用del删除大对象。看门狗STM32的IWDG独立看门狗如果启用必须在主循环中定期“喂狗”HAL_IWDG_Refresh(hiwdg)否则会导致复位。如果没启用看门狗却复位重点查电源和硬件连接。关于Adaboost与神经网络模型选择的个人体会在OpenMV上Haar Cascade Adaboost方案实现口罩检测优点是速度快、资源占用极低整个.xml文件可能只有几十KB加载和运行都很快。但它的缺点是需要大量正负样本进行训练且特征设计相对固定对于口罩的款式、颜色、佩戴方式如露出鼻子的泛化能力较弱容易误检漏检。而神经网络模型如TFLite Micro只要有一个足够好的训练数据集它能学习到更本质、更鲁棒的特征准确率通常更高。但代价是模型文件较大几百KB到几MB推理速度更慢会显著增加OpenMV的循环时间。对于本项目如果对准确率要求极高且OpenMV H7的Flash有足够空间比如4MB版本我推荐花时间走神经网络路线。如果追求极致的速度和低成本可以先用Haar Cascade方案快速验证原型同时收集实际场景数据为后续训练神经网络模型做准备。最后这个项目最有趣的地方不在于任何一个单一模块的使用而在于如何让STM32、OpenMV、传感器这三个“伙伴”高效、可靠地对话。调试过程就像在解一个多维的谜题硬件、软件、算法交织在一起。当系统终于稳定运行屏幕上清晰地显示出正确的体温、姓名和“口罩已佩戴”的提示时那种成就感是单纯调用一个库函数无法比拟的。它带给你的是对嵌入式系统全链条的深入理解从电路板上的信号到C语言和Python的代码逻辑再到最终的智能决策输出。本文还有配套的精品资源点击获取