混合编程---c++调用matlab生成的dll----findCircles的应用

最近有个任务要对如下图找其中的圆圈,在opencv上面使用cvHoughCircles效果很不好,所以在matlab下试了下imfindcircles函数,发现效果非常的好啊,之前老师提到说图中下半部分有些圆中间的噪声特别多,导致找不到,不过在matlab强大的找圆能力下,这些噪声都是小意思啦大笑


混合编程---c++调用matlab生成的dll----findCircles的应用_第1张图片

所以问题便转为怎么在c++中调用matlab了,我选择的是matlab生成动态链接库dll的方式。

matlab生成dll

这里最重要的就是选对自己使用的平台工具,要不在vs中Initialize是会出错的。
我选用的编程平台是matlab2012a+vs2010。

在命令行中首先输入mex –setp


混合编程---c++调用matlab生成的dll----findCircles的应用_第2张图片


在命令行中输入mbuild  -setup

混合编程---c++调用matlab生成的dll----findCircles的应用_第3张图片

之后便是生成dll了,输入命令

mcc –W cpplib:findCircles –T link:lib findCircles.m –C

这个时候要等一小段时间让它慢慢生成啦。Be patient!吐舌头

findCircles是接下来要生成的dll的前缀名,会生成findCircles.dll。

findCircles.m是使用的.m文件名。

注意-C选项必须要的,不然生成的文件中会少掉一个很重要的.ctf文件哦。

如下图所示,背景为蓝色的四个文件便是我们需要使用的文件啦。


使用的findCircle.m中必须是一个函数文件

function [number, centers, radius] = findCircles(img, Rmin, Rmax)
[centers, radius] = imfindcircles(img,[Rmin Rmax],'ObjectPolarity','dark');
number = size(radius, 1);
end
函数很简单,只是简单的调用了一下imfindcircles函数,不过要好好设计一下输入输出参数,这里我便添加了一个输出number值记录得到的centers的个数。

因为在c++中调用此函数的时候输出是一系列的指针,我们要事先为输出分配好空间,然后把这些指针指向的值复制到输出空间中去,所以要知道要动态申请多少空间,就必须要知道函数得到了多少个centers,所以就加了个number输出。

C++中的调用

dll等文件的放置

首先我们生成一个vs2010C++工程,起名就叫Finding吧,之后新建一个main.cpp文件。
那findCircle.dll等四个文件放在工程的哪里呢? 放到代码所在的文件夹中。
之后添加包含目录和库目录。
右键点击方案名Finding,选择最下面的属性
混合编程---c++调用matlab生成的dll----findCircles的应用_第4张图片
打开之后选择VC++目录,要修改的是包含目录和库目录。
我添加的包含目录是

D:\MATLAB\R2012a\extern\include

C:\opencv2.4\opencv\build\include\opencv

C:\opencv2.4\opencv\build\include\opencv2

C:\opencv2.4\opencv\build\include

注意,opencv中的3个都需要添加的哦!
库目录则是

C:\opencv2.4\opencv\build\x86\vc10\lib

D:\MATLAB\R2012a\extern\lib\win32\microsoft

opencv中根据自己选用的vs版本以及操作系统选择 ,x86文件夹中的是32位操作系统用的,x64则是64位系统用的。
vc10则是vs2010使用。



之后添加lib文件,把生成的findCircles.lib加入,
matlab则加入mclmcrrt.lib,
opencv则加入(根据opencv版本不同替换后面的246即可,因为我是在debug模式下使用,所以后面是246d.lib,如果是release,则是246.lib)

opencv_calib3d246d.lib

opencv_contrib246d.lib

opencv_core246d.lib

opencv_features2d246d.lib

opencv_flann246d.lib

opencv_gpu246d.lib

opencv_highgui246d.lib

opencv_imgproc246d.lib

opencv_legacy246d.lib

opencv_ml246d.lib

opencv_objdetect246d.lib

opencv_ts246d.lib

opencv_video246d.lib


C++文件的编写

首先在头文件目录中将findCircles.h加入。

我们来看看findCircles.h,它里边有3个函数是我们需要使用的,

bool MW_CALL_CONV findCirclesInitialize(void);
void MW_CALL_CONV findCirclesTerminate(void);
bool MW_CALL_CONV mlxFindCircles(int nlhs, mxArray *plhs[], int nrhs, mxArray *prhs[]);
前两个从文件名中可看出是初始化&结束函数。

之后一个则是找圆圈的函数啦,它的输入参数名挺有意思的,int nlhs和mxArray *plhs[]是用于记录输出值的参数,从它的起名上看出包含left hand,也即放在‘=’左边,所以是用于输出的值,int nrhs,mxArray *prhs[]则包含right hand,说明是输入用的参数。

prhs是一个mxArray *的数组,数组中元素的个数便由nrhs来记录,我们的每个输入参数都用一个mxArray *指针来指向。

输出参数同理。

下面便来看看具体的编程吧。

首先用findCirclesInitialize()进行初始化,

接着准备输入输出参数,

最后用findCirclesTerminate()结束DLL库。

输入参数

假定我们要传入N个输入参数,那么nrhs = N,每个参数我们都要用mxArray *进行表示。
比如对于一个有5个int数组成的数组参数,我们可以如下表示:
//传入的参数实际上都要转化成double的。
mxArray *array = mxCreateDoubleMatrix(1, 5, mxREAL)。
对于1个int值的参数,则可以写成
mxArray *arg = mxCreateDoubleMatrix(1, 1, mxREAL)。

对于一幅图片IplImage *img,则可以写成

mxArray *arrImg = mxCreateDoubleMatrix(img->height, img->width, mxREAL)。

那么如何赋值呢?

可以采用memcpy函数,比如对array,我们便可以

double input[5] = {1, 2, 3, 4, 5};

memcpy(mxGetPr(array), (void *)input, sizeof(input));

对于arg,则可以

double input = 5;

memcpy(mxGetPr(arg), (void *)&input, sizeof(input));

如果是个文件名,可以采用mxCreateString方式进行创建。

对于图片的话,

UINT8 *pMatPr = (UINT8*)mxGetData(pMat);
之后遍历图像每一个像素,一一赋值。

另外单通道和多通道图像赋值有所不同的哦。

注意一下下面的赋值顺序,可以看出我们赋值得到的pMat中图像是按照列连续存储的哦!这一点在接下来的输出参数处理上很重要的。

在下面的代码中我传入了三个参数,所以最后我的

nrhs = 3;

mxArray *input[3] = {pMat, rMin, rMax};

输出参数

根据自己设计的matlab函数可以知道有多少个输出参数的。
我自己设计的有3个输出参数,所以如下:
mxArray *output[3];
bool functionRet = mlxFindCircles(3, output, 3, input);
output[0]中存储了一个double number,
所以用memcpy得到输出数据:
memcpy(&number, mxGetPr(output[0]), sizeof(number));
这样便知道了得到的圆圈个数。
之后动态申请圆心和半径空间,
        centers = new double*[(int)number];
	double *outputCenters = mxGetPr(output[1]);
	for (int i = 0; i < (int)number; i++){
		centers[i] = new double[2];
		//centers是一个二维数组,在matlab里边存储的二维数组和在c++中不同
		//比如a[2][3],在c++中是a[0][0]->a[0][1]->a[0][2]->a[1][0]这样的行连续存储,
		//在matlab中是a[0][0]->a[1][0]->a[0][1]这样的列连续存储
		centers[i][0] = outputCenters[i + 0 * (int)number];
		centers[i][1] = outputCenters[i + 1 * (int)number];
	}



完整代码如下:

#include "findCircles.h"
#include <iostream>

#include "cv.h"
#include "highgui.h"
using namespace cv;
using namespace std;


/*****************************
输入:
	pImage为输入图片,彩色灰色无所谓
	radiusMin和radiusMax是查找圆时的最小半径和最大半径,不同取值得到结果也不同,我发现radius = 2,3的时候结果蛮好的,具体问题具体分析吧
输出:	
	number表示找到圆圈的个数
	centers记录圆心坐标,centers[i][0]记录x值,centers[i][1]记录y值
	radius则记录圆半径的坐标
	输出参数centers和radius需要都初始化为NULL,之后会在函数中为它们分配空间

*****************************/
void findCircles(IplImage *pImage, double radiusMin, double radiusMax, double &number, double **& centers, double *&radius){
	
	assert(NULL != pImage && radiusMin > 0 && radiusMax > 0 && NULL == centers && NULL == radius);  
	//初始化DLL动态连接文件
	bool init = findCirclesInitialize();
	assert(init);
	mxArray *pMat = NULL;
	//单通道图像参数的转化
	if (pImage->nChannels == 1){
		pMat = mxCreateDoubleMatrix(pImage->height,pImage->width,mxREAL);

		UINT8 *pMatPr = (UINT8*)mxGetData(pMat);

		for(int j = 0; j < pImage->height; j++)
		for(int i = 0; i < pImage->width; i++)
		{
			pMatPr[i * pImage->height + j] = CV_IMAGE_ELEM(pImage,uchar,j,i);
		}
	}
	
	//多通道彩色图像参数的转化
	else{
		const mwSize dims[3] = {pImage->height,pImage->width,3};
		pMat = mxCreateNumericArray(3,dims,mxUINT8_CLASS,mxREAL);
		UINT8 *pMatPr = (UINT8*)mxGetData(pMat);
		for(int k = 0; k < 3; k++)
		for(int j = 0; j < pImage->height; j++)
		for(int i = 0; i < pImage->width; i++)
		{
			pMatPr[i * pImage->height + j + k * pImage->height * pImage->width] = CV_IMAGE_ELEM(pImage,uchar,j,i * 3 + 2 - k);
		}	
	}
	
	//输入参数的设置
	mxArray *rMin = mxCreateDoubleMatrix(1, 1, mxREAL);
	mxArray *rMax = mxCreateDoubleMatrix(1, 1, mxREAL);
	memcpy(mxGetPr(rMin), (void*)&radiusMin, sizeof(radiusMin));
	memcpy(mxGetPr(rMax), (void*)&radiusMax, sizeof(radiusMax));
	
	mxArray *input[3] = {pMat, rMin, rMax};
	mxArray *output[3];
	//调用函数
	bool functionRet = mlxFindCircles(3, output, 3, input);
	assert(functionRet);
	//输出参数的转化
	memcpy(&number, mxGetPr(output[0]), sizeof(number));
	cout << number << endl;
	centers = new double*[(int)number];
	double *outputCenters = mxGetPr(output[1]);
	for (int i = 0; i < (int)number; i++){
		centers[i] = new double[2];
		//centers是一个二维数组,在matlab里边存储的二维数组和在c++中不同
		//比如a[2][3],在c++中是a[0][0]->a[0][1]->a[0][2]->a[1][0]这样的行连续存储,
		//在matlab中是a[0][0]->a[1][0]->a[0][1]这样的列连续存储
		centers[i][0] = outputCenters[i + 0 * (int)number];
		centers[i][1] = outputCenters[i + 1 * (int)number];
	}
	radius = new double[(int)number];
	memcpy(radius, mxGetPr(output[2]), sizeof(double)*number);
	//释放空间
	mxDestroyArray(pMat);
	mxDestroyArray(rMin);
	mxDestroyArray(rMax);
	/*mxDestroyArray(output[0]);
	mxDestroyArray(output[1]);
	mxDestroyArray(output[2]);
	*/
	//结束DLL库
	findCirclesTerminate();                    
}
int main(){
	double number;
	double ** centers = NULL;
	double *radius = NULL;
	IplImage *pImage = cvLoadImage("../c.jpg");
	IplImage *grayImage = NULL;
	double radiusMin = 2, radiusMax = 25;
	grayImage = pImage;
	/*if (pImage->nChannels == 1){
		grayImage = pImage;
	}
	else{
		grayImage = cvCreateImage(cvGetSize(pImage), pImage->depth, 1);
		cvCvtColor(pImage, grayImage, CV_BGR2GRAY);
	}*/
	findCircles(grayImage, radiusMin, radiusMax, number, centers, radius);
	
	for (int i = 0; i < (int)number; i++){
		cout << "center: " << i  << " : " << centers[i][0] << ":" << centers[i][1] << endl;
		cout << "radius " << radius[i] << endl;
		//因为opencv中的CvPoint点x,y坐标都是整数,而找出的圆心点的坐标是double,所以在opencv里边画圆double->int会出现很大误差
		CvPoint point;
		point.x = (int)centers[i][0];
		point.y = (int)centers[i][1];
		cvCircle(grayImage, point, (int)radius[i],CV_RGB(255, 0,0));
	}
	cout << pImage->height << endl;
	cvShowImage("image", grayImage);
	cvWaitKey(0);
	return 0;
}

运行:

如果出现初始化错误,那一般来说是因为matlab生成的时候版本没对应好,最好是用新一些的matlab版本,好像是之前的matlab版本有些不稳定,至少我用matlab2009+ vs2005初始化这一步就一直没成功过,坑死了 哭
运行成功,感觉蛮慢的诶,结果如下:
混合编程---c++调用matlab生成的dll----findCircles的应用_第5张图片

在debug目录下直接运行

老师提出了一个新问题,就是在debug目录下点击exe运行,如果只是按照之前的设置,这个时候会出错的,会提示少dll。


这个时候该怎么解决呢?把这个dll放进去么?哈哈哈哈,没这么简单的哦。
想想我们一共使用了生成的4个文件对吧,我们要把dll和ctf文件都放进去才能正常运行的哦!
混合编程---c++调用matlab生成的dll----findCircles的应用_第6张图片
然后ok啦^_^。所以说最开始的mcc 命令里边-C命令选项不能少的哦,要不少了ctf,这里就不能正常运行啦。

在没有matlab和opencv的环境下运行

老师又提出了新问题了,要在裸机上运行。
如果只是没有opencv的话倒是蛮简单的哦。

不安装opencv的设置

把我们包含的lib文件夹和include文件夹拷贝出来放到工程里边,包含的时候直接包含工程目录就是啦。
不过还是会提示缺少dll,这里我的工程只缺少了两个dll,opencv_core246d.dll和opencv_highgui246d.dll,把这两个dll放到exe所在的debug目录下就ok啦!
opencv的问题就是so easy!

不安装matlab的设置

包含的几个目录同opencv设置。
不过dll的问题挺大的,它会提示你缺dll,然后我傻乎乎地一个一个又一个一个地加了进去, 敲打matlab的dll真是你中有我,我中有你啊,仅仅是简单地调用一下你,你就给我扯出了一堆的dll。一怒之下我把D:\MATLAB\R2012a\bin\win32目录中所有的dll都加到了包含exe的debug目录下。
。。。。。。。。
。。。。。。。。
成功了么?No,运行页面一闪而过,加了system("pause")也毛用哦,恍惚中就看到这飘逸的页面上写了locale initialize.........的文字。
所以还是要装个runtime库哦!
这里要使用到一个runtime的MCRInstaller.exe,它位于我的D:\MATLAB\R2012a\toolbox\compiler\deploy\win32。

MCRInstaller.exe

在一台没有装matlab的机器上,点击这个exe,然后随便它装到哪里。
它会加一个环境变量到PATH里边,这个时候我们 或者注销一下,或者重启,让新加的环境变量起效果,之后就能正常运行啦!


64位环境下

因为老师那边用的是64位电脑,我上面一直用的是32位系统,所以不满足要求。
32位编译的程序无论如何在64位上都会挂掉。
所以要采用64位系统,64位matlab,vs2010默认是win32,所以也要如下图所示新建x64模式。在活动解决方案平台这里选新建。
混合编程---c++调用matlab生成的dll----findCircles的应用_第7张图片

大部分设置和32位是一样的,只需要把所有x86目录改成x64就ok啦。运行成功!
注意,所有东西都要用64位的,matlab要在64位机器下装才可以,MCRInstaller也要使用64位机器下生成的matlab中的。也可以去官网下载。

在没有vs2010的情况下运行


既然要在裸机上运行,那就裸个彻底呗,之前没有matlab,opencv的情况已经进行了说明,这里我连visual studio也没有了。
这次不用debug模式来生成,会出错,采用release模式。

具体运行的exe在第一个x64目录中的release目录里边。

其余要修改的就是把链接输入的lib以及加入到文件夹中的dll中,比如opencv_core246d.dll它们后面的d后缀去掉,有d的是debug用的,转而加入release用的opencv_core246.dll。

如果要在没有visual studio 2010的机器上运行需要安装Microsoft Visual C++ 2010 Redistributable Package (x64)

地址是: http://www.microsoft.com/en-us/download/details.aspx?id=14632



re
rea

你可能感兴趣的:(编程,C++,matlab,混合编程)