本文所用的 Matlab 版本为 Matlab R2017b,OpenCV 版本为 opencv-3.4.3,C++ IDE 为 Visual Studio 2017,系统环境为 Windows 10_x64。
前言 秋招告一段落了,又要回到最初的起点,继续搞(qu)科(hua)研(shui)了,由于前人的代码主要是用 C++ 和 Matlab 混编实现的,而 Shaun 比较熟悉的是 C++ 和 OpenCV,而用 OpenCV 完全重写前人的代码工作量又太大了而且有些 API 不是很好互换,为了方便站在巨人的肩膀上继续前进,所以只能学习一下 OpenCV 和 Matlab 的混合编程了,这样在前人的基础上实现 Shaun 自己的想法相对来说更容易一些。
准备篇 由于目前主要用 C++ 实现的是一些小功能,也不需要调试,所以就直接使用 VSCode 进行编程了(或许以后还是会用 VS 进行一些简单的调试),而在没有配置相关环境的前提下,VSCode 无法实现自动补全,所以需要在 VSCode 中配置相应环境。具体添加方法如下:在 VSCode 中点击菜单栏 “查看 ” ==》“命令面板... ” ==》选择 “C/Cpp: Edit Configurations... ”==》在出现的 c_cpp_properties.json
文件中 "includePath"
对应的值中添加 OpenCV 的 include 目录和 Matlab 的 include 目录,添加之后的 "includePath"
如下:
1 2 3 4 5 6 7 { "includePath" : [ "${workspaceFolder}/**" , "D:/ProgramFiles/OpenCV/3.4.3/build/include/**" , "C:/Program Files/MATLAB/R2017b/extern/include/**" ] }
如此就能在 VSCode 中写 OpenCV 和 Matlab 相关函数时实现自动补全了。
Matlab 和 C++ 混编篇 由于 Shaun 使用的是 OpenCV 的 C++ 接口,所以需要先知道 Matlab 和 C++ 混合编程如何进行。以实现两个数的加法为例,首先创建一个 mexAdd.cpp 文件,其中 C++ 代码具体如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <mex.h> #include <iostream> void checkInputs (int nrhs, const mxArray *prhs[]) { if (nrhs != 2 ) { mexErrMsgTxt ("Incorrect number of inputs. Function expects 2 inputs." ); } if (!mxIsDouble (prhs[0 ])) { mexErrMsgTxt ("Input number must be double." ); } } double add (double x, double y) { return x + y; } void mexFunction (int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { checkInputs (nrhs, prhs); double *a = nullptr ; double b = 0.0 , c = 0.0 ; plhs[0 ] = mxCreateDoubleMatrix (1 , 1 , mxREAL); a = mxGetPr (plhs[0 ]); b = *(mxGetPr (prhs[0 ])); c = *(mxGetPr (prhs[1 ])); *a = add (b, c); }
若要使用 Matlab 混合编译 C++,必须要添加头文件 mex.h
,使用 void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
函数接收输入输出参数,如此编译完成之后,就和使用普通的 Matlab 函数一样了。具体编译调用方法如下,新建 addTest.m 文件,其中 Matlab 代码如下:
1 2 3 4 5 6 7 8 9 clc, clear, close all; current_folder = pwd; addpath(genpath(current_folder)); mex mexAdd.cpp; a = 3.1 ; b = 2.6 ; c = mexAdd(a, b);
其中 mex mexAdd.cpp
可以直接在 Matlab 命令行窗口下预先执行编译动作,编译成功后会输出一个 mexAdd.mexw64 文件,若是 32 位系统则后缀为 mexw32,之后直接执行 ans = mexAdd(3.1, 2.6);
即可在 Matlab 中调用该函数。在 Matlab 首次执行 mex 命令时,Matlab 会自动选择 VS 编译器作为默认 C++ 编译器,也可以执行 mex -setup
初始化或更换默认编译器。
BTW: 最好在安装 Matlab 之前安装 Visual Studio,否则使用 mex
编译时,可能会出现找不到编译器的情况。
Matlab 和 OpenCV 混编篇 Matlab 和 OpenCV 混编大体上和 C++ 混编差不多,最大的区别在于如何利用 OpenCV 的 cv::Mat 对象和相关的库函数,Matlab 良心的提供了 OpenCV 接口以实现 mexArray 和 cv::Mat 格式之间的互相转化,使用这些接口需要包含头文件 opencvmex.hpp
。如果不使用 Matlab 提供的这些接口而是自己写转换过程的话有点麻烦,因为 Matlab 的数据是以列优先方式存储的,而 OpenCV 的数据是以行优先方式存储的。至于如何进行混编,主要有以下三种方式:
第一种是自己写 make.m 文件,相当于 gcc 编译时需要的 Makefile 文件,需要手动拼接各种编译命令和添加相应的附加依赖库; 第二种是通过 Matlab 官方提供的 Computer Vision System Toolbox OpenCV Interface 功能,Matlab 没有默认安装该功能,这个功能需要另外安装,具体安装方法为:在 Matlab 命令行窗口输入 visionSupportPackages
,即可弹出“附加功能资源管理器 ”窗口选择对应附加功能安装即可,安装完之后可通过 mexOpenCV
命令对包含 OpenCV 库函数的 .cpp 文件进行编译,查看 mexOpenCV.m 的源码可知,mexOpenCV
其实是对 mex
命令进行了封装,其调用的 OpenCV 库也是其工具箱自带的 OpenCV,而且有些库还没有包含,有一定的局限性,不过该附加功能自带了些示例程序,可以参考学习一下; 第三种是使用第三方的 mexopencv ,不过需要安装与该工具对应的 OpenCV 版本,并需要进行一定的配置工作,略显麻烦。 Shaun 这里直接使用的是第一种方式,自己写 make.m 文件,比较灵活,想怎么配置就怎么配置。下面具体以 RGB 转 GRAY 为例,首先新建 mexRGB2GRAY.cpp,其中 C++ 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 #include <opencvmex.hpp> #define _DO_NOT_EXPORT #if defined(_DO_NOT_EXPORT) #define DllExport #else #define DllExport __declspec(dllexport) #endif void checkInputs (int nrhs, const mxArray *prhs[]) { if (nrhs != 1 ) { mexErrMsgTxt ("Incorrect number of inputs. Function expects 1 inputs." ); } if (mxGetNumberOfDimensions (prhs[0 ]) != 3 ) { mexErrMsgTxt ("Incorrect number of dimensions. First input must be a RGB image." ); } if (!mxIsUint8 (prhs[0 ])) { mexErrMsgTxt ("Template and image must be UINT8." ); } } void exit_with_help () { mexPrintf ("Uasge: [image_matrix] = mexRGB2GRAY('image_file.jpg');\n" ); } static void fakeAnswer (mxArray *plhs[]) { plhs[0 ] = mxCreateNumericMatrix (0 , 0 , mxDOUBLE_CLASS, mxREAL); } cv::Mat RGB2GRAY (const mxArray *prhs[]) { cv::Ptr<cv::Mat> img_cv = ocvMxArrayToMat_uint8 (prhs[0 ], true ); if (img_cv.empty ()) { return cv::Mat_<double >(0 , 0 ); } cv::Mat gray ((*img_cv).size(), CV_8UC1) ; if ((*img_cv).channels () == 3 ) { cv::cvtColor (*img_cv, gray, CV_RGB2GRAY); } else if ((*img_cv).channels () == 4 ) { cv::cvtColor (*img_cv, gray, CV_RGBA2GRAY); } else { (*img_cv).copyTo (gray); } return gray; } void mexFunction (int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { checkInputs (nrhs, prhs); if (nrhs == 1 ) { cv::Mat gray = RGB2GRAY (prhs); plhs[0 ] = ocvMxArrayFromMat_uint8 (gray); } else { exit_with_help (); fakeAnswer (plhs); return ; } }
然后新建 makefile.m 文件,自己配置相关编译环境, Shaun 这里具体如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 function makefile () is_64bit = strcmp(computer, 'MACI64' ) || strcmp(computer, 'GLNXA64' ) || strcmp(computer, 'PCWIN64' ); out_dir = '.' ; CPPFLAGS = ' -O -DNDEBUG -I./ -ID:/ProgramFiles/OpenCV/3.4.3/build/include' ; LDFLAGS = ' -LD:/ProgramFiles/OpenCV/3.4.3/build/x64/vc15/lib -LC:/PROGRA~1/MATLAB/R2017b/extern/lib/win64/microsoft' ; LIBS = ' -lopencv_world343 -lmwocvmex' ; if is_64bit CPPFLAGS = [CPPFLAGS ' -largeArrayDims' ]; end compile_files = { 'mexRGB2GRAY.cpp' 'mexAdd.cpp' }; for k = 1 : length (compile_files) str = compile_files{k}; fprintf('compilation of: %s\n' , str); str = [str ' -outdir ' out_dir CPPFLAGS LDFLAGS LIBS]; args = regexp(str, '\s+' , 'split' ); mex(args{:}); end end
其中 Matlab 配置路径中的 PROGRA~1
是指 Windows 下的 C 盘中的 Program Files 文件夹,为了使用 Matlab 提供的转换接口,libmwocvmex.lib
是必须要添加的一个库。最后具体使用示例 Matlab 代码如下:
1 2 3 4 5 6 7 8 9 10 11 clc, clear, close all; current_folder = pwd; addpath(genpath(current_folder)); makefile(); image = imread('lena.jpg' ); I = mexRGB2GRAY(image); figure , imshow(I);
也可以将 makefile();
函数预先执行。※注: 这里如果出现编译报错 “缺少依赖共享库 ” 的情况可能还需要把 OpenCV 的 bin 目录加到系统环境变量 Path 中,Shaun 这里是路径 D:\ProgramFiles\OpenCV\3.4.3\build\x64\vc15\bin
,然后重启 Matlab 。
调试篇 若要对写的 mexAdd.cpp 文件进行调试,则需要
先使用 mex -g mexAdd.cpp
编译该文件,由于添加了 -g 参数,此时除了会生成 mexAdd.mexw64 文件之外,还会生成一个 mexAdd.mexw64.pdb 文件,该文件即包含调试信息;
然后在相应 .m 文件中调用 mexAdd 函数的位置设置断点,运行该相关文件,matlab 程序会在调用 mexAdd 函数之前停下;
此时使用 VS2017 打开 mexAdd.cpp 文件,在需要调试的地方设置好断点,选中菜单栏中的“调试 ” ==》“附加到进程 ”,或者直接点击菜单栏上的绿色三角 附加... ,选中 MATLAB.exe ,点击附加,即可看到 VS2017 调试程序已启动;
最后回到 matlab 中继续运行相关文件,即可看到程序跳转到 VS2017,并执行到设置断点的地方,此时即可在 VS2017 中按调试 C++ 程序一样对其进行调试。
完成调试并执行完 mexAdd.cpp 之后,程序将回到 matlab 界面继续执行,直到整个 matlab 程序执行完成。
后记 这次主要是记录 Matlab 如何调用 C++ 编写的函数,其实还可以用 C++ 调用 Matlab 编写的函数,不过那是另一种混编方式了,以后有机会碰到的话再继续记录吧。
参考资料 [1] Matlab与C++混合编程(依赖OpenCV)
[2] 更改默认编译器
[3] OpenCV Interface Support
[4] Matlab OpenCV混合编程
[5] vc与matlab连接的实用函数简介