#include "test.h"
#include "ui_test.h"
#include <QImage>
#include <QPixmap>
#include <opencv2/opencv.hpp>
test::test(QWidget *parent)
: QWidget(parent)
, ui(new Ui::test)
{
ui->setupUi(this);
timer = new QTimer(this);
// readFrame通过定时器时间循环来触发
// 如果通过while(1)循环,UI会卡死
connect(timer, &QTimer::timeout, this, &test::readFrame);
// 使用分类器加载opencv自带的模型
if(!faceCascade.load("haarcascade_frontalface_default.xml")) {
qDebug()<<"face cascade load failed";
}
// 加载人脸检测模型,SSD CNN检测器
faceDetector = cv::dnn::readNetFromCaffe("models/deploy.prototxt",
"models/res10_300x300_ssd_iter_140000.caffemodel");
// 人脸特征模型:128维人脸特征
faceRecognizer = cv::dnn::readNetFromTorch("models/nn4.small2.v1.t7");
loadDatabase();
}
test::~test()
{
delete ui;
}
void test::on_ptn_start_clicked()
{
//testopencv();
cap.open(0); // 0 = 默认摄像头
if(!cap.isOpened()) {
qDebug()<<"camera open failed";
return;
}
timer->start(30); // 30ms ≈ 33fps
}
void test::readFrame()
{
cv::Mat frame;
cv::Mat gray;
cap >> frame;
if(frame.empty())
return;
#if 0
std::vector<cv::Rect> faces;
cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY);
// OpenCV 默认BGR, 而QT是RGB
// cv::cvtColor(frame, frame, cv::COLOR_BGR2RGB);
// scaleFactor含义: 图像缩放比例。原理: 模型训练时的窗口大小是固定的(比如 24*24),
// 为了检测图像中不同大小的人脸,算法会不断缩放原图。
// 影响: * 1.1 表示每次缩放时图像缩小 10%。
// 值越小(如 1.05): 缩放层级越密,检测更精细,不容易漏掉小脸,但计算量剧增,速度变慢。
// 值越大(如 1.4): 缩放速度快,检测速度快,但可能会跳过某些尺寸的人脸,导致漏检。
// minNeighbors含义: 最小相邻矩形个数。
// 原理: 滑动窗口在检测时,同一个目标附近可能会产生多个重叠的检测框。
// 该参数规定:只有当一个目标被至少 3 个重叠框同时检测到时,才会被最终判定为“人脸”。
// 影响:值越高: 准确度越高,能有效过滤噪声和误报(False Positives),但可能导致难以识别光线不好的人脸。
// 值越低(如 0 或 1): 极其灵敏,但会有大量错误识别(把背景里的阴影看成人脸)。
// minSize含义: 目标的最小尺寸。说明: 低于这个尺寸(比如 30*30)的对象会被忽略。
// 如果你是在近距离摄像头前,可以调大这个值以提高速度;如果你需要检测远处的人脸,则需要调小它。
faceCascade.detectMultiScale(gray, faces,
1.1, 3, 0, cv::Size(30,30));
// 画面中识别到多张脸,都画出来
for(auto &face : faces)
{
cv::rectangle(frame, face, cv::Scalar(0,255,0), 2);
}
cv::cvtColor(frame, frame, cv::COLOR_BGR2RGB);
QImage img(frame.data, frame.cols, frame.rows, frame.step, QImage::Format_RGB888);
ui->label_camera->setPixmap(QPixmap::fromImage(img));
#else
// 人脸检测
cv::Mat blob = cv::dnn::blobFromImage(frame, 1.0, cv::Size(300,300), cv::Scalar(104,177,123));
faceDetector.setInput(blob);
cv::Mat detections = faceDetector.forward();
// 解析检测结果
cv::Mat detMat(
detections.size[2],
detections.size[3],
CV_32F,
detections.ptr<float>());
for(int i=0;i<detMat.rows;i++)
{
float confidence = detMat.at<float>(i,2);
if(confidence < 0.6) continue;
int x1 = detMat.at<float>(i,3)*frame.cols;
int y1 = detMat.at<float>(i,4)*frame.rows;
int x2 = detMat.at<float>(i,5)*frame.cols;
int y2 = detMat.at<float>(i,6)*frame.rows;
cv::Rect faceRect(x1,y1,x2-x1,y2-y1);
cv::rectangle(frame,faceRect,
cv::Scalar(0,255,0),2);
// 裁剪人脸
cv::Mat face = frame(faceRect);
// 生成输入
cv::Mat faceBlob = cv::dnn::blobFromImage(
face,
1.0/255,
cv::Size(96,96),
cv::Scalar(),
true,
false );
// 送入网络
faceRecognizer.setInput(faceBlob);
cv::Mat feature = faceRecognizer.forward();
lastFeature = feature.clone();
QString name = recognizeFace(feature);
cv::putText(frame,
name.toStdString(),
cv::Point(x1,y1-10),
cv::FONT_HERSHEY_SIMPLEX,
0.8,
cv::Scalar(0,255,0),
2);
}
cv::cvtColor(frame,frame,
cv::COLOR_BGR2RGB);
QImage img(frame.data,
frame.cols,
frame.rows,
frame.step,
QImage::Format_RGB888);
ui->label_camera->setPixmap(
QPixmap::fromImage(img)
.scaled(ui->label_camera->size(),
Qt::KeepAspectRatio)
);
#endif
}
void test::on_ptn_record_clicked()
{
QString name = ui->lineEdit_name->text();
if(lastFeature.empty()) {
qDebug()<<"no face feature";
return;
}
if(name.isEmpty()) {
qDebug()<<"name empty";
return;
}
faceDatabase[name] = lastFeature;
saveDatabase();
}
void test::loadDatabase()
{
cv::FileStorage fs("database/faces.yml", cv::FileStorage::READ);
if(!fs.isOpened()) {
qDebug() << "database not exist";
return;
}
for(auto it = fs.root().begin(); it != fs.root().end(); ++it) {
cv::Mat feature;
(*it) >> feature;
faceDatabase[ QString::fromStdString((*it).name()) ] = feature;
}
fs.release();
}
void test::saveDatabase()
{
cv::FileStorage fs("database/faces.yml", cv::FileStorage::WRITE);
if(!fs.isOpened()) {
qDebug() << "database open error";
return;
}
for(auto &item : faceDatabase) {
fs << item.first.toStdString() << item.second;
}
fs.release();
}
QString test::recognizeFace(cv::Mat feature)
{
double minDist = 999;
QString name = "Unknown";
for(auto &item : faceDatabase) {
double dist = cv::norm(feature,item.second);
if(dist < minDist) {
minDist = dist;
name = item.first;
}
}
if(minDist < 0.6)
return name;
return "Unknown";
}
void test::testopencv()
{
cv::Mat img = cv::imread("test.jpg");
if(img.empty())
qDebug()<<"opencv load fail";
else
cv::imshow("test", img);
}