【Qt+OpenCV项目开发学习】二、图片查看器应用程序开发

一、前言

本博客将讲解如何用Qt+OpenCV开发一款图片查看器的Windows应用程序,其实不用OpenCV也能开发出这类软件,作者目的是为了学习Qt+OpenCV开发项目,所以会使用OpenCV,本人会将项目开发的源代码上传到CSDN资源供大家学习参考,下载链接在文末。该款软件要实现的主要功能如下:

  • 能够读取单张图片或文件夹下图片并显示。
  • 能够用鼠标移动图片、滚轮缩放图片、左键双击让图片适应窗体显示等。
  • 软件能够切换中英文显示。

开发环境:Windows7(64)         OpenCV4.1.0(支持VC14)     QtCreator4.3.0(编译器使用VSVC2015 64bit)

二、新建项目及环境配置

2.1 新建项目

打开Qt Creator,文件->新建文件或项目->Application->Qt Widgets Application,然后根据向导一步步创建项目即可。本人项目名取为TDPictureViewer,为本项目选择的编译器是MSVC2015 64bit,类名取为TdMainWindow。

注意,请确保开发本项目以前MSVC2015 64bit编译器和能安装的OpenCV能正常使用。如果电脑没有装VS2015和Windows Kits的相应调试器,则Qt Creator中的MSVC2015 64bit编译器将无法正常使用。如果你安装的OpenCV不支持vc14,则无法正常使用,官网下载的exe文件较高版本中一般都支持vc14和vc5,如本人安装的OpenCV4.1.0。如若你想要选择MinGW编译器,则需要下载OpenCV源码,然后使用cmake编译该源码,方法见本人另一篇博客【Qt+OpenCV项目开发学习】一、环境配置。

2.2 项目环境配置

要想在项目中使用OpenCV,需要进行简单配置。双击打开项目文件(即后缀名为pro的文件),本人打开的是TDPictureViewer.pro文件。在该文件最后面加几行代码,代码如下所示。注意本人OpenCV安装在C:/Qt/opencv410路径下,如果你的OpenCV安装在其他路径,请修改下面代码中的路径。请将OpenCV安装在全英文的路径下。

TDPictureViewer.pro:



# OpenCV配置
CONFIG(release, debug|release): LIBS += -LC:/Qt/opencv410/build/x64/vc14/lib/ -lopencv_world410
else:CONFIG(debug, debug|release): LIBS += -LC:/Qt/opencv410/build/x64/vc14/lib/ -lopencv_world410d

INCLUDEPATH += C:/Qt/opencv410/build/include
DEPENDPATH += C:/Qt/opencv410/build/include

想要实现界面的多语言,即本项目界面的中英文切换,也需要简单配置。也是在项目文件中加几行代码,代码如下,其中文件名可以任意取,只要区分开来并你知道哪个文件对应的是哪种语言即可。注意如果你想要某些代码中的一些字符串也能多语言,请使用tr()函数。

TDPictureViewer.pro:

# 多语言配置
TRANSLATIONS += \
             multi-language_cn.ts \
             multi-language_en.ts

三、主界面UI设计

本次项目主界面较为简单,加一个QGraphicsView控件用于显示图片,为菜单栏和工具栏的加点内容,然后修改控件名字等属性值,设置快捷键等操作,最后简单布局一下即可。不做详细介绍,上本人设计的主界面图片供大家参考。

                     【Qt+OpenCV项目开发学习】二、图片查看器应用程序开发_第1张图片 

                                    【Qt+OpenCV项目开发学习】二、图片查看器应用程序开发_第2张图片

四、控件的功能实现

4.1 Mat类型转化为QPixmap/QImage

ui界面设计好后,就需要实现相应功能,即要完成上述action控件的槽函数。QGraphicsView存放的图片的是QPixmap类型,而OpenCV中存放图片的是Mat类型,用OpenCV的imread()函数读取图片文件后,不能直接在QGraphicsView显示,所以在开始之前,需要实现一个功能就是Mat类型转化为QPixmap。此外,本人也写了一个readImage()函数用于读取图片文件,分别需要在源文件和头文件中加代码,代码如下:

tdmainwindow.h

//别忘了包含头文件
#include 


public:
    QPixmap readImage(QString FileName);
    QPixmap mat2QPixmap(const cv::Mat &img);

tdmainwindow.cpp

QPixmap TdMainWindow::mat2QPixmap(const cv::Mat &img)
{
    QPixmap imgQ;
    if(img.empty())
    {
        return imgQ;
    }
    if(img.channels() == 1)//灰度图像
    {
        QImage Qi = QImage( img.data, img.cols, img.rows,
                            img.cols * img.channels(),
                            QImage::Format_Grayscale8 );
        imgQ = QPixmap::fromImage(Qi);
    }
    else if(img.channels() == 3)//彩色图像
    {
        cv::Mat RGBimg;
        cv::cvtColor(img, RGBimg, cv::COLOR_BGR2RGB);
        QImage Qi = QImage( RGBimg.data, RGBimg.cols, RGBimg.rows,
                            RGBimg.cols * RGBimg.channels(),
                            QImage::Format_RGB888 );
        imgQ = QPixmap::fromImage(Qi);
    }
    return imgQ;
}
QPixmap TdMainWindow::readImage(QString FileName)
{
    QPixmap imgQ;
    cv::Mat imgMat = cv::imread(FileName.toStdString());
    //格式转化
    imgQ = mat2QPixmap(imgMat);
    return imgQ;
}

在完成基本功能时,还有有一些其他辅助的代码添加,如要加一些变量存放数据,辅助的方法实现等,代码如下:

tdmainwindow.h

//别忘了包含必要的头文件,这里加的头文件仅供参考
#include 
#include "formabout.h"//这是自己设计的一个子窗体,后面会提到它


public:
    void initUI();    //将ui的一些设置操作放在该方法中
    void initConnect();    //将所有的信号与槽的连接都放在该方法中
    void writeIniFile(QString Language);    //把语言设置写入配置文件中,用于多语言功能实现
    QString readIniFile();  //读取配置文件中的语言设置,,用于多语言功能实现

private:
    QGraphicsScene *scene; //用于存放要显示图片
    FormAbout *about; //关于子窗体,当点击关于/帮助时,会显示该子窗体
    QStringList fileNames;    //用于存放图片文件名
    QString currentDir; //用于存放路径
    QString TitleName;//用于存放程序标题名
    int currentNum;//当前显示的图片序号
    int Maxnum;//图片数量

tdmainwindow.cpp

//别忘了包含必要的头文件
#include 

TdMainWindow::TdMainWindow( QWidget *parent ) :
                            QMainWindow(parent),
                            ui(new Ui::TdMainWindow),
                            scene(new QGraphicsScene),
                            about(new FormAbout)
{
    ui->setupUi(this);
    initUI();
    initConnect();
}
TdMainWindow::~TdMainWindow()
{
    delete ui;
    delete scene;
    delete about;
}
void TdMainWindow::initUI()
{
    about->setWindowFlags(Qt::WindowCloseButtonHint);
    QString language = readIniFile();
    if(language == "chinese")
    {
        ui->actionChinese->setChecked(true);
        ui->actionEnglish->setChecked(false);
    }
    else
    {
        ui->actionChinese->setChecked(false);
        ui->actionEnglish->setChecked(true);
    }
    this->TitleName = this->windowTitle();
}
void TdMainWindow::initConnect()
{
    //目前内容为空
}
QString TdMainWindow::readIniFile()
{
    QSettings *settings = new QSettings("./SystemSettings.ini", QSettings::IniFormat);
    QString language = settings->value("language", "chinese").toString();
    delete settings;
    return language;
}
void TdMainWindow::writeIniFile(QString Language)
{
    QSettings *settings = new QSettings("./SystemSettings.ini", QSettings::IniFormat);
    settings->setValue("language", Language);
    delete settings;
}

4.2 基本功能实现

现在要实现按某个按钮后能执行想要的操作。如读取图片、文件夹下图片、上一张、下一张、放大、缩小、适应窗体显示、中英文切换等,不做过多介绍,直接上代码。

tdmainwindow.h

public slots:
    void actionOpenPictureTriggeredSlot();
    void actionOpenFolderTriggeredSlot();
    void actionViewPreviousTriggeredSlot();
    void actionViewNextTriggeredSlot();
    void actionZoomInTriggeredSlot();
    void actionZoomOutTriggeredSlot();
    void actionZoomToFitTriggeredSlot();
    void actionChineseTriggeredSlot();
    void actionEnglishTriggeredSlot();
    void actionAboutTriggeredSlot();
    void actionHelpTriggeredSlot();

tdmainwindow.cpp

//注意包含必要的头文件,仅供参考
#include 
#include 
#include 

void TdMainWindow::initConnect()
{
    connect( ui->actionOpen_Picture,
             SIGNAL(triggered(bool)),
             this,
             SLOT(actionOpenPictureTriggeredSlot()) );
    connect( ui->actionOpen_Folder,
             SIGNAL(triggered(bool)),
             this,
             SLOT(actionOpenFolderTriggeredSlot()) );
    connect( ui->actionView_Previous,
             SIGNAL(triggered(bool)),
             this,
             SLOT(actionViewPreviousTriggeredSlot()) );
    connect( ui->actionView_Next,
             SIGNAL(triggered(bool)),
             this,
             SLOT(actionViewNextTriggeredSlot()) );
    connect( ui->actionZoom_In,
             SIGNAL(triggered(bool)),
             this,
             SLOT(actionZoomInTriggeredSlot()) );
    connect( ui->actionZoom_Out,
             SIGNAL(triggered(bool)),
             this,
             SLOT(actionZoomOutTriggeredSlot()) );
    connect( ui->actionAdapt_Window,
             SIGNAL(triggered(bool)),
             this,
             SLOT(actionZoomToFitTriggeredSlot()) );
    connect( ui->actionChinese,
             SIGNAL(triggered(bool)),
             this,
             SLOT(actionChineseTriggeredSlot()) );
    connect( ui->actionEnglish,
             SIGNAL(triggered(bool)),
             this,
             SLOT(actionEnglishTriggeredSlot()) );
    connect( ui->actionAbout,
             SIGNAL(triggered(bool)),
             this,
             SLOT(actionAboutTriggeredSlot()) );
    connect( ui->actionHelp,
             SIGNAL(triggered(bool)),
             this,
             SLOT(actionHelpTriggeredSlot()) );
    connect( ui->actionExit,
             SIGNAL(triggered(bool)),
             this,
             SLOT(close()) );
}

void TdMainWindow::actionOpenPictureTriggeredSlot()
{
    //选择图像文件
    QString fileName = QFileDialog::getOpenFileName(this,
                                                    tr("Open Image"),
                                                    "C:/",
                                                    tr("Image Files (*.png *.jpg *.bmp)")
                                                    );
    if(!fileName.isEmpty())
    {
        this->fileNames.clear();
        this->fileNames += fileName;
        this->currentNum = 0;
        this->Maxnum = 1;
        QPixmap imgQ = readImage(fileNames[currentNum]);
        if(!imgQ.isNull())
        {
            //将图片加到场景中
            this->scene->clear();
            this->scene->addPixmap(imgQ);
            ui->graphicsView->setScene(this->scene);
            //显示
            ui->graphicsView->show();
            this->setWindowTitle(this->TitleName + " - " + fileNames[currentNum]);
        }
    }

}
void TdMainWindow::actionOpenFolderTriggeredSlot()
{
    //用户选择文件夹
    QString dirStr = QFileDialog::getExistingDirectory(this,
                                                          tr("Open Directory"),
                                                          "C:/",
                                                          QFileDialog::ShowDirsOnly
                                                          );
    if(!dirStr.isEmpty())
    {
        //筛选出文件夹下的图片文件
        QDir dir = QDir(dirStr);
        QStringList filters;
        filters << "*.png" << "*.jpg" << "*.bmp";
        dir.setNameFilters(filters);
        QStringList fms = dir.entryList();
        if(!fms.isEmpty())
        {
            this->fileNames.clear();
            this->fileNames = fms;
            this->currentDir = dirStr + "/";
            this->currentNum = 0;
            this->Maxnum = this->fileNames.length();
            //读取文件夹下的图片文件并显示
            QPixmap imgQ =  readImage(this->currentDir + this->fileNames[this->currentNum]);
            if(!imgQ.isNull())
            {
                //将所有图片加到场景中
                this->scene->clear();
                this->scene->addPixmap(imgQ);
                ui->graphicsView->setScene(this->scene);
                //显示
                ui->graphicsView->show();
                this->setWindowTitle(this->TitleName + " - " + this->currentDir + this->fileNames[this->currentNum]);
            }
        }
    }
}
void TdMainWindow::actionViewPreviousTriggeredSlot()
{
    if(ui->graphicsView->items().isEmpty())
    {
        return;
    }
    this->currentNum--;
    if(this->currentNum < 0)
    {
        this->currentNum = this->Maxnum - 1;
    }
    //读取文件夹下的图片文件并显示
    QPixmap imgQ =  readImage(this->currentDir + this->fileNames[this->currentNum]);
    if(!imgQ.isNull())
    {
        //将所有图片加到场景中
        this->scene->clear();
        this->scene->addPixmap(imgQ);
        ui->graphicsView->setScene(this->scene);
        //显示
        ui->graphicsView->show();
        this->setWindowTitle(this->TitleName + " - " + this->currentDir + this->fileNames[this->currentNum]);
    }
}
void TdMainWindow::actionViewNextTriggeredSlot()
{
    if(ui->graphicsView->items().isEmpty())
    {
        return;
    }
    this->currentNum++;
    if(this->currentNum >= this->Maxnum)
    {
        this->currentNum = 0;
    }
    //读取文件夹下的图片文件并显示
    QPixmap imgQ =  readImage(this->currentDir + this->fileNames[this->currentNum]);
    if(!imgQ.isNull())
    {
        //将所有图片加到场景中
        this->scene->clear();
        this->scene->addPixmap(imgQ);
        ui->graphicsView->setScene(this->scene);
        //显示
        ui->graphicsView->show();
        this->setWindowTitle(this->TitleName + " - " + this->currentDir + this->fileNames[this->currentNum]);
    }
}
void TdMainWindow::actionZoomInTriggeredSlot()
{
    if(ui->graphicsView->items().isEmpty())
    {
        return;
    }
    ui->graphicsView->scale(1.1, 1.1);
}
void TdMainWindow::actionZoomOutTriggeredSlot()
{
    if(ui->graphicsView->items().isEmpty())
    {
        return;
    }
    ui->graphicsView->scale(0.9, 0.9);
}
void TdMainWindow::actionZoomToFitTriggeredSlot()
{
    if(ui->graphicsView->items().isEmpty())
    {
        return;
    }
    ui->graphicsView->setTransformationAnchor(QGraphicsView::AnchorViewCenter);
    int vh = ui->graphicsView->height()-4;
    int sh = ui->graphicsView->scene()->height(); 
    qreal m22 = (qreal)vh / sh;
    QMatrix q;
    q.setMatrix(m22,
                ui->graphicsView->matrix().m12(),
                ui->graphicsView->matrix().m21(),
                m22,
                ui->graphicsView->matrix().dx(),
                ui->graphicsView->matrix().dy()
                );
    ui->graphicsView->setMatrix(q);
}
void TdMainWindow::actionChineseTriggeredSlot()
{
    ui->actionChinese->setChecked(true);
    ui->actionEnglish->setChecked(false);
    writeIniFile("chinese");
    QMessageBox::information(this,
                             tr("Warning"),
                             tr("Restart the software for the settings to take effect"));
}
void TdMainWindow::actionEnglishTriggeredSlot()
{
    ui->actionChinese->setChecked(false);
    ui->actionEnglish->setChecked(true);
    writeIniFile("English");
    QMessageBox::information(this,
                             tr("Warning"),
                             tr("Restart the software for the settings to take effect"));
}
void TdMainWindow::actionAboutTriggeredSlot()
{
    about->show();
}
void TdMainWindow::actionHelpTriggeredSlot()
{
    about->show();
}

五、自定义控件和子窗体

前面有用到一个FormAbout子窗体,在这简单讲解以下。在项目上右键->添加新文件->Qt->Qt设计师界面类->choose。然后根据向导一步步完成即可。本人让界面继承Widget,类名为FormAbout。创建好后,只需要简单设计以下ui文件即可,上我设计的界面供大家参考。

                                【Qt+OpenCV项目开发学习】二、图片查看器应用程序开发_第3张图片

此外要想实现鼠标控制图片缩放、移动等功能,则需要继承QGraphicsView控件,写个自定义控件,重新实现其鼠标事件。方法是,在项目上右键->添加新文件->C++->C++ Class->choose。然后根据向导一步步完成即可。类名取为TDGraphicsView,继承QGraphicsView。创建好后,写好头文件和源文件,代码如下:

tdgraphicsview.h

#ifndef TDGRAPHICSVIEW_H
#define TDGRAPHICSVIEW_H

#include 
#include 

class TDGraphicsView : public QGraphicsView
{
    Q_OBJECT

public:
    TDGraphicsView(QWidget *parent = 0);
    void mouseMoveEvent(QMouseEvent *event);
    void mousePressEvent(QMouseEvent *event);
    void mouseReleaseEvent(QMouseEvent *event);
    void mouseDoubleClickEvent(QMouseEvent *event);
    void wheelEvent(QWheelEvent *event);
signals:
    void mousePress(QPoint point); //鼠标按下
    void mouseMove(QPoint point); //鼠标移动
    void mouseDoubleClick(QPoint point);//鼠标双击
    void mouseRelease(QPoint point); //鼠标释放
    void wheelScroll(bool direction); //滚轮滚动
};

#endif // TDGRAPHICSVIEW_H

tdgraphicsview.cpp

#include "tdgraphicsview.h"
#include
TDGraphicsView::TDGraphicsView(QWidget *parent):QGraphicsView(parent)
{

}
void TDGraphicsView::mousePressEvent(QMouseEvent *event)
{
    if (event->button()==Qt::LeftButton)
    {
        QPoint point=event->pos(); //QGraphicsView的坐标
        emit mousePress(point);
    }
    QGraphicsView::mousePressEvent(event);
}
void TDGraphicsView::mouseReleaseEvent(QMouseEvent *event)
{
    if (event->button()==Qt::LeftButton)
    {
        QPoint point=event->pos(); //QGraphicsView的坐标
        emit mouseRelease(point);
    }
    QGraphicsView::mouseReleaseEvent(event);
}
void TDGraphicsView::mouseDoubleClickEvent(QMouseEvent *event)
{
    if (event->button()==Qt::LeftButton)
    {
        QPoint point=event->pos(); //QGraphicsView的坐标
        emit mouseDoubleClick(point);
    }
    QGraphicsView::mouseDoubleClickEvent(event);
}
void TDGraphicsView::mouseMoveEvent(QMouseEvent *event)
{
    if(event->buttons() & Qt::LeftButton)
    {
        QPoint point = event->pos(); //QGraphicsView的坐标
        emit mouseMove(point);
    }
}
void TDGraphicsView::wheelEvent(QWheelEvent *event)
{
    if(event->delta() > 0)
    {
        emit wheelScroll(true);
    }
    else
    {
        emit wheelScroll(false);
    }
}

六、鼠标控制图片功能实现

要想实现鼠标移动图片、双击图片变成合适大小、滚轮缩放图片等功能,首先切换到主界面的ui设计中,将QGraphicsView控件提升为上面实现的TDGraphicsView自定义控件。方法鼠标放置在控件位置,右键选择提升为->TDGraphicsView,如若没有,请自行输入提升的类名称。完成以上操作后即可开始实现鼠标的单击、释放、移动、双击以及滚轮功能。代码如下:

tdmainwindow.h

public slots:
    void mousePressSlot(QPoint point);
    void mouseReleaseSlot(QPoint point);
    void mouseMoveSlot(QPoint point);
    void wheelScrollSlot(bool direction);
private:
    QPointF offset;

tdmainwindow.cpp

void TdMainWindow::initConnect()
{
    connect(ui->graphicsView,
            SIGNAL(mousePress(QPoint)),
            this,
            SLOT(mousePressSlot(QPoint)) );
    connect(ui->graphicsView,
            SIGNAL(mouseRelease(QPoint)),
            this,
            SLOT(mouseReleaseSlot(QPoint)) );
    connect(ui->graphicsView,
            SIGNAL(mouseMove(QPoint)),
            this,
            SLOT(mouseMoveSlot(QPoint)) );
    connect(ui->graphicsView,
            SIGNAL(wheelScroll(bool)),
            this,
            SLOT(wheelScrollSlot(bool)) );
    connect(ui->graphicsView,
            SIGNAL(mouseDoubleClick(QPoint)),
            this,
            SLOT(actionZoomToFitTriggeredSlot()) );
}

void TdMainWindow::mousePressSlot(QPoint point)
{
    if(ui->graphicsView->items().isEmpty())
    {
        return;
    }
    QCursor cursor;
    cursor.setShape(Qt::ClosedHandCursor);
    QApplication::setOverrideCursor(cursor);
    QPointF pointScence = ui->graphicsView->mapToScene(point); //转换到Scene坐标
    offset = pointScence;

}
void TdMainWindow::mouseReleaseSlot(QPoint point)
{
    QApplication::restoreOverrideCursor();
}

void TdMainWindow::mouseMoveSlot(QPoint point)
{
    if(ui->graphicsView->items().isEmpty())
    {
        return;
    }
    QPointF tmp = ui->graphicsView->mapToScene(point) - offset; //转换到Scene坐标
    int x = ui->graphicsView->horizontalScrollBar()->sliderPosition() - tmp.toPoint().x();
    int y = ui->graphicsView->verticalScrollBar()->sliderPosition() - tmp.toPoint().y();
    ui->graphicsView->horizontalScrollBar()->setSliderPosition(x);
    ui->graphicsView->verticalScrollBar()->setSliderPosition(y);
}
void TdMainWindow::wheelScrollSlot(bool direction)
{
    if(direction)
    {
        actionZoomInTriggeredSlot();
    }
    else
    {
        actionZoomOutTriggeredSlot();
    }
}

七、多语言功能实现

首先需要编译一下,然后点击工具->外部->Qt预言家->更新翻译(lupdate),此时项目文件夹下会产生两个ts文件如下图所示。

                              【Qt+OpenCV项目开发学习】二、图片查看器应用程序开发_第4张图片

     【Qt+OpenCV项目开发学习】二、图片查看器应用程序开发_第5张图片

然后请用MVSC 2015(64-bit)下的Linguist程序进行翻译工作,如果原程序是全用的英文,则只需要打开中文ts文件,,注意如果你是别的编译器,就要选择相应编译器下的Linguist程序进行翻译工作。以下截图供大家参考。

                     【Qt+OpenCV项目开发学习】二、图片查看器应用程序开发_第6张图片

【Qt+OpenCV项目开发学习】二、图片查看器应用程序开发_第7张图片

翻译好后保存ts文件,关闭Linguist程序,然后点击工具->外部->Qt预言家->发布翻译(lrelease),此时项目文件夹下会产生两个qm文件。

【Qt+OpenCV项目开发学习】二、图片查看器应用程序开发_第8张图片

最后还需要在main函数中加代码,注意多语言的相关代码,一定要在窗体创建之前加,且本次多语言实现重启软件后界面才会更改,没有热切换功能,代码如下:

main.cpp

   QApplication a(argc, argv);
    //多语言,以下为加的代码
    QTranslator *translator = new QTranslator();
    QSettings *settings = new QSettings("./SystemSettings.ini", QSettings::IniFormat);
    QString language = settings->value("language", "chinese").toString();
    delete settings;
    if(language == "chinese")
    {
        translator->load("./multi-language_cn.qm");
        a.installTranslator(translator);
    }
    else
    {
        translator->load("./multi-language_en.qm");
        a.installTranslator(translator);
    }
    //以上为加的代码
    //创建主窗体并显示
    TdMainWindow w;
    w.show();   
    return a.exec();

八、编译运行

最后就是编译运行,测试应用程序相关功能,整个开发到此结束。贴上运行的结果图。

动图效果,鼠标移动图片,滚轮缩放图片、左键双击适应窗口显示。

中文界面     【Qt+OpenCV项目开发学习】二、图片查看器应用程序开发_第9张图片

英文界面【Qt+OpenCV项目开发学习】二、图片查看器应用程序开发_第10张图片

项目开发源代码下载链接

你可能感兴趣的:(Qt,opencv)