Qt 学习笔记(五):OpenCV、摄像头与交叉编译

发布于 11 天前  65 次阅读


Qt 学习笔记(五):OpenCV、摄像头与交叉编译

整理 Qt Creator 集成 OpenCV、图像与摄像头处理、人脸相关示例,以及面向嵌入式平台的交叉编译。

OpenCV

OpenCV(Open Source Computer Vision Library)是基于Apache2.0协议开源的跨平台计算机视觉库,2000年6月首次发布,支持Linux、Windows、Android及[Mac OS](https://baike.baidu.com/item/Mac OS/2840867?fromModule=lemma_inlink)等操作系统,提供C 、PythonJava等多语言接口。其核心代码采用C++编写并针对实时视觉应用优化,可利用MMXSSE指令提升性能,2022年12月发布的4.7.0版本新增对ONNX层的支持并优化DNN卷积性能,同时适配国产龙芯LoongArch架构 。该库起源于1999年CVL项目,历经多次版本迭代,2017年起强化深度学习模块支持 。OpenCV涵盖图像处理运动跟踪、人脸检测等功能模块,提供基础数据结构、图像滤波、视频分析、目标检测等核心功能,通过标准化API集成机器学习算法与深度学习框架模型。作为计算机视觉领域通用算法库,其应用场景覆盖人机交互智能安防、医学影像分析及工业自动化检测,支持从图像加载、特征提取到视频捕捉的完整开发流程 。

文档


在线文档: https://docs.opencv.org/
离线文档: 共享文件夹/3-资料/4.5.5.zip

下载


从飞秋下载压缩包opencv4.5.5-MinGw7.3.0.zip。解压到磁盘的根目录即可。

如解压到E盘的根目录后,如下图

image-20251118114225425

QtCreator中配置opencv4.5.5

创建一个新的Qt项目,在Qt项目的pro文件中添加以下代码


# 指定opencv头文件所在路径
INCLUDEPATH += E:/opencv4.5.5-MinGw7.3.0/include
# 指定opencv库文件路径及库名称
LIBS += -LE:/opencv4.5.5-MinGw7.3.0/x64/mingw/bin  -lopencv_world455

配置完成后,记得保存。

测试

修改Qt项目的main.cpp,进行测试。


#include "mainwindow.h"
#include <QApplication>
// 包含opencv的常用头文件
#include <opencv2/opencv.hpp>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

	// 在主函数中添加以下代码
    cv::Mat img = cv::imread("../../../pics/lena.jpg");
    std::cout << img << std::endl;

    return a.exec();
}

如果配置正确,则项目构建与运行都正常的情况下,会打印图片的像素数据。

说明:opencv不支持中文字符。

opencv简单示例


#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;

int main(int argc, char const* argv[])
{
    
    cv::Mat img = cv::imread("E:/lena.png");
    if (img.empty())
    {
        cout << "imread error" << endl;
        exit(1);
    }

    cout << img.channels() << endl ; // 通道数 
    cout << img.rows << endl; // 行数 
    cout << img.cols << endl; // 列数 


    cv::namedWindow("dialog", cv::WINDOW_NORMAL);
    cv::imshow("dialog", img);

    cv::waitKey();
    cv::destroyAllWindows();

    return 0;
}

图像加载


Mat cv::imread	(const String &	filename, int	flags = IMREAD_COLOR_BGR )
功能:	从磁盘加载图像到内存
参数:	
	filename 带路径的文件名,不支持中文
	flags 打开标志,默认为IMREAD_COLOR_BGR,自动把加载的图像转成3通道的彩色图像,也可以使用其他的,如IMREAD_GRAYSCALE,自动把加载的图像转成单通道的灰度图。
返回:
	成功返回Mat类型的对象,代表要操作的图像,本质上是一个多维数组。
	失败返回一个空的Mat对象。
说明:
	加载图像后,一般需要判断是否加载成功
示例:		
cv::Mat img = cv::imread("E:/lena.png");
if (img.empty())
{
    cout << "imread error" << endl;
    exit(1);
}    		

图像属性

加载图像成功后,可以查看它的各种属性,如通道数、行数、列数等


img.channels() ; // 通道数
img.rows ; // 行数 
img.cols ; // 列数 
img.type(); // 矩阵中元素的类型,返回的是一个整数,通过cv::typeToString()函数,可以字符串形式输出

注:opencv中数据类型可参考文档中的 Data types


file:///E:/%E7%8F%AD%E7%BA%A7/CS2525F-C++/3-%E8%B5%84%E6%96%99/4.5.5/d1/d1b/group__core__hal__interface.html

显示图像

使用opencv内置的gui显示图像


void cv::namedWindow	(const String &	winname, int	flags = WINDOW_AUTOSIZE )
功能: 创建一个命名窗体
参数: 
	winname 窗体名称    
	flags 窗体标志,默认为WINDOW_AUTOSIZE,用户无法调整窗体大小,如果需要调整窗体大小,使用WINDOW_NORMAL即可        
void cv::imshow	(const String &	winname, InputArray	mat )        
功能: 使用opencv内置的窗体显示图像
参数: 
	winname 窗体名称,如果与之前的namedWindow的第一个参数相同,则使用已创建的命名窗体,否则自动创建一个新的窗体         mat 待显示的图片对象,Mat类型的对象
        
示例:
cv::namedWindow("dialog", cv::WINDOW_NORMAL);
cv::imshow("dialog", img);

使用Qt显示Mat类型的图像


// 把Mat对象转成QImage,参数说明:data(图像像素在内存中的起始地址),cols(宽),rows(高),step[0](一行的字节数), Format_BGR888(像素格式,即排列顺序)
QImage qimg(img.data, img.cols, img.rows, img.step[0], QImage::Format_BGR888);

QLabel label;
// 再把QImage转成QPixmap,使用QPixmap的静态函数fromImage
label.setPixmap(QPixmap::fromImage(qimg));
label.show();

等待按键


int cv::waitKey	(	int	delay = 0	)	
功能: 阻塞程序,等待任意按键    
参数: delay 默认为0,表示一直阻塞,直到按键为止 ,单为ms
返回: int类型的键值,如果不需要,可忽略    

销毁窗体


cv::destroyAllWindows();

颜色空间转换



void cv::cvtColor(InputArray src, OutputArray dst, int code)
功能: 把一个Mat类型图像的颜色空间 转成 其他的颜色空间
参数: 
	src 待转换的图像    
	dst 转换后的结果,需要用户提供一个Mat对象,结果自动保存到用户提供的Mat对象中
	code 转换方式 常用如下:
        COLOR_BGR2GRAY
        COLOR_RGB2GRAY 
        COLOR_BGR2RGB
        COLOR_RGB2BGR
        ...

示例:        
// 把彩色图像转成灰度图像,结果保存到新对象中
cv::Mat grayimg;
cv::cvtColor(img, grayimg, cv::COLOR_BGR2GRAY);
cv::imshow("gray", grayimg);

// 把BGR转成RGB,结果覆盖原图
cv::cvtColor(img, img, cv::COLOR_BGR2RGB);
cv::imshow("rgb", img);   

调整大小


void cv::resize	(InputArray	src, OutputArray dst, Size	dsize)
参数:
	src 待转换的图像    
	dst 转换后的结果,需要用户提供一个Mat对象,结果自动保存到用户提供的Mat对象中
    dsize 大小对象,需要构造一个对象,来表示宽和高

练习:找一张大图片,把颜色空间转成灰度图,再调整大小,使其宽不超过640,高不超过480

图形与文本绘制


void cv::rectangle	(InputOutputArray img, Rect	rec, const Scalar &	color,int thickness = 1,
int	lineType = LINE_8)
功能: 在指定的图像上绘制矩形
参数: 
	img 待操作的图像
	rec 矩形对象,由x,y,w,h组成
	color  颜色对象,由b,g,r组成
	thickness 线条的粗细程度,一般默认即可
	lineType 线程类型,一般默认为实线LINE_8,可选其他如 LINE_4 LINE_AA
        

void cv::circle	(InputOutputArray img,Point	center,int	radius,const Scalar &color,int thickness = 1,int lineType = LINE_8)
功能: 在指定的图像上画一个圆 
参数:    
	img 待操作的图像
	center 点对象,由x,y组成,表示圆心坐标
	color  颜色对象,由b,g,r组成
	thickness 线条的数细程度,一般默认即可
	lineType 线程类型,一般默认为实线LINE_8,可选其他如 LINE_4 LINE_AA     
    

void cv::putText(InputOutputArray img,const String & text,Point	org,int	fontFace,
double	fontScale,Scalar color,int thickness = 1,int lineType = LINE_8,bool bottomLeftOrigin = false )
功能: 在指定的图像上绘制文本    
参数:
	img 待操作的图像
	text 待绘制的文本字符串,不支持中文
	org 绘制位置,文本左下角的坐标
	fontFace 字体,枚举类型,如 FONT_HERSHEY_SIMPLEX
	fontScale 缩放因子,浮点数     
	color  颜色对象,由b,g,r组成
	thickness 线条的数细程度,一般默认即可
	lineType 线程类型,一般默认为实线LINE_8,可选其他如 LINE_4 LINE_AA  
    bottomLeftOrigin 默认为false, 表示默认文本的起始位置为左下角。
        
    

ROI(感兴趣的区域)


//从给定的图像中,选择一个矩形区域,构造一个新的图片对象
cv::Mat roi(img, cv::Rect(10,10, 100,100));
cv::imshow("roi", roi);

保存图像


bool cv::imwrite(const String &	filename, InputArray img)
功能: 把图片对象 按格式保存到磁盘
参数: 
	filename 带路径的文件名,根据文件的扩展名进行格式判断
    img 待保存的图像
返回:
	成功返回true,失败返回false

练习:结合Qt与opencv完成,要求实现以下功能:点击界面上的按钮,选择一张图片打开,并在图片中绘制一个圆,再保存为其他格式的图片。

摄像头图像采集


// 实例化视频采集对象
cv::VideoCapture cap;
// 打开默认的摄像头,只有一个摄像头时,索引默认为0,如果有多个摄像头,则索引依次增加
cap.open(0);
// 判断摄像头是否打开成功
if (!cap.isOpened())
{
	std::cout << "open camera error" << std::endl;
    return 1;
}
// 设置图像帧的宽与高
cap.set(cv::CAP_PROP_FRAME_WIDTH, 320);
cap.set(cv::CAP_PROP_FRAME_HEIGHT, 240);
// 实例化一个空的图片对象,用来保存采集到的视频帧
cv::Mat frame;
// 视频采集循环
while (1)
{
    // 从摄像头中读取一帧数据
    cap.read(frame);
    // 判断是否读取成功
    if (frame.empty())
    {
        break;
    }
    // 显示图像
    cv::imshow("video", frame);
    // 按键退出
    if (cv::waitKey(30) >= 0)
        break;
}

人脸检测

OpenCV自4.5.4版本起,在objdetect模块中提供了基于DNN(深度神经网络)的人脸检测器FaceDetectorYN,它集成在OpenCV中,无需额外安装第三方库即可使用,并随库提供了官方的YuNet模型(model download link: https://github.com/opencv/opencv_zoo/tree/master/models/face_detection_yunet)。

YuNet模型来自OpenCV China团队,该模型接口简洁,创建一次检测器,调用detect()即可输出每张人脸的15维向量(左上角坐标、宽、高、左右眼睛坐标、鼻尖坐标、左右嘴角坐标、置信度)。

使用示例


// 加载带有人脸的图像
cv::Mat img = cv::imread("E:/3.jpeg");
if (img.empty())
{
    qDebug() << "img read error";
    return 1;
}

// 准备人脸检测模型
std::string fd_model = "E:/opencv4.5.5-MinGw7.3.0/face_detection_yunet_2022mar.onnx";
// 创建人脸检测对象
cv::Ptr<cv::FaceDetectorYN> faceDetector = cv::FaceDetectorYN::create(fd_model, "", cv::Size(img.cols, img.rows));
// 创建一个Mat对象,用于保存检测到的结果
cv::Mat faces_pos;
// 调用detect函数进行检测,检测结果自动保存到第二个参数中
faceDetector->detect(img, faces_pos); 
// 输出测试,检测到的人脸数据是一组32位的浮点数(15个),分别表示人脸位置的矩形框、两个眼睛的坐标、鼻子的坐标、嘴角的坐标
std::cout << "faces_pos = " << faces_pos << std::endl; // [206.20856, 194.14378, 160.28357, 209.77396, 277.95172, 279.16061, 339.71997, 286.16574, 320.38632, 329.69275, 273.40726, 355.2948, 318.22571, 361.798, 0.91915637]
// 通过rows属性,可知检测到的人脸个数
qDebug() << "face.size = " << faces_pos.rows;
// 绘制检测到的第一个人脸地标
int x = faces_pos.at<float>(0,0);
int y = faces_pos.at<float>(0,1);
int w = faces_pos.at<float>(0,2);
int h = faces_pos.at<float>(0,3);
cv::rectangle(img,cv::Rect(x,y,w,h),cv::Scalar(0,255,255));
cv::imshow("face", img);

人脸识别


// 准备人脸识别模型
std::string fr_model = "E:/20250928-Qt/opencv/face_recognition_sface_2021dec.onnx";
// 创建人脸识别对象
cv::Ptr<cv::FaceRecognizerSF> faceRecognizer = cv::FaceRecognizerSF::create(fr_model, "");
// 对齐规范人脸(从原图中根据人脸地标,提取人脸图像并规范为112*112大小)
cv::Mat face_align1;
faceRecognizer->alignCrop(img, faces_pos.row(1), face_align1); // 示例图像中有3个人脸,规范对齐第2个
cv::Mat face_align2;
faceRecognizer->alignCrop(img, faces_pos.row(2), face_align2); // 示例图像中有3个人脸,规范对齐第3个

// 提取特征值(特征值由128个32位的浮点数组成,共512个字节)
cv::Mat face_feature1;
faceRecognizer->feature(face_align1, face_feature1);			// 提取第2个人脸的特征值
face_feature1 = face_feature1.clone(); // 对特征值做深拷贝

cv::Mat face_feature2;
faceRecognizer->feature(face_align2, face_feature2);			// 提取第3个人脸的特征值
face_feature2 = face_feature2.clone(); // 对特征值做深拷贝


// 特征值匹配(默认使用FR_COSINE算法,该算法的默认阈值为0.363,即当两个特征值之间的距离大于等于0.363时表示同一个人的脸部特征)
double dist = faceRecognizer->match(face_feature1, face_feature2);// 匹配两个特征值
if (dist >= 0.363)
{
    qDebug() << "这两个特征值表示同一个人!";
}
else
{
    qDebug() << "这是两个不同的人" ;
}

交叉编译

1. 拷贝Qt的ARM库, 放到Ubuntu的共享文件夹中


    6818:qtlib-arm32-571.tar.xz
    3568:qtlib-aarch64-5152.tar.xz

2. 解压qt的arm库

进入共享文件夹,找到qt的arm库,执行以下指令,进行解压


    sudo tar xvf qtlib-arm32-571.tar.xz -C /opt
    sudo tar xvf qtlib-aarch64-5152.tar.xz -C /opt

3. 进入需要交叉编译的qt项目的源码路径,假项目名为Demo, 创建构建目录,指令如下


    cd Demo
    mkdir build-arm
    cd build-arm

4. 使用arm版本的qmake构建项目

正常情况下会生成Makefile


    /opt/qt库的路径名/bin/qmake  ../
    如:
    /opt/qtlib-aarch64-5152/bin/qmake  ../
    或
    /opt/qtlib-arm32-571/bin/qmake  ../

5. 编译

如果一切正常,会生成与项目文件夹同名的 可执行文件。如果出错,则根据提示,进行修改,再重新编译。


    make

6. 执行

把生成的可执行文件,传输到开发板的工作目录下,添加可执行权限,即可执行

6818中直接执行,3568则需要加一个选项-platform linuxfb才能正常执行


    chmod +x Demo
    ./Demo 
    ./Demo -platform linuxfb

综合案例

基于人脸识别的考勤管理系统

要实现的功能

1、人员信息的管理,包括添加、修改、删除、查询,在添加人员人信息时,需要保存员工的人脸特征值

2、考勤打卡,通过摄像头实时采集图像,进行人脸检测以及特征值提取,用提取的特征值与之前录入的人脸特征值进行匹配,如果匹配成功,则能获取该员工的基本信息,进行考勤打卡,同时需要保存考勤记录。

3、考勤信息管理,包括考勤信息的修改、删除、查询等

4、系统设置,如上下班打卡时间的设置

关系表示例


-- 人员信息表
CREATE TABLE personnel (
	id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    gender TEXT,
    department TEXT,
    position TEXT,
    phone TEXT,
    face_feature BLOB UNIQUE,
    status TEXT NOT NULL DEFAULT '在职', -- 在职、离职、试用期等
    remarks TEXT
);

-- 考勤记录表
CREATE TABLE attendance_records (
    record_id INTEGER PRIMARY KEY AUTOINCREMENT,
    date DATE NOT NULL,			-- 日期
    personnel_id TEXT NOT NULL, --人员id
    check_in_time TIMESTAMP,	-- 上班打卡时间
    check_out_time TIMESTAMP,	-- 下班打卡时间
    check_in_status TEXT, 		-- 正常、迟到、早退、缺勤等
    check_out_status TEXT,
    FOREIGN KEY (personnel_id) REFERENCES personnel(id),
    UNIQUE (personnel_id, date)
);


// 下班打卡示例
QString date = "2023-10-17";
QString personnelId = "P001";
QString checkOutTime = "2023-10-17 18:10:00";
QString workEndTime = "18:00:00"; // 下班时间阈值

QSqlQuery query(db);

// 步骤1:插入新记录(若不存在)
query.prepare(R"(
    INSERT OR IGNORE INTO attendance_records (
        date, personnel_id, check_in_time, check_in_status
    ) VALUES (:date, :pid, :in_time, 
              CASE WHEN :in_time <= :end_time THEN '正常' 
                   ELSE '迟到' END)
)");
query.bindValue(":date", date);
query.bindValue(":pid", personnelId);
query.bindValue(":out_time", checkOutTime);
query.bindValue(":end_time", workEndTime);
query.exec();

// 步骤2:更新已有记录
query.prepare(R"(
    UPDATE attendance_records 
    SET check_out_time = :out_time,
        check_out_status = CASE WHEN :out_time >= :end_time THEN '正常' ELSE '早退' END
    WHERE personnel_id = :pid AND date = :date
)");
query.bindValue(":out_time", checkOutTime);
query.bindValue(":end_time", workEndTime);
query.bindValue(":pid", personnelId);
query.bindValue(":date", date);
query.exec();

"When faced with uncertainty, ask the spring breeze."