前言
本篇文章主要介绍使用Qt编程进行串口调试,并且基于 OpenCV实现人脸识别、以及模型训练及预测。我也是一个初学者,借此文章记录学习过程,并且进行自我总结,在写文章的过程中可以使得自己的思路更加清晰,也可以大家互相学习。
项目框架
整体的项目框架可以分为(1)串口调试:创建串口、设置相应的参数信息,并打开串口;(2)人脸识别:打开摄像头、读取图片并对图片进行处理、进行人脸识别,并将图片转为Qt可识别类型;(3)模型训练:读取识别到的脸的位置图、进行灰度处理、将face存到数组、进行模型训练,并将训练结果保存;(4)检测是否有训练模型(训练完才预测)、读取识别到的脸的位置图并进行灰度处理、进行机器预测;
串口调试
首先运行keil5中的串口程序,再打开硬件仿真,最后打开虚拟串口驱动。用Qt实现com1口。这里我们要添加核心库,在**.pro文件中添加如下代码:
QT += core gui serialport
在mainwindow.h中声明开串口函数,并定义指向串口的指针变量;
void openUart();
QSerialPort *port;
接下来就是创建一个串口,定义串口名,设置串口的相应参数,并打开该串口(串口的设置可参考串口调试助手,如下图:)
相关代码如下:
void MainWindow::openUart()
{
QString uartName = "com1";//定义要使用的串口的名字
port = new QSerialPort(uartName);//创建com1串口的实例指针变量
port->setBaudRate(QSerialPort::Baud9600);//设置串口通信速度 9600bit/s
port->setDataBits(QSerialPort::Data8);//设置串口通信的数据位
port->setStopBits(QSerialPort::OneStop);//设置串口通信的停止位
port->setParity(QSerialPort::NoParity);//设置串口通信的校验 无校验
port->setFlowControl(QSerialPort::NoFlowControl);//设置串口通信的流控制:无(不存在串口和设备通信数据不同步)
//打开串口
if(port->open(QSerialPort::ReadWrite))
QMessageBox::warning(this,"serival test","serial open OK!");
else
QMessageBox::warning(this,"seival test","serial open ERROR!");
}
添加按钮,编写按钮的槽函数。UI布局如下:
部分代码如下:
void MainWindow::on_open_LED_clicked()
{
qDebug()<<"open_LED";
char cmd = '0';
port->write(&cmd,1);
}
void MainWindow::on_close_LED_clicked()
{
qDebug()<<"close_LED";
char cmd = '1';
port->write(&cmd,1);
}
void MainWindow::on_open_RELAY_clicked()
{
qDebug()<<"open_RELAY";
char cmd = '2';
port->write(&cmd,1);
}
void MainWindow::on_close_RELAY_clicked()
{
qDebug()<<"close_RELAY";
char cmd = '3';
port->write(&cmd,1);
}
void MainWindow::on_open_FAN_clicked()
{
qDebug()<<"open_FAN";
char cmd = '4';
port->write(&cmd,1);
}
void MainWindow::on_close_FAN_clicked()
{
qDebug()<<"close_FAN";
char cmd = '5';
port->write(&cmd,1);
}
void MainWindow::on_open_BUZZER_clicked()
{
qDebug()<<"open_BUZZER";
char cmd = '6';
port->write(&cmd,1);
}
void MainWindow::on_close_BUZZER_clicked()
{
qDebug()<<"close_BUZZER";
char cmd = '7';
port->write(&cmd,1);
}
void MainWindow::on_open_camera_clicked()
{
timer_ID = startTimer(30);
ui->open_camera->setEnabled(false);
}
void MainWindow::on_end_camera_clicked()
{
killTimer(timer_ID);
timer_ID = 0;
ui->open_camera->setEnabled(true);
}
void MainWindow::on_face_train_clicked()
{
cout<<"开始录入人脸"<<endl;
flag = 0;
study_timerID = startTimer(50);
}
人脸识别
首先打开摄像头、读取图片并对图片进行处理、进行人脸识别,将图片转成QImage对象,将QPixmap图像显示到label中(QPixmap主要是用于绘图,针对屏幕显示而最佳化设计),部分代码如下:
void MainWindow::timerEvent(QTimerEvent *event)//定时器处理函数
//若timer_ID、study_timerID、check_timerID定的时间到时,则会进入此函数
{
/***人脸识别显示到label中***/
if(timer_ID == event->timerId())
{
v.read(src);//把读到的摄像头图片存储到原图src中
flip(src,src,1);//将图片进行翻转,否则label中呈现出来的是和实际动作相反的
cvtColor(src,rgb,CV_BGR2RGB);//要在label中显示图片需要将bgr图片转换为rgb图片
cvtColor(rgb,gray,CV_BGR2GRAY);//将后续用到的rgb图片转换为gray灰度图为了处理人脸识别时数据是单通道的,数据量小
c.detectMultiScale(gray,faces,1.1,3,0,Size(24,24));//人脸识别
for(uint i=0;i<faces.size();i++)//循环找屏幕中的多个脸
rectangle(rgb,faces[i],Scalar(255,0,0),2,LINE_8,0);//并给检测到的人脸画红框
cv::resize(rgb,rgb,Size(400,300));//更改rgb图片的大小
ui->label_show->resize(QSize(400,300));//更改label的大小
QImage img(rgb.data,rgb.cols,rgb.rows,rgb.cols*rgb.channels(),QImage::Format_RGB888);
//要在label中显示图片需把Mat类型的rgb图片转换为QImage类型图片
ui->label_show->setPixmap(QPixmap::fromImage(img));//要在label中显示图片需把QImage图片转换为QPixmap类型
}
模型训练
根据提供的图片模型通过算法生成数据模型,从而在其它图片中查找相关的目标。在判断之前,我们要先进行学习,生成人脸的模型以便后续识别使用。这里需要用到FaceRecognizer类,FaceRecognizer类是opencv提供的人脸识别器基类,LBPHFaceRecognizer是根据LBPH算法实现的识别器类,其中LBPHFaceRecognizer识别器支持在原有模型基础上继续学习(模型数据可以累计)。
创建LBPHFaceRecognizer识别器对象:所需的头文件:#include <opencv2/face.hpp>、using namespace cv::face;
创建空的人脸识别器对象:Ptr recognizer =LBPHFaceRecognizer::create();
根据已有的模型创建人脸识别器对象,在创建人脸识别器的时候,需要一个已经学习好的模型文件:Ptr recognizer = FaceRecognizer::load(“模型文件.xml”);然后进行机器学习并保存模型;相关代码如下:
if(study_timerID == event->timerId())
{
qDebug()<<"start model_train";
Mat face;
if(faces.empty())return;//如果vector<Rect> faces中是空的直接返回
face = src(faces[0]);//把src中人脸位置的部分图给face
cv::resize(face,face,Size(100,100));//把人脸位置的部分图的大小设置为100*100
cvtColor(face,face,CV_BGR2GRAY);//将face图进行灰度转换
train_faces.push_back(face);//把face图存储到vector<Mat> train_faces中
train_labels.push_back(1);//把label标签存储到vector<int> train_labels中
count++;//每进一次中断训练的图个数+1
if(count==100)//训练到100张为止
{
count = 0;
recognizer->update(train_faces,train_labels);//图片模型训练
recognizer->save("D:/opencv/heads/myface.xml");//保存训练的模型
killTimer(study_timerID);//关闭定时器
QMessageBox::warning(this,"train","train OK");//提示
flag = 1;//训练完之后可以进行预测置1
train_faces.clear();//清空图片数组
train_labels.clear();//图片标签数组
}
}
机器预测
如果成功则把按钮打开
进行机器预测:判断这个人脸到底是谁,用到的函数如下:
void predict(InputArray src, int &label, double &confidence)
int &label:预测后的标签,学习时对应的标签,标签为-1时匹配不到训练的图片标签;
double &confidence:预测出结果的可信度,数值越小可信度越高;
设置可信度极值:功能函数为:
void setThreshold(double val);
double val:预测可信度极值,预测可信度超出极值则预测失败。
预测相关代码如下:
if(check_timerID == event->timerId())//将人脸进行预测
{
if(flag == 1)//训练完了的话可以进行预测了
{
qDebug()<<"start pridict";
QFile file("D:/opencv/heads/myface.xml");//判断是否有训练的模型
if(file.exists())//如果训练模型的文件存在
{
if(faces.empty() || recognizer->empty())return;
Mat face = src(faces[0]);//将src文件中的人脸位置图片存储到face中
cvtColor(face,face,CV_BGR2GRAY);//人脸位置图片进行灰度处理
cv::resize(face,face,Size(100,100));//重新设置图片大小
int label = -1;//标签为-1时匹配不到训练的图片标签
double confidence = 0.0;
recognizer->predict(face,label,confidence);//机器预测
qDebug()<<"label:"<<label;
qDebug()<<"confidence:"<<confidence;
if(label != -1)
{
ui->open_LED->setEnabled(true);
ui->open_RELAY->setEnabled(true);
ui->open_FAN->setEnabled(true);
ui->open_BUZZER->setEnabled(true);
ui->close_LED->setEnabled(true);
ui->close_RELAY->setEnabled(true);
ui->close_FAN->setEnabled(true);
ui->close_BUZZER->setEnabled(true);
}
else qDebug()<<"myface.xml NOT exits";
}
}
}
关键技术
通过级联分类器实现人脸识别
opencv级联分类器工具类 : CascadeClassifier //struct Student
2>加载级联分类器配置文件 : bool load( const String& filename )
参数1:分类器数据文件的名字
返回值:成功true,失败false
3>更改色彩区间位灰度图
void cvtColor( InputArray src, OutputArray dst, int code, int dstCn = 0 );
参数1:输入图像
参数2:输出图像
参数3:色彩空间转换码
槽函数
信号和槽机制是Qt的核心机制当一个对象的状态发生变化时,通过信号的方式通知其他对象,其他对象通过执行相应的槽函数来响应该信号。
槽:其实就是一个处理函数,是在对象中声明为slots:之下的函数及其实现。槽是一个对象对他感兴趣的对象的某个事件做出处理。其信号槽工作的过程是:当一个对象发射一个信号的时候,则和其连接的对象的槽函数进行处理,等槽函数处理完成之后退出并执行接下来的内容。
槽函数:本质 就是类的成员函数,我们可以调用类的成员函数一样来调用槽函数,槽函数可以跟信号建立起关联,而普通的成员函数不可以。
定时器事件
需要包含头文件:#include< QTimerEvent>
(1)定时器事件处理函数:void timerEvent(QTimerEvent* event);
当定时器发生(或超时)时,会调用timerEvent(QTimerEvent* event),如果定时器在运行,可用通
过event->timerId()来区分当前是哪一个定时器被触发了。
(2)启动定时器:int startTimer(int interval,Qt::TimerType timerType = Qt::CoarseTime);
调用startTimer启动一个定时器,并返回定时器id,如果启动失败则返回0。
参数1:定时器事件,默认为毫秒,定时器每隔interval毫秒就会启动一次,直到调用killTimer()。
参数2:定时器精度
(3)关闭定时器:void killTimer(int id);
参数:关闭id定时器
注意:当一个定时器被关闭后使用的id标识变量要清零。
项目演示
人脸识别项目演示