基于OpenCV實(shí)現(xiàn)的人臉簽到系統(tǒng)源代碼
效果圖

目錄文件

camerathread.h 功能實(shí)現(xiàn)全寫在.h里了
class CameraThread : public QThread
{
Q_OBJECT
public:
CameraThread()
{
//打開序號(hào)為0的攝像頭
m_cap.open(0);
if (!m_cap.isOpened()) {
qDebug() << "Error: Cannot open camera";
}
//判斷是否有文件,人臉識(shí)別模型,檢測(cè)和識(shí)別用的
if(!m_cascada.load("D:/research/CV/Opencv/haarcascade_frontalface_alt2.xml"))
{
QMessageBox::information(NULL,"失敗", "人臉識(shí)別模型裝載失敗");
}
//實(shí)例化定時(shí)器,子線程中數(shù)據(jù)傳輸?shù)街芯€程并顯示出來
m_timer = new QTimer(this);
//綁定時(shí)間信號(hào)及獲取圖像幀的圖像
connect(m_timer,SIGNAL(timeout()),this,SLOT(readFarme()));
//大概每秒24幀
//開始定時(shí)器
m_timer->start(42);
//打卡模塊
// 定義 FisherFaceRecognizer 模型,訓(xùn)練用的模型
m_model = LBPHFaceRecognizer::create();
// 加載訓(xùn)練好的模型,自己訓(xùn)練的模型
m_model->read("MyFaceLBPHModel.xml");
if (m_model.empty())
{
qDebug() << "Error: Failed to load model!";
}
else
{
qDebug() << "Model loaded successfully!";
}
//錄入時(shí)候的定時(shí)器,錄入大概幾秒,獲取二十張灰色圖像并保存,等待訓(xùn)練
//實(shí)例化定時(shí)器
m_Very_timer = new QTimer(this);
//數(shù)據(jù)庫的初始化部分
//鏈接數(shù)據(jù)庫
m_db = QSqlDatabase::addDatabase("QMYSQL");
m_db.setHostName("localhost"); // 主機(jī)名
m_db.setDatabaseName("face"); // 數(shù)據(jù)庫名
m_db.setUserName("root"); // 用戶名
m_db.setPassword("31415926"); // 密碼
}
~CameraThread()
{
//釋放攝像頭
m_cap.release();
}
void run() override
{
}
//圖像數(shù)據(jù)類型轉(zhuǎn)換
QImage MatImageToQt(const cv::Mat &src)
{
if(src.type() == CV_8UC1)
{
QImage qImage(src.cols,src.rows,QImage::Format_Indexed8);
qImage.setColorCount(256);
for(int i = 0; i < 256; i ++)
{
qImage.setColor(i,qRgb(i,i,i));
}
uchar *pSrc = src.data;
for(int row = 0; row < src.rows; row ++)
{
uchar *pDest = qImage.scanLine(row);
memcmp(pDest,pSrc,src.cols);
pSrc += src.step;
}
return qImage;
}
else if(src.type() == CV_8UC3)
{
const uchar *pSrc = (const uchar*)src.data;
QImage qImage(pSrc,src.cols,src.rows,src.step,QImage::Format_RGB888);
return qImage.rgbSwapped();
}
else if(src.type() == CV_8UC4)
{
const uchar *pSrc = (const uchar*)src.data;
QImage qImage(pSrc, src.cols, src.rows, src.step, QImage::Format_ARGB32);
return qImage.copy();
}
else
{
return QImage();
}
}
//人臉檢測(cè)
void Check(Mat &image, Mat &gray)
{
//直方圖均勻化(改善圖像的對(duì)比度和亮度)
Mat equalizedImg;
equalizeHist(gray,equalizedImg);
int flags = CASCADE_SCALE_IMAGE; //檢測(cè)多個(gè)人
Size minFeatureSize(30,30);
float searchScaleFactor = 1.1f; //默認(rèn)1.1倍
int minNeighbors = 4;
m_cascada.detectMultiScale(equalizedImg,m_faces,searchScaleFactor,minNeighbors,flags,minFeatureSize);
m_current_people = m_faces.size();
//檢測(cè)到的個(gè)數(shù)
//qDebug() << "檢測(cè)到人臉的個(gè)數(shù):" << m_faces.size() << endl;
QString str;
str.setNum(m_faces.size());
//qDebug() << m_current_people << endl;
// //畫矩形框
Mat face;
// for(int i = 0; i < m_faces.size(); i++)
// {
// if(m_faces[i].height > 0 && m_faces[i].width >0 )
// {
// face = gray(m_faces[i]);
// m_text_lb = Point(m_faces[i].x,m_faces[i].y);
// rectangle(image, m_faces[i], Scalar(50, 50, 150), 2, 8, 0); //線太細(xì)了會(huì)導(dǎo)致在QLabel上面丟失線框
// }
// int iP = Predict(image);
// }
// 畫矩形框和顯示姓名
for(int i = 0; i < m_faces.size(); i++)
{
string name; // 從數(shù)據(jù)庫中獲取姓名
if(m_faces[i].height > 0 && m_faces[i].width >0 )
{
face = gray(m_faces[i]);
m_text_lb = Point(m_faces[i].x,m_faces[i].y);
rectangle(image, m_faces[i], Scalar(50, 50, 150), 2, 8, 0); // 畫矩形框
//灰度圖
Mat tImagGray;
cvtColor(image, tImagGray, COLOR_BGR2GRAY);
// 識(shí)別人臉
int id = Predict(tImagGray);
if (!m_db.open())
{
qDebug() << "Failed to connect to database:" ;
}
// 執(zhí)行查詢語句
QSqlQuery query;
QString queryString = QString("SELECT name FROM staff_info WHERE num = %1").arg(id);
if (!query.exec(queryString))
{
qDebug() << "Failed to execute query:";
m_db.close();
}
// 處理查詢結(jié)果
if (query.next())
{
name = query.value(0).toString().toUtf8().constData();
m_db.close();
}
else
{
m_db.close();
name = "Unknown";
}
}
qDebug() << name.data();
// 在圖像上顯示姓名
Point textPosition(m_faces[i].x, m_faces[i].y - 10); // 文本位置在矩形框上方一點(diǎn)
putText(image, name.data(), textPosition, FONT_HERSHEY_SIMPLEX, 0.5, Scalar(255, 255, 255), 1, LINE_AA);
}
m_faces.clear();
}
// 裁剪出的臉部區(qū)域的圖像
cv::Mat cropFace(const cv::Mat& faceImg, const cv::Rect& faceRect) {
// 不再進(jìn)行顏色空間轉(zhuǎn)換,直接使用輸入的 faceImg
cv::Mat frame_gray = faceImg;
// 確保矩形區(qū)域在圖像范圍內(nèi)
if (faceRect.x >= 0 && faceRect.y >= 0 &&
faceRect.x + faceRect.width <= frame_gray.cols &&
faceRect.y + faceRect.height <= frame_gray.rows)
{
// 使用區(qū)域選擇功能提取矩形區(qū)域
cv::Mat faceROI = frame_gray(faceRect).clone(); // 使用 clone() 來復(fù)制圖像區(qū)域
return faceROI;
}
else
{
// 處理矩形區(qū)域超出圖像范圍的情況
// 這里可以選擇合適的處理方式,比如調(diào)整矩形區(qū)域的大小或者放棄處理該臉部區(qū)域
return cv::Mat();
}
}
// 在文本文件末尾添加數(shù)據(jù)
void appendToTextFile(const QString& filename, const QString& data)
{
QFile file(filename);
if (file.open(QIODevice::Append | QIODevice::Text))
{
QTextStream out(&file);
out << data << "\n";
file.close();
}
else
{
qDebug() << "Failed to open file for appending.";
}
}
//預(yù)測(cè)功能,檢測(cè)輸入的圖像中的人臉是否在訓(xùn)練集中
int Predict(Mat src_image)
{
Mat face_test;
int ispredict = 0;
//截取的ROI人臉尺寸調(diào)整
if (src_image.rows >= 120)
{
//改變圖像大小,使用雙線性差值
resize(src_image, face_test, Size(92, 112));
}
//判斷是否正確檢測(cè)ROI
if (!face_test.empty())
{
//測(cè)試圖像應(yīng)該是灰度圖
ispredict = m_model->predict(face_test);
}
//cout << ispredict << endl;
return ispredict;
}
signals:
//向主線程傳輸圖像
void Set_image(QImage *image);
//打卡之后設(shè)置ui部分和數(shù)據(jù)庫部分
void Send_Card_Data(QSqlDatabase *m_db,QString num);
public slots:
//攝像頭讀取函數(shù)
void readFarme()
{
//讀取一幀圖像
m_cap.read(m_src_image) ;
//處理一下數(shù)據(jù),人臉檢測(cè)
//生成灰度圖
Mat dst_gray;
cvtColor(m_src_image, dst_gray, COLOR_BGR2GRAY);
Check(m_src_image, dst_gray);
//轉(zhuǎn)換圖像數(shù)據(jù)類型
QImage imag = MatImageToQt(m_src_image);
//發(fā)送圖像
Set_image(&imag);
}
//執(zhí)行二十次檢測(cè)人臉并保存下來的功能
void Verity()
{
//判斷目前攝像頭中的臉有幾個(gè)
if (!m_facenum.isEmpty())
{
//如果目前只有一個(gè)人
if(m_current_people == 1)
{
qDebug() << m_executionCount;
//裁剪出人臉區(qū)域
Mat faceROI = cropFace(m_src_image, m_faces[0]);
if (!faceROI.empty())
{
// 調(diào)整裁剪后的臉部圖像大小
cv::Mat resizedFace;
cv::resize(faceROI, resizedFace, cv::Size(92, 112));
// 將調(diào)整大小后的臉部圖像存儲(chǔ)在 m_src_image 中
m_src_image = resizedFace.clone();
} else
{
qDebug() << "裁剪后的臉部圖像為空! ";
}
//存儲(chǔ)的地址
QString dir_str = "D:\\research\\CV\\Opencv\\facedata\\" + m_facenum + "\\" + QString::number(m_executionCount) + ".jpg";
//用來判斷這個(gè)地址的文件夾是否存在
QString is_dir = "D:\\research\\CV\\Opencv\\facedata\\" + m_facenum;
qDebug() << dir_str;
QDir dir(is_dir);
if (!dir.exists())
{
if (!dir.mkpath("."))
{
qDebug() << "default ";
}
}
//扣的臉部的圖像如果不為空
if (!faceROI.empty())
{
string filename = dir_str.toStdString();
// 將彩色圖像轉(zhuǎn)換為灰度圖像
cv::Mat frame_gray;
cv::cvtColor(faceROI, frame_gray, cv::COLOR_BGR2GRAY);
//存儲(chǔ)圖像
imwrite(filename, frame_gray);
//在存儲(chǔ)訓(xùn)練集需要的圖片的地址的txt里也更新地址
QString csvsave = "D:/research/CV/Opencv/facedata/" + m_facenum + "/" + QString::number(m_executionCount) + ".jpg" + ";" + m_facenum ;
//存放要訓(xùn)練的模型的圖片的地址的txt
QString csvfilename = "D:\\research\\CV\\Opencv\\at.txt";
//添加數(shù)據(jù)
appendToTextFile(csvfilename, csvsave);
m_executionCount++; // 每次執(zhí)行計(jì)數(shù)器加一
} else
{
qDebug() << "臉部圖像為空,無法寫入文件! ";
}
//存儲(chǔ)20張之后
if (m_executionCount >= 20)
{
m_executionCount = 0;
m_Very_timer->stop();
}
}
}
else
{
QMessageBox::about(NULL, "提示", "請(qǐng)輸入工號(hào)!");
m_executionCount = 0;
m_Very_timer->stop();
//delete m_Very_timer;
}
}
//錄入臉
void Set_Verity_face(QString facenum,QString facename)
{
m_facenum = facenum;
m_facename = facename;
qDebug()<< QSqlDatabase::drivers();
//判斷數(shù)據(jù)庫是否開著
if (!m_db.open())
{
qDebug() << "Failed to connect to database:" ;
} else
{
qDebug() << "Connected to database!";
}
//查找全部,看數(shù)量以便添加id
QSqlQuery countQuery("SELECT COUNT(*) FROM staff_info");
if (countQuery.exec() && countQuery.next())
{
int rowCount = countQuery.value(0).toInt(); // 獲取數(shù)據(jù)條數(shù)
int newId = rowCount + 1; // 新的 ID 就是數(shù)據(jù)條數(shù)加一
QSqlQuery query;
query.prepare("INSERT INTO staff_info (num, id, name) VALUES (:facenum, :faceid, :facename)");
query.bindValue(":facenum", m_facenum);
query.bindValue(":faceid", newId); // 使用新的 ID
query.bindValue(":facename", m_facename);
if (query.exec())
{
qDebug() << "Data inserted into database successfully!";
}
else
{
qDebug() << "Failed to insert data into database:" ;
}
}
else
{
qDebug() << "Failed to retrieve row count from database:" ;
}
//關(guān)閉數(shù)據(jù)庫
m_db.close();
qDebug() << "開始錄入\n";
//綁定時(shí)間信號(hào)及獲取圖像幀的圖像
connect(m_Very_timer, SIGNAL(timeout()), this, SLOT(Verity()));
//開始定時(shí)器
m_Very_timer->start(200);
}
//設(shè)置打卡部分
void Set_Card()
{
Mat gray;
cvtColor(m_src_image, gray, CV_BGR2GRAY); // 將輸入圖像轉(zhuǎn)換為灰度圖像
vector<Rect> faces; // 存放檢測(cè)到的人臉矩形的向量容器
// 使用級(jí)聯(lián)分類器檢測(cè)人臉
m_cascada.detectMultiScale(gray, faces, 1.1, 4, 0, Size(30, 30), Size(500, 500));
// 遍歷檢測(cè)到的每張人臉
for (size_t i = 0; i < faces.size(); i++)
{
Rect faceRect = faces[i]; // 獲取當(dāng)前人臉的矩形框
// 從灰度圖像中提取當(dāng)前人臉區(qū)域
Mat faceROI = gray(faceRect);
// 檢查人臉區(qū)域是否為空
if (!faceROI.empty())
{
// 調(diào)用 Predict 函數(shù)對(duì)人臉進(jìn)行預(yù)測(cè)
int temp = Predict(faceROI);
qDebug() << "預(yù)測(cè)結(jié)果:" << temp;
// 發(fā)送打卡數(shù)據(jù),然后讓主線程在數(shù)據(jù)庫中查找
Send_Card_Data(&m_db,QString::number(temp));
}
}
faces.clear();
}
private:
//聲明opencv的視頻類
cv::VideoCapture m_cap;
//更新顯示的定時(shí)器
QTimer *m_timer;
//錄入的定時(shí)器
QTimer *m_Very_timer;
//聲明Mat類圖像變量,存儲(chǔ)當(dāng)前攝像頭前的圖像
cv::Mat m_src_image;
//檢測(cè)的分類器
CascadeClassifier m_cascada;
//矩形框的點(diǎn)
Point m_text_lb;
//人臉個(gè)數(shù)
vector<Rect> m_faces;
// 聲明一個(gè)錄入計(jì)數(shù)器變量
int m_executionCount = 0;
//工號(hào)和姓名
QString m_facenum;
QString m_facename;
//識(shí)別的模型
Ptr<LBPHFaceRecognizer> m_model;
//當(dāng)前鏡頭前識(shí)別出的人數(shù)
int m_current_people = 0;
//數(shù)據(jù)庫
QSqlDatabase m_db;
};
#endif // CAMERATHREAD_H
faceverify.cpp
#include "faceverify.h"
#include "ui_faceverify.h"
#include <QDebug>
#include <QCamera> //管理攝像頭的大類
#include <QCameraInfo> //管理攝像頭的設(shè)備表
#include <QCameraViewfinder> //管理攝像頭顯示區(qū)域
#include <QCameraImageCapture> //管理圖片
#include <QDateTime> //管理時(shí)間
#include <QString> //管理字符串
#include "verityface.h"
#pragma execution_character_set("utf-8")
FaceVerify::FaceVerify(QWidget *parent)
: QWidget(parent)
, ui(new Ui::FaceVerify)
{
ui->setupUi(this);
}
FaceVerify::~FaceVerify()
{
delete m_cthread;
delete ui;
}
void FaceVerify::Get_image(QImage *image)
{
//設(shè)置圖片大小和label的長(zhǎng)寬一致
QImage timage = image->scaled(ui->Camera->width(), ui->Camera->height(),Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
ui->Camera->setPixmap(QPixmap::fromImage(timage));
}
//打開攝像頭
void FaceVerify::on_pushButton_Open_Camera_clicked()
{
if(ui->pushButton_Open_Camera->text() == "打開攝像頭")
{
m_cthread = new CameraThread();
connect(m_cthread, &CameraThread::Set_image, this, &FaceVerify::Get_image);
ui->pushButton_Open_Camera->setText("關(guān)閉攝像頭");
}
else
{
disconnect(m_cthread, &CameraThread::Set_image, this, &FaceVerify::Get_image);
delete m_cthread;
m_cthread = nullptr;
ui->Camera->clear();
ui->pushButton_Open_Camera->setText("打開攝像頭");
}
}
//打卡
void FaceVerify::on_pushButton_Card_clicked()
{
// 先斷開之前的連接
disconnect(this, &FaceVerify::Send_Card, m_cthread, &CameraThread::Set_Card);
disconnect(m_cthread, &CameraThread::Send_Card_Data, this, &FaceVerify::Show_Data);
// 連接信號(hào)和槽
connect(this, &FaceVerify::Send_Card, m_cthread, &CameraThread::Set_Card);
connect(m_cthread, &CameraThread::Send_Card_Data, this, &FaceVerify::Show_Data);
// 發(fā)送信號(hào)
emit Send_Card();
}
//打卡數(shù)據(jù)顯示
void FaceVerify::Show_Data(QSqlDatabase *m_db,QString num)
{
if (!m_db->open())
{
qDebug() << "Failed to connect to database:";
}
else
{
qDebug() << "Connected to database!";
// 執(zhí)行檢查并插入數(shù)據(jù)
QSqlQuery query;
query.prepare("SELECT * FROM record_info WHERE num = :facenum AND DATE(mtime) = CURDATE()");
query.bindValue(":facenum", num);
if (query.exec())
{
if (query.next())
{
// 如果有記錄,表示今天已經(jīng)簽到
QMessageBox::information(NULL, tr("提示 "), tr("今天已簽到 "));
// 讀取數(shù)據(jù)庫中的數(shù)據(jù)并顯示在textEdit_data中
QString data;
QSqlRecord record = query.record();
int numField = record.indexOf("num"); // 獲取字段索引
int timeField = record.indexOf("mtime");
// 遍歷查詢結(jié)果
do
{
QString num = query.value(numField).toString(); // 獲取編號(hào)
QString time = query.value(timeField).toDateTime().toString("yyyy-MM-dd hh:mm:ss"); // 獲取時(shí)間
data += "編號(hào): " + num + ", 時(shí)間: " + time + "\n";
} while (query.next());
// 將數(shù)據(jù)顯示在textEdit_data中
ui->textEdit_data->setText(data);
}
else
{
QDateTime currentDateTime = QDateTime::currentDateTime();
QString currentTimeString = currentDateTime.toString("yyyy-MM-dd hh:mm:ss");
qDebug() << "Current time:" << currentTimeString;
// 如果沒有記錄,表示今天還未簽到,執(zhí)行插入操作
query.prepare("INSERT INTO record_info (num, id, mtime) VALUES (:facenum, :faceid, :facemtime)");
query.bindValue(":facenum", num);
query.bindValue(":faceid", num); // 使用相同的 num 作為 id
query.bindValue(":facemtime", currentTimeString);
if (query.exec())
{
QMessageBox::information(NULL, tr("提示 "), tr("簽到成功 "));
}
else
{
QMessageBox::critical(NULL, tr("錯(cuò)誤 "), tr("簽到失敗: ") ); // 顯示錯(cuò)誤信息
}
}
}
else
{
QMessageBox::critical(NULL, tr("錯(cuò)誤"), tr("查詢數(shù)據(jù)失?。?"));
}
// 關(guān)閉數(shù)據(jù)庫連接
m_db->close();
}
}
//錄入人臉
void FaceVerify::on_pushButton_face_clicked()
{
//打開錄入界面
m_verifity = new VerityFace();
connect(m_verifity, &VerityFace::Send_Face, this, &FaceVerify::GetFaceNum);
m_verifity->show();
}
//訓(xùn)練模型
void FaceVerify::on_pushButton_Train_clicked()
{
m_train = new train();
connect(m_train, &train::finished, this, &FaceVerify::Train_Finish);
m_train->run();
}
//模型訓(xùn)練完
void FaceVerify::Train_Finish()
{
delete m_train;
QMessageBox::information(NULL,"訓(xùn)練", "訓(xùn)練完成");
}總結(jié)
到此這篇關(guān)于基于OpenCV實(shí)現(xiàn)的人臉簽到系統(tǒng)的文章就介紹到這了,更多相關(guān)OpenCV人臉簽到系統(tǒng)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
DSP中浮點(diǎn)轉(zhuǎn)定點(diǎn)運(yùn)算--舉例及編程中的心得
本文主要講解DSP浮點(diǎn)轉(zhuǎn)定點(diǎn)運(yùn)算舉例及編程中的心得 ,具有參考價(jià)值,需要的朋友可以參考一下。2016-06-06
C++用一棵紅黑樹同時(shí)封裝出set與map的實(shí)現(xiàn)代碼
set中存儲(chǔ)的一般為鍵K即可,而map存儲(chǔ)的一般都是鍵值對(duì)KV,也就是說他們結(jié)構(gòu)是不同的,那么我們?nèi)绾尾拍苡靡活w紅黑樹同時(shí)封裝出set與map兩種容器呢,那么接下來我們具體地來研究下STL庫中是怎樣實(shí)現(xiàn)的,并且進(jìn)行模擬實(shí)現(xiàn),需要的朋友可以參考下2024-03-03
vc6.0中c語言控制臺(tái)程序中的定時(shí)技術(shù)(定時(shí)器)
這篇文章主要介紹了vc6.0中c語言控制臺(tái)程序中的定時(shí)技術(shù)(定時(shí)器),需要的朋友可以參考下2014-04-04
C++中結(jié)構(gòu)體的類型定義和初始化以及變量引用
這篇文章主要介紹了C++中結(jié)構(gòu)體的類型定義和初始化以及變量引用,是C++入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-09-09
C++設(shè)計(jì)模式編程中使用Bridge橋接模式的完全攻略
這篇文章主要介紹了C++設(shè)計(jì)模式編程中使用Bridge橋接模式的完全攻略,Bridge將抽象部分與它的實(shí)現(xiàn)部分分離,使它們都可以獨(dú)立地變化需要的朋友可以參考下2016-03-03
C++類的靜態(tài)成員變量與靜態(tài)成員函數(shù)詳解
下面小編就為大家?guī)硪黄狢++類的靜態(tài)成員變量與靜態(tài)成員函數(shù)的文章。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2021-11-11

