天天看點

[轉] Matlab與C++混合程式設計(依賴OpenCV)

  之前在運作别人論文的代碼的時候,經常有遇到Matlab與C++混合程式設計的影子。實際上就是通過Matlab的Mex工具将C++的代碼編譯成 Matlab支援調用的可執行檔案和函數接口。這樣一方面可以在Matlab中利用已經編寫好的函數,盡管這個函數是用C++編寫的。實作了交流無國界, 沒有江山一統的誰,隻有四海之内皆兄弟的豪氣。另一方面,取C++所長補己之短。Matlab擅長矩陣運算,但對循環操作的效率不及C++來得高效,例如 Hilbert矩陣的建立。是以對于具有大循環的運算,可以借C++之力來完成。

      看到它的魅力,之前也一直想學下,可惜機緣不對。但在昨天緣分就到了。我需要用到一個論文給出來的代碼,但是它的代碼是C++的,而且還依賴了 OpenCV的庫,基于Linux平台。這與實驗室給我定出來的平台有很大的不同,我們是得統一基于Windows + Matlab來實作的,這樣組内各個同學的工作才好統一。是以沒辦法了,就得把這個原作者的代碼編譯成Matlab支援的可執行檔案。

一、初級

      在使用MATLAB編譯C/C++代碼時,我們需要修改C/C++代碼,在裡面添加Matlab能支援的函數接口。這樣Matlab才能調用它。然後再通過Matlab的Mex工具來編譯它。下面就具體的舉例子說明這兩個步驟。

      假設我們有一個很簡單的C++代碼,實作的就是兩個double型數的加法:

mexAdd.cpp

#include <iostream>  

using namespace std;  

double add(double x, double y)  

{  

    return x + y;  

}  

1、修改代碼檔案

1)添加頭檔案mex.h

      在我們的c++檔案開頭處添加頭檔案:

#include"mex.h"

2)添加接口函數mexFunction()

      mexFunction的定義為:

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

{

}

       首先,這個函數是沒有傳回值的。它不是通過傳回值把c++代碼的計算結果傳回Matlab的,而是通過對參數plhs的指派。例如我們在Matlab中,調用這個add函數一般是這樣:

        >> a = 0.5; b = 0.8;

        >> c = add(a, b);

        那mexFunction怎麼将輸入參數a和b傳入給c++的add函數,然後就怎麼把計算結果傳回給c呢?這些粗重活全部通過mexFunction的四個參數來實作:

         nlhs: 感覺是number of left hand size parameters,也就是Matlab調用語句左邊的變量個數,實際上就是需要傳回給Matlab的傳回值變量有多少個。例如上面c = add(a, b);就隻有一個傳回參數c,是以nlhs就是1;

         plhs: 感覺是pointer of left hand size parameters,也就是函數傳回參數的指針。但它是一個指針數組。換句話說,它是一個數組,每個元素是個指針,每個指針指向一個資料類型為 mxArray的傳回參數。例如上面c = add(a, b);就隻有一個傳回參數c,是以該數組隻有一個指針,plhs[0]指向的結果會指派給c。

         nrhs: 這個是number of right hand size parameters,也就是Matlab調用語句右邊的變量個數。例如上面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的指針數組,即不能改變其指向内容。

       因為Matlab最基本的單元為array,無論是什麼類型也好,如有doublearray、 cell array、struct array……是以a,b,c都是array,b = 1.1便是一個1x1的double array。而在C語言中,Matlab的array使用mxArray類型來表示。是以就不難明白為什麼plhs和prhs都是指向mxArray類型 的指針數組(參考資料[1])。

       那mexFunction函數的函數體要怎麼寫呢?怎麼樣通過這個接口函數将Matlab的參數和c++代碼中的相對應的參數聯系起來呢?我們先把這個代碼全部展現出來。

       最後的mexAdd.cpp是這樣:

#include "opencv2/opencv.hpp"  

#include "mex.h"  

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);  

      mexFunction的内容是什麼意思呢?我們知道,如果在Matlab中這樣調用函數時:

      >> output = add(0.5, 0.8);

      在未涉及具體的計算時,output的值是未知的,是未指派的。是以在具體的程式中,我們建立一個1x1的實double矩陣(使用 mxCreateDoubleMatrix函數,其傳回指向剛建立的mxArray的指針),然後令plhs[0]指向它。接着令指針a指向plhs [0]所指向的mxArray的第一個元素(使用mxGetPr函數,傳回指向mxArray的首元素的指針)。同樣地,我們把prhs[0]和prhs [1]所指向的元素(即0.5和0.8)取出來賦給b和c。于是我們可以把b和c作自變量傳給函數add,得出給果賦給指針a所指向的mxArray中的 元素。因為a是指向plhs[0]所指向的mxArray的元素,是以最後作輸出時,plhs[0]所指向的mxArray指派給output,則 output便是已計算好的結果了。

       實際上mexFunction是沒有這麼簡單的,我們要對使用者的輸入自變量的個數和類型進行測試,以確定輸入正确。如在add函數的例子中,使用者輸入char array便是一種錯誤了。

       從上面的講述中我們總結出,MEX檔案實作了一種接口,把C語言中的計算結果适當地傳回給Matlab罷了。當我們已經有用C編寫的大型程式時,大可不 必在 Matlab裡重寫,隻寫個接口,做成MEX檔案就成了。另外,在Matlab程式中的部分計算瓶頸(如循環),可通過MEX檔案用C語言實作,以提高計 算速度(參考資料[1])。

2、編譯修改後的c++檔案

       檔案修改完後,我們需要将他編譯,生成Matlab支援的可執行檔案。這裡需要的是Matlab自帶的Mex工具。但在編譯器,我們需要配置下這個工具,告訴它你要采用什麼編譯器來編譯我們的c/c++代碼。在Matlab中運作:

       >> mex -setup

       就會出現叫你選擇一個預設的編譯器。例如我這裡是叫選擇Matlab自帶的Lcc或者我自己在電腦上安裝的Microsoft Visual C++ 2010。一般都是選擇後者。配置這個就可以編譯了。編譯也有以下幾種情況:

>> mex XXX.cpp

>> mex X1.cpp X2.cpp X3.cpp %多個cpp檔案,且有依賴。生成的庫名字叫X1

>> mex -O X1.cpp  %大寫O選項,優化編譯

>> mex -largeArrayDims X1.cpp %對64位系統,通過這個選項來指定使用處理大容量數組的API。因為Matlab與C++之間的接口是以32位系統作為标準的,這就導緻了人們在處理大 容量資料時沒辦法利用C和C++語言的速度優勢。但對64位系統來說,系統資源一般都比32位系統要充足,是以指定該接口,讓它對大容量資料處理更遊刃有 餘。

       還有一些編譯選項,和gcc一樣。例如-I指定額外需要include的目錄,-L指定額外需要連接配接的庫的目錄,-l指定額外需要連結的庫等。

       對于我們的程式就簡單了。在MATLAB指令視窗輸入以下指令:mexmexAdd.cpp,即可編譯成功。編譯成功後,在同檔案夾下會出現一個同名 的,但字尾是mexw32(32位的系統)或者mexw64(64位的系統)的檔案,例如mexAdd.mexw32。然後在Matlab中就可以直接調 用它來運算了:

       >> ans = mexAdd(0.5, 0.8);

二、進階

       上面我們針對的是處理标量的情況,也就是數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]  

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;  

    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);  

        exit_with_help();  

       和上面的相比,裡面多了幾個東西。第一個就是傳入參數的測試,看看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  : [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 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{:});  

fprintf('Congratulations, compilation successful!!!\n');  

三、使用方法和結果

1、編譯

      直接在Matlab中運作make.m。即可生成RGB2Gray.mexw64。然後在Matlab中運作:

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

      >> imshow(uint8(img));

      即可顯示轉換結果,如圖:

[轉] Matlab與C++混合程式設計(依賴OpenCV)

注:以上Matlab的說明都是在你的cpp檔案所在目錄下。

四、參考資料

沒有整理與歸納的知識,一文不值!高度概括與梳理的知識,才是自己真正的知識與技能。 永遠不要讓自己的自由、好奇、充滿創造力的想法被現實的架構所束縛,讓創造力自由成長吧! 多花時間,關心他(她)人,正如别人所關心你的。理想的騰飛與實作,沒有别人的支援與幫助,是萬萬不能的。

   本文轉自wenglabs部落格園部落格,原文連結:http://www.cnblogs.com/arxive/p/5154402.html,如需轉載請自行聯系原作者