跳至正文
View Categories

6 min read

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