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

View File

@@ -0,0 +1,11 @@
#pragma once
#include <QImage>
#include <opencv2/core.hpp>
class ImageConverter final
{
public:
static QImage matToQImage(const cv::Mat &mat);
static cv::Mat qImageToMat(const QImage &image);
};

View File

@@ -0,0 +1,23 @@
#pragma once
#include <QString>
#include <QMetaType>
#include <opencv2/core.hpp>
struct ProcessResult
{
cv::Mat image;
QString description;
};
class ImageProcessor final
{
public:
static ProcessResult rotate(const cv::Mat &source, double angleDegrees);
static ProcessResult scale(const cv::Mat &source, double factor);
static ProcessResult translate(const cv::Mat &source, int dx, int dy);
static ProcessResult extractTargets(const cv::Mat &source);
static ProcessResult classifySimple(const cv::Mat &source);
};
Q_DECLARE_METATYPE(ProcessResult)

35
app/include/ImageView.h Normal file
View File

@@ -0,0 +1,35 @@
#pragma once
#include <QGraphicsView>
#include <QImage>
#include <QPixmap>
class QGraphicsPixmapItem;
class QGraphicsScene;
class QWheelEvent;
class QMouseEvent;
class ImageView final : public QGraphicsView
{
Q_OBJECT
public:
explicit ImageView(QWidget *parent = nullptr);
void setImage(const QImage &image);
void clearImage();
void fitImageToView();
bool hasImage() const;
protected:
void wheelEvent(QWheelEvent *event) override;
void mouseDoubleClickEvent(QMouseEvent *event) override;
private:
void resetSceneRect();
QGraphicsScene *scene_ = nullptr;
QGraphicsPixmapItem *pixmapItem_ = nullptr;
QPixmap pixmap_;
double zoomFactor_ = 1.0;
};

33
app/include/ImageWorker.h Normal file
View File

@@ -0,0 +1,33 @@
#pragma once
#include "ImageProcessor.h"
#include <QObject>
#include <QMetaType>
#include <opencv2/core.hpp>
Q_DECLARE_METATYPE(cv::Mat)
class ImageWorker final : public QObject
{
Q_OBJECT
public:
enum class Operation {
Rotate,
Scale,
Translate,
ExtractTargets,
ClassifySimple
};
Q_ENUM(Operation)
public Q_SLOTS:
void process(cv::Mat image, ImageWorker::Operation operation, double value, int dx, int dy);
Q_SIGNALS:
void finished(ProcessResult result);
void failed(QString message);
};
Q_DECLARE_METATYPE(ImageWorker::Operation)

72
app/include/MainWindow.h Normal file
View File

@@ -0,0 +1,72 @@
#pragma once
#include "ImageWorker.h"
#include <QMainWindow>
#include <QThread>
#include <opencv2/core.hpp>
class QAction;
class QLabel;
class QSpinBox;
class QDoubleSpinBox;
class QPushButton;
class ImageView;
class MainWindow final : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow() override;
Q_SIGNALS:
void processRequested(cv::Mat image, ImageWorker::Operation operation, double value, int dx, int dy);
private Q_SLOTS:
void openImage();
void saveImage();
void saveImageAs();
void resetToOriginal();
void onProcessFinished(ProcessResult result);
void onProcessFailed(const QString &message);
void rotateLeft();
void rotateRight();
void scaleUp();
void scaleDown();
void translateUp();
void translateDown();
void translateLeft();
void translateRight();
void extractTargets();
void classifyImage();
private:
void buildUi();
void setupWorkerThread();
void updateViews();
void setBusy(bool busy);
bool ensureHasImage() const;
void requestProcess(ImageWorker::Operation operation, double value = 0.0, int dx = 0, int dy = 0);
bool writeCurrentImageTo(const QString &path);
ImageView *originalView_ = nullptr;
ImageView *processedView_ = nullptr;
QLabel *infoLabel_ = nullptr;
QLabel *statusLabel_ = nullptr;
QDoubleSpinBox *scaleSpin_ = nullptr;
QDoubleSpinBox *angleSpin_ = nullptr;
QSpinBox *translateSpin_ = nullptr;
QAction *saveAction_ = nullptr;
QAction *saveAsAction_ = nullptr;
QThread workerThread_;
ImageWorker *worker_ = nullptr;
cv::Mat originalImage_;
cv::Mat processedImage_;
QString currentPath_;
bool busy_ = false;
};

View File

@@ -0,0 +1,47 @@
#include "ImageConverter.h"
#include <opencv2/imgproc.hpp>
QImage ImageConverter::matToQImage(const cv::Mat &mat)
{
if (mat.empty()) {
return {};
}
switch (mat.type()) {
case CV_8UC1: {
QImage image(mat.data, mat.cols, mat.rows, static_cast<int>(mat.step), QImage::Format_Grayscale8);
return image.copy();
}
case CV_8UC3: {
cv::Mat rgb;
cv::cvtColor(mat, rgb, cv::COLOR_BGR2RGB);
QImage image(rgb.data, rgb.cols, rgb.rows, static_cast<int>(rgb.step), QImage::Format_RGB888);
return image.copy();
}
case CV_8UC4: {
QImage image(mat.data, mat.cols, mat.rows, static_cast<int>(mat.step), QImage::Format_ARGB32);
return image.copy();
}
default: {
cv::Mat converted;
mat.convertTo(converted, CV_8U);
return matToQImage(converted);
}
}
}
cv::Mat ImageConverter::qImageToMat(const QImage &image)
{
if (image.isNull()) {
return {};
}
const QImage converted = image.convertToFormat(QImage::Format_RGB888);
cv::Mat rgb(converted.height(), converted.width(), CV_8UC3,
const_cast<uchar *>(converted.bits()),
static_cast<size_t>(converted.bytesPerLine()));
cv::Mat bgr;
cv::cvtColor(rgb, bgr, cv::COLOR_RGB2BGR);
return bgr.clone();
}

149
app/src/ImageProcessor.cpp Normal file
View File

@@ -0,0 +1,149 @@
#include "ImageProcessor.h"
#include <opencv2/imgproc.hpp>
#include <algorithm>
ProcessResult ImageProcessor::rotate(const cv::Mat &source, double angleDegrees)
{
if (source.empty()) {
return {};
}
const cv::Point2f center(static_cast<float>(source.cols) / 2.0F, static_cast<float>(source.rows) / 2.0F);
cv::Mat matrix = cv::getRotationMatrix2D(center, angleDegrees, 1.0);
const double absCos = std::abs(matrix.at<double>(0, 0));
const double absSin = std::abs(matrix.at<double>(0, 1));
const int boundW = static_cast<int>(source.rows * absSin + source.cols * absCos);
const int boundH = static_cast<int>(source.rows * absCos + source.cols * absSin);
matrix.at<double>(0, 2) += boundW / 2.0 - center.x;
matrix.at<double>(1, 2) += boundH / 2.0 - center.y;
cv::Mat output;
cv::warpAffine(source, output, matrix, cv::Size(boundW, boundH), cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(30, 30, 30));
return {output, QStringLiteral("旋转 %1° 完成").arg(angleDegrees)};
}
ProcessResult ImageProcessor::scale(const cv::Mat &source, double factor)
{
if (source.empty() || factor <= 0.0) {
return {};
}
cv::Mat output;
cv::resize(source, output, cv::Size(), factor, factor, factor >= 1.0 ? cv::INTER_CUBIC : cv::INTER_AREA);
return {output, QStringLiteral("缩放 %1 倍完成,尺寸:%2 × %3").arg(factor).arg(output.cols).arg(output.rows)};
}
ProcessResult ImageProcessor::translate(const cv::Mat &source, int dx, int dy)
{
if (source.empty()) {
return {};
}
const cv::Mat matrix = (cv::Mat_<double>(2, 3) << 1, 0, dx, 0, 1, dy);
cv::Mat output;
cv::warpAffine(source, output, matrix, source.size(), cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(30, 30, 30));
return {output, QStringLiteral("平移完成dx=%1, dy=%2").arg(dx).arg(dy)};
}
ProcessResult ImageProcessor::extractTargets(const cv::Mat &source)
{
if (source.empty()) {
return {};
}
cv::Mat gray;
if (source.channels() == 1) {
gray = source.clone();
} else {
cv::cvtColor(source, gray, cv::COLOR_BGR2GRAY);
}
cv::Mat blurred;
cv::GaussianBlur(gray, blurred, cv::Size(5, 5), 0);
cv::Mat binary;
cv::threshold(blurred, binary, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);
std::vector<std::vector<cv::Point>> contours;
cv::findContours(binary, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
cv::Mat output;
if (source.channels() == 1) {
cv::cvtColor(source, output, cv::COLOR_GRAY2BGR);
} else {
output = source.clone();
}
int validCount = 0;
double totalArea = 0.0;
const double minArea = std::max(80.0, source.cols * source.rows * 0.0005);
for (const auto &contour : contours) {
const double area = cv::contourArea(contour);
if (area < minArea) {
continue;
}
++validCount;
totalArea += area;
const cv::Rect box = cv::boundingRect(contour);
cv::rectangle(output, box, cv::Scalar(0, 0, 255), 2);
cv::drawContours(output, std::vector<std::vector<cv::Point>>{contour}, -1, cv::Scalar(0, 255, 0), 2);
const cv::Moments moments = cv::moments(contour);
if (moments.m00 != 0.0) {
const int cx = static_cast<int>(moments.m10 / moments.m00);
const int cy = static_cast<int>(moments.m01 / moments.m00);
cv::circle(output, cv::Point(cx, cy), 4, cv::Scalar(255, 0, 0), -1);
}
}
return {output, QStringLiteral("目标提取完成:检测到 %1 个目标,总面积约 %2 像素")
.arg(validCount)
.arg(static_cast<int>(totalArea))};
}
ProcessResult ImageProcessor::classifySimple(const cv::Mat &source)
{
if (source.empty()) {
return {};
}
cv::Mat hsv;
if (source.channels() == 1) {
cv::cvtColor(source, hsv, cv::COLOR_GRAY2BGR);
cv::cvtColor(hsv, hsv, cv::COLOR_BGR2HSV);
} else {
cv::cvtColor(source, hsv, cv::COLOR_BGR2HSV);
}
const cv::Scalar meanHsv = cv::mean(hsv);
const double hue = meanHsv[0];
const double saturation = meanHsv[1];
const double value = meanHsv[2];
QString type;
if (value < 70) {
type = QStringLiteral("偏暗图像");
} else if (saturation < 45) {
type = QStringLiteral("低饱和 / 灰度感图像");
} else if (hue < 15 || hue > 160) {
type = QStringLiteral("红色系图像");
} else if (hue < 45) {
type = QStringLiteral("黄色 / 橙色系图像");
} else if (hue < 85) {
type = QStringLiteral("绿色系图像");
} else if (hue < 130) {
type = QStringLiteral("蓝色系图像");
} else {
type = QStringLiteral("紫色系图像");
}
cv::Mat output = source.clone();
return {output, QStringLiteral("简单分类:%1平均 H=%2, S=%3, V=%4")
.arg(type)
.arg(hue, 0, 'f', 1)
.arg(saturation, 0, 'f', 1)
.arg(value, 0, 'f', 1)};
}

89
app/src/ImageView.cpp Normal file
View File

@@ -0,0 +1,89 @@
#include "ImageView.h"
#include <QGraphicsPixmapItem>
#include <QGraphicsScene>
#include <QMouseEvent>
#include <QPainter>
#include <QScrollBar>
#include <QWheelEvent>
ImageView::ImageView(QWidget *parent)
: QGraphicsView(parent), scene_(new QGraphicsScene(this))
{
setScene(scene_);
pixmapItem_ = scene_->addPixmap(QPixmap());
pixmapItem_->setTransformationMode(Qt::SmoothTransformation);
setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
setDragMode(QGraphicsView::ScrollHandDrag);
setViewportUpdateMode(QGraphicsView::SmartViewportUpdate);
setBackgroundBrush(QColor(36, 36, 36));
setFrameShape(QFrame::StyledPanel);
setAlignment(Qt::AlignCenter);
}
void ImageView::setImage(const QImage &image)
{
if (image.isNull()) {
clearImage();
return;
}
pixmap_ = QPixmap::fromImage(image);
pixmapItem_->setPixmap(pixmap_);
resetSceneRect();
fitImageToView();
}
void ImageView::clearImage()
{
pixmap_ = QPixmap();
pixmapItem_->setPixmap(QPixmap());
scene_->setSceneRect(QRectF());
resetTransform();
zoomFactor_ = 1.0;
}
void ImageView::fitImageToView()
{
if (!hasImage()) {
return;
}
resetTransform();
fitInView(pixmapItem_, Qt::KeepAspectRatio);
zoomFactor_ = transform().m11();
}
bool ImageView::hasImage() const
{
return !pixmap_.isNull();
}
void ImageView::wheelEvent(QWheelEvent *event)
{
if (!hasImage()) {
QGraphicsView::wheelEvent(event);
return;
}
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
const int delta = event->angleDelta().y();
#else
const int delta = event->delta();
#endif
const double factor = delta > 0 ? 1.15 : 1.0 / 1.15;
zoomFactor_ *= factor;
scale(factor, factor);
event->accept();
}
void ImageView::mouseDoubleClickEvent(QMouseEvent *event)
{
Q_UNUSED(event)
fitImageToView();
}
void ImageView::resetSceneRect()
{
scene_->setSceneRect(pixmap_.rect());
}

35
app/src/ImageWorker.cpp Normal file
View File

@@ -0,0 +1,35 @@
#include "ImageWorker.h"
void ImageWorker::process(cv::Mat image, ImageWorker::Operation operation, double value, int dx, int dy)
{
if (image.empty()) {
Q_EMIT failed(QStringLiteral("没有可处理的图片"));
return;
}
ProcessResult result;
switch (operation) {
case Operation::Rotate:
result = ImageProcessor::rotate(image, value);
break;
case Operation::Scale:
result = ImageProcessor::scale(image, value);
break;
case Operation::Translate:
result = ImageProcessor::translate(image, dx, dy);
break;
case Operation::ExtractTargets:
result = ImageProcessor::extractTargets(image);
break;
case Operation::ClassifySimple:
result = ImageProcessor::classifySimple(image);
break;
}
if (result.image.empty()) {
Q_EMIT failed(QStringLiteral("图像处理失败"));
return;
}
Q_EMIT finished(result);
}

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;
}

20
app/src/main.cpp Normal file
View File

@@ -0,0 +1,20 @@
#include <QApplication>
#include <QMetaType>
#include "MainWindow.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QApplication::setApplicationName("chengnan");
QApplication::setOrganizationName("luochen570");
QApplication::setApplicationVersion(CHENGNAN_APP_VERSION);
qRegisterMetaType<cv::Mat>("cv::Mat");
MainWindow window;
window.resize(1280, 760);
window.show();
return QApplication::exec();
}