Initial Linux Qt OpenCV project
This commit is contained in:
369
app/src/MainWindow.cpp
Normal file
369
app/src/MainWindow.cpp
Normal file
@@ -0,0 +1,369 @@
|
||||
#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;
|
||||
}
|
||||
Reference in New Issue
Block a user