#include "MainWindow.h" #include "ImageConverter.h" #include "ImageView.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { qRegisterMetaType("ProcessResult"); qRegisterMetaType("ImageWorker::Operation"); qRegisterMetaType("cv::Mat"); buildUi(); setupWorkerThread(); setWindowTitle(QStringLiteral("chengnan - Qt/OpenCV 图像处理")); } MainWindow::~MainWindow() { workerThread_.quit(); workerThread_.wait(); } void MainWindow::buildUi() { auto *openAction = new QAction(QStringLiteral("打开图片"), this); saveAction_ = new QAction(QStringLiteral("保存图片"), this); saveAsAction_ = new QAction(QStringLiteral("另存图片"), this); auto *resetAction = new QAction(QStringLiteral("还原原图"), this); auto *exitAction = new QAction(QStringLiteral("退出"), this); connect(openAction, &QAction::triggered, this, &MainWindow::openImage); connect(saveAction_, &QAction::triggered, this, &MainWindow::saveImage); connect(saveAsAction_, &QAction::triggered, this, &MainWindow::saveImageAs); connect(resetAction, &QAction::triggered, this, &MainWindow::resetToOriginal); connect(exitAction, &QAction::triggered, qApp, &QApplication::quit); auto *fileMenu = menuBar()->addMenu(QStringLiteral("文件")); fileMenu->addAction(openAction); fileMenu->addAction(saveAction_); fileMenu->addAction(saveAsAction_); fileMenu->addSeparator(); fileMenu->addAction(exitAction); auto *editMenu = menuBar()->addMenu(QStringLiteral("处理")); editMenu->addAction(resetAction); auto *toolbar = addToolBar(QStringLiteral("主工具栏")); toolbar->addAction(openAction); toolbar->addAction(saveAction_); toolbar->addAction(saveAsAction_); toolbar->addAction(resetAction); originalView_ = new ImageView(this); processedView_ = new ImageView(this); auto *leftBox = new QGroupBox(QStringLiteral("处理前图像"), this); auto *leftLayout = new QVBoxLayout(leftBox); leftLayout->addWidget(originalView_); auto *rightBox = new QGroupBox(QStringLiteral("处理后图像"), this); auto *rightLayout = new QVBoxLayout(rightBox); rightLayout->addWidget(processedView_); auto *splitter = new QSplitter(Qt::Horizontal, this); splitter->addWidget(leftBox); splitter->addWidget(rightBox); splitter->setStretchFactor(0, 1); splitter->setStretchFactor(1, 1); scaleSpin_ = new QDoubleSpinBox(this); scaleSpin_->setRange(0.1, 5.0); scaleSpin_->setSingleStep(0.1); scaleSpin_->setValue(1.25); scaleSpin_->setSuffix(QStringLiteral(" 倍")); angleSpin_ = new QDoubleSpinBox(this); angleSpin_->setRange(-360.0, 360.0); angleSpin_->setSingleStep(5.0); angleSpin_->setValue(90.0); angleSpin_->setSuffix(QStringLiteral("°")); translateSpin_ = new QSpinBox(this); translateSpin_->setRange(1, 1000); translateSpin_->setValue(40); translateSpin_->setSuffix(QStringLiteral(" px")); auto *scaleUpButton = new QPushButton(QStringLiteral("放大"), this); auto *scaleDownButton = new QPushButton(QStringLiteral("缩小"), this); auto *rotateLeftButton = new QPushButton(QStringLiteral("逆时针旋转"), this); auto *rotateRightButton = new QPushButton(QStringLiteral("顺时针旋转"), this); auto *upButton = new QPushButton(QStringLiteral("上移"), this); auto *downButton = new QPushButton(QStringLiteral("下移"), this); auto *leftButton = new QPushButton(QStringLiteral("左移"), this); auto *rightButton = new QPushButton(QStringLiteral("右移"), this); auto *extractButton = new QPushButton(QStringLiteral("目标提取"), this); auto *classifyButton = new QPushButton(QStringLiteral("简单分类"), this); connect(scaleUpButton, &QPushButton::clicked, this, &MainWindow::scaleUp); connect(scaleDownButton, &QPushButton::clicked, this, &MainWindow::scaleDown); connect(rotateLeftButton, &QPushButton::clicked, this, &MainWindow::rotateLeft); connect(rotateRightButton, &QPushButton::clicked, this, &MainWindow::rotateRight); connect(upButton, &QPushButton::clicked, this, &MainWindow::translateUp); connect(downButton, &QPushButton::clicked, this, &MainWindow::translateDown); connect(leftButton, &QPushButton::clicked, this, &MainWindow::translateLeft); connect(rightButton, &QPushButton::clicked, this, &MainWindow::translateRight); connect(extractButton, &QPushButton::clicked, this, &MainWindow::extractTargets); connect(classifyButton, &QPushButton::clicked, this, &MainWindow::classifyImage); auto *controlBox = new QGroupBox(QStringLiteral("图像处理操作"), this); auto *controlLayout = new QHBoxLayout(controlBox); controlLayout->addWidget(new QLabel(QStringLiteral("缩放比例:"), this)); controlLayout->addWidget(scaleSpin_); controlLayout->addWidget(scaleUpButton); controlLayout->addWidget(scaleDownButton); controlLayout->addSpacing(12); controlLayout->addWidget(new QLabel(QStringLiteral("旋转角度:"), this)); controlLayout->addWidget(angleSpin_); controlLayout->addWidget(rotateLeftButton); controlLayout->addWidget(rotateRightButton); controlLayout->addSpacing(12); controlLayout->addWidget(new QLabel(QStringLiteral("平移距离:"), this)); controlLayout->addWidget(translateSpin_); controlLayout->addWidget(upButton); controlLayout->addWidget(downButton); controlLayout->addWidget(leftButton); controlLayout->addWidget(rightButton); controlLayout->addSpacing(12); controlLayout->addWidget(extractButton); controlLayout->addWidget(classifyButton); controlLayout->addStretch(1); infoLabel_ = new QLabel(QStringLiteral("请打开一张图片开始处理。提示:鼠标滚轮可缩放视图,双击图片可适配窗口。"), this); infoLabel_->setWordWrap(true); auto *central = new QWidget(this); auto *mainLayout = new QVBoxLayout(central); mainLayout->addWidget(splitter, 1); mainLayout->addWidget(controlBox); mainLayout->addWidget(infoLabel_); setCentralWidget(central); statusLabel_ = new QLabel(QStringLiteral("就绪"), this); statusBar()->addPermanentWidget(statusLabel_, 1); setBusy(false); } void MainWindow::setupWorkerThread() { worker_ = new ImageWorker(); worker_->moveToThread(&workerThread_); connect(&workerThread_, &QThread::finished, worker_, &QObject::deleteLater); connect(this, &MainWindow::processRequested, worker_, &ImageWorker::process, Qt::QueuedConnection); connect(worker_, &ImageWorker::finished, this, &MainWindow::onProcessFinished, Qt::QueuedConnection); connect(worker_, &ImageWorker::failed, this, &MainWindow::onProcessFailed, Qt::QueuedConnection); workerThread_.start(); } void MainWindow::openImage() { const QString path = QFileDialog::getOpenFileName( this, QStringLiteral("打开图片"), QString(), QStringLiteral("图片文件 (*.png *.jpg *.jpeg *.bmp *.tif *.tiff);;所有文件 (*.*)")); if (path.isEmpty()) { return; } const std::string localPath = QFile::encodeName(path).toStdString(); cv::Mat image = cv::imread(localPath, cv::IMREAD_COLOR); if (image.empty()) { QMessageBox::warning(this, QStringLiteral("打开失败"), QStringLiteral("无法读取图片:%1").arg(path)); return; } originalImage_ = image; processedImage_ = image.clone(); currentPath_ = path; updateViews(); infoLabel_->setText(QStringLiteral("已打开:%1,尺寸:%2 × %3") .arg(QFileInfo(path).fileName()) .arg(image.cols) .arg(image.rows)); statusLabel_->setText(QStringLiteral("已打开图片")); } void MainWindow::saveImage() { if (!ensureHasImage()) { return; } if (currentPath_.isEmpty()) { saveImageAs(); return; } writeCurrentImageTo(currentPath_); } void MainWindow::saveImageAs() { if (!ensureHasImage()) { return; } const QString path = QFileDialog::getSaveFileName( this, QStringLiteral("另存图片"), currentPath_.isEmpty() ? QStringLiteral("processed.png") : currentPath_, QStringLiteral("PNG 图片 (*.png);;JPEG 图片 (*.jpg *.jpeg);;BMP 图片 (*.bmp);;所有文件 (*.*)")); if (path.isEmpty()) { return; } if (writeCurrentImageTo(path)) { currentPath_ = path; } } void MainWindow::resetToOriginal() { if (!ensureHasImage()) { return; } processedImage_ = originalImage_.clone(); updateViews(); infoLabel_->setText(QStringLiteral("已还原为原始图片")); } void MainWindow::onProcessFinished(ProcessResult result) { processedImage_ = result.image; updateViews(); infoLabel_->setText(result.description); statusLabel_->setText(QStringLiteral("处理完成")); setBusy(false); } void MainWindow::onProcessFailed(const QString &message) { QMessageBox::warning(this, QStringLiteral("处理失败"), message); statusLabel_->setText(QStringLiteral("处理失败")); setBusy(false); } void MainWindow::rotateLeft() { requestProcess(ImageWorker::Operation::Rotate, -angleSpin_->value()); } void MainWindow::rotateRight() { requestProcess(ImageWorker::Operation::Rotate, angleSpin_->value()); } void MainWindow::scaleUp() { requestProcess(ImageWorker::Operation::Scale, scaleSpin_->value()); } void MainWindow::scaleDown() { requestProcess(ImageWorker::Operation::Scale, 1.0 / scaleSpin_->value()); } void MainWindow::translateUp() { requestProcess(ImageWorker::Operation::Translate, 0.0, 0, -translateSpin_->value()); } void MainWindow::translateDown() { requestProcess(ImageWorker::Operation::Translate, 0.0, 0, translateSpin_->value()); } void MainWindow::translateLeft() { requestProcess(ImageWorker::Operation::Translate, 0.0, -translateSpin_->value(), 0); } void MainWindow::translateRight() { requestProcess(ImageWorker::Operation::Translate, 0.0, translateSpin_->value(), 0); } void MainWindow::extractTargets() { requestProcess(ImageWorker::Operation::ExtractTargets); } void MainWindow::classifyImage() { requestProcess(ImageWorker::Operation::ClassifySimple); } void MainWindow::updateViews() { originalView_->setImage(ImageConverter::matToQImage(originalImage_)); processedView_->setImage(ImageConverter::matToQImage(processedImage_)); } void MainWindow::setBusy(bool busy) { busy_ = busy; saveAction_->setEnabled(!busy && !processedImage_.empty()); saveAsAction_->setEnabled(!busy && !processedImage_.empty()); if (busy) { statusLabel_->setText(QStringLiteral("处理中...")); } } bool MainWindow::ensureHasImage() const { if (processedImage_.empty()) { QMessageBox::information(const_cast(this), QStringLiteral("提示"), QStringLiteral("请先打开图片。")); return false; } return true; } void MainWindow::requestProcess(ImageWorker::Operation operation, double value, int dx, int dy) { if (busy_) { return; } if (!ensureHasImage()) { return; } setBusy(true); Q_EMIT processRequested(processedImage_.clone(), operation, value, dx, dy); } bool MainWindow::writeCurrentImageTo(const QString &path) { if (processedImage_.empty()) { return false; } const std::string localPath = QFile::encodeName(path).toStdString(); if (!cv::imwrite(localPath, processedImage_)) { QMessageBox::warning(this, QStringLiteral("保存失败"), QStringLiteral("无法保存图片:%1").arg(path)); return false; } statusLabel_->setText(QStringLiteral("已保存图片")); infoLabel_->setText(QStringLiteral("已保存处理后图片:%1").arg(path)); return true; }