当前位置:网站首页>基于STM32单片机设计的红外测温仪(带人脸检测)
基于STM32单片机设计的红外测温仪(带人脸检测)
2022-07-06 12:39:00 【DS小龙哥】
由于医学发展的需要,在很多情况下,一般的温度计己经满足不了快速而又准确的测温要求,例如:车站、地铁、机场等人口密度较大的地方进行人体温度测量。
当前设计的这款红外非接触式测温仪由测温硬件+上位机软件组合而成,主要用在地铁、车站入口等地方,可以准确识别人脸进行测温,如果有人温度超标会进行语音提示并且保存当前人脸照片。
1、 硬件选型与设计思路
(1). 设备端
主控单片机采用STM32F103C8T6,人体测温功能采用非接触式红外测温模块。
(2). 上位机设计思路
上位机采用Qt5设计,Qt5是一套基于C++语言的跨平台软件库,性能非常强大,目前桌面端很多主流的软件都是采用QT开发。比如: 金山办公旗下的-WPS,字节跳动旗下的-剪映,暴雪娱乐公司旗下-多款游戏登录器等等。Qt在车联网领域用的也非常多,比如,哈佛,特斯拉,比亚迪等等很多车的中控屏整个系统都是采用Qt设计。
在测温项目里,上位机与STM32之间采用串口协议进行通信,上位机可以打开笔记本电脑默认的摄像头,进行人脸检测;当检测到人脸时,控制STM32测量当前人体的实时温度实时,再将温度传递到上位机显示;当温度正常时,上位机上显示绿色的提示字样“温度正常”,并有语音播报,语音播报的声音使用笔记本自带的声卡发出。如果温度过高,上位机显示红色提示字样“温度异常,请重新测量”,并有语音播报提示。温度过高时,会自动将当前人脸拍照留存,照片存放在当前软件目录下的“face”目录里,文件的命名规则是“38.8_2022-01-05-22-12-34.jpg”,其中38.8表示温度值,后面是日期(年月日时分秒)。
(3) 上位机运行效果
上位机需要连接STM32设备之后才可以获取温度数据,点击软件上的打开摄像头按钮,开启摄像头,让检测到人脸时,下面会显示当前测量的温度。如果没有连接STM32设备,那么默认会显示一个正常的固定温度值。界面上右边红色的字,表示当前处理一帧图像的耗时时间,电脑性能越好,检测速度越快。
(4) 拿到可执行文件之后如何运行?
先解压压缩包,进入“测温仪上位机-可执行文件”目录,将“haarcascade_frontalface_alt2.xml”拷贝到C盘根目录。
然后双击“FaceTemperatureCheck.exe”运行程序。
未连接设备,也可以打开摄像头检测人脸,只不过温度值是一个固定的正常温度值范围。
二、上位机设计
2.1 安装编译环境
如果需要自己编译运行源代码,需要先安装Qt5开发环境。
下载地址: https://download.qt.io/official_releases/qt/5.12/5.12.0/
下载之后,先将电脑网络断掉(不然会提示要求输入QT的账号),然后双击安装包进行安装。
安装可以只需要选择一个MinGW 32位编译器就够用了,详情看下面截图,选择“MinGW 7.3.0 32-bit”后,就点击下一步,然后等待安装完成即可。
2.2 软件代码整体效果
如果需要完整的工程,可以在这里去下载:
https://download.csdn.net/download/xiaolong1126626497/85892490
打开工程后(工程文件的后缀是xxx.pro),点击左下角的绿色三角形按钮就可以编译运行程序。
2.3 UI设计界面
2.4 人脸检测核心代码
//人脸检测代码
bool ImageHandle::opencv_face(QImage qImage)
{
bool check_flag=0;
QTime time;
time.start();
static CvMemStorage* storage = nullptr;
static CvHaarClassifierCascade* cascade = nullptr;
//模型文件路径
QString face_model_file = QCoreApplication::applicationDirPath()+"/"+FACE_MODEL_FILE;
//加载分类器:正面脸检测
cascade = (CvHaarClassifierCascade*)cvLoad(face_model_file.toUtf8().data(), 0, 0, 0 );
if(!cascade)
{
qDebug()<<"分类器加载错误.\n";
return check_flag;
}
//创建内存空间
storage = cvCreateMemStorage(0);
//加载需要检测的图片
IplImage* img = QImageToIplImage(&qImage);
if(img ==nullptr )
{
qDebug()<<"图片加载错误.\n";
return check_flag;
}
double scale=1.2;
//创建图像首地址,并分配存储空间
IplImage* gray = cvCreateImage(cvSize(img->width,img->height),8,1);
//创建图像首地址,并分配存储空间
IplImage* small_img=cvCreateImage(cvSize(cvRound(img->width/scale),cvRound(img->height/scale)),8,1);
cvCvtColor(img,gray, CV_BGR2GRAY);
cvResize(gray, small_img, CV_INTER_LINEAR);
cvEqualizeHist(small_img,small_img); //直方图均衡
/* * 指定相应的人脸特征检测分类器,就可以检测出图片中所有的人脸,并将检测到的人脸通过矩形的方式返回。 * 总共有8个参数,函数说明: 参数1:表示输入图像,尽量使用灰度图以加快检测速度。 参数2:表示Haar特征分类器,可以用cvLoad()函数来从磁盘中加载xml文件作为Haar特征分类器。 参数3:用来存储检测到的候选目标的内存缓存区域。 参数4:表示在前后两次相继的扫描中,搜索窗口的比例系数。默认为1.1即每次搜索窗口依次扩大10% 参数5:表示构成检测目标的相邻矩形的最小个数(默认为3个)。如果组成检测目标的小矩形的个数和小于 min_neighbors - 1 都会被排除。如果min_neighbors 为 0, 则函数不做任何操作就返回所有的被检候选矩形框,这种设定值一般用在用户自定义对检测结果的组合程序上。 参数6:要么使用默认值,要么使用CV_HAAR_DO_CANNY_PRUNING,如果设置为CV_HAAR_DO_CANNY_PRUNING,那么函数将会使用Canny边缘检测来排除边缘过多或过少的区域,因此这些区域通常不会是人脸所在区域。 参数7:表示检测窗口的最小值,一般设置为默认即可。 参数8:表示检测窗口的最大值,一般设置为默认即可。 函数返回值:函数将返回CvSeq对象,该对象包含一系列CvRect表示检测到的人脸矩形。 */
CvSeq* objects = cvHaarDetectObjects(small_img,
cascade,
storage,
1.1,
3,
0/*CV_HAAR_DO_CANNY_PRUNING*/,
cvSize(50,50)/*大小决定了检测时消耗的时间多少*/);
qDebug()<<"人脸数量:"<<objects->total;
//遍历找到对象和周围画盒
QPainter painter(&qImage);//构建 QPainter 绘图对象
QPen pen;
pen.setColor(Qt::blue); //画笔颜色
pen.setWidth(5); //画笔宽度
painter.setPen(pen); //设置画笔
CvRect *max=nullptr;
for(int i=0;i<(objects->total);++i)
{
//得到人脸的坐标位置和宽度高度信息
CvRect* r=(CvRect*)cvGetSeqElem(objects,i);
if(max==nullptr)max=r;
else
{
if(r->width > max->width || r->height > max->height)
{
max=r;
}
}
}
//如果找到最大的目标脸
if(max!=nullptr)
{
check_flag=true;
//将人脸区域绘制矩形圈起来
painter.drawRect(max->x*scale,max->y*scale,max->width*scale,max->height*scale);
}
cvReleaseImage(&gray); //释放图片内存
cvReleaseImage(&small_img); //释放图片内存
cvReleaseHaarClassifierCascade(&cascade); //释放内存-->分类器
cvReleaseMemStorage(&objects->storage); //释放内存-->检测出图片中所有的人脸
//释放图片
cvReleaseImage(&img);
qint32 time_ms=0;
time_ms=time.elapsed();
//耗时时间
emit ss_log_text(QString("%1").arg(time_ms));
//保存结果
m_image=qImage.copy();
return check_flag;
}
2.5 配置文件(修改参数-很重要)
参数说明:
如果电脑上有多个摄像头,可以修改配置文件里的摄像头编号,具体的数量在程序启动时会自动查询,通过打印代码输出到终端。
如果自己第一次编译运行源码,运行之后,
(1)需要将软件源码目录下的“haarcascade_frontalface_alt2.xml” 文件拷贝到C盘根目录,或者其他非中文目录下,具体路径可以在配置文件里修改,默认就是C盘根目录。
(2)需要将软件源码目录下的“OpenCV-MinGW-Build-OpenCV-3.4.7\x86\mingw\bin”目录里所有文件拷贝到,生成的程序执行文件同级目录下。
这样才能保证程序可以正常运行。
报警温度的阀值范围,也可以自行更改,在配置文件里有说明。
2.6 语音提示文件与背景图
语音提示文件,背景图是通过资源文件加载的。
源文件存放在,源代码的“FaceTemperatureCheck\res”目录下。
自己也可以自行替换,重新编译程序即可生效。
2.7 语音播报与图像显示处理代码
//图像处理的结果
void Widget::slot_HandleImage(bool flag,QImage image)
{
bool temp_state=0;
//检测到人脸
if(flag)
{
//判断温度是否正常
if(current_temp<MAX_TEMP && current_temp>MIN_TEMP)
{
temp_state=true;
//显示温度正常
ui->label_temp->setStyleSheet("color: rgb(0, 255, 127);font: 20pt \"Arial\";");
ui->label_temp->setText(QString("%1℃").arg(current_temp));
}
else //语音播报,温度异常
{
temp_state=false;
//显示温度异常
ui->label_temp->setStyleSheet("color: rgb(255, 0, 0);font: 20pt \"Arial\";");
ui->label_temp->setText(QString("%1℃").arg(current_temp));
}
//获取当前ms时间
long long currentTime = QDateTime::currentDateTime().toMSecsSinceEpoch();
//判断时间间隔是否到达
if(currentTime-old_currentTime>AUDIO_RATE_MS)
{
//更改当前时间
old_currentTime=currentTime;
//温度正常
if(temp_state)
{
//语音播报,温度正常
QSound::play(":/res/ok.wav");
}
//温度异常
else
{
//语音播报,温度异常
QSound::play(":/res/error.wav");
//拍照留存
QString dir_str = QCoreApplication::applicationDirPath()+"/face";
//检查目录是否存在,若不存在则新建
QDir dir;
if (!dir.exists(dir_str))
{
bool res = dir.mkpath(dir_str);
qDebug() << "新建目录状态:" << res;
}
//目录存在就保存图片
QDir dir2;
if (dir2.exists(dir_str))
{
//拼接名称
QDateTime dateTime(QDateTime::currentDateTime());
//时间效果: 2020-03-05 16:25::04 周一
QString qStr=QString("%1/%2_").arg(dir_str).arg(current_temp);
qStr+=dateTime.toString("yyyy-MM-dd-hh-mm-ss-ddd");
image.save(qStr+".jpg");
}
}
}
}
else //不显示温度
{
ui->label_temp->setStyleSheet("color: rgb(0, 255, 127);font: 20pt \"Arial\";");
ui->label_temp->setText("----");
}
//处理图像的结果画面
ui->widget_player->slotGetOneFrame(image);
}
2.8 STM32的温度接收处理代码
//刷新串口端口
void Widget::on_pushButton_flush_uart_clicked()
{
QList<QSerialPortInfo> UartInfoList=QSerialPortInfo::availablePorts(); //获取可用串口端口信息
ui->comboBox_ComSelect->clear();
if(UartInfoList.count()>0)
{
for(int i=0;i<UartInfoList.count();i++)
{
if(UartInfoList.at(i).isBusy()) //如果当前串口 COM 口忙就返回真,否则返回假
{
QString info=UartInfoList.at(i).portName();
info+="(占用)";
ui->comboBox_ComSelect->addItem(info); //添加新的条目选项
}
else
{
ui->comboBox_ComSelect->addItem(UartInfoList.at(i).portName()); //添加新的条目选项
}
}
}
else
{
ui->comboBox_ComSelect->addItem("无可用COM口"); //添加新的条目选项
}
}
//连接测温设备
void Widget::on_pushButton_OpenUart_clicked()
{
if(ui->pushButton_OpenUart->text()=="连接测温设备") //打开串口
{
ui->pushButton_OpenUart->setText("断开连接");
/*配置串口的信息*/
UART_Config->setPortName(ui->comboBox_ComSelect->currentText()); //COM的名称
if(!(UART_Config->open(QIODevice::ReadWrite))) //打开的属性权限
{
QMessageBox::warning(this, tr("状态提示"),
tr("设备连接失败!\n请选择正确的COM口"),
QMessageBox::Ok);
ui->pushButton_OpenUart->setText("连接测温设备");
return;
}
}
else //关闭串口
{
ui->pushButton_OpenUart->setText("连接测温设备");
/*关闭串口-*/
UART_Config->clear(QSerialPort::AllDirections);
UART_Config->close();
}
}
//读信号
void Widget::ReadUasrtData()
{
/*返回可读的字节数*/
if(UART_Config->bytesAvailable()<=0)
{
return;
}
/*定义字节数组*/
QByteArray rx_data;
/*读取串口缓冲区所有的数据*/
rx_data=UART_Config->readAll();
//转换温度
current_temp=rx_data.toDouble();
}
边栏推荐
- [DSP] [Part 1] start DSP learning
- Core principles of video games
- Unity making plug-ins
- Mécanisme de fonctionnement et de mise à jour de [Widget Wechat]
- [DIY]自己设计微软MakeCode街机,官方开源软硬件
- JS get browser system language
- APS taps home appliance industry into new growth points
- 知识图谱之实体对齐二
- Summary of different configurations of PHP Xdebug 3 and xdebug2
- 7、数据权限注解
猜你喜欢
Number of schemes from the upper left corner to the lower right corner of the chessboard (2)
【每周一坑】输出三角形
Tencent T4 architect, Android interview Foundation
Intel 48 core new Xeon run point exposure: unexpected results against AMD zen3 in 3D cache
Le lancement du jupyter ne répond pas après l'installation d'Anaconda
【微信小程序】運行機制和更新機制
2022 nurse (primary) examination questions and new nurse (primary) examination questions
Utilisation de l'écran OLED
SSO single sign on
Pycharm remote execution
随机推荐
Deep learning classification network -- zfnet
Quel genre de programmation les enfants apprennent - ils?
Discussion on beegfs high availability mode
Implementation of packaging video into MP4 format and storing it in TF Card
Continuous test (CT) practical experience sharing
Why do novices often fail to answer questions in the programming community, and even get ridiculed?
[wechat applet] operation mechanism and update mechanism
PHP online examination system version 4.0 source code computer + mobile terminal
逻辑是个好东西
Recyclerview GridLayout bisects the middle blank area
JS implementation force deduction 71 question simplified path
Case ① | host security construction: best practice of 3 levels and 11 capabilities
The mail command is used in combination with the pipeline command statement
[weekly pit] positive integer factorization prime factor + [solution] calculate the sum of prime numbers within 100
Learn to punch in Web
JVM_ Common [interview questions]
小孩子学什么编程?
[cloud native and 5g] micro services support 5g core network
OAI 5G NR+USRP B210安装搭建
Boder radius has four values, and boder radius exceeds four values