本博客将讲解如何用Qt+OpenCV开发一款图片查看器的Windows应用程序,其实不用OpenCV也能开发出这类软件,作者目的是为了学习Qt+OpenCV开发项目,所以会使用OpenCV,本人会将项目开发的源代码上传到CSDN资源供大家学习参考,下载链接在文末。该款软件要实现的主要功能如下:
开发环境:Windows7(64) OpenCV4.1.0(支持VC14) QtCreator4.3.0(编译器使用VSVC2015 64bit)
打开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项目开发学习】一、环境配置。
要想在项目中使用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
本次项目主界面较为简单,加一个QGraphicsView控件用于显示图片,为菜单栏和工具栏的加点内容,然后修改控件名字等属性值,设置快捷键等操作,最后简单布局一下即可。不做详细介绍,上本人设计的主界面图片供大家参考。
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;
}
现在要实现按某个按钮后能执行想要的操作。如读取图片、文件夹下图片、上一张、下一张、放大、缩小、适应窗体显示、中英文切换等,不做过多介绍,直接上代码。
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文件即可,上我设计的界面供大家参考。
此外要想实现鼠标控制图片缩放、移动等功能,则需要继承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文件如下图所示。
然后请用MVSC 2015(64-bit)下的Linguist程序进行翻译工作,如果原程序是全用的英文,则只需要打开中文ts文件,,注意如果你是别的编译器,就要选择相应编译器下的Linguist程序进行翻译工作。以下截图供大家参考。
翻译好后保存ts文件,关闭Linguist程序,然后点击工具->外部->Qt预言家->发布翻译(lrelease),此时项目文件夹下会产生两个qm文件。
最后还需要在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();
最后就是编译运行,测试应用程序相关功能,整个开发到此结束。贴上运行的结果图。
动图效果,鼠标移动图片,滚轮缩放图片、左键双击适应窗口显示。