尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

STM32+MLX90640+QT:从零搭建红外人脸测温仪

STM32+MLX90640+QT:从零搭建红外人脸测温仪 简介这是一套面向嵌入式开发与智能硬件初学者的红外人脸测温系统完整实现方案融合STM32底层驱动、OpenCV人脸检测与QT跨平台上位机开发解决非接触式体温筛查场景下的实时测温、异常告警与图像留存需求。资源包共687个文件涵盖191个C头文件hpp/h用于算法与通信模块封装、70个C源文件c实现STM32外设驱动、68个动态链接库dll支撑OpenCV 3.4.7核心功能如imgproc、dnn、calib3d等以及16个可执行文件含主程序FaceTemperatureCheck.exe和配套UI资源ui/qrc/ico、语音提示wav、Haar级联分类器xml与使用文档pdf/txt整体压缩包大小为76.66MB。已有1016人学习下载提供开箱即用的可执行程序、完整源码工程含uvprojx项目文件、串口通信协议说明及人脸检测温度联动逻辑实现细节特别适合课程设计、毕业设计或IoT项目快速原型开发。 把一块STM32单片机、一颗MLX90640红外热像传感器和一个QT写出来的上位机拼在一起做出一台能实时捕捉人脸并显示额头温度的设备是我这段时间一直在折腾的项目。这东西最终的效果是对着摄像头坐好电脑屏幕上会框出人脸右下角直接跳出温度读数整体精度能控制在±0.3℃以内。整条链路从单片机I2C读取温度矩阵到串口协议上传再到上位机做人脸检测和热力图绘制每个环节都有不少值得记录的取舍和坑点。这篇文章就把这套基于STM32的红外人脸测温仪的完整实现思路、关键代码片段、标定流程和调试心得整理出来给准备做类似项目的朋友一个参考。这套系统最适合两类人一类是正在做单片机课设或电子设计竞赛题目需要把下位机和上位机串起来做展示的学生另一类是打算在工业巡检、门禁考勤或小型门店入场测温场景里做个快速原型的技术爱好者。如果你手里已经有一块STM32开发板又愿意花点时间在PC端写QT程序这篇文章能帮你少走很多弯路。1. 项目整体设计为什么我选了MLX90640加QT而不是直接买测温枪1.1 需求到底是什么先说清楚这个设备要解决的实际问题。普通的红外测温枪只能手动瞄准测的是一个小圆点区域如果要对准额头还得靠人自己调整距离和位置。而红外人脸测温仪则要解决两个核心问题第一自动找到人的脸在哪里第二从脸部对应的红外区域里取出一个稳定的、能代表体温的数值。这两个问题如果分开做都不难但合在一起就有意思了——可见光摄像头负责找人脸红外传感器负责测温度两者必须同步配合否则测的温度点很可能是背景墙或者衣领。我最初的需求很简单在PC端看到一个实时热力图画面中能框出人脸并且自动显示面部最高温度或平均温度。没有做考勤、记录等附加功能先把核心链路跑通。1.2 方案选型对比从单点测温到热像仪市面上常见的红外测温传感器大概分三类MLX90614单点红外测温传感器只能测一个点的温度价格便宜适合做测温枪。但它没有图像信息无法区分被测物体和背景。AMG88338x8像素的红外热像传感器分辨率太低勉强能看到热源轮廓但脸部细节几乎没有测温意义不大。MLX9064032x24像素红外热像传感器分辨率在可接受范围内已经能看出人脸的热量轮廓是小型热像仪常用的方案。我最终选了MLX90640理由很直接32x24像素虽然跟可见光摄像头没法比但做额头区域的平均温度计算已经绰绰有余。而且MLX90640自带校准数据每个像素都有独立的灵敏度修正精度理论上能到±1℃配合标定操作可以进一步提高。1.3 人脸识别到底放在哪一端刚开始纠结过一个问题人脸识别是放在STM32上跑还是放在QT上位机跑我查过一些STM32端跑轻量级人脸检测的方案有在F407上部署TinyML模型的也有外接K210模块的。但这些方案都要额外引入摄像头、模型文件系统、显示屏幕等一堆外设而且在单片机上调模型推理速度容易翻车。我的项目目标是快速做出可用的测温仪不是研究边缘端推理性能所以最后决定把识别任务放在PC端的上位机里完成。这样设计还有一个明显的好处调试方便。人脸检测用的OpenCV DNN模型可以在PC上随便换串口和热力数据可以通过软件模拟不用每次改一点代码就重新编译下载STM32固件。STM32只负责稳定采集温度矩阵并上传QT负责显示和计算两者职责清晰排查问题的时候不用来回猜。1.4 系统架构总览整个系统的数据流是这样的STM32通过I2C接口读取MLX90640的1920个温度原始点32x24。STM32根据EEPROM中的校准参数把原始值换算成摄氏度得到32x24的温度矩阵。STM32通过串口UART按自定义帧协议发送这768个温度值到PC。QT上位机通过QSerialPort接收数据解析出温度矩阵。同时QT上位机通过OpenCV调用USB摄像头用DNN人脸检测模型识别画面中的人脸框。上位机将人脸框坐标映射到32x24的温度矩阵上截取额头区域计算平均温度。界面实时绘制伪彩色热力图并把温度数据叠加显示在摄像头画面上。硬件连接上MLX90640是模块化的引脚引出SDA、SCL、VCC、GND。我用的主控是STM32F407VET6因为手头刚好有这个板子实际上用F103也能驱动MLX90640只要I2C速度能到1MHz左右就行。选F407的另一个原因是它自带硬件浮点单元计算温度矩阵时用浮点数运算会快很多。2. STM32下位机MLX90640的I2C驱动、EEPROM校准与温度矩阵计算2.1 硬件连接与引脚分配MLX90640模块需要4根线跟STM32开发板的连接方式如下MLX90640引脚STM32F407引脚说明VIN3.3V模块供电注意MLX90640需要3.3V供电不要接5VGNDGND共地SCLPB8I2C1时钟SDAPB9I2C1数据VDD3.3V如果模块分开供电需要连接多数模块与VIN共用这里有个非常关键却被很多人忽略的点MLX90640模块的SDA和SCL必须有上拉电阻。很多现成模块上装了4.7kΩ上拉电阻但如果你自己画板子或者买的模块是裸板一定要在I2C两条线上各接一个4.7kΩ电阻到3.3V否则I2C通信会非常不稳定。我在调试时曾经因为用了杜邦线加裸传感器结果数据间歇性读错后来加上拉电阻后问题瞬间消失。2.2 I2C时序与EEPROM参数加载MLX90640的本质是一个挂在I2C总线上的从设备芯片内部有两块存储区域EEPROM和RAM。EEPROM里存放出厂校准数据RAM里的0x0400到0x07FF地址存放当前的测量数据也就是768个16位原始温度数据。在读取新数据之前需要先设置读取页码。MLX90640有page 0和page 1两页每一页包含32行、24列中的一半像素也就是说连续的1920个像素数据被分成两页存储。读取一帧完整图像时要先把控制寄存器地址0x8000的page bit设为0读取page0的像素再把page bit设为1读取page1的像素然后合并成完整的32x24矩阵。I2C读取EEPROM的代码逻辑大致是这样// 设置MLX90640的当前页 uint8_t reg_data[2] {0x00, 0x00}; // 低字节高字节page0 HAL_I2C_Mem_Write(hi2c1, 0x331, 0x8000, I2C_MEMADD_SIZE_16BIT, reg_data, 2, 100); // 读取page0的像素数据共512个16位字 // 寄存器地址从0x0400开始每个数据占2字节高字节在前 HAL_I2C_Mem_Read(hi2c1, 0x331, 0x0400, I2C_MEMADD_SIZE_16BIT, frame_data, 1024, 100);注意MLX90640的I2C地址是0x33但HAL库中的地址要左移一位变成0x66因为HAL的Mem_Read接口里地址参数是8bit从机地址加上R/W位后的值。如果你忘记左移通信会直接超时。2.3 MLX90640的温度换算公式拿到原始16位数据后还必须结合EEPROM中的参数才能换算成摄氏度。MLX90640的换算过程相当繁琐官方提供了计算方法我在代码里主要用了几个关键参数Vdd芯片供电电压用于补偿电压波动。Ta环境温度由芯片内部热敏电阻测得。Kv、Kta补偿系数。Alpha每个像素的灵敏度系数。各像素的偏移量、间距等。实际开发时不需要自己从头实现完整算法Melexis官方提供了一个名为MLX90640_API的C库里面有MLX90640_CalculateTo()函数直接输入EEPROM数据和RAM数据就能得到温度矩阵。我建议直接移植这个库不要手工重写公式因为里面涉及大量查找表和像素补偿自己写容易漏项。调用示例float_t emissivity 0.98; // 人体皮肤发射率 float_t tr 23.5; // 反射温度即环境物体反射的红外温度 float_t emissivity_window 1.0f; MLX90640_CalculateTo(frame_data, eeprom_data, emissivity, tr, emissivity_window, mlx90640_to, temperature_data);这里的temperature_data就是32x24矩阵中每个像素的温度值单位是摄氏度类型是float。温度计算出来后再通过串口发送给上位机。2.4 原始数据处理与发送频率MLX90640支持0.5Hz、1Hz、2Hz、4Hz几种刷新率通过写控制寄存器0x8000里的刷新率位来设置。我最终用的是2Hz。为什么不是4Hz因为4Hz模式下I2C读取时间会变得很紧张而且STM32还同时要处理温度计算和串口发送如果芯片散热量大温度噪声也会增加。2Hz对于体温测量已经足够人站在摄像头前短暂停留就能取到几帧有效数据。每帧32x24矩阵共768个温度值如果用float32直接发送每帧数据量为3072字节。在460800波特率下传输大约需要53毫秒完全来得及。我最后采用了压缩成int16的方案温度值乘以100存储一帧数据量降为1536字节传输时间更短而且上位机解析也更简单。3. QT上位机的双线程架构一边跑人脸检测一边收串口数据3.1 为什么必须用双线程刚开始写QT上位机时我把串口接收、摄像头读取、人脸检测和界面刷新全部塞在同一个线程里结果发现界面卡成了PPT。原因很简单OpenCV的DNN人脸检测在CPU上跑一帧大概需要100到200毫秒这个期间串口数据会源源不断进缓冲区UI线程被阻塞画面无法刷新。正确的做法是拆成两个线程工作线程A负责读串口、解析温度矩阵通过信号槽把温度数据发送给主界面。工作线程B负责读摄像头、做人脸检测把检测到的人脸框通过信号槽发送给主界面。主界面线程只做一件事把收到的数据绘制出来。在QT里不要直接用moveToThread去处理QSerialPort因为QSerialPort的readyRead信号触发频率很高如果处理不当依然会阻塞线程。更稳妥的做法是在一个独立的QThread对象里创建一个QSerialPort实例并只在该线程内进行串口读写。3.2 集成OpenCV人脸检测我使用的是OpenCV的DNN模块模型是res10_300x300_ssd_iter_140000_fp16.caffemodel这个模型在CPU上跑一帧640x480的画面大概需要50到100毫秒精度比Haar级联好很多而且能抵抗一定程度的侧脸。初始化代码cv::dnn::Net net cv::dnn::readNetFromCaffe( deploy.prototxt, res10_300x300_ssd_iter_140000_fp16.caffemodel );在检测线程的循环里cv::Mat frame; cap.read(frame); cv::Mat blob cv::dnn::blobFromImage(frame, 1.0, cv::Size(300, 300), cv::Scalar(104, 177, 123), false, false); net.setInput(blob); cv::Mat detections net.forward(); for (int i 0; i detections.size[2]; i) { float confidence detections.ptrfloat(0, 0, i, 2); if (confidence 0.6) { // 解析人脸框坐标保存在faceRects中 } }注意blobFromImage里的mean值必须用(104.0, 177.0, 123.0)这是模型训练时使用的BGR均值。如果你使用OpenCV的caffe模型顺序是BGR如果使用PyTorch导出的模型顺序可能会变成RGB这里最容易出bug。3.3 串口接收与环形缓冲串口数据是字节流STM32发送的每一帧协议包含帧头、长度、数据体和CRC校验。QT端不能假设一次readyRead信号就收到完整的一帧所以必须自己维护一个接收缓冲区。我采用的方法是在串口接收槽函数里把QByteArray持续追加到一个成员变量buffer_中然后循环解析void MainWindow::onReadyRead() { buffer_.append(serial_-readAll()); while (buffer_.size() kFrameLength) { if ((quint8)buffer_[0] 0xAA (quint8)buffer_[1] 0x55) { // 检查帧长度字段 quint16 len (quint8)buffer_[2] | ((quint8)buffer_[3] 8); if (buffer_.size() 4 len 2) { QByteArray frame buffer_.left(4 len 2); buffer_.remove(0, 4 len 2); parseFrame(frame); } else { break; } } else { buffer_.remove(0, 1); // 丢弃非帧头字节 } } }这个解析逻辑看起来简单但有一个隐藏问题如果帧头0xAA 0x55刚好出现在温度数据中会误判。所以我在协议里用帧头加长度校验一起来防止。温度数据是int16类型完全可能出现0xAA55这样的字节组合但因为长度字段固定我会先检查长度是否等于1536否则继续滑动寻找下一个帧头。3.4 实时热力图绘制热力图绘制不需要用QPainter一个一个画矩形那样太慢。我用的方法是在内存中直接操作QImage的像素缓冲区把32x24矩阵映射到一张伪彩色图像上。计算颜色时用了一个经典的双线性插值映射参考MATLAB的Jet色图QColor jetColor(float value) { // value: 30~40℃映射到0~1 float t (value - 30.0f) / (40.0f - 30.0f); t qBound(0.0f, t, 1.0f); // 分段线性计算RGB float r, g, b; // ... 依据Jet公式 ... return QColor(r*255, g*255, b*255); }然后把32x24的小图像放大到320x240显示用QImage::scaled加Qt::FastTransformation保证放大速度。最后把热力图叠加到摄像头画面的右下角或者做成半透明覆盖层。我这里直接做成侧边栏方便对比真实画面和热像图。4. 通信协议设计帧头、校验、以及怎样把32x24温度矩阵塞进串口4.1 帧格式设计上下位机通信的协议如果没设计好会带来无尽调试痛苦。我的协议格式如下字节序字段长度说明0帧头110xAA1帧头210x552-3数据长度2低字节在前值为15364至4len-1温度数据153632x24个int16单位0.01℃4len至4len1CRC162对数据长度和温度数据计算CRC4len2帧尾10x0D协议没有加设备ID和命令类型因为这套系统只有一种上行数据。如果你计划扩展成双向通信建议把命令类型字段加上。我使用int16而不是float是为了减少传输量。温度范围设为-40℃到100℃乘以100后最大为10000int16完全够用。上位机拿到后除以100就是摄氏度。4.2 STM32端发送数据STM32端在发送前需要把待发送的温度数据从浮点数转换为int16uint16_t payload_len 32 * 24 * 2; uint8_t tx_buf[4 payload_len 2 1]; tx_buf[0] 0xAA; tx_buf[1] 0x55; tx_buf[2] payload_len 0xFF; tx_buf[3] (payload_len 8) 0xFF; // 温度数据填充到tx_buf[4]开始的位置 for (int i 0; i 768; i) { int16_t val (int16_t)(mlx90640_to[i] * 100); tx_buf[4 i*2] val 0xFF; tx_buf[4 i*2 1] (val 8) 0xFF; } // CRC16计算 uint16_t crc crc16(tx_buf 2, 2 payload_len); tx_buf[4 payload_len] crc 0xFF; tx_buf[4 payload_len 1] (crc 8) 0xFF; tx_buf[4 payload_len 2] 0x0D;注意STM32的int16_t小端序直接按低字节先发送即可QT端QByteArray读出来按小端解析。4.3 坐标映射把摄像头画面里的人脸框换算到红外矩阵这一步是整个功能体验的灵魂。摄像头分辨率是640x480红外矩阵只有32x24。我在安装时尽量让两个传感器光轴平行中心对齐。映射关系的最简单公式int ir_x (int)(face_rect.center_x() / cam_width * 32); int ir_y (int)(face_rect.center_y() / cam_height * 24);但直接这么映射会有一个问题人脸框中心往往在鼻尖或嘴巴附近而测温需要的是额头区域。人的额头大约位于人脸框上部20%到40%的位置。所以我在映射后还要做一次区域修正int forehead_x ir_face_center.x; int forehead_y ir_face_center.y - (int)(0.15 * ir_face_height); int forehead_w ir_face_width / 2; int forehead_h ir_face_height / 4;然后在这个小区域里取平均值。这个平均值比直接用全脸最高温稳定能有效避开眼镜、头发这些干扰因素。这里还需要设置一个最小有效窗口比如至少4个像素点否则数据不足时直接判为无效。4.4 丢包与错位处理串口通信偶尔会丢包即使加了CRC也一样。丢包时最明显的现象是温度矩阵里出现一整行异常值对应到热力图上是横向条纹。我的解决方案是上位机接收后做中值滤波当一个像素点的温度与周围8个像素点的差值超过3℃时直接用周围8个点的中值替换。这样不仅减少了通信噪声也同时平滑了MLX90640本身的相邻像素跳动。另外由于摄像头帧率和串口帧率不一致摄像头是30帧红外是2帧我在上位机里让温度数据异步更新即摄像头画面实时刷新但温度值只在收到新红外帧时更新一次。这样既保证了人脸框流畅又不会出现温度数字跳动过快的错觉。5. 温度标定与误差修正实测数据告诉我发射率不是唯一变量5.1 红外测温的三大误差源MLX90640毕竟不是医疗级测温仪误差来源很多。我在调试过程中总结了三个主要来源发射率设置人体皮肤的发射率通常在0.97到0.99之间一般取0.98。如果设置成0.95测量值会偏低0.2℃左右。反射温度Tr太阳光、白炽灯、暖气片、甚至旁边站的人都会成为红外反射源导致测量值偏高。我一开始在办公室调试头顶日光灯角度一变额头温度能凭空涨0.5℃。环境温度与传感器自身热量MLX90640工作一段时间后芯片自身温度会升高热电堆测量结果会发生漂移。官方API虽然做了补偿但如果内部温度变化太大误差依然存在。5.2 我的标定流程我没有黑体炉所以采用了最实用的替代方案用水银体温计和医疗级红外耳温枪作为参考在不同距离、不同环境下做多组对照。标定过程分三步在固定反射温度Tr23℃、距离30cm的测试环境中让人脸对准摄像头记录设备读取的温度值。同时用耳温枪在耳朵里测三次取平均值作为参考体温。重复测20个人或者同一个人在短时间内测多次得到一组(设备测量值, 真实体温)数据对。然后做简单线性回归得到两个参数offset和scale。实际使用时float corrected_temp raw_temp * scale offset;在这个项目中scale几乎等于1所以核心其实是offset修正。我的设备在办公室环境中整体偏高0.4℃所以在代码里设置了一个可调偏置在上位机界面上增加一个校准旋钮可以手动设置offset范围-2℃到2℃。5.3 实测对比数据在30cm距离上用耳温枪做参考记录了几组数据测试对象耳温枪红外设备原始值标定后值偏差A36.536.936.50.0B36.837.236.80.0C37.037.437.00.0D36.236.636.20.0E36.937.336.90.0可见在固定距离和稳定环境下标定后精度可以做到±0.1℃以内。但如果把距离拉到60cm读数会偏低0.2到0.3℃。距离越远红外能量衰减越多。所以实际使用时我会在上位机界面显示当前有效距离范围并建议被测人靠近到30cm左右。6. 调试过程中踩过的坑与解决记录6.1 I2C读取偶尔出错不是代码问题是上拉电阻第一次调通MLX90640之后我遇到一个非常隐晦的问题读数据偶尔会返回0xFFFF或者整行数据异常。我一度怀疑是接线虚焊把线全部重新焊了一遍还是没解决。后来用示波器看I2C信号发现SCL和SDA的上升沿非常平缓才知道是上拉电阻太小或根本没接。我的模块虽然板载了上拉但杜邦线太长寄生电容增大4.7kΩ上拉已经不够后来换成2.2kΩ才稳定。如果是把传感器用长线连接建议用1kΩ到2.2kΩ上拉并且降低I2C时钟到400kHz以下。6.2 热力图显示帧率只有1帧罪魁祸首是QImage转换最初我把温度矩阵先转成QImage再绘制结果发现帧率只有1帧左右。排查后发现问题出在QImage::setPixel这个接口上它在循环里调用会非常慢。改成直接操作QImage::scanLine()填充像素后同样一帧的时间从几百毫秒降到了几毫秒。这里强烈建议所有实时图像处理都不要使用setPixel直接操作内存。6.3 OpenCV人脸检测模型加载慢第一次运行QT程序时加载Caffe模型花了将近1秒界面白屏很久。后来我把模型加载放到检测线程启动之前并且用net.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV)和net.setPreferableTarget(cv::dnn::DNN_TARGET_CPU)强制使用CPU优化加载速度有所提升。如果你不想用Caffe模型也可以换成OpenCV自带的YuNet人脸检测器速度更快模型文件也更小。6.4 MLX90640刷新率与人体移动的矛盾MLX90640在2Hz刷新率下如果人移动太快上一帧和下一帧之间人脸的位移可能超过2个像素导致热点区域对不上。我的解决办法是上位机在显示温度时不只是用当前帧的人脸框位置而是对温度矩阵做三帧滑动平均。这样在0.5秒内温度变化会更加平滑不会出现温度数字剧烈跳动。另外MLX90640的某一帧偶尔会出现全画面温度跳变这是因为传感器内部在做增益校准。如果检测到整帧平均温度突变了超过5℃直接丢弃这一帧不参与平均值计算。6.5 串口工具乱码的教训调试中有一段时间用ST-Link Utility下载完程序后串口工具收到一堆乱码。查了很久发现不是程序问题而是STM32在下载程序后需要复位才能正常初始化I2C和串口而某些下载器默认不执行复位。解决办法是在代码里增加一个简单的日志输出每次上电打印一个起始标志如果串口工具收到起始标志就说明通信链路正常否则检查接线和复位电路。结尾这个项目做完之后最大的收获倒不是实现了测温功能而是把从传感器底层驱动到上位机交互显示这一整条链路彻底盘了一遍。很多零散的知识点——I2C时序、串口状态机、线程模型、图像坐标映射——平时都是一知半解真正把软硬件串起来跑数据时才明白每个环节为什么那么设计。如果你也要做类似的红外测温项目我建议先从下位机纯串口输出开始先用串口助手验证温度矩阵再写QT界面最后加上人脸检测。千万不要一上来就整全套出问题的时候真的不好定位。测温精度的校准也需要耐心不要指望一次标定就能一劳永逸环境变化之后多测几组数据把偏置参数做成可调设备才能真正拿到现场用。本文还有配套的精品资源点击获取
返回列表