目录
- 本文实现的效果
- 软件版本
- 编译
- zlib
- libpng
- MathGL
- 说明
- 例1:心形
- 例2:直方图
- 参考资料
本文实现的效果
- 运用CMake、VS2019编译zlib、libpng、MathGL,得到静态链接库(debug & release,X64)
- 例1:基于Visual Studio 2019 IDE,运用MathGL和Qt绘制心形函数。
3. 例2:基于Visual Studio 2019 IDE,由openCV读入图像,得到直方图数据,并由MathGL画出直方图,在Qt中显示。
软件版本
- CMake-3.18.0-rc3
- zlib-1.2.11,源代码
- libpng-1.6.37,源代码
- MathGL-2.4.4,源代码
- openCV-4.3.0
- Qt-5.14.2
- Visual Studio 2019-16.4.3,MSVC-19.24.28315.0
- Qt Visual Studio Tools-2.5.2.1
编译
说明:MathGL依赖zlib和libpng,libpng依赖zlib。因此,按顺序编译zlib、libpng和MathGL。
zlib
-
打开CMake-gui
-
指定源码目录(1)和编译输出目录(2).
-
单击
Configure
进行配置(3)。
(1) 选择合适的Visual Studio版本(4)
(2) 选择计算机类型(5),本文均以x64平台为例。
(3) 结束配置Finish
(6),会提示“Configuring done”,结果如下图。
-
单击
Generate
生成Visual Studio的*.sln项目文件。会提示“Generating done”。 -
在(2)所示的输出目录中找到zlib.sln,双击在Visual Studio 2019中打开。
-
先生成debug版的静态链接库。
(1)确认解决方案为debug、X64平台。
(2) 在zlibstatic项目上右击–>生成
(3) Visual Studio 2019的输出窗口会提示生成成功,并给出静态链接库的位置。
(4) 这样,zlib的debug、X64版的静态链接库已经编译成功。为了方便使用,可以将所有的静态链接库和头文件拷贝至统一位置。本文采用的zlib的1.2.11版的源文件中没有include文件夹,头文件直接在根
(5) 需要注意的是,cmake在configure时会把头文件zlib.h的名字改为zlib.h.include。编译成功后,需要将它改回来。 -
再生成release版的静态链接库,只需将编译平台配置为Release、X64,其它步骤相同。
-
CMake的File菜单下有
Delete Cache
选项,可以清除缓存。清楚缓存后,单击Configure
就会弹出选择编译器版本的对话框。
libpng
-
打开CMake-gui,选择源码目录和输出目录,单击
Configure
进行配置。 -
报错,大意为找不到zlib的链接库。单击
OK
接受这个错误。
3. 确保勾选Advanced
,手动指定ZLIB_INCLUDE_DIR目录和ZLIB_LIBRARY_DEBUG、ZLIB_LIBRARY_RELEASE静态链接库。再Configure
,这样就OK了。
4. 单击Generate
,生成Visual Studio 2019的*.sln项目。单击Open Project
即可打开。
5. 在Visual Studio 2019中分别以Debug和Release方式编译png_static项目。
6. 可以发现,zlib和libpng的debug静态链接库都要比release版的更大。
MathGL
现在可以编译MathGL,需要依赖zlib、libpng、Qt的头文件及静态链接库。
-
打开CMake-gui,选择源码目录和输出目录,单击
Configure
进行配置。 -
报错,无非就是找不到zlib、libpng的头文件和静态链接库,我们手动指定就好。
修改后(在我的计算机中,第一次configure只能找到ZLIB_xxx_xxx的项目,再configure才能找到PNG_xxx_xxx的项目)
-
搜索qt,勾选enable-qt-5的选项,
Configure
-
继续报错,这次是找不到qt的目录。
(1) 手动指定目录,具体路径见图,修改后:
(2) 需要注意Qt匹配的编译器,这里选择的是msvc2017_64文件夹下的。
(3) 这里有个小坑(虽然这个坑还没有坑我):在CMake中指定的是msvc2017_64文件夹中的路径,意味着这些lib是匹配msvc2017的;而在接下来的编译中,采用的是visual studio 2019,即msvc2019。还要说明另一件事,我给visual studio 2019安装了2017版的工具集,这可能是一种解决方案?或者根本就直接用Visual Studio 2017,别用太新的Visual Studio。 -
Generate
生成*.sln项目文件。忽略这两个不太严重的警告。
-
打开MathGL2.sln项目文件。编译mgl-static和mgl-qt5-static项目,分别以Debug和Release的形式。
(1) MathGL的Debug版和Release版的静态链接库的名字是一致的,我们可以给Debug版的静态链接库的名字加个’d’,便于区分。在
属性-->常规-->目标文件名
处可以修改。(2) 生成mgl-static时,我这里出现了一个error
C:\open\lpng-1.6.37\png.h(330,13): fatal error C1083: 无法打开包括文件: “pnglibconf.h”: No such file or directory
找到
C:\open\lpng-1.6.37\png.h(330,13)
,贴心地提示了一句,我们按它说的去做即可。#ifndef PNGLCONF_H /* If pnglibconf.h is missing, you can* copy scripts/pnglibconf.h.prebuilt to pnglibconf.h*/ # include "pnglibconf.h" #endif
(3) MathGL生成的静态链接库的路径比较奇怪,可以Visual Studio 2019输出窗口这一行的提示去找。
-
编译mgl和mgl-qt5项目可以得到动态链接库。但会出问题,暂时没有深究。
-
在CMake的输出目录下的
include/mgl2
下可以看到三个*.h头文件,将其复制到源文件的include/mgl2
目录下。
说明
至此,zlib、libpng和MathGL已经编译完毕,做几点说明。
- 这是我最顺利的一次编译~
- 以前为了编译这三个静态链接库,查遍网络,有很多提示是指定预编译头的,但是这里统统没有用上~
- 在编译MathGL时,遇到的最奇怪的问题时,Visual Studio在生成时提示需要GNU compiler,稀里糊涂就消失了~
例1:心形
目标:MathGL+Qt绘制心形。
-
在Visual Studio 2019中创建Qt项目。
(1) 在Visual Studio 2019中添加Qt插件,配置Qt开发环境(可百度)。
(2) 选择项目类型:Qt Widgets Application
(3) 指定项目名称、项目位置。
(4) 配置Qt,需要选择X64平台的(因为前面编译的静态链接库是X64平台的)
(5) 文件名可以自行修改。
-
在Visual Studio 2019中配置包含目录、库目录、附加依赖项。需要分别配置Debug和Release。Debug只能使用Debug的静态链接库,Release只能使用Release的静态链接库。
(1) 包含目录(Debug)
在解决方案管理器中的项目名称
Heart
上右击,选择属性
。按下图操作。(2) 库目录(Debug)
(3) 附加依赖项(Debug)
(4) Release与Debug的内容基本一致,需要在库目录中给定Release的zlib、libpng、MathGL静态链接库的路径。
(5) 配置过程还是比较复杂的,可以将Visual Studio 2019的配置保存到属性表,以便今后使用,具体方法请百度。
-
代码
简便起见,我们直接写在
main.cpp
中。#include "MainWindow.h" #include <QtWidgets/QApplication> #include <QtWidgets/QMainWindow> #include <mgl2/mgl.h> #include <mgl2/qmathgl.h>int heart(mglGraph * gr) {gr->Title("Heart");gr->SetOrigin(0,0); // 原点gr->SetRanges(-3.5, 2, -3, 3); // 坐标轴范围gr->Axis(); // 开启坐标轴gr->Grid(); // 开启坐标网格gr->FPlot("2*cos(6.28*t)-cos(6.28*2*t)", "2*sin(6.28*t)-sin(6.28*2*t)", "0", "r2"); // 绘图,函数FPlot绘制参数t在0到1时的参数方程。return 0; }int main(int argc, char *argv[]) {QApplication a(argc, argv);QMainWindow* win = new QMainWindow();win->resize(800, 600);win->setWindowTitle("MathGL-Heart");QMathGL* qmgl = new QMathGL(win);qmgl->setDraw(heart);qmgl->update();win->show();return a.exec(); }
-
以Debug或Release方式运行,会出现很多问题,我们逐一解决。
(1) 预处理命令问题,表现为:
LNK2038 检测到“_CRT_STDIO_ISO_WIDE_SPECIFIERS”的不匹配项: 值“1”不匹配值“0”(MainWindow.obj 中) Heart G:\008_Practice\005_PracticeCPP\20200713_MathGL-Qt-openCV\Heart\Heart\mgl-qt5-static.lib(mocs_compilation.obj)
解决:进入项目的
属性
页面,修改预处理命令(Debug和Release分别修改),如下图。(2) Qt链接库问题,表现为:
LNK2019 无法解析的外部符号 "__declspec(dllimport) public: __cdecl QPrinter::QPrinter(enum QPrinter::PrinterMode)" (__imp_??0QPrinter@@QEAA@W4PrinterMode@0@@Z),该符号在函数 "public: void __cdecl QMathGL::print(void)" (?print@QMathGL@@QEAAXXZ) 中被引用 Heart G:\008_Practice\005_PracticeCPP\20200713_MathGL-Qt-openCV\Heart\Heart\mgl-qt5-static.lib(qt.obj) LNK2001 无法解析的外部符号 "public: virtual void __cdecl QPrinter::setPageSizeMM(class QSizeF const &)" (?setPageSizeMM@QPrinter@@UEAAXAEBVQSizeF@@@Z) Heart G:\008_Practice\005_PracticeCPP\20200713_MathGL-Qt-openCV\Heart\Heart\mgl-qt5-static.lib(qt.obj)
解决:(Debug和Release分别修改)
(aa)
属性
页面–>包含目录
添加:C:\Qt\Qt5.14.2\5.14.2\msvc2017_64\include\QtPrintSupport
(bb)
属性
页面–>库目录
添加:C:\Qt\Qt5.14.2\5.14.2\msvc2017_64\lib
(cc)
属性
页面–>附加依赖项
添加:Qt5PrintSupportd.lib
(3) 到这一步,Release已经可以了,但Debug还有一些问题。见下一点。
-
在以Debug方式运行时,遇到了玄学问题,如实记录。
(1) 描述:Visual Studio 2019 提示有两个无法解析的符号
1>main.obj : error LNK2019: 无法解析的外部符号 "__declspec(dllimport) public: void __cdecl mglGraph::Title(char const *,char const *,double)" (__imp_?Title@mglGraph@@QEAAXPEBD0N@Z),该符号在函数 "int __cdecl heart(class mglGraph *)" (?heart@@YAHPEAVmglGraph@@@Z) 中被引用 1>main.obj : error LNK2019: 无法解析的外部符号 "__declspec(dllimport) public: void __cdecl QMathGL::setDraw(int (__cdecl*)(class mglGraph *))" (__imp_?setDraw@QMathGL@@QEAAXP6AHPEAVmglGraph@@@Z@Z),该符号在函数 main 中被引用 1>G:\008_Practice\005_PracticeCPP\20200713_MathGL-Qt-openCV\Heart\x64\Debug\Heart.exe : fatal error LNK1120: 2 个无法解析的外部命令
(2) 分析:
这两个符号分别是
int heart(mglGraph * gr)
函数中用到的gr->Title("Heart");
以及int main(int argc, char *argv[])
中用到的qmgl->setDraw(heart);
。按道理来说,这两个函数Title
和setDraw
应该是在静态链接库mgl-static.lib
和mgl-qt5-static.lib
中定义的。可以很容易找到
Title
的头文件mgl.h
,该头文件中同时定义了SetOrigin
、SetRanges
、Axis
、Grid
、FPlot
等函数,这些函数都在int heart(mglGraph * gr)
中调用过,都没有问题。可以很容易找到
setDraw
的头文件qmathgl.h
,该头文件中同时定义了update
、show
等函数,这些函数都在int main(int argc, char *argv[])
中调用过,都没有问题。(3) 稀里糊涂地解决:
找到
Title
的函数定义,在mgl.h
中,显然我们现在用的是第一种重载类型。/// Add title for current subplot/inplot /** Style '#' draw box around the title. */ inline void Title(const char *title,const char *stl="",double size=-2) {mgl_title(gr,title,stl,size); } /// Add title for current subplot/inplot /** Style '#' draw box around the title. */ inline void Title(const wchar_t *title,const char *stl="",double size=-2){mgl_titlew(gr,title,stl,size); }
其中,
mgl_title
定义在canvas_cf.h
中的。我们这样修改程序:// 在main.cpp中再包含头文件 #include <mgl2/canvas_cf.h>// 在int heart(mglGraph * gr)中 // 将gr->Title("Heart");替换为 mgl_title((HMGL)gr, "Heart", "", -2);
找到
setDraw
的头文件qmathgl.h
,定义为:inline void setDraw(int (*func)(mglGraph *gr)) {setDraw(func?mgl_draw_graph:0,(void*)func); }
其中,函数中调用的
setDraw
仍然定义再qmathgl.h
中,mgl_draw_graph
声明在wnd.h
中。我们这样修改程序:// 在main.cpp中再包含头文件 #include <mgl2/wnd.h>// 在int main(int argc, char *argv[])中 // 将qmgl->setDraw(heart);替换为 qmgl->setDraw(mgl_draw_graph, (void *)heart);
(4) 此时此刻,Debug调试,问题解决。而且,将刚刚
include
的两个头文件注释掉,并恢复Title
和setDraw
为原来的调用方式,仍然没有问题。就很奇怪( ╯□╰ )(5) 正确地解决:
添加预处理命令:
MGL_STATIC_DEFINE
,这个命令说明我们给的lib是静态链接库。 -
我们可以很OK地画出一个心形啦!好丑,但就是这个意思 φ(゜▽゜)?*
-
配置总结
(1) 包含目录:
【MathGL】C:\open\mathgl-2.4.4\include 【Qt】C:\Qt\Qt5.14.2\5.14.2\msvc2017_64\include\QtPrintSupport
(2) 库目录【Debug】(我把所有自己编译的*.lib都放到一个文件夹了):
C:\open\lib_Debug_X64 C:\Qt\Qt5.14.2\5.14.2\msvc2017_64\lib
(3) 库目录【Release】(我把所有自己编译的*.lib都放到一个文件夹了):
C:\open\lib_Release_X64 C:\Qt\Qt5.14.2\5.14.2\msvc2017_64\lib
(4) 预处理器定义
_CRT_STDIO_ISO_WIDE_SPECIFIERS MGL_STATIC_DEFINE
(5) 附加依赖项【Debug】
mgl-qt5-static-d.lib mgl-static-d.lib Qt5PrintSupportd.lib libpng16_staticd.lib zlib-staticd.lib
(6) 附加依赖项【Release】
zlibstatic.lib libpng16_static.lib mgl-static.lib mgl-qt5-static.lib Qt5PrintSupport.lib
例2:直方图
-
在Visual Studio 2019中创建Qt项目,并配置。
(1) MathGL、Qt、zlib、libpng的配置与例1相同。
(2) 还需要对openCV进行配置(详见百度)。主要包括指定openCV的include目录、lib目录、附加依赖项,并将openCV的*.dll
文件拷贝至正确位置。 -
在Qt Designer中进行可视化窗口设计:1个Widget区域,2个Push Button。
采用uic命令将*.ui
文件编译为*.h
文件,并导入至Visual Studio 2019项目。 -
代码
// main.cpp#include "MainWindow.h" #include <QtWidgets/QApplication> #include <QtWidgets/QMainWindow>int main(int argc, char* argv[]) {QApplication a(argc, argv);MainWindow w;w.setWindowTitle("openCV-MathGL-Qt");w.show();return a.exec(); }
// MainWindow.h#pragma once#include <QtWidgets/QMainWindow> #include "ui_MainWindow.h" #include <mgl2/mgl.h> #include <mgl2/qmathgl.h>class MainWindow : public QMainWindow {Q_OBJECTpublic:MainWindow(QWidget *parent = Q_NULLPTR);~MainWindow();Ui::MainWindowClass ui;private:QString imagename;QMathGL* QMGL; // 定义一个QMathGL对象private slots: //槽函数void on_pushButton_open_clicked();void on_pushButton_hist_clicked();public: // 静态变量、函数static double* d_hist;static int drawHist(mglGraph* gr); };
// MainWindow.cpp#include "MainWindow.h" #include <QDir> #include <QtWidgets/QApplication> #include <QtWidgets/QScrollArea> #include <QFileDialog> #include "ui_MainWindow.h" #include <opencv2/opencv.hpp> #include <string> #include <mgl2/qmathgl.h> #include <mgl2/data.h>double* MainWindow::d_hist = NULL;// 构造函数 MainWindow::MainWindow(QWidget *parent): QMainWindow(parent) {ui.setupUi(this);QMGL = new QMathGL(ui.widget_main); }// 析构函数 MainWindow::~MainWindow() {delete this->QMGL; }// static 绘图函数 int MainWindow::drawHist(mglGraph* gr) {mglData y{ 256, d_hist }; // MathGL 专有的数据类型y.Norm((mreal)0,(mreal)1); // 归一化gr->SetRanges(0, 255, 0, 1); // 坐标范围gr->Axis(); // 坐标轴mglDataA* yA;yA = &y;gr->Bars(*yA);return 0; }// 选择文件 void MainWindow::on_pushButton_open_clicked() {// 选择单个文件QString curPath = QDir::currentPath();QString dlgTitle = "选择一张图片:";QString filter = "Image(*.jpg)";QString aFileName = QFileDialog::getOpenFileName((QWidget*)this, dlgTitle, curPath, filter);if (!aFileName.isEmpty()){this->imagename = aFileName;this->ui.lineEdit_image->setText(imagename);} }// 直方图 void MainWindow::on_pushButton_hist_clicked() {if (this->imagename.isEmpty()){return;}cv::Mat image = cv::imread((this->imagename).toStdString());int channels = 1;int histsize = 256;float range[] = { 0,255 };const float* histRanges = { range };cv::Mat mHist;cv::calcHist(&image, 1, &channels, cv::Mat(), mHist, 1, &histsize, &histRanges, true, false);d_hist = new double[256];for (int i = 0; i < 256; ++i){d_hist[i] = mHist.at<float>(i);}// 绘图this->QMGL->setDraw(drawHist);this->QMGL->adjust();this->QMGL->setZoom(true);this->QMGL->setRotate(true);this->QMGL->update(); }
-
结果
-
验证
以Lena图为例,本程序绘制的直方图与ImageJ相同。以下为ImageJ的结果。
参考资料
- https://blog.csdn.net/vaincury/article/details/107248421
- https://blog.csdn.net/vaincury/article/details/105438971
- https://www.bilibili.com/video/BV11C4y1s7Jp?from=search&seid=7524128681462799090
- https://www.bilibili.com/video/BV1yQ4y1K79n?from=search&seid=7524128681462799090
- https://www.bilibili.com/video/BV1q4411r7tM?from=search&seid=7524128681462799090