370 lines
12 KiB
C++
370 lines
12 KiB
C++
#include "MainWindow.h"
|
||
|
||
#include "ImageConverter.h"
|
||
#include "ImageView.h"
|
||
|
||
#include <QAction>
|
||
#include <QApplication>
|
||
#include <QBoxLayout>
|
||
#include <QDoubleSpinBox>
|
||
#include <QFile>
|
||
#include <QFileDialog>
|
||
#include <QFileInfo>
|
||
#include <QGroupBox>
|
||
#include <QLabel>
|
||
#include <QMenuBar>
|
||
#include <QMessageBox>
|
||
#include <QPushButton>
|
||
#include <QSpinBox>
|
||
#include <QSplitter>
|
||
#include <QStatusBar>
|
||
#include <QToolBar>
|
||
#include <QWidget>
|
||
#include <opencv2/imgcodecs.hpp>
|
||
|
||
MainWindow::MainWindow(QWidget *parent)
|
||
: QMainWindow(parent)
|
||
{
|
||
qRegisterMetaType<ProcessResult>("ProcessResult");
|
||
qRegisterMetaType<ImageWorker::Operation>("ImageWorker::Operation");
|
||
qRegisterMetaType<cv::Mat>("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<MainWindow *>(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;
|
||
}
|