• 基于STM32单片机设计的红外测温仪(带人脸检测)


    由于医学发展的需要,在很多情况下,一般的温度计己经满足不了快速而又准确的测温要求,例如:车站、地铁、机场等人口密度较大的地方进行人体温度测量。

    当前设计的这款红外非接触式测温仪由测温硬件+上位机软件组合而成,主要用在地铁、车站入口等地方,可以准确识别人脸进行测温,如果有人温度超标会进行语音提示并且保存当前人脸照片。

    1、 硬件选型与设计思路

    (1). 设备端

    主控单片机采用STM32F103C8T6,人体测温功能采用非接触式红外测温模块。

    imgimg

    (2). 上位机设计思路

    上位机采用Qt5设计,Qt5是一套基于C++语言的跨平台软件库,性能非常强大,目前桌面端很多主流的软件都是采用QT开发。比如: 金山办公旗下的-WPS,字节跳动旗下的-剪映,暴雪娱乐公司旗下-多款游戏登录器等等。Qt在车联网领域用的也非常多,比如,哈佛,特斯拉,比亚迪等等很多车的中控屏整个系统都是采用Qt设计。

    在测温项目里,上位机与STM32之间采用串口协议进行通信,上位机可以打开笔记本电脑默认的摄像头,进行人脸检测;当检测到人脸时,控制STM32测量当前人体的实时温度实时,再将温度传递到上位机显示;当温度正常时,上位机上显示绿色的提示字样“温度正常”,并有语音播报,语音播报的声音使用笔记本自带的声卡发出。如果温度过高,上位机显示红色提示字样“温度异常,请重新测量”,并有语音播报提示。温度过高时,会自动将当前人脸拍照留存,照片存放在当前软件目录下的“face”目录里,文件的命名规则是“38.8_2022-01-05-22-12-34.jpg”,其中38.8表示温度值,后面是日期(年月日时分秒)。

    (3) 上位机运行效果

    img

    img

    上位机需要连接STM32设备之后才可以获取温度数据,点击软件上的打开摄像头按钮,开启摄像头,让检测到人脸时,下面会显示当前测量的温度。如果没有连接STM32设备,那么默认会显示一个正常的固定温度值。界面上右边红色的字,表示当前处理一帧图像的耗时时间,电脑性能越好,检测速度越快。

    (4) 拿到可执行文件之后如何运行?

    先解压压缩包,进入“测温仪上位机-可执行文件”目录,将“haarcascade_frontalface_alt2.xml”拷贝到C盘根目录。

    img

    img

    然后双击“FaceTemperatureCheck.exe”运行程序。

    img

    未连接设备,也可以打开摄像头检测人脸,只不过温度值是一个固定的正常温度值范围。

    二、上位机设计

    2.1 安装编译环境

    如果需要自己编译运行源代码,需要先安装Qt5开发环境。

    下载地址: https://download.qt.io/official_releases/qt/5.12/5.12.0/

    img

    下载之后,先将电脑网络断掉(不然会提示要求输入QT的账号),然后双击安装包进行安装。

    安装可以只需要选择一个MinGW 32位编译器就够用了,详情看下面截图,选择“MinGW 7.3.0 32-bit”后,就点击下一步,然后等待安装完成即可。

    img

    2.2 软件代码整体效果

    如果需要完整的工程,可以在这里去下载:
    https://download.csdn.net/download/xiaolong1126626497/85892490
    img

    打开工程后(工程文件的后缀是xxx.pro),点击左下角的绿色三角形按钮就可以编译运行程序。

    img

    2.3 UI设计界面

    img

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117

    2.5 配置文件(修改参数-很重要)

    img

    参数说明:

    如果电脑上有多个摄像头,可以修改配置文件里的摄像头编号,具体的数量在程序启动时会自动查询,通过打印代码输出到终端。

    如果自己第一次编译运行源码,运行之后,

    (1)需要将软件源码目录下的“haarcascade_frontalface_alt2.xml” 文件拷贝到C盘根目录,或者其他非中文目录下,具体路径可以在配置文件里修改,默认就是C盘根目录。

    (2)需要将软件源码目录下的“OpenCV-MinGW-Build-OpenCV-3.4.7\x86\mingw\bin”目录里所有文件拷贝到,生成的程序执行文件同级目录下。

    这样才能保证程序可以正常运行。

    报警温度的阀值范围,也可以自行更改,在配置文件里有说明。

    2.6 语音提示文件与背景图

    语音提示文件,背景图是通过资源文件加载的。

    源文件存放在,源代码的“FaceTemperatureCheck\res”目录下。

    img

    自己也可以自行替换,重新编译程序即可生效。

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
  • 相关阅读:
    翻译软件Mate Translate mac中文版介绍说明
    nacos源码下
    辅助驾驶功能开发-功能对标篇(14)-NOA领航辅助系统-集度
    强化学习中的并行方法:ApeX框架 梯度并行,A3C经验并行 | 分布式异步参数更新, 分布式数据生成
    QT笔记——用VS + qt 生成dll 和 调用生成的dll
    CoT 的方式使用 LLM 设计测试用例实践
    【每日一题】咒语和药水的成功对数
    考虑碳交易机制的园区综合能源系统电热协同运行优化研究(Matlab代码实现)
    jquery 中input /checkbox/radio/button/select未选中的值/等取值问题
    一个很少见但很有用的SQL功能
  • 原文地址:https://blog.csdn.net/xiaolong1126626497/article/details/125612825