使用OpenCV、MySQL和Qt实现机器视觉处理
系统架构设计
机器视觉处理系统通常分为图像采集、处理、存储和显示模块。OpenCV负责图像处理算法,MySQL用于存储处理结果和配置信息,Qt提供用户界面和交互逻辑。
环境配置
安装OpenCV库、MySQL数据库和Qt开发环境。确保Qt项目配置中包含OpenCV和MySQL的头文件及库路径。在Qt项目的.pro文件中添加以下内容:(注意改成自己的路径)
qmake
QT += widgets sql
CONFIG += c++17
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
# MySQL配置
INCLUDEPATH += "D:/mysql/mysql-8.0.32-winx64/mysql-8.0.32-winx64/include"
LIBS += -L"D:/mysql/mysql-8.0.32-winx64/mysql-8.0.32-winx64/lib" -llibmysql
# OpenCV配置
INCLUDEPATH += "F:/opencv/opencv/build/include"
CONFIG(debug, debug|release) {
LIBS += -L"F:/opencv/opencv/build/x64/vc16/lib" -lopencv_world4120d
} else {
LIBS += -L"F:/opencv/opencv/build/x64/vc16/lib" -lopencv_world4120
}
SOURCES += \
main.cpp \
mainwindow.cpp \
visionprocessor.cpp \
databasemanager.cpp
HEADERS += \
mainwindow.h \
visionprocessor.h \
databasemanager.h
FORMS += \
mainwindow.ui
# 自动复制运行时 DLL
win32 {
MYSQL_DLL_PATH = "D:/mysql/mysql-8.0.32-winx64/mysql-8.0.32-winx64/lib/libmysql.dll"
QMAKE_POST_LINK += $$quote(copy /Y $$MYSQL_DLL_PATH $$OUT_PWD/)
CONFIG(debug, debug|release) {
OPENCV_DLL_PATH = "F:/opencv/opencv/build/x64/vc16/bin/opencv_world4120d.dll"
} else {
OPENCV_DLL_PATH = "F:/opencv/opencv/build/x64/vc16/bin/opencv_world4120.dll"
}
QMAKE_POST_LINK += $$quote(copy /Y $$OPENCV_DLL_PATH $$OUT_PWD/)
}
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
数据库设计
创建MySQL数据库表存储图像处理结果:
cpp
#include "databasemanager.h"
#include <QSqlError>
#include <QDebug>
DatabaseManager::DatabaseManager()
{
}
DatabaseManager::~DatabaseManager()
{
if (m_db.isOpen()) {// 如果数据库连接处于打开状态
m_db.close();// 关闭数据库连接,释放资源
}
}
bool DatabaseManager::initialize()
{
// 检查MySQL驱动是否可用
if (!QSqlDatabase::drivers().contains("QMYSQL")) {
qDebug() << "MySQL驱动不可用";
return false;
}
// 添加MySQL数据库驱动
m_db = QSqlDatabase::addDatabase("QMYSQL");
m_db.setHostName("localhost");
m_db.setDatabaseName("vision_db");
m_db.setUserName("name");
m_db.setPassword("password");
m_db.setPort(3306);
if (!m_db.open()) {
qDebug() << "数据库连接失败:" << m_db.lastError().text();
return false;
}
return createTables(); // 连接成功后,创建数据库表
}
bool DatabaseManager::createTables()
{
QSqlQuery query;// 创建数据库查询对象
// 创建识别结果表
QString createTableSql = ""
"CREATE TABLE IF NOT EXISTS recognition_results ("
"id INT AUTO_INCREMENT PRIMARY KEY,"
"image_path VARCHAR(255) NOT NULL,"
"result VARCHAR(255) NOT NULL,"
"type VARCHAR(50) NOT NULL,"
"create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
if (!query.exec(createTableSql)) { // 执行SQL语句
qDebug() << "创建表失败:" << query.lastError().text();
return false;
}
return true;
}
bool DatabaseManager::saveRecognitionResult(const QString &imagePath, const QString &result, const QString &type)
{
if (!m_db.isOpen()) {
return false;
}
QSqlQuery query;
query.prepare("INSERT INTO recognition_results (image_path, result, type) VALUES (?, ?, ?)");
query.addBindValue(imagePath);
query.addBindValue(result);
query.addBindValue(type);
if (!query.exec()) {
qDebug() << "保存结果失败:" << query.lastError().text();
return false;
}
return true;
}
QSqlQuery DatabaseManager::getHistoryRecords()
{
QSqlQuery query;
query.exec("SELECT * FROM recognition_results ORDER BY create_time DESC");
return query;
}
基于OpenCV的视觉处理类VisionProcesso。
cpp
#include "visionprocessor.h"
VisionProcessor::VisionProcessor()
{
loadClassifiers();
}
VisionProcessor::~VisionProcessor()
{
closeCamera();
}
bool VisionProcessor::loadImage(const QString &imagePath)
{
m_image = cv::imread(imagePath.toStdString());
if (m_image.empty()) {
return false;
}
m_processedImage = m_image.clone();
m_recognitionResult = "";
return true;
}
bool VisionProcessor::openCamera(int cameraIndex)
{
// 先关闭之前可能打开的摄像头
closeCamera();
// 尝试打开摄像头
if (!m_camera.open(cameraIndex)) {
return false;
}
// 检查摄像头是否成功打开
if (!m_camera.isOpened()) {
return false;
}
// 设置摄像头分辨率
m_camera.set(cv::CAP_PROP_FRAME_WIDTH, 640);
m_camera.set(cv::CAP_PROP_FRAME_HEIGHT, 480);
return true;
}
bool VisionProcessor::captureFrame()
{
if (!m_camera.isOpened()) {
return false;
}
if (!m_camera.read(m_image)) {
return false;
}
m_processedImage = m_image.clone();
m_recognitionResult = "";
return true;
}
void VisionProcessor::closeCamera()
{
if (m_camera.isOpened()) {
m_camera.release();
}
}
bool VisionProcessor::getCameraFrame(cv::Mat &frame)
{
if (!m_camera.isOpened()) {
return false;
}
if (!m_camera.read(frame)) {
return false;
}
return true;
}
bool VisionProcessor::loadClassifiers()
{
// 加载人脸分类器
std::string faceCascadePath = "F:/opencv/opencv/build/etc/haarcascades/haarcascade_frontalface_default.xml";
if (!m_faceCascade.load(faceCascadePath)) {
return false;
}
// 加载全身分类器
std::string fullBodyCascadePath = "F:/opencv/opencv/build/etc/haarcascades/haarcascade_fullbody.xml";
if (!m_fullBodyCascade.load(fullBodyCascadePath)) {
return false;
}
// 加载上半身分类器
std::string upperBodyCascadePath = "F:/opencv/opencv/build/etc/haarcascades/haarcascade_upperbody.xml";
if (!m_upperBodyCascade.load(upperBodyCascadePath)) {
return false;
}
// 加载下半身分类器
std::string lowerBodyCascadePath = "F:/opencv/opencv/build/etc/haarcascades/haarcascade_lowerbody.xml";
if (!m_lowerBodyCascade.load(lowerBodyCascadePath)) {
return false;
}
return true;
}
void VisionProcessor::detectFaces()
{
if (m_image.empty()) return;
cv::Mat gray;
cv::cvtColor(m_image, gray, cv::COLOR_BGR2GRAY);
cv::equalizeHist(gray, gray);
std::vector<cv::Rect> faces;
// 优化参数以提高人脸检测精度
m_faceCascade.detectMultiScale(gray, faces, 1.1, 5, 0 | cv::CASCADE_SCALE_IMAGE, cv::Size(40, 40));
for (size_t i = 0; i < faces.size(); i++) {
cv::rectangle(m_processedImage, faces[i], cv::Scalar(255, 0, 0), 2);
// 添加人脸标记
cv::putText(m_processedImage, QString("Face %1").arg(i+1).toStdString(),
cv::Point(faces[i].x, faces[i].y - 10),
cv::FONT_HERSHEY_SIMPLEX, 0.6, cv::Scalar(255, 0, 0), 2);
}
// 同时检测物体(使用多个分类器)
std::vector<cv::Rect> objects;
// 检测全身
std::vector<cv::Rect> fullBodies;
m_fullBodyCascade.detectMultiScale(gray, fullBodies, 1.1, 3, 0 | cv::CASCADE_SCALE_IMAGE, cv::Size(100, 100));
objects.insert(objects.end(), fullBodies.begin(), fullBodies.end());
// 检测上半身
std::vector<cv::Rect> upperBodies;
m_upperBodyCascade.detectMultiScale(gray, upperBodies, 1.1, 3, 0 | cv::CASCADE_SCALE_IMAGE, cv::Size(80, 80));
objects.insert(objects.end(), upperBodies.begin(), upperBodies.end());
// 检测下半身
std::vector<cv::Rect> lowerBodies;
m_lowerBodyCascade.detectMultiScale(gray, lowerBodies, 1.1, 3, 0 | cv::CASCADE_SCALE_IMAGE, cv::Size(80, 80));
objects.insert(objects.end(), lowerBodies.begin(), lowerBodies.end());
// 检测耳机盒(基于颜色和形状)
cv::Mat hsv;
cv::cvtColor(m_image, hsv, cv::COLOR_BGR2HSV);
// 定义耳机盒的颜色范围(白色/银色)
cv::Scalar lower(0, 0, 150); // HSV 下限
cv::Scalar upper(180, 30, 255); // HSV 上限
cv::Mat mask;
cv::inRange(hsv, lower, upper, mask);
// 形态学操作
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5));
cv::morphologyEx(mask, mask, cv::MORPH_CLOSE, kernel);
cv::morphologyEx(mask, mask, cv::MORPH_OPEN, kernel);
// 查找轮廓
std::vector<std::vector<cv::Point>> contours;
cv::findContours(mask, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
// 分析轮廓
for (size_t i = 0; i < contours.size(); i++) {
double area = cv::contourArea(contours[i]);
if (area < 1000) continue; // 过滤小轮廓
cv::Rect boundingRect = cv::boundingRect(contours[i]);
double aspectRatio = (double)boundingRect.width / boundingRect.height;
// 耳机盒的宽高比通常在 0.8-1.2 之间(接近正方形)
if (aspectRatio > 0.8 && aspectRatio < 1.2) {
objects.push_back(boundingRect);
}
}
// 非最大抑制(NMS)过滤重叠的检测结果
std::vector<cv::Rect> filteredObjects;
const double overlapThreshold = 0.5; // 重叠阈值
// 按面积排序
std::sort(objects.begin(), objects.end(), [](const cv::Rect& a, const cv::Rect& b) {
return a.area() > b.area();
});
while (!objects.empty()) {
// 选择面积最大的检测框
filteredObjects.push_back(objects[0]);
// 过滤重叠的检测框
std::vector<cv::Rect> remainingObjects;
for (size_t i = 1; i < objects.size(); i++) {
// 计算重叠面积
cv::Rect intersection = objects[0] & objects[i];
double overlap = (double)intersection.area() / objects[i].area();
// 如果重叠小于阈值,保留该检测框
if (overlap < overlapThreshold) {
remainingObjects.push_back(objects[i]);
}
}
objects = remainingObjects;
}
// 绘制检测结果
for (size_t i = 0; i < filteredObjects.size(); i++) {
cv::rectangle(m_processedImage, filteredObjects[i], cv::Scalar(0, 255, 0), 2);
// 添加物体标记
cv::putText(m_processedImage, QString("Object %1").arg(i+1).toStdString(),
cv::Point(filteredObjects[i].x, filteredObjects[i].y - 10),
cv::FONT_HERSHEY_SIMPLEX, 0.6, cv::Scalar(0, 255, 0), 2);
}
// 更新物体数量
objects = filteredObjects;
m_recognitionResult = QString("检测到 %1 个人脸和 %2 个物体").arg(faces.size()).arg(objects.size());
}
void VisionProcessor::detectObjects()
{
if (m_image.empty()) return;
cv::Mat gray;
cv::cvtColor(m_image, gray, cv::COLOR_BGR2GRAY);
cv::equalizeHist(gray, gray);
// 使用多个分类器检测物体
std::vector<cv::Rect> objects;
// 检测全身
std::vector<cv::Rect> fullBodies;
m_fullBodyCascade.detectMultiScale(gray, fullBodies, 1.1, 3, 0 | cv::CASCADE_SCALE_IMAGE, cv::Size(100, 100));
objects.insert(objects.end(), fullBodies.begin(), fullBodies.end());
// 检测上半身
std::vector<cv::Rect> upperBodies;
m_upperBodyCascade.detectMultiScale(gray, upperBodies, 1.1, 3, 0 | cv::CASCADE_SCALE_IMAGE, cv::Size(80, 80));
objects.insert(objects.end(), upperBodies.begin(), upperBodies.end());
// 检测下半身
std::vector<cv::Rect> lowerBodies;
m_lowerBodyCascade.detectMultiScale(gray, lowerBodies, 1.1, 3, 0 | cv::CASCADE_SCALE_IMAGE, cv::Size(80, 80));
objects.insert(objects.end(), lowerBodies.begin(), lowerBodies.end());
// 检测耳机盒(基于颜色和形状)
cv::Mat hsv;
cv::cvtColor(m_image, hsv, cv::COLOR_BGR2HSV);
// 定义耳机盒的颜色范围(白色/银色)
cv::Scalar lower(0, 0, 150); // HSV 下限
cv::Scalar upper(180, 30, 255); // HSV 上限
cv::Mat mask;
cv::inRange(hsv, lower, upper, mask);
// 形态学操作
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5));
cv::morphologyEx(mask, mask, cv::MORPH_CLOSE, kernel);
cv::morphologyEx(mask, mask, cv::MORPH_OPEN, kernel);
// 查找轮廓
std::vector<std::vector<cv::Point>> contours;
cv::findContours(mask, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
// 分析轮廓
for (size_t i = 0; i < contours.size(); i++) {
double area = cv::contourArea(contours[i]);
if (area < 1000) continue; // 过滤小轮廓
cv::Rect boundingRect = cv::boundingRect(contours[i]);
double aspectRatio = (double)boundingRect.width / boundingRect.height;
// 耳机盒的宽高比通常在 0.8-1.2 之间(接近正方形)
if (aspectRatio > 0.8 && aspectRatio < 1.2) {
objects.push_back(boundingRect);
}
}
// 非最大抑制(NMS)过滤重叠的检测结果
std::vector<cv::Rect> filteredObjects;
const double overlapThreshold = 0.5; // 重叠阈值
// 按面积排序
std::sort(objects.begin(), objects.end(), [](const cv::Rect& a, const cv::Rect& b) {
return a.area() > b.area();
});
while (!objects.empty()) {
// 选择面积最大的检测框
filteredObjects.push_back(objects[0]);
// 过滤重叠的检测框
std::vector<cv::Rect> remainingObjects;
for (size_t i = 1; i < objects.size(); i++) {
// 计算重叠面积
cv::Rect intersection = objects[0] & objects[i];
double overlap = (double)intersection.area() / objects[i].area();
// 如果重叠小于阈值,保留该检测框
if (overlap < overlapThreshold) {
remainingObjects.push_back(objects[i]);
}
}
objects = remainingObjects;
}
// 绘制检测结果
for (size_t i = 0; i < filteredObjects.size(); i++) {
cv::rectangle(m_processedImage, filteredObjects[i], cv::Scalar(0, 255, 0), 2);
// 添加物体标记
cv::putText(m_processedImage, QString("Object %1").arg(i+1).toStdString(),
cv::Point(filteredObjects[i].x, filteredObjects[i].y - 10),
cv::FONT_HERSHEY_SIMPLEX, 0.6, cv::Scalar(0, 255, 0), 2);
}
// 更新物体数量
objects = filteredObjects;
m_recognitionResult = QString("检测到 %1 个物体").arg(objects.size());
}
cv::Mat VisionProcessor::getProcessedImage() const
{
return m_processedImage;
}
QString VisionProcessor::getRecognitionResult() const
{
return m_recognitionResult;
}
以下是代码的主要功能解析:
核心功能实现
构造函数与析构函数
- 构造函数调用
loadClassifiers()加载预训练的分类器 - 析构函数确保摄像头资源被正确释放
图像加载与处理
loadImage()方法从文件路径加载图像- 检查图像是否成功加载,并初始化处理后的图像副本
- 清空之前的识别结果
摄像头操作
openCamera()打开指定索引的摄像头设备- 设置摄像头分辨率为640x480
captureFrame()从摄像头捕获当前帧并存储closeCamera()释放摄像头资源getCameraFrame()获取当前摄像头帧到外部变量
分类器加载
loadClassifiers()加载多个Haar级联分类器- 包括人脸、全身、上半身和下半身的检测模型
- 使用绝对路径指定分类器文件位置(应考虑改为相对路径)
人脸检测
detectFaces()实现人脸检测功能- 将图像转换为灰度并直方图均衡化
- 使用优化参数进行多尺度人脸检测
- 在检测到的人脸区域绘制矩形框和标签
扩展物体检测
- 同时检测全身、上半身和下半身
- 合并所有检测结果到objects向量
- 实现基于颜色和形状的耳机盒检测
- 使用HSV颜色空间过滤特定颜色范围
- 应用形态学操作去除噪声
- 通过轮廓分析识别符合特定宽高比的物体
代码改进建议
路径处理
- 分类器路径应改为相对路径或配置文件指定
- 考虑跨平台路径分隔符问题
性能优化
- 可添加图像尺寸检查和处理
- 检测参数可配置化
- 考虑使用更先进的检测算法(如DNN)
功能扩展
- 添加检测结果保存功能
- 实现多摄像头支持
- 增加检测置信度显示
错误处理
- 增强各类操作的错误检查
- 添加更详细的错误信息返回
代码结构
- 考虑将不同检测功能分离到不同方法
- 添加注释说明关键算法参数
耳机盒检测算法
当前代码中耳机盒检测部分未完成,建议补充以下逻辑:
cpp
if (aspectRatio >= 0.8 && aspectRatio <= 1.2) {
cv::rectangle(m_processedImage, boundingRect, cv::Scalar(0, 255, 0), 2);
cv::putText(m_processedImage, "Earphone Case",
cv::Point(boundingRect.x, boundingRect.y - 10),
cv::FONT_HERSHEY_SIMPLEX, 0.6, cv::Scalar(0, 255, 0), 2);
objects.push_back(boundingRect);
}
这段代码应添加在轮廓分析的宽高比检查之后,用于标记和存储检测到的耳机盒区域。
实现了一个基于Qt和OpenCV的视觉处理应用主窗口类,主要功能包括图像加载、人脸检测、物体检测、摄像头操作和结果保存。
cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "visionprocessor.h"// 包含视觉处理器头文件
#include "databasemanager.h"// 包含数据库管理器头文件
#include <QFileDialog>
#include <QMessageBox>
#include <QTimer>// 包含定时器类
#include <opencv2/opencv.hpp>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
, m_visionProcessor(new VisionProcessor())
, m_databaseManager(new DatabaseManager())
, m_model(new QSqlTableModel(this))
{
ui->setupUi(this);
// 安装事件过滤器
ui->originalImageLabel->installEventFilter(this);// 为原始图像标签安装事件过滤器
ui->processedImageLabel->installEventFilter(this);// 为处理后图像标签安装事件过滤器
// 初始化数据库
if (!m_databaseManager->initialize()) {
QMessageBox::warning(this, "数据库错误", "无法连接到数据库,请检查MySQL服务是否启动");
}
// 设置历史记录表格
m_model->setTable("recognition_results"); // 设置模型关联的表名
m_model->setSort(4, Qt::DescendingOrder); // 按第4列(create_time)降序排序
m_model->select(); // 从数据库选择数据填充模型
ui->historyTableView->setModel(m_model); // 将模型设置到表格视图
ui->historyTableView->resizeColumnsToContents(); // 调整列宽以适应内容
}
MainWindow::~MainWindow()
{
delete m_visionProcessor;
delete m_databaseManager;
delete m_model;
delete ui;
}
void MainWindow::on_actionOpenImage_triggered() // 打开图像菜单槽函数
{
QString fileName = QFileDialog::getOpenFileName(this, "打开图像", "", "图像文件 (*.jpg *.jpeg *.png *.bmp)");
if (fileName.isEmpty()) {
return;
}
m_currentImagePath = fileName;
// 加载图像
if (m_visionProcessor->loadImage(fileName)) {
// 显示原始图像
QImage image(fileName);
displayImage(image, ui->originalImageLabel);
// 清空处理结果
ui->processedImageLabel->setText("处理结果");
ui->resultLineEdit->clear();
} else {
QMessageBox::warning(this, "错误", "无法加载图像");
}
}
void MainWindow::on_actionDetectFaces_triggered()
{
if (m_currentImagePath.isEmpty()) {
QMessageBox::warning(this, "警告", "请先打开图像");
return;
}
// 检测人脸
m_visionProcessor->detectFaces();
m_currentRecognitionType = "人脸检测";
// 获取处理后的图像
cv::Mat processedImage = m_visionProcessor->getProcessedImage();
if (!processedImage.empty()) {
// 转换为QImage
QImage qImage(processedImage.data, processedImage.cols, processedImage.rows, processedImage.step, QImage::Format_BGR888);
displayImage(qImage, ui->processedImageLabel);
// 显示识别结果
QString result = m_visionProcessor->getRecognitionResult();
ui->resultLineEdit->setText(result);
}
}
void MainWindow::on_actionDetectObjects_triggered()
{
if (m_currentImagePath.isEmpty()) {
QMessageBox::warning(this, "警告", "请先打开图像");
return;
}
// 检测物体
m_visionProcessor->detectObjects();
m_currentRecognitionType = "物体检测";
// 获取处理后的图像
cv::Mat processedImage = m_visionProcessor->getProcessedImage();
if (!processedImage.empty()) {
// 转换为QImage
QImage qImage(processedImage.data, processedImage.cols, processedImage.rows, processedImage.step, QImage::Format_BGR888);
displayImage(qImage, ui->processedImageLabel);
// 显示识别结果
QString result = m_visionProcessor->getRecognitionResult();
ui->resultLineEdit->setText(result);
}
}
void MainWindow::on_saveButton_clicked()
{
if (m_currentImagePath.isEmpty() && m_visionProcessor->getProcessedImage().empty()) {
QMessageBox::warning(this, "警告", "请先进行识别");
return;
}
// 保存结果到数据库
QString imagePath = m_currentImagePath.isEmpty() ? "摄像头捕获" : m_currentImagePath;
if (m_databaseManager->saveRecognitionResult(imagePath, ui->resultLineEdit->text(), m_currentRecognitionType)) {
QMessageBox::information(this, "成功", "结果已保存到数据库");
updateHistoryTable();
} else {
QMessageBox::warning(this, "错误", "保存结果失败");
}
}
void MainWindow::on_actionOpenCamera_triggered()
{
// 尝试打开默认摄像头(索引0)
if (m_visionProcessor->openCamera(0)) {
QMessageBox::information(this, "成功", "摄像头已打开");
} else {
// 尝试打开索引1的摄像头
if (m_visionProcessor->openCamera(1)) {
QMessageBox::information(this, "成功", "摄像头已打开(使用索引1)");
} else {
QMessageBox::warning(this, "错误", "无法打开摄像头,请检查摄像头是否连接并被其他程序占用");
}
}
}
void MainWindow::on_actionCaptureFrame_triggered()
{
if (m_visionProcessor->captureFrame()) {
// 显示捕获的图像
cv::Mat frame = m_visionProcessor->getProcessedImage();
QImage qImage(frame.data, frame.cols, frame.rows, frame.step, QImage::Format_BGR888);
displayImage(qImage, ui->originalImageLabel);
// 清空处理结果
ui->processedImageLabel->setText("处理结果");
ui->resultLineEdit->clear();
m_currentImagePath = "";
} else {
QMessageBox::warning(this, "错误", "无法捕获图像");
}
}
void MainWindow::on_actionSaveImage_triggered()
{
cv::Mat processedImage = m_visionProcessor->getProcessedImage();
if (processedImage.empty()) {
QMessageBox::warning(this, "警告", "没有可保存的图像");
return;
}
QString fileName = QFileDialog::getSaveFileName(this, "保存图像", "", "图像文件 (*.jpg *.jpeg *.png *.bmp)");
if (fileName.isEmpty()) {
return;
}
if (cv::imwrite(fileName.toStdString(), processedImage)) {
QMessageBox::information(this, "成功", "图像已保存");
} else {
QMessageBox::warning(this, "错误", "无法保存图像");
}
}
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::MouseButtonDblClick) {
if (obj == ui->originalImageLabel || obj == ui->processedImageLabel) {
// 创建一个新窗口显示放大的图像
QDialog *dialog = new QDialog(this);
dialog->setWindowTitle(obj == ui->originalImageLabel ? "原始图像" : "处理后图像");
dialog->resize(800, 600);
QVBoxLayout *layout = new QVBoxLayout(dialog);
QLabel *label = new QLabel(dialog);
label->setAlignment(Qt::AlignCenter);
layout->addWidget(label);
// 获取对应的图像
QImage image = obj == ui->originalImageLabel ? m_originalImage : m_processedImage;
if (!image.isNull()) {
QPixmap pixmap = QPixmap::fromImage(image);
label->setPixmap(pixmap.scaled(label->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
} else {
label->setText("没有图像");
}
dialog->exec();
delete dialog;
return true;
}
}
return QMainWindow::eventFilter(obj, event);
}
void MainWindow::updateHistoryTable()
{
// 重新设置表格模型,确保数据更新
m_model->setTable("recognition_results");
m_model->setSort(4, Qt::DescendingOrder);
m_model->select();
ui->historyTableView->setModel(m_model);
ui->historyTableView->resizeColumnsToContents();
// 检查是否有数据
if (m_model->rowCount() == 0) {
qDebug() << "历史记录为空";
} else {
qDebug() << "历史记录有" << m_model->rowCount() << "条记录";
}
}
void MainWindow::displayImage(const QImage &image, QLabel *label)
{
// 保存图像副本
if (label == ui->originalImageLabel) {
m_originalImage = image;
} else if (label == ui->processedImageLabel) {
m_processedImage = image;
}
// 调整图像大小以适应标签
QPixmap pixmap = QPixmap::fromImage(image);
QPixmap scaledPixmap = pixmap.scaled(label->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
label->setPixmap(scaledPixmap);
}
void MainWindow::on_actionCameraPreview_triggered()
{
// 创建摄像头预览窗口
QDialog *previewDialog = new QDialog(this);
previewDialog->setWindowTitle("摄像头预览");
previewDialog->resize(800, 600);
// 创建布局
QVBoxLayout *layout = new QVBoxLayout(previewDialog);
// 创建图像标签
QLabel *previewLabel = new QLabel(previewDialog);
previewLabel->setAlignment(Qt::AlignCenter);
previewLabel->setText("正在打开摄像头...");
layout->addWidget(previewLabel);
// 创建按钮布局
QHBoxLayout *buttonLayout = new QHBoxLayout();
QPushButton *captureButton = new QPushButton("确认拍摄", previewDialog);
QPushButton *cancelButton = new QPushButton("取消", previewDialog);
buttonLayout->addStretch();
buttonLayout->addWidget(captureButton);
buttonLayout->addWidget(cancelButton);
layout->addLayout(buttonLayout);
// 打开摄像头
if (!m_visionProcessor->openCamera(0)) {
if (!m_visionProcessor->openCamera(1)) {
QMessageBox::warning(this, "错误", "无法打开摄像头,请检查摄像头是否连接并被其他程序占用");
delete previewDialog;
return;
}
}
// 创建定时器用于刷新预览
QTimer *timer = new QTimer(previewDialog);
connect(timer, &QTimer::timeout, [=]() {
// 捕获当前帧
cv::Mat frame;
if (m_visionProcessor->getCameraFrame(frame)) {
// 转换为QImage
QImage qImage(frame.data, frame.cols, frame.rows, frame.step, QImage::Format_BGR888);
// 调整大小以适应标签
QPixmap pixmap = QPixmap::fromImage(qImage);
QPixmap scaledPixmap = pixmap.scaled(previewLabel->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
previewLabel->setPixmap(scaledPixmap);
}
});
// 连接按钮信号
connect(captureButton, &QPushButton::clicked, [=]() {
// 停止定时器
timer->stop();
// 捕获当前帧
if (m_visionProcessor->captureFrame()) {
// 显示捕获的图像
cv::Mat frame = m_visionProcessor->getProcessedImage();
QImage qImage(frame.data, frame.cols, frame.rows, frame.step, QImage::Format_BGR888);
displayImage(qImage, ui->originalImageLabel);
// 清空处理结果
ui->processedImageLabel->setText("处理结果");
ui->resultLineEdit->clear();
m_currentImagePath = "";
// 关闭对话框
previewDialog->accept();
// 自动开始识别(同时检测人脸和物体)
m_visionProcessor->detectFaces();
m_currentRecognitionType = "人脸检测";
// 获取处理后的图像
cv::Mat processedImage = m_visionProcessor->getProcessedImage();
if (!processedImage.empty()) {
// 转换为QImage
QImage processedQImage(processedImage.data, processedImage.cols, processedImage.rows, processedImage.step, QImage::Format_BGR888);
displayImage(processedQImage, ui->processedImageLabel);
// 显示识别结果
QString result = m_visionProcessor->getRecognitionResult();
ui->resultLineEdit->setText(result);
}
} else {
QMessageBox::warning(this, "错误", "无法捕获图像");
}
});
connect(cancelButton, &QPushButton::clicked, [=]() {
// 停止定时器
timer->stop();
// 关闭对话框
previewDialog->reject();
});
// 连接对话框关闭信号
connect(previewDialog, &QDialog::rejected, [=]() {
// 停止定时器
timer->stop();
// 关闭摄像头
m_visionProcessor->closeCamera();
});
// 启动定时器,每33毫秒刷新一次(约30fps)
timer->start(33);
// 显示对话框
previewDialog->exec();
// 清理资源
delete previewDialog;
}
数据库连接优化
在构造函数中初始化数据库连接时,建议添加重试机制。当连接失败时,可以设置定时重试或提供更详细的错误信息:
cpp
int retryCount = 0;
while (!m_databaseManager->initialize() && retryCount < 3) {
QThread::sleep(1);
retryCount++;
}
图像显示改进
displayImage方法建议实现自适应缩放,确保不同尺寸图像都能正确显示:
cpp
void MainWindow::displayImage(QImage image, QLabel* label) {
QPixmap pixmap = QPixmap::fromImage(image);
pixmap = pixmap.scaled(label->size(), Qt::KeepAspectRatio);
label->setPixmap(pixmap);
}
摄像头操作增强
在打开摄像头时,建议增加设备检测功能:
cpp
void MainWindow::on_actionOpenCamera_triggered() {
int cameraIndex = QInputDialog::getInt(this, "选择摄像头", "请输入摄像头索引:", 0, 0, 10);
if (m_visionProcessor->openCamera(cameraIndex)) {
// 启动定时器实时显示画面
QTimer* timer = new QTimer(this);
connect(timer, &QTimer::timeout, [this](){
cv::Mat frame = m_visionProcessor->getCameraFrame();
if (!frame.empty()) {
QImage qImage(frame.data, frame.cols, frame.rows, frame.step, QImage::Format_BGR888);
displayImage(qImage, ui->originalImageLabel);
}
});
timer->start(33); // 约30fps
}
}
结果保存功能优化
保存结果时建议添加数据验证:
cpp
void MainWindow::on_saveButton_clicked() {
if (ui->resultLineEdit->text().isEmpty()) {
QMessageBox::warning(this, "警告", "识别结果为空");
return;
}
// 其余保存逻辑...
}
历史记录表格更新
建议将表格更新封装为独立方法:
cpp
void MainWindow::updateHistoryTable() {
m_model->select();
ui->historyTableView->resizeColumnsToContents();
ui->historyTableView->scrollToBottom();
}
内存管理建议
在析构函数中,建议添加资源释放检查:
cpp
MainWindow::~MainWindow() {
if (m_visionProcessor->isCameraOpen()) {
m_visionProcessor->closeCamera();
}
// 原有释放代码...
}
异常处理增强
建议在关键操作处添加try-catch块:
cpp
void MainWindow::on_actionDetectFaces_triggered() {
try {
// 原有检测逻辑...
} catch (cv::Exception& e) {
QMessageBox::critical(this, "OpenCV错误", e.what());
}
}
界面交互改进
建议添加处理状态提示:
cpp
void MainWindow::on_actionDetectObjects_triggered() {
ui->statusBar->showMessage("正在检测物体...");
QApplication::processEvents();
// 检测逻辑...
ui->statusBar->clearMessage();
}
多线程考虑
对于耗时操作(如物体检测),建议使用QThread避免界面冻结:
cpp
void MainWindow::startDetectionThread() {
QThread* thread = new QThread();
Worker* worker = new Worker(m_visionProcessor); // 自定义Worker类
worker->moveToThread(thread);
connect(thread, &QThread::started, worker, &Worker::process);
connect(worker, &Worker::finished, thread, &QThread::quit);
connect(worker, &Worker::finished, worker, &Worker::deleteLater);
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
thread->start();
}
部分演示:




以上建议可根据实际需求选择性实现,重点改进方向包括:健壮性增强、用户体验优化和性能提升。