MATLAB调用C/C++进行混合编程

时间:2022-09-22 09:16:32

1 引言


第一次接触Matlab和C/C++混合编程是在阅读BM3D代码时,那时候对.mexw32、.mexw64文件还不是太懂。后来了解到这是C/C++写的。目的有两个,其一是加快程序的运行,C/C++循环效率高、运行快,Matlab运行慢,擅长矩阵运算。混合编程,相当于取长补短。其二是防止算法的核心部分外泄,mex生成的源码不可以被用户看到,只能被调用。最近在做超分辨率项目时,发现MATLAB程序运行的太慢,每次都要25分钟左右,为了加速又重新看了一下混合编程的知识。主要参考了zouxy09(http://blog.csdn.net/zouxy09)和有来有去-CV(http://blog.csdn.net/shaoxiaohu1)两位大佬的博客,在此表示感谢。
本文的开发环境是VS2015 Professional+Matlab 2015b+opencv3.2.0,如果读者的开发环境不同的时候可能会有错误。

2 初出茅庐

我最初是按照zouxy09的博客进行混合编程,在这个过程中发现了很多的bug,最终顺利实现了两个代码。下面先讲解一个简单的代码。
我们需要先写一个C++代码(mexAdd.cpp),计算两个double型数字的和。
#include <iostream>
using namespace std;
double add(double x, double y)
{
return x + y;
}
为了能够在Matlab中调用我们需要做以下几个修改:

(1)、在C++文件开头处添加头文件:#include”mex.h”
(2)、添加接口函数mexFunction()

void mexFunction(int nlhs, mxArray *plhs[],int nrhs, const mxArray *prhs[])
{
}

其中,nlhs:(number of left hand size parameters)代表了函数返回的变量个数,例如上面c = add(a, b);就只有一个返回参数c,所以nlhs就是1。
plhs:(pointer of left hand size parameters)代表了函数返回的指针数组。换句话说,它是一个数组,每个元素是个指针,每个指针指向一个数据类型为mxArray的返回参数。例如上面c = add(a, b);就只有一个返回参数c,所以该数组只有一个指针,plhs[0]指向的结果会赋值给c。
nlrs:(number of right hand size parameters)代表了函数输入的变量个数,例如上面c = add(a, b),它给c++代码传入了两个参数a和b,所以nrhs为2。
prhs:(pointer of right hand size parameters)和plhs类似,因为右手面有两个自变量,即该数组有两个指针,prhs[0]指向了a,prhs[1]指向了b。要注意prhs是const的指针数组,即不能改变其指向内容。
改完之后的mexAdd.cpp如下:


#include "opencv2/opencv.hpp"
#include "mex.h"

double add(double x, double y)
{
return x + y;
}


void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
double *a;
double b, c;
plhs[0] = mxCreateDoubleMatrix(1, 1, mxREAL);
a = mxGetPr(plhs[0]);
b = *(mxGetPr(prhs[0]));
c = *(mxGetPr(prhs[1]));
*a = add(b, c);
}

mxCreateDoubleMatrix函数,其返回指向刚建立的mxArray的指针,然后令plhs[0]指向它。接着令指针a指向plhs [0]所指向的mxArray的第一个元素(使用mxGetPr函数,返回指向mxArray的首元素的指针)
修改后编译过程如下:
MATLAB调用C/C++进行混合编程
这个过程主要就是用来选择C++程序的编译器,我这里用的是VS2015。
MATLAB调用C/C++进行混合编程
紧接着就是编译目标文件。
MATLAB调用C/C++进行混合编程
这就是调用的过程,这里我第一次弄的时候一直出错,就是因为我搞混了几个概念。调用的这个函数名称是你的cpp文件的名字,而不是cpp文件里面的具体函数名。

3 牛刀小试

这一部分主要是复制了 zouxy09的博文(其实上一部分也有,尴尬),后面我会写一部分我在运行这部分代码时出错的地方以及改正的方法。

上面我们针对的是处理标量的情况,也就是数a,b或者c。这节我们让它处理二维数组,也就是图像。为了验证,我们很傻瓜地完成以下功能:

[grayImage] =RGB2Gray(‘imageFile.jpeg’);

也就是将一个图像文件名,传递给c++的代码,然后c++代码将这个图像读入,再转成灰度图,然后返回给Matlab。而c++代码里面的图像读入和灰度转换的操作通过调用OpenCV的库函数来实现。是不是很傻瓜呢?因为Matlab已经有实现同样功能的函数了。对,没错,就是多此一举。但我们只是为了说明二维数组的传递过程,没有什么用意。不过,如果要计算两个图像的光流的话,Matlab可能就真正需要OpenCV的帮助了。
另外,因为cpp文件要链接OpenCV的库,所以为了统一或者规范编译工程,我写了一个make.m文件,它的功能类似于Makefile,实际上就实现了mex编译这个工程时候的编译规则。具体可以看后面的代码,然后就知道在里面做了什么了。
首先是RGB2Gray.cpp代码:


// Interface: convert an image to gray and return to Matlab
// Author : zouxy
// Date : 2014-03-05
// HomePage : http://blog.csdn.net/zouxy09
// Email : zouxy09@qq.com

#include "opencv2/opencv.hpp"
#include "mex.h"

using namespace cv;

/*******************************************************
Usage: [imageMatrix] = RGB2Gray('imageFile.jpeg');
Input:
a image file
OutPut:
a matrix of image which can be read by Matlab

**********************************************************/



void exit_with_help()
{
mexPrintf(
"Usage: [imageMatrix] = DenseTrack('imageFile.jpg');\n"
);
}

static void fake_answer(mxArray *plhs[])
{
plhs[0] = mxCreateDoubleMatrix(0, 0, mxREAL);
}

void RGB2Gray(char *filename, mxArray *plhs[])
{
// read the image
Mat image = imread(filename);
if(image.empty()) {
mexPrintf("can't open input file %s\n", filename);
fake_answer(plhs);
return;
}

// convert it to gray format
Mat gray;
if (image.channels() == 3)
cvtColor(image, gray, CV_RGB2GRAY);
else
image.copyTo(gray);

// convert the result to Matlab-supported format for returning
int rows = gray.rows;
int cols = gray.cols;
plhs[0] = mxCreateDoubleMatrix(rows, cols, mxREAL);
double *imgMat;
imgMat = mxGetPr(plhs[0]);
for (int i = 0; i < rows; i++)
for (int j = 0; j < cols; j++)
*(imgMat + i + j * rows) = (double)gray.at<uchar>(i, j);

return;
}

void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
if(nrhs == 1)
{
char filename[256];
mxGetString(prhs[0], filename, mxGetN(prhs[0]) + 1);
if(filename == NULL)
{
mexPrintf("Error: filename is NULL\n");
exit_with_help();
return;
}

RGB2Gray(filename, plhs);
}
else
{
exit_with_help();
fake_answer(plhs);
return;
}
}
    和上面的相比,里面多了几个东西。第一个就是传入参数的测试,看看Matlab传入的参数是否存在错误,还包括了些异常处理。第二个就是帮助信息。第三个就是主要的实现函数了。只有OpenCV的读图像和灰度转换这里就不讲了,就是两个函数的调用。关键的地方还是如果把一个图像,也就是二维数组,传递给mexFunction的参数,让它返回给Matlab。实际上,我们只要清楚一点:

plhs[0] = mxCreateDoubleMatrix(2, 3,mxREAL);

这个函数建立的矩阵的指针plhs[0]是按照列的方式来存储的。假设imgMat是它的指针,那么*(imgMat+1)就是矩阵元素[1, 0],*(imgMat+2)就是矩阵元素[0, 1],*(imgMat+4)就是矩阵元素[0, 2]。上面的代码就是按照这个方式,将图像gray中像素值赋值给参数plhs[0]相应的位置(实际上也许可以直接内存拷贝,但因为里面是指针操作,涉及到局部变量gray的销毁问题,所以就简单的用上面的笨但稳当的方式来实现了)。

好了,下面是make.m文件。里面需要获取你的电脑的系统版本是32还是64位的,来选择编译选项。然后添加OpenCV的相关配置。如果您需要使用使用,请修改成您的OpenCV的相关目录。然后给出一个需要编译的文件的列表。最后分析这个列表,加上编译选项,用mex来编译列表里面的所有文件。

%// This make.m is for MATLAB
%// Function: compile c++ files which rely on OpenCV for Matlab using mex
%// Author : zouxy
%// Date : 2014-03-05
%// HomePage : http://blog.csdn.net/zouxy09
%// Email : zouxy09@qq.com

%% Please modify your path of OpenCV
%
% If your have any question, please contact Zou Xiaoyi

% Notice: first use "mex -setup" to choose your c/c++ compiler
clear all;

%-------------------------------------------------------------------
%% get the architecture of this computer
is_64bit = strcmp(computer,'MACI64') || strcmp(computer,'GLNXA64') || strcmp(computer,'PCWIN64');


%
-------------------------------------------------------------------
%% the configuration of compiler
%
You need to modify this configuration according to your own path of OpenCV
% Notice: if your system is 64bit, your OpenCV must be 64bit!
out_dir='./';
CPPFLAGS = ' -O -DNDEBUG -I.\ -ID:\OpenCV_64\include'; % your OpenCV "include" path
LDFLAGS = ' -LD:\OpenCV_64\lib'; % your OpenCV "lib" path
LIBS = ' -lopencv_core240 -lopencv_highgui240 -lopencv_video240 -lopencv_imgproc240';
if is_64bit
CPPFLAGS = [CPPFLAGS ' -largeArrayDims'];
end
%% add your files here!
compile_files = {
%
the list of your code files which need to be compiled
'RGB2Gray.cpp'
};


%-------------------------------------------------------------------
%% compiling...
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

fprintf('
Congratulations, compilation successful!!!\n');
  直接在Matlab中运行make.m。即可生成RGB2Gray.mexw64。然后在Matlab中运行:

>> img = RGB2Gray(‘d:\test.jpg’);

>> imshow(uint8(img));

即可显示转换结果。

4 遇到的问题及改正方法

在运行灰度图程序时,我的MATLAB一直在报lik2019错误,最终定位是这里出现了问题:
MATLAB调用C/C++进行混合编程
就是在配置库的时候出现了问题,原来是我把lib库的位置写错了,opencv3.0 的库的配置应该是这样的。
MATLAB调用C/C++进行混合编程
其中opencv3.0以上版本只有opencv_world320和opencv_world320d两个(320是我的opencv版本号,这个自己要改),还有lib路径应该是带VC**的路径(如VS2015 64位就是VC14),我当时用的cmake的那个路径,明显错误了。
我也是看了这个才懂的。MATLAB调用C/C++进行混合编程
最后运行文件,MATLAB调用C/C++进行混合编程
得到女神灰度图片MATLAB调用C/C++进行混合编程