之前在運作别人論文的代碼的時候,經常有遇到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的說明都是在你的cpp檔案所在目錄下。
四、參考資料
沒有整理與歸納的知識,一文不值!高度概括與梳理的知識,才是自己真正的知識與技能。 永遠不要讓自己的自由、好奇、充滿創造力的想法被現實的架構所束縛,讓創造力自由成長吧! 多花時間,關心他(她)人,正如别人所關心你的。理想的騰飛與實作,沒有别人的支援與幫助,是萬萬不能的。
本文轉自wenglabs部落格園部落格,原文連結:http://www.cnblogs.com/arxive/p/5154402.html,如需轉載請自行聯系原作者