本项目实现一个简单的图像处理工具,支持打开并显示本地图像文件,以及对原始图像进行简单的处理。目的在于熟悉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); }