openCV入门:环境搭建VS2019+openCV4.13+Ot5.15及简版人脸识别程序

前言

1、openCV安装或编译

1.1 openCV下载安装

1)下载地址:下载地址 https://github.com/opencv/opencv/releases

下载完成后安装exe文件,安装路径最好不要有中文,否则用起来比较麻烦。安装后的库文件include/lib/bin可以直接使用,如下所示:

1.2 openCV编译

1)下载地址:

opencv下载地址 https://github.com/opencv/opencv/releases

opencv_contrib下载地址 https://github.com/opencv/opencv_contrib/releases/tag/4.13.0

自己编译源码的话,就把扩展库也一起编译了,两个文件的版本必须保持一致,都是4.13.0,方便后续自己使用。

打开cmake软件:

设定opencv_contrib路径:OPENCV_EXTRA_MODULES_PATH;

BUILD_opencv_world:ON,这个选项不开启,编译出来的是很多个分开的dll;开启后编译后就一个opencv_world4130.dll。

其余的很多选项,比如CUDA/FMPEG等自己按需求进行开启设定。

ALL_BUILD 右键-生成即可完成编译。然后 INSTALL右键-生成即可完成打包dll。

2、人脸图片识别程序

目的:做一个最简单的人脸识别及对比程序。

2.1 使用openCV安装版实现

用Qt设计一个简介的界面,配合安装版本的openCV4.13,暂时不用编译版本的。直接利用自带的 <opencv2/objdetect/face.hpp> 进行人脸对比,主要使用其中的 FaceDetectorYN(检测)和 FaceRecognizerSF(识别)两个类。它们都基于深度学习模型,精度高且使用方便。需要提前下载模型文件,下载地址: https://github.com/opencv/opencv_zoo

// FaceDetectorYN 模型: face_detection_yunet_2026may.onnx

// FaceRecognizerSF 模型: face_recognition_sface_2021dec.onnx

QOpencvApp.h

cpp 复制代码
#pragma once

#include <QtWidgets/QMainWindow>
#include <QMessageBox>
#include <QFileDialog>
#include <QPixmap>
#include <QString>
#include <QCoreApplication>
#include <QDir>
#include <iostream>

#include <opencv2/opencv.hpp>
#include <opencv2/objdetect/face.hpp>
using namespace cv;

#include "ui_QOpencvApp.h"

class QOpencvApp : public QMainWindow
{
    Q_OBJECT

public:
    QOpencvApp(QWidget *parent = nullptr);
    ~QOpencvApp();

private slots:
	void onBaseImgButtonClicked();
    void onSourceImgButtonClicked(); 
    void onCompareButtonClicked();
    
private:
    Ui::QOpencvAppClass ui;
};

QOpencvApp.cpp

cpp 复制代码
#include "QOpencvApp.h"

QOpencvApp::QOpencvApp(QWidget *parent)
    : QMainWindow(parent)
{
    ui.setupUi(this);

    connect(ui.baseImgBtn, SIGNAL(clicked()), this, SLOT(onBaseImgButtonClicked()));
    connect(ui.sourceImageBtn, SIGNAL(clicked()), this, SLOT(onSourceImgButtonClicked()));
	connect(ui.compareBtn, SIGNAL(clicked()), this, SLOT(onCompareButtonClicked()));
}

QOpencvApp::~QOpencvApp()
{}

void QOpencvApp::onBaseImgButtonClicked()
{
	QString filter = "图片文件 (*.png *.jpg *.jpeg *.bmp *.gif *.tiff *.webp);;"
		"PNG 图片 (*.png);;"
		"JPG 图片 (*.jpg *.jpeg);;"
		"所有文件 (*.*)";

	// 2. 打开文件选择对话框
	QString filePath = QFileDialog::getOpenFileName(
		this,                       // 父窗口
		u8"请选择一张图片",            // 对话框标题
		"",                         // 默认打开路径(空=上次路径或默认)
		filter                      // 文件过滤器
	);

	// 3. 判断用户是否取消了选择(路径为空表示取消)
	if (filePath.isEmpty()) 
	{
		QMessageBox::information(this, u8"提示", u8"未选择任何文件");
		return;
	}

	// 4. 处理选中的图片(例如:显示在 QLabel 中)
	QPixmap pixmap(filePath);
	if (!pixmap.isNull()) 
	{
		// 缩放图片以适应 Label 大小(保持宽高比)
		QPixmap scaled = pixmap.scaled(ui.label_base->size(),
			Qt::KeepAspectRatio,
			Qt::SmoothTransformation);
		ui.label_base->setPixmap(scaled);
		ui.label_base->setText("");  // 清除文本
		ui.lineEdit->setText(filePath);
		//QMessageBox::information(this, u8"成功", QString(u8"已加载图片:%1").arg(filePath));
	}
	else 
	{
		QMessageBox::warning(this, u8"错误", u8"无法加载图片,文件可能已损坏!");
	}
}

void QOpencvApp::onSourceImgButtonClicked()
{
	QString filter = u8"图片文件 (*.png *.jpg *.jpeg *.bmp *.gif *.tiff *.webp);;"
		"PNG 图片 (*.png);;"
		"JPG 图片 (*.jpg *.jpeg);;"
		"所有文件 (*.*)";

	// 2. 打开文件选择对话框
	QString filePath = QFileDialog::getOpenFileName(
		this,                       // 父窗口
		u8"请选择一张图片",            // 对话框标题
		"",                         // 默认打开路径(空=上次路径或默认)
		filter                      // 文件过滤器
	);

	// 3. 判断用户是否取消了选择(路径为空表示取消)
	if (filePath.isEmpty())
	{
		QMessageBox::information(this, u8"提示", u8"未选择任何文件");
		return;
	}

	// 4. 处理选中的图片(例如:显示在 QLabel 中)
	QPixmap pixmap(filePath);
	if (!pixmap.isNull())
	{
		// 缩放图片以适应 Label 大小(保持宽高比)
		QPixmap scaled = pixmap.scaled(ui.label_source->size(),
			Qt::KeepAspectRatio,
			Qt::SmoothTransformation);
		ui.label_source->setPixmap(scaled);
		ui.label_source->setText("");  // 清除文本
		ui.lineEdit_2->setText(filePath);
		//QMessageBox::information(this, u8"成功", QString(u8"已加载图片:%1").arg(filePath));
	}
	else {
		QMessageBox::warning(this, u8"错误", u8"无法加载图片,文件可能已损坏!");
	}
}

void QOpencvApp::onCompareButtonClicked()
{
	QString sBaseImage = ui.lineEdit->text();
	QString sSourceImage = ui.lineEdit_2->text();

	// 1. 读取两张人脸图片,建议转换为灰度图
	cv::Mat img1 = cv::imread(sBaseImage.toStdString());
	cv::Mat img2 = cv::imread(sSourceImage.toStdString());

	if (img1.empty() || img2.empty())
	{
		QMessageBox::warning(this, u8"错误", u8"无法读取图片!");
		std::cerr << "无法读取图片!" << std::endl;
		return;
	}

	// ========== 2. 创建检测器和识别器 ==========
   // 需要提前下载模型文件
   // FaceDetectorYN 模型: face_detection_yunet_2023mar.onnx
   // FaceRecognizerSF 模型: face_recognition_sface_2021dec.onnx
   // 下载地址: https://github.com/opencv/opencv_zoo

	// 使用 try-catch 捕获异常,查看具体错误信息
	//try {
	//	Ptr<FaceDetectorYN> detector = FaceDetectorYN::create(
	//		modelPath,
	//		"",
	//		Size(320, 320),
	//		0.9f,
	//		0.3f,
	//		5000
	//	);

	//	if (detector.empty()) {
	//		std::cerr << "创建检测器失败,返回空指针" << std::endl;
	//		return ;
	//	}

	//	std::cout << "检测器创建成功!" << std::endl;

	//	// 后续检测代码...

	//}
	//catch (const cv::Exception& e) {
	//	std::cerr << "OpenCV 异常: " << e.what() << std::endl;
	//	return;
	//}
	//catch (const std::exception& e) {
	//	std::cerr << "标准异常: " << e.what() << std::endl;
	//	return;
	//}

	QString configPath1 = QCoreApplication::applicationDirPath() + "/face_detection_yunet_2026may.onnx";
	QString configPath2 = QCoreApplication::applicationDirPath() + "/face_recognition_sface_2021dec.onnx";

	Ptr<FaceDetectorYN> detector = FaceDetectorYN::create(
		configPath1.toStdString(),  // 检测模型路径
		"",                                    // 配置文件(ONNX不需要)
		Size(320, 320),                        // 输入尺寸
		0.9f,                                  // 分数阈值
		0.3f,                                  // NMS阈值
		5000                                   // Top K
	);

	Ptr<FaceRecognizerSF> recognizer = FaceRecognizerSF::create(
		configPath2.toStdString(), // 识别模型路径
		""                                     // 配置文件(ONNX不需要)
	);

	if (!detector || !recognizer) 
	{  
		// 隐式转换为 bool
		QMessageBox::warning(this, u8"错误", u8"模型加载失败!请检查模型文件路径!");
		return;
	}

	// ===== 2. 检查并打印图片信息 =====
	Size size1 = img1.size();
	Size size2 = img2.size();

	std::cout << "========== 图片信息 ==========" << std::endl;
	std::cout << "图片1尺寸: " << size1.width << " x " << size1.height
		<< ", 通道数: " << img1.channels() << std::endl;
	std::cout << "图片2尺寸: " << size2.width << " x " << size2.height
		<< ", 通道数: " << img2.channels() << std::endl;

	// ===== 3. 判断图片尺寸是否相等 =====
	bool sizesEqual = (size1 == size2);
	std::cout << "图片尺寸是否相等: " << (sizesEqual ? "是" : "否") << std::endl;

	// ===== 4. 根据尺寸相等性进行不同处理 =====
	Mat img1_processed = img1.clone();
	Mat img2_processed = img2.clone();

	if (!sizesEqual) 
	{
		std::cout << "警告: 图片尺寸不同,将统一缩放到 " << size1.width << " x " << size1.height << std::endl;
		// 将 img2 缩放到与 img1 相同的尺寸(也可以选择缩放 img1)
		cv::resize(img2, img2_processed, size1);
		std::cout << "缩放后图片2尺寸: " << img2_processed.size().width
			<< " x " << img2_processed.size().height << std::endl;
	}
	else 
	{
		std::cout << "图片尺寸相同,无需缩放" << std::endl;
	}

	// ========== 3. 人脸检测 ==========
	// 注意:检测时需要设置输入尺寸为图片实际大小
	detector->setInputSize(img1.size());

	Mat faces1, faces2;
	// 3. 使用 try-catch 捕获异常,查看具体错误信息
	try {
		detector->detect(img1, faces1);
		detector->detect(img2, faces2);
	}
	catch (const cv::Exception& e) {
		std::cerr << "OpenCV 异常: " << e.what() << std::endl;
		return;
	}
	catch (const std::exception& e) {
		std::cerr << "标准异常: " << e.what() << std::endl;
		return;
	}

	if (faces1.rows == 0 || faces2.rows == 0) 
	{
		QMessageBox::warning(this, u8"错误", u8"某张图片未检测到人脸!");
		return;
	}

	// ========== 4. 提取人脸特征 ==========
	// faces 中每行格式: [x, y, width, height, left_eye_x, left_eye_y, right_eye_x, right_eye_y, nose_x, nose_y, mouth_x, mouth_y, right_ear_x, right_ear_y, left_ear_x, left_ear_y]
	// 取第一张人脸
	Mat face1 = faces1.row(0);
	Mat face2 = faces2.row(0);

	// 获取人脸框 (前4个元素)
	Rect rect1(face1.at<float>(0), face1.at<float>(1),
		face1.at<float>(2), face1.at<float>(3));
	Rect rect2(face2.at<float>(0), face2.at<float>(1),
		face2.at<float>(2), face2.at<float>(3));

	// 对齐并裁剪人脸
	Mat aligned1, aligned2;
	recognizer->alignCrop(img1, face1, aligned1);  // 使用关键点自动对齐
	recognizer->alignCrop(img2, face2, aligned2);

	// 提取特征向量 (128维浮点数)
	Mat feature1, feature2;
	recognizer->feature(aligned1, feature1);
	recognizer->feature(aligned2, feature2);

	// ========== 5. 计算相似度 ==========
	// 方法1: 使用内置 match 方法 (返回相似度分数, 0~1, 越高越相似)
	double similarity = recognizer->match(feature1, feature2, FaceRecognizerSF::FR_COSINE);

	// 方法2: 手动计算余弦相似度 (与上面等价)
	//double similarity = feature1.dot(feature2) / (norm(feature1) * norm(feature2));

	std::cout << "人脸相似度: " << similarity << std::endl;

	// ========== 6. 判断结果 ==========
	// 阈值需要根据实际场景调整,一般 0.5~0.7 之间
	double threshold = 0.7;
	if (similarity >= threshold) 
	{
		QMessageBox::information(this, u8"错误", u8"判断为同一个人!");
	}
	else 
	{
		QMessageBox::warning(this, u8"错误", u8"判断为不同的人!");
	}

	// ========== 7. 绘制检测结果 ==========
	// 在图片上绘制人脸框和5个关键点
	rectangle(img1, rect1, Scalar(0, 255, 0), 2);
	rectangle(img2, rect2, Scalar(0, 255, 0), 2);

	// 遍历检测到的每一张人脸
	for (int i = 0; i < face1.rows; i++)
	{
		// 从 faces 矩阵中获取第 i 行数据的指针
		float* data = (float*)face1.ptr(i);

		// 2. 提取并绘制5个关键点
		// 右眼 (索引 4, 5)
		circle(img1, Point2i(int(data[4]), int(data[5])), 5, Scalar(255, 0, 0), 2);
		// 左眼 (索引 6, 7)
		circle(img1, Point2i(int(data[6]), int(data[7])), 5, Scalar(0, 0, 255), 2);
		// 鼻尖 (索引 8, 9)
		circle(img1, Point2i(int(data[8]), int(data[9])), 5, Scalar(0, 255, 0), 2);
		// 右嘴角 (索引 10, 11)
		circle(img1, Point2i(int(data[10]), int(data[11])), 5, Scalar(255, 0, 255), 2);
		// 左嘴角 (索引 12, 13)
		circle(img1, Point2i(int(data[12]), int(data[13])), 5, Scalar(0, 255, 255), 2);
	}

	// 遍历检测到的每一张人脸
	for (int i = 0; i < face2.rows; i++)
	{
		// 从 faces 矩阵中获取第 i 行数据的指针
		float* data = (float*)face2.ptr(i);

		// 2. 提取并绘制5个关键点
		// 右眼 (索引 4, 5)
		circle(img2, Point2i(int(data[4]), int(data[5])), 5, Scalar(255, 0, 0), 2);
		// 左眼 (索引 6, 7)
		circle(img2, Point2i(int(data[6]), int(data[7])), 5, Scalar(0, 0, 255), 2);
		// 鼻尖 (索引 8, 9)
		circle(img2, Point2i(int(data[8]), int(data[9])), 5, Scalar(0, 255, 0), 2);
		// 右嘴角 (索引 10, 11)
		circle(img2, Point2i(int(data[10]), int(data[11])), 5, Scalar(255, 0, 255), 2);
		// 左嘴角 (索引 12, 13)
		circle(img2, Point2i(int(data[12]), int(data[13])), 5, Scalar(0, 255, 255), 2);
	}

	QString imgPath1 = QCoreApplication::applicationDirPath() + "/result1.jpg";
	QString imgPath2 = QCoreApplication::applicationDirPath() + "/result2.jpg";

	imwrite(imgPath1.toStdString(), img1);
	imwrite(imgPath2.toStdString(), img2);

	// 缩放图片以适应 Label 大小(保持宽高比)
	QPixmap pixmap(imgPath1);
	if (!pixmap.isNull())
	{
		QPixmap scaled = pixmap.scaled(ui.label_base->size(),
			Qt::KeepAspectRatio,
			Qt::SmoothTransformation);
		ui.label_base->setPixmap(scaled);
	}
	
	QPixmap pixmap2(imgPath2);
	if (!pixmap2.isNull())
	{
		QPixmap scaled = pixmap2.scaled(ui.label_source->size(),
			Qt::KeepAspectRatio,
			Qt::SmoothTransformation);
		ui.label_source->setPixmap(scaled);
	}
}

运行效果:

2.2 使用openCV编译版实现
cpp 复制代码
void QOpencvApp::onCompareButtonClicked()
{
	QString sBaseImage = ui.lineEdit->text();
	QString sSourceImage = ui.lineEdit_2->text();

	// 1. 读取两张人脸图片,建议转换为灰度图
	cv::Mat faceA = cv::imread(sBaseImage.toStdString(), cv::IMREAD_GRAYSCALE);
	cv::Mat faceB = cv::imread(sSourceImage.toStdString(), cv::IMREAD_GRAYSCALE);

	if (faceA.empty() || faceB.empty())
	{
		QMessageBox::warning(this, u8"错误", u8"无法读取图片!");
		std::cerr << "无法读取图片!" << std::endl;
		return;
	}

	// 2. 创建LBPH模型并提取特征 (训练模式)
	cv::Ptr<cv::face::LBPHFaceRecognizer> model = cv::face::LBPHFaceRecognizer::create();
	std::vector<cv::Mat> images = { faceA, faceB };
	std::vector<int> labels = { 1, 2 };
	model->train(images, labels);

	// 3. 预测 (计算距离)
	int predictedLabel;
	double confidence = 0.0;
	model->predict(faceB, predictedLabel, confidence);

	//confidence值越低相似度越高
	double threshold = 80.0; // 需要根据实际情况调整
	if (confidence < threshold) 
	{
		std::cout << "两张人脸相似,为同一个人。置信度: " << confidence << std::endl;
	}
	else 
	{
		std::cout << "两张人脸不相似。置信度: " << confidence << std::endl;
	}

	return;
}
2.3 使用openCV编译版实现68个特征点
cpp 复制代码
void QOpencvApp::onCompareButtonClicked()
{
	QString sBaseImage = ui.lineEdit->text();
	
	// 1. 加载图像
	cv::Mat img = cv::imread(sBaseImage.toStdString());
	if (img.empty()) 
	{
		QMessageBox::warning(this, u8"错误", u8"无法读取图片!");
		return;
	}

	// 2. 初始化人脸检测器 (用于检测人脸区域)
	// 使用 OpenCV 自带的 Haar 或 LBP 级联分类器
	QString configPath1 = QCoreApplication::applicationDirPath() + "/haarcascade_frontalface_default.xml";

	CascadeClassifier faceDetector;
	if (!faceDetector.load(configPath1.toStdString())) 
	{
		QMessageBox::warning(this, u8"错误", u8"无法加载人脸检测模型!");
		std::cerr << "无法加载人脸检测模型!" << std::endl;
		return ;
	}

	// 3. 初始化特征点检测器 (FacemarkLBF)
	Ptr<cv::face::Facemark> facemark = cv::face::FacemarkLBF::create();

	// 加载预训练好的LBF模型
	QString configPath2 = QCoreApplication::applicationDirPath() + "/lbfmodel.yaml";
	facemark->loadModel(configPath2.toStdString());

	// 4. 执行人脸检测,获取人脸区域 (ROI)
	std::vector<Rect> faces;
	Mat gray;
	cvtColor(img, gray, COLOR_BGR2GRAY);
	faceDetector.detectMultiScale(gray, faces);

	// 5. 在检测到的人脸区域上,执行特征点拟合
	std::vector<std::vector<Point2f>> landmarks;
	bool success = facemark->fit(img, faces, landmarks);

	if (success) 
	{
		// 6. 绘制检测到的 68 个特征点
		for (size_t i = 0; i < landmarks.size(); i++) 
		{
			for (size_t j = 0; j < landmarks[i].size(); j++) 
			{
				circle(img, landmarks[i][j], 5, Scalar(0, 255, 0), -1);
			}
		}
	}

	QString imgPath = QCoreApplication::applicationDirPath() + "/result68Landmarks.jpg";
	imwrite(imgPath.toStdString(), img);

	// 缩放图片以适应 Label 大小(保持宽高比)
	QPixmap pixmap(imgPath);
	if (!pixmap.isNull())
	{
		QPixmap scaled = pixmap.scaled(ui.label_base->size(),
			Qt::KeepAspectRatio,
			Qt::SmoothTransformation);
		ui.label_base->setPixmap(scaled);
	}
}

3、源码