OpenCV+MySQL+Qt构建智能视觉系统(msvc)

使用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();
}

部分演示:

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

相关推荐
Ulyanov2 小时前
《玩转QT Designer Studio:从设计到实战》 QT Designer Studio动画与动效系统深度解析
开发语言·python·qt·系统仿真·雷达电子对抗仿真
Chasing__Dreams2 小时前
Mysql--基础知识点--105--分布式事务
数据库·分布式·mysql
键盘会跳舞2 小时前
【Qt】分享一个笔者持续更新的项目: https://github.com/missionlove/NQUI
c++·qt·用户界面·qwidget
与数据交流的路上2 小时前
mysql-通过binlog分析大事务
数据库·mysql
NoSi EFUL3 小时前
学生成绩管理系统(MySQL)
android·数据库·mysql
Yeats_Liao3 小时前
Trae 配置 MySQL MCP 指南
数据库·mysql
史迪仔01123 小时前
[QML] Qt Quick Dialogs 模块使用指南
开发语言·前端·c++·qt
DevilSeagull3 小时前
MySQL(1) 安装与配置
java·数据库·git·mysql·http·开源·github
dLYG DUMS4 小时前
如何在docker中的mysql容器内执行命令与执行SQL文件
sql·mysql·docker