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 、Python、Java等多语言接口。其核心代码采用C++编写并针对实时视觉应用优化,可利用MMX和SSE指令提升性能,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盘的根目录后,如下图

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();






Comments | NOTHING