[轉] Matlab與C++混合編程(依賴OpenCV)

做者 zouxy09@qq.com,原文 Matlab與C++混合編程(依賴OpenCV)

  以前在運行別人論文的代碼的時候,常常有遇到Matlab與C++混合編程的影子。實際上就是經過Matlab的Mex工具將C++的代碼編譯成 Matlab支持調用的可執行文件和函數接口。這樣一方面能夠在Matlab中利用已經編寫好的函數,儘管這個函數是用C++編寫的。實現了交流無國界, 沒有江山一統的誰,只有四海以內皆兄弟的豪氣。另外一方面,取C++所長補己之短。Matlab擅長矩陣運算,但對循環操做的效率不及C++來得高效,例如 Hilbert矩陣的建立。因此對於具備大循環的運算,能夠借C++之力來完成。html

      看到它的魅力,以前也一直想學下,惋惜機緣不對。但在昨天緣分就到了。我須要用到一個論文給出來的代碼,可是它的代碼是C++的,並且還依賴了 OpenCV的庫,基於Linux平臺。這與實驗室給我定出來的平臺有很大的不一樣,咱們是得統一基於Windows + Matlab來實現的,這樣組內各個同窗的工做纔好統一。因此沒辦法了,就得把這個原做者的代碼編譯成Matlab支持的可執行文件。ios

 

1、初級c++

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

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

mexAdd.cppide

 

  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. double add(double x, double y)  
  5. {  
  6.     return x + y;  
  7. }  

一、修改代碼文件函數

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是這樣:

mexAdd.cpp

 

  1. #include "opencv2/opencv.hpp"  
  2. #include "mex.h"  
  3.   
  4. double add(double x, double y)  
  5. {  
  6.     return x + y;  
  7. }  
  8.   
  9.    
  10. void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])  
  11. {  
  12.     double *a;  
  13.     double b, c;  
  14.     plhs[0] = mxCreateDoubleMatrix(1, 1, mxREAL);  
  15.     a = mxGetPr(plhs[0]);  
  16.     b = *(mxGetPr(prhs[0]));  
  17.     c = *(mxGetPr(prhs[1]));  
  18.     *a = add(b, c);  
  19. }  

      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])。

 

二、編譯修改後的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);

 

2、進階

       上面咱們針對的是處理標量的狀況,也就是數a,b或者c。這節咱們讓它處理二維數組,也就是圖像。爲了驗證,咱們很傻瓜地完成如下功能:

        >> [grayImage] =RGB2Gray('imageFile.jpeg');

       也就是將一個圖像文件名,傳遞給c++的代碼,而後c++代碼將這個圖像讀入,再轉成灰度圖,而後返回給Matlab。而c++代碼裏面的圖像讀入和灰 度轉換的操做經過調用OpenCV的庫函數來實現。是否是很傻瓜呢?由於Matlab已經有實現一樣功能的函數了。對,沒錯,就是畫蛇添足。但咱們只是爲 了說明二維數組的傳遞過程,沒有什麼用意。不過,若是要計算兩個圖像的光流的話,Matlab可能就真正須要OpenCV的幫助了。

       另外,由於cpp文件要連接OpenCV的庫,因此爲了統一或者規範編譯工程,我寫了一個make.m文件,它的功能相似於Makefile,實際上就實現了mex編譯這個工程時候的編譯規則。具體能夠看後面的代碼,而後就知道在裏面作了什麼了。

       首先是RGB2Gray.cpp代碼:

 

  1. // Interface: convert an image to gray and return to Matlab  
  2. // Author : zouxy  
  3. // Date   : 2014-03-05  
  4. // HomePage : http://blog.csdn.net/zouxy09  
  5. // Email  : zouxy09@qq.com  
  6.   
  7. #include "opencv2/opencv.hpp"  
  8. #include "mex.h"  
  9.   
  10. using namespace cv;  
  11.   
  12. /******************************************************* 
  13. Usage: [imageMatrix] = RGB2Gray('imageFile.jpeg'); 
  14. Input:  
  15.     a image file 
  16. OutPut:  
  17.     a matrix of image which can be read by Matlab 
  18.  
  19. **********************************************************/  
  20.   
  21.   
  22. void exit_with_help()  
  23. {  
  24.     mexPrintf(  
  25.     "Usage: [imageMatrix] = DenseTrack('imageFile.jpg');\n"  
  26.     );  
  27. }  
  28.   
  29. static void fake_answer(mxArray *plhs[])  
  30. {  
  31.     plhs[0] = mxCreateDoubleMatrix(0, 0, mxREAL);  
  32. }  
  33.   
  34. void RGB2Gray(char *filename, mxArray *plhs[])  
  35. {  
  36.     // read the image  
  37.     Mat image = imread(filename);  
  38.     if(image.empty()) {  
  39.         mexPrintf("can't open input file %s\n", filename);  
  40.         fake_answer(plhs);  
  41.         return;  
  42.     }  
  43.       
  44.     // convert it to gray format  
  45.     Mat gray;  
  46.     if (image.channels() == 3)  
  47.         cvtColor(image, gray, CV_RGB2GRAY);  
  48.     else  
  49.         image.copyTo(gray);  
  50.       
  51.     // convert the result to Matlab-supported format for returning  
  52.     int rows = gray.rows;  
  53.     int cols = gray.cols;  
  54.     plhs[0] = mxCreateDoubleMatrix(rows, cols, mxREAL);  
  55.     double *imgMat;  
  56.     imgMat = mxGetPr(plhs[0]);  
  57.     for (int i = 0; i < rows; i++)  
  58.         for (int j = 0; j < cols; j++)  
  59.             *(imgMat + i + j * rows) = (double)gray.at<uchar>(i, j);  
  60.       
  61.     return;  
  62. }  
  63.   
  64. void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])  
  65. {  
  66.     if(nrhs == 1)  
  67.     {  
  68.         char filename[256];  
  69.         mxGetString(prhs[0], filename, mxGetN(prhs[0]) + 1);  
  70.         if(filename == NULL)  
  71.         {  
  72.             mexPrintf("Error: filename is NULL\n");  
  73.             exit_with_help();  
  74.             return;  
  75.         }  
  76.   
  77.         RGB2Gray(filename, plhs);  
  78.     }  
  79.     else  
  80.     {  
  81.         exit_with_help();  
  82.         fake_answer(plhs);  
  83.         return;  
  84.     }  
  85. }  

       和上面的相比,裏面多了幾個東西。第一個就是傳入參數的測試,看看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來編譯列表裏面的全部文件。

 

  1. %// This make.m is for MATLAB  
  2. %// Function: compile c++ files which rely on OpenCV for Matlab using mex  
  3. %// Author : zouxy  
  4. %// Date   : 2014-03-05  
  5. %// HomePage : http://blog.csdn.net/zouxy09  
  6. %// Email  : zouxy09@qq.com  
  7.   
  8. %% Please modify your path of OpenCV  
  9. %% If your have any question, please contact Zou Xiaoyi  
  10.   
  11. % Notice: first use "mex -setup" to choose your c/c++ compiler  
  12. clear all;  
  13.   
  14. %-------------------------------------------------------------------  
  15. %% get the architecture of this computer  
  16. is_64bit = strcmp(computer,'MACI64') || strcmp(computer,'GLNXA64') || strcmp(computer,'PCWIN64');  
  17.   
  18.   
  19. %-------------------------------------------------------------------  
  20. %% the configuration of compiler  
  21. % You need to modify this configuration according to your own path of OpenCV  
  22. % Notice: if your system is 64bit, your OpenCV must be 64bit!  
  23. out_dir='./';  
  24. CPPFLAGS = ' -O -DNDEBUG -I.\ -ID:\OpenCV_64\include'; % your OpenCV "include" path  
  25. LDFLAGS = ' -LD:\OpenCV_64\lib';                       % your OpenCV "lib" path  
  26. LIBS = ' -lopencv_core240 -lopencv_highgui240 -lopencv_video240 -lopencv_imgproc240';  
  27. if is_64bit  
  28.     CPPFLAGS = [CPPFLAGS ' -largeArrayDims'];  
  29. end  
  30. %% add your files here!  
  31. compile_files = {   
  32.     % the list of your code files which need to be compiled  
  33.     'RGB2Gray.cpp'  
  34. };  
  35.   
  36.   
  37. %-------------------------------------------------------------------  
  38. %% compiling...  
  39. for k = 1 : length(compile_files)  
  40.     str = compile_files{k};  
  41.     fprintf('compilation of: %s\n', str);  
  42.     str = [str ' -outdir ' out_dir CPPFLAGS LDFLAGS LIBS];  
  43.     args = regexp(str, '\s+', 'split');  
  44.     mex(args{:});  
  45. end  
  46.   
  47. fprintf('Congratulations, compilation successful!!!\n');  

3、使用方法和結果

一、編譯

      直接在Matlab中運行make.m。便可生成RGB2Gray.mexw64。而後在Matlab中運行:

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

      >> imshow(uint8(img));

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

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

 

4、參考資料

[1] 如何寫mexFunction函數

[2] matlab用mex編譯cpp文件

相關文章
相關標籤/搜索