基于OpenCV實現(xiàn)的人臉簽到系統(tǒng)源代碼
更新時間:2024年04月27日 09:56:43 作者:狗蛋兒l
本文從實際背景和需求出發(fā),采用人臉識別簽到考勤改變了傳統(tǒng)人工檢驗的做法,極大提高了組織效率和辦事能力,這篇文章主要給大家介紹了關于如何基于OpenCV實現(xiàn)的人臉簽到系統(tǒng)的相關資料,需要的朋友可以參考下
效果圖
目錄文件
camerathread.h 功能實現(xiàn)全寫在.h里了
class CameraThread : public QThread { Q_OBJECT public: CameraThread() { //打開序號為0的攝像頭 m_cap.open(0); if (!m_cap.isOpened()) { qDebug() << "Error: Cannot open camera"; } //判斷是否有文件,人臉識別模型,檢測和識別用的 if(!m_cascada.load("D:/research/CV/Opencv/haarcascade_frontalface_alt2.xml")) { QMessageBox::information(NULL,"失敗", "人臉識別模型裝載失敗"); } //實例化定時器,子線程中數(shù)據(jù)傳輸?shù)街芯€程并顯示出來 m_timer = new QTimer(this); //綁定時間信號及獲取圖像幀的圖像 connect(m_timer,SIGNAL(timeout()),this,SLOT(readFarme())); //大概每秒24幀 //開始定時器 m_timer->start(42); //打卡模塊 // 定義 FisherFaceRecognizer 模型,訓練用的模型 m_model = LBPHFaceRecognizer::create(); // 加載訓練好的模型,自己訓練的模型 m_model->read("MyFaceLBPHModel.xml"); if (m_model.empty()) { qDebug() << "Error: Failed to load model!"; } else { qDebug() << "Model loaded successfully!"; } //錄入時候的定時器,錄入大概幾秒,獲取二十張灰色圖像并保存,等待訓練 //實例化定時器 m_Very_timer = new QTimer(this); //數(shù)據(jù)庫的初始化部分 //鏈接數(shù)據(jù)庫 m_db = QSqlDatabase::addDatabase("QMYSQL"); m_db.setHostName("localhost"); // 主機名 m_db.setDatabaseName("face"); // 數(shù)據(jù)庫名 m_db.setUserName("root"); // 用戶名 m_db.setPassword("31415926"); // 密碼 } ~CameraThread() { //釋放攝像頭 m_cap.release(); } void run() override { } //圖像數(shù)據(jù)類型轉換 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(); } } //人臉檢測 void Check(Mat &image, Mat &gray) { //直方圖均勻化(改善圖像的對比度和亮度) Mat equalizedImg; equalizeHist(gray,equalizedImg); int flags = CASCADE_SCALE_IMAGE; //檢測多個人 Size minFeatureSize(30,30); float searchScaleFactor = 1.1f; //默認1.1倍 int minNeighbors = 4; m_cascada.detectMultiScale(equalizedImg,m_faces,searchScaleFactor,minNeighbors,flags,minFeatureSize); m_current_people = m_faces.size(); //檢測到的個數(shù) //qDebug() << "檢測到人臉的個數(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); //線太細了會導致在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); // 識別人臉 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(); } // 處理查詢結果 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); // 文本位置在矩形框上方一點 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) { // 不再進行顏色空間轉換,直接使用輸入的 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() 來復制圖像區(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."; } } //預測功能,檢測輸入的圖像中的人臉是否在訓練集中 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)); } //判斷是否正確檢測ROI if (!face_test.empty()) { //測試圖像應該是灰度圖 ispredict = m_model->predict(face_test); } //cout << ispredict << endl; return ispredict; } signals: //向主線程傳輸圖像 void Set_image(QImage *image); //打卡之后設置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ù),人臉檢測 //生成灰度圖 Mat dst_gray; cvtColor(m_src_image, dst_gray, COLOR_BGR2GRAY); Check(m_src_image, dst_gray); //轉換圖像數(shù)據(jù)類型 QImage imag = MatImageToQt(m_src_image); //發(fā)送圖像 Set_image(&imag); } //執(zhí)行二十次檢測人臉并保存下來的功能 void Verity() { //判斷目前攝像頭中的臉有幾個 if (!m_facenum.isEmpty()) { //如果目前只有一個人 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)整大小后的臉部圖像存儲在 m_src_image 中 m_src_image = resizedFace.clone(); } else { qDebug() << "裁剪后的臉部圖像為空! "; } //存儲的地址 QString dir_str = "D:\\research\\CV\\Opencv\\facedata\\" + m_facenum + "\\" + QString::number(m_executionCount) + ".jpg"; //用來判斷這個地址的文件夾是否存在 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(); // 將彩色圖像轉換為灰度圖像 cv::Mat frame_gray; cv::cvtColor(faceROI, frame_gray, cv::COLOR_BGR2GRAY); //存儲圖像 imwrite(filename, frame_gray); //在存儲訓練集需要的圖片的地址的txt里也更新地址 QString csvsave = "D:/research/CV/Opencv/facedata/" + m_facenum + "/" + QString::number(m_executionCount) + ".jpg" + ";" + m_facenum ; //存放要訓練的模型的圖片的地址的txt QString csvfilename = "D:\\research\\CV\\Opencv\\at.txt"; //添加數(shù)據(jù) appendToTextFile(csvfilename, csvsave); m_executionCount++; // 每次執(zhí)行計數(shù)器加一 } else { qDebug() << "臉部圖像為空,無法寫入文件! "; } //存儲20張之后 if (m_executionCount >= 20) { m_executionCount = 0; m_Very_timer->stop(); } } } else { QMessageBox::about(NULL, "提示", "請輸入工號!"); 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:" ; } //關閉數(shù)據(jù)庫 m_db.close(); qDebug() << "開始錄入\n"; //綁定時間信號及獲取圖像幀的圖像 connect(m_Very_timer, SIGNAL(timeout()), this, SLOT(Verity())); //開始定時器 m_Very_timer->start(200); } //設置打卡部分 void Set_Card() { Mat gray; cvtColor(m_src_image, gray, CV_BGR2GRAY); // 將輸入圖像轉換為灰度圖像 vector<Rect> faces; // 存放檢測到的人臉矩形的向量容器 // 使用級聯(lián)分類器檢測人臉 m_cascada.detectMultiScale(gray, faces, 1.1, 4, 0, Size(30, 30), Size(500, 500)); // 遍歷檢測到的每張人臉 for (size_t i = 0; i < faces.size(); i++) { Rect faceRect = faces[i]; // 獲取當前人臉的矩形框 // 從灰度圖像中提取當前人臉區(qū)域 Mat faceROI = gray(faceRect); // 檢查人臉區(qū)域是否為空 if (!faceROI.empty()) { // 調(diào)用 Predict 函數(shù)對人臉進行預測 int temp = Predict(faceROI); qDebug() << "預測結果:" << temp; // 發(fā)送打卡數(shù)據(jù),然后讓主線程在數(shù)據(jù)庫中查找 Send_Card_Data(&m_db,QString::number(temp)); } } faces.clear(); } private: //聲明opencv的視頻類 cv::VideoCapture m_cap; //更新顯示的定時器 QTimer *m_timer; //錄入的定時器 QTimer *m_Very_timer; //聲明Mat類圖像變量,存儲當前攝像頭前的圖像 cv::Mat m_src_image; //檢測的分類器 CascadeClassifier m_cascada; //矩形框的點 Point m_text_lb; //人臉個數(shù) vector<Rect> m_faces; // 聲明一個錄入計數(shù)器變量 int m_executionCount = 0; //工號和姓名 QString m_facenum; QString m_facename; //識別的模型 Ptr<LBPHFaceRecognizer> m_model; //當前鏡頭前識別出的人數(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> //管理攝像頭的設備表 #include <QCameraViewfinder> //管理攝像頭顯示區(qū)域 #include <QCameraImageCapture> //管理圖片 #include <QDateTime> //管理時間 #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) { //設置圖片大小和label的長寬一致 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("關閉攝像頭"); } 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); // 連接信號和槽 connect(this, &FaceVerify::Send_Card, m_cthread, &CameraThread::Set_Card); connect(m_cthread, &CameraThread::Send_Card_Data, this, &FaceVerify::Show_Data); // 發(fā)送信號 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"); // 遍歷查詢結果 do { QString num = query.value(numField).toString(); // 獲取編號 QString time = query.value(timeField).toDateTime().toString("yyyy-MM-dd hh:mm:ss"); // 獲取時間 data += "編號: " + num + ", 時間: " + 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("錯誤 "), tr("簽到失敗: ") ); // 顯示錯誤信息 } } } else { QMessageBox::critical(NULL, tr("錯誤"), tr("查詢數(shù)據(jù)失?。?")); } // 關閉數(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(); } //訓練模型 void FaceVerify::on_pushButton_Train_clicked() { m_train = new train(); connect(m_train, &train::finished, this, &FaceVerify::Train_Finish); m_train->run(); } //模型訓練完 void FaceVerify::Train_Finish() { delete m_train; QMessageBox::information(NULL,"訓練", "訓練完成"); }
總結
到此這篇關于基于OpenCV實現(xiàn)的人臉簽到系統(tǒng)的文章就介紹到這了,更多相關OpenCV人臉簽到系統(tǒng)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
C++用一棵紅黑樹同時封裝出set與map的實現(xiàn)代碼
set中存儲的一般為鍵K即可,而map存儲的一般都是鍵值對KV,也就是說他們結構是不同的,那么我們?nèi)绾尾拍苡靡活w紅黑樹同時封裝出set與map兩種容器呢,那么接下來我們具體地來研究下STL庫中是怎樣實現(xiàn)的,并且進行模擬實現(xiàn),需要的朋友可以參考下2024-03-03C++類的靜態(tài)成員變量與靜態(tài)成員函數(shù)詳解
下面小編就為大家?guī)硪黄狢++類的靜態(tài)成員變量與靜態(tài)成員函數(shù)的文章。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2021-11-11