最近有个任务要对如下图找其中的圆圈,在opencv上面使用cvHoughCircles效果很不好,所以在matlab下试了下imfindcircles函数,发现效果非常的好啊,之前老师提到说图中下半部分有些圆中间的噪声特别多,导致找不到,不过在matlab强大的找圆能力下,这些噪声都是小意思啦
所以问题便转为怎么在c++中调用matlab了,我选择的是matlab生成动态链接库dll的方式。
mcc –W cpplib:findCircles –T link:lib findCircles.m –C
这个时候要等一小段时间让它慢慢生成啦。Be patient!
findCircles是接下来要生成的dll的前缀名,会生成findCircles.dll。
findCircles.m是使用的.m文件名。
注意-C选项必须要的,不然生成的文件中会少掉一个很重要的.ctf文件哦。
如下图所示,背景为蓝色的四个文件便是我们需要使用的文件啦。
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输出。
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位系统用的。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
首先在头文件目录中将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库。
对于一幅图片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};
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; }
具体运行的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)