Initial Linux Qt OpenCV project

This commit is contained in:
luochen570
2026-05-29 22:29:30 +08:00
commit 81d586c032
22 changed files with 1748 additions and 0 deletions

369
app/src/MainWindow.cpp Normal file
View 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;
}