本项目实现一个简单的图像处理工具,支持打开并显示本地图像文件,以及对原始图像进行简单的处理。目的在于熟悉Qt中的文件打开对话框,以及在QLabel显示图像,自定义对话框,以及对图像像素点的访问等功能。
新建项目 #
新建Qt Widget Application 项目

默认QMainWindow为基类。

项目文件目录结构如下。

拖放控件 #
双击mainwindow.ui文件,在界面中拖放一个LineEdit,两个PushButton,两个Label。
使用LineEdit显示图片的路径;
使用第一个PushButton供用户打开图片;
第二个PushButton点击后进行图像处理;
底下左边的Label用来显示打开的图像;
底下右边的Label用来显示处理后的图像;

为了方便区分这些组件,我们在右键菜单选择更改对象名称。

le是LineEdit的缩写,path表示该空间功能为显示图片路径,所以叫做le_path。
(非固定格式,以方便阅读为前提)

以此类推,修改其他组件对象名称和UI显示的名称。




添加布局 #
选中LineEdit,open,process进行水平布局,并调整位置。

显示图片的两个Label使用分裂水平布局。

选中主窗口,点击栅格布局。

运行后,预览程序的界面。

可在右侧选择MainWindow对象,在下方找到windowsTitle修改窗口标题。


打开图像 #
首先定义一个函数来打开文件。

在mainwindow.cpp文件里实现函数的定义。
void MainWindow::open_file()
{
QString filename;
// 显示打开文件对话框,指定默认目录和过滤文件类型
filename = QFileDialog::getOpenFileName(this, tr("Open image"),
"./", tr("Images (*.png *.xpm *.jpg)"));
if(filename.isEmpty()) {
// 如果没有选择文件,则在状态栏提示
ui->statusbar->showMessage("No File");
} else {
// 如果打开文件成功,则在UI编辑框显示路径
ui->le_path->setText(filename);
}
}

在构造函数中绑定按钮点击信号和上面的打开文件槽函数。
connect(ui->pb_open, SIGNAL(clicked(bool)), this, SLOT(open_file()));

运行结果演示。


显示图像 #
Qt中与图像相关常用的的两个类分别是QPixmap和QImage。
QPixmap 类对绘图进行了优化,直接加载到显存,和平台相关。
QImage 类提供对图片像素以及文件的读写,和平台无关。
我们使用QImage读取本地图像,然后转换为QPixmap对象在Label上显示。
定义show_image()函数用来显示图片。
void MainWindow::show_image(QString filename)
{
if (m_img_input == nullptr) {
m_img_input = new QImage();
}
// 从本地加载图像文件
m_img_input->load(filename);
if(m_img_input->isNull()) {
// 状态栏提示错误消息
ui->statusbar->showMessage("Open fail:" + filename);
} else {
// 获取label大小
QSize size = ui->lb_input->size();
// 将图像所放到与Label大小一致
QImage img = m_img_input->scaled(size, Qt::KeepAspectRatio);
// QImage格式转换为QPixmap格式
QPixmap pix = QPixmap::fromImage(img);
// 将QPixmap格式图像设置在Label上
ui->lb_input->setPixmap(pix);
}
}
在open_file()函数里面调用show_image()函数

运行后打开并显示图片。

图像处理 #
Qimage类提供了大量读写图像像素的接口。
获取某点颜色。

设置某点颜色。

接下来写一个image_process()函数对图像像素点读写进行一个简单的演示。
void MainWindow::image_process()
{
// 将右边的label设置和左边同样大小
QSize size = ui->lb_input->size();
ui->lb_output->resize(size);
QImage img = m_img_input->scaled(size, Qt::KeepAspectRatio);
// 处理缩放后的图像,每隔50像素画一条黑线
for (int i = 0; i < size.height(); i+=50) {
for (int j = 0; j < size.width(); j++) {
// 设置j,i处的颜色为黑色
img.setPixelColor(j, i, QColor(0,0,0));
}
}
// 处理后的图片保存至文件
img.save("./out_img.jpg");
// QImage格式转换为QPixmap格式
QPixmap pix = QPixmap::fromImage(img);
// 程序界面右侧显示
ui->lb_output->setPixmap(pix);
}
在构造函数中,将上面的函数绑定到处理按钮的点击信号上。
connect(ui->pb_process, SIGNAL(clicked(bool)), this, SLOT(image_process()));
运行后,先打开图片,然后点击处理。

项目的根目录下生成了我们处理后的图片。

参数输入 #
上面我们在处理图像时每间隔50个像素点画一行,其中50为代码中固定值,但在实际应用中更好的应该是让用户来决定,所以我们给程序加一个对话框,获取用户输入参数。
参数输入对话框有两个滑块(QSlider)和两个标签(QLabel)分别让用户来设置和显示行和列需要间隔的数值。
对话框初始化代码如下。
void MainWindow::init_dialog_ui()
{
QGridLayout *layout = new QGridLayout();
m_dialog = new QDialog(this);
m_slider_x = new QSlider(Qt::Horizontal);
m_slider_y = new QSlider(Qt::Horizontal);
m_slider_x->setRange(0, 200);
m_slider_y->setRange(0, 200);
m_pb_dialog_ok = new QPushButton("ok");
QLabel *lb_x = new QLabel();
QLabel *lb_y = new QLabel();
connect(m_slider_x, SIGNAL(valueChanged(int)), lb_x, SLOT(setNum(int)));
connect(m_slider_y, SIGNAL(valueChanged(int)), lb_y, SLOT(setNum(int)));
connect(m_pb_dialog_ok, SIGNAL(clicked(bool)), m_dialog, SLOT(accept()));
layout->addWidget(m_slider_x, 0, 0);
layout->addWidget(lb_x, 0, 1);
layout->addWidget(m_slider_y, 1, 0);
layout->addWidget(lb_y, 1, 1);
layout->addWidget(m_pb_dialog_ok, 2, 0, 1, 2);
m_dialog->setLayout(layout);
m_dialog->setModal(true);
}
在image_process函数一开始调用对话框获取用户参数,并在后续处理中应用。
void MainWindow::image_process()
{
m_dialog->exec();
int x = m_slider_x->value() + 1;
int y = m_slider_y->value() + 1;
QSize size = ui->lb_input->size();
QImage img = m_img_input->scaled(size, Qt::KeepAspectRatio);
size = img.size();
qDebug()<<img.size();
// 处理缩放后的图像,每隔下,y像素画一个点
for (int i = 0; i < size.height(); i += x) {
for (int j = 0; j < size.width(); j += y) {
// 设置j,i处的颜色为黑色
img.setPixelColor(j, i, QColor(0,0,0));
}
}
// 处理后的图片保存至文件
img.save("./out_img.jpg");
if (m_img_output == nullptr) {
m_img_output = new QImage();
}
*m_img_output = img.copy();
// QImage格式转换为QPixmap格式
QPixmap pix = QPixmap::fromImage(img);
// 程序界面右侧显示
ui->lb_output->setPixmap(pix);
}
窗口适配 #
上述情况,当用户拖动窗口时候,底下的两幅图像为固定大小,为了更加美观,我们需要进行对图片大小调整进行自适应。
当窗口大小发生变化时,会调用resizeEvent()函数,我们只需要在这个重写这个函数就可以实现大小自适应。
代码如下。
void MainWindow::resizeEvent(QResizeEvent *event)
{
QSize size = event->size();
ui->splitter->setGeometry(0, 50, size.width(), size.height() - 50);
size.setWidth(size.width() / 2 -50);
size.setHeight(size.height() - 50);
ui->lb_input->resize(size);
ui->lb_output->resize(size);
QImage img;
QPixmap pix;
if(m_img_input != nullptr) {
img = m_img_input->scaled(size, Qt::KeepAspectRatio);
pix = QPixmap::fromImage(img);
ui->lb_input->setPixmap(pix);
}
if(m_img_output != nullptr) {
img = m_img_output->scaled(size, Qt::KeepAspectRatio);
pix = QPixmap::fromImage(img);
ui->lb_output->setPixmap(pix);
}
QWidget::resizeEvent(event);
}
结果演示


项目代码 #
本项目简单演示了使用Qt打开,显示,读写图像等操作,更多接口请查阅Qt的官网帮助文档。
本项目MainWindows类全部代码如下。
mainwindow.h文件.
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QDialog>
#include <QSlider>
#include <QResizeEvent>
#include <QDebug>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
void init_dialog_ui();
private:
Ui::MainWindow *ui;
QDialog *m_dialog = nullptr;
QSlider *m_slider_x = nullptr;
QSlider *m_slider_y = nullptr;
QPushButton *m_pb_dialog_ok = nullptr;
private slots:
void open_file();
void image_process();
private:
QImage *m_img_input = nullptr;
QImage *m_img_output = nullptr;
private:
void show_image(QString filename);
void resizeEvent(QResizeEvent *event);
};
#endif // MAINWINDOW_H
mainwindow.cpp文件
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QFileDialog>
#include <QGridLayout>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
init_dialog_ui();
connect(ui->pb_open, SIGNAL(clicked(bool)), this, SLOT(open_file()));
connect(ui->pb_process, SIGNAL(clicked(bool)), this, SLOT(image_process()));
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::init_dialog_ui()
{
QGridLayout *layout = new QGridLayout();
m_dialog = new QDialog(this);
m_slider_x = new QSlider(Qt::Horizontal);
m_slider_y = new QSlider(Qt::Horizontal);
m_slider_x->setRange(0, 200);
m_slider_y->setRange(0, 200);
m_pb_dialog_ok = new QPushButton("ok");
QLabel *lb_x = new QLabel();
QLabel *lb_y = new QLabel();
connect(m_slider_x, SIGNAL(valueChanged(int)), lb_x, SLOT(setNum(int)));
connect(m_slider_y, SIGNAL(valueChanged(int)), lb_y, SLOT(setNum(int)));
connect(m_pb_dialog_ok, SIGNAL(clicked(bool)), m_dialog, SLOT(accept()));
layout->addWidget(m_slider_x, 0, 0);
layout->addWidget(lb_x, 0, 1);
layout->addWidget(m_slider_y, 1, 0);
layout->addWidget(lb_y, 1, 1);
layout->addWidget(m_pb_dialog_ok, 2, 0, 1, 2);
m_dialog->setLayout(layout);
m_dialog->setModal(true);
}
void MainWindow::open_file()
{
QString filename;
// 显示打开文件对话框,指定默认目录和过滤文件类型
filename = QFileDialog::getOpenFileName(this, tr("Open image"),
"./", tr("Images (*.png *.xpm *.jpg)"));
if(filename.isEmpty()) {
// 如果没有选择文件,则在状态栏提示
ui->statusbar->showMessage("No File");
} else {
// 如果打开文件成功,则在UI编辑框显示路径
ui->le_path->setText(filename);
show_image(filename);
}
}
void MainWindow::show_image(QString filename)
{
if (m_img_input == nullptr) {
m_img_input = new QImage();
}
m_img_input->load(filename);
if(m_img_input->isNull()) {
// 状态栏提示错误消息
ui->statusbar->showMessage("Open fail:" + filename);
} else {
// 获取label大小
QSize size = ui->lb_input->size();
// 将图像所放到与Label大小一致
QImage img = m_img_input->scaled(size, Qt::KeepAspectRatio);
// QImage格式转换为QPixmap格式
QPixmap pix = QPixmap::fromImage(img);
// 将QPixmap格式图像设置在Label上
ui->lb_input->setPixmap(pix);
}
}
void MainWindow::image_process()
{
m_dialog->exec();
int x = m_slider_x->value() + 1;
int y = m_slider_y->value() + 1;
QSize size = ui->lb_input->size();
QImage img = m_img_input->scaled(size, Qt::KeepAspectRatio);
size = img.size();
qDebug()<<img.size();
// 处理缩放后的图像,每隔50像素画一条黑线
for (int i = 0; i < size.height(); i += x) {
for (int j = 0; j < size.width(); j += y) {
// 设置j,i处的颜色为黑色
img.setPixelColor(j, i, QColor(0,0,0));
}
}
// 处理后的图片保存至文件
img.save("./out_img.jpg");
if (m_img_output == nullptr) {
m_img_output = new QImage();
}
*m_img_output = img.copy();
// QImage格式转换为QPixmap格式
QPixmap pix = QPixmap::fromImage(img);
// 程序界面右侧显示
ui->lb_output->setPixmap(pix);
}
void MainWindow::resizeEvent(QResizeEvent *event)
{
QSize size = event->size();
ui->splitter->setGeometry(0, 50, size.width(), size.height() - 50);
size.setWidth(size.width() / 2 -50);
size.setHeight(size.height() - 50);
ui->lb_input->resize(size);
ui->lb_output->resize(size);
QImage img;
QPixmap pix;
if(m_img_input != nullptr) {
img = m_img_input->scaled(size, Qt::KeepAspectRatio);
pix = QPixmap::fromImage(img);
ui->lb_input->setPixmap(pix);
}
if(m_img_output != nullptr) {
img = m_img_output->scaled(size, Qt::KeepAspectRatio);
pix = QPixmap::fromImage(img);
ui->lb_output->setPixmap(pix);
}
QWidget::resizeEvent(event);
}