天天看點

MATLAB調用C/C++進行混合程式設計

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[] = mxCreateDoubleMatrix(, , mxREAL);
    a = mxGetPr(plhs[]);
    b = *(mxGetPr(prhs[]));
    c = *(mxGetPr(prhs[]));
    *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  : [email protected]

#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[] = mxCreateDoubleMatrix(, , 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() == )
        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[] = mxCreateDoubleMatrix(rows, cols, mxREAL);
    double *imgMat;
    imgMat = mxGetPr(plhs[]);
    for (int i = ; i < rows; i++)
        for (int j = ; 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 == )
    {
        char filename[];
        mxGetString(prhs[], filename, mxGetN(prhs[]) + );
        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   : --
%// HomePage : http://blog.csdn.net/zouxy09
%// Email  : [email protected]

%% 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 bit, your OpenCV must be bit!
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++進行混合程式設計