C#调用封装Opencv函数的dll文件之KNN算法调用

C#调用封装Opencv函数的dll文件之KNN算法调用

最近将opencv(版本为2.4.13.6)中的KNN函数(KNN算法相关介绍见后续文章。。。)封装为dll文件,提供函数接口来给C#调用。后来发现opencv有C#版本叫opencvsharp,可以直接使用,多走了不少弯路,好了,回到正题。我自己最开始写了一个dll,C++中函数声明如下,函数功能是实现KNN算法,对待测试数据进行分类。

 KNN_Process(double* sampleData, int* lableData, int sample_num,
	  int sample_length, double* TestData, int* result)
 {
         //创建样本数据cv矩阵
		CvMat sampleDataCvMat = cvMat(sample_num, sample_length, CV_32FC1, sampleData);
		//创建标签数据cv矩阵
		CvMat responsesCvMat = cvMat(1, sample_num, CV_32SC1, lableData);
		//创建knn模型
		CvKNearest knn(&sampleDataCvMat, &responsesCvMat, 0, false, 32);
		int K = 1;	//参数K值
		//创建待测试数据cv矩阵
		CvMat TestDataCvMat = cvMat(1, sample_length, CV_32FC1, TestData);
		//nearests表示K个最邻近样本的响应值
		CvMat* nearests = cvCreateMat(1, K, CV_32FC1);
		//对测试数据进行分类,并返回标签
		float r = knn.find_nearest(&TestDataCvMat, K, 0, 0, nearests, 0);
		result[0] = r;
		for (int i = 0; i<K; i++)
			result[i + 1] = (int)nearests->data.fl[i];
		return (int)r;
 }

C#测试程序

 static void Main(string[] args)
        {
            double[] trainFeaturesData =
			{
                 2,2,2,2,
                 3,3,3,3,
                 4,4,4,4,
                 5,5,5,5,
                 6,6,6,6,
                 7,7,7,7
            };
            int[] trainLabelsData = { 2, 3, 4, 5, 6, 7 };
            double[] testFeatureData = { 7, 7, 7, 7 };
            float[] kresult = new float[5];
            int Label = KNN_Process(trainFeaturesData, trainLabelsData, 6, 4,testFeatureData, kresult);
            Console.WriteLine("result: {0}",Label);

​ 正常情况下,分类标签Label应该是7,但是出来的结果要么是0,要么是个不相关的数。问题来了,输入没问题,函数没问题(单独在C++平台上测试过),输出为啥不对。在线Debug,两个int型参数sample_num, sample_length没问题,sampleData作为指针,可以发现第一个数是正确的。开始以为是C#传入的数组有问题,于是写了下面的测试函数:

void __declspec(dllexport) test(short n[], int N, int& Z)
{
	for (int i = 0; i<N; i++)
	{
		Z += n[i];
	}
}

到C#中去测试,测试结果正确。此时判断会不会是由于double类型的原因,导致传入的数据无法建立模型。将所有double换为int型后,测试结果正确如下:
使用int的结果
为什么double类型传入就就会有问题?于是在dll中写了打印函数,查看每个CvMat的值:
CvMat数据
终于找到问题所在:之所以分类不成功,是因为数据矩阵的值全部有问题,所以出来的标签值一直是0。
百度opencv的数据结构参数后,发现double是64bits,在CvMat数据结构参数:CV_64FC1,CV_64FC2,CV_64FC3,CV_64FC4,于是将此处的CV_32FC1改为CV_64FC1。

CvMat sampleDataCvMat = cvMat(sample_num, sample_length, CV_32FC1, sampleData);

再次出错:训练数据只能是float型的矩阵
只能是float
继续改,所有的double型改为float型。测试结果如下:
float型
我的标签最开始设置为int型,float测试完成后,将trainLabelsData的类型设置为int型,并将CvMat格式化为int格式CV_32SC1,测试结果如下:
int标签
不明白为什么样本标签为int型的时候,分类的结果不对。于是去查看opencv中K-Nearest Neighbors Classifier的源码。
在源代码中

 CV_CALL( cvPrepareTrainData( "CvKNearest::train", _train_data, CV_ROW_SAMPLE,
        _responses, CV_VAR_ORDERED, 0, _sample_idx, true, (const float***)&_data,
        &_count, &_dims, &_dims_all, &responses, 0, 0 ));

创建KNN模型的时候和训练的时候,首先用cvPrepareTrainData和cvCheckTrainData对输入的各种数据进行了预处理。其中cvCheckTrainData对_train_data进行了判断

if( !CV_IS_MAT(train_data) || CV_MAT_TYPE(train_data->type) != CV_32FC1 )
        CV_ERROR( CV_StsBadArg, "train data must be floating-point matrix" );

这解决了问题1:为什么训练数据一定要为CV_32FC1格式(CV_32F也可以),我用double转CV_64FC1,用int转CV_32SC1均会出错的原因。
问题2:样本标签为int型为什么会分类错误?
在cvPreprocessOrderedResponses中对标签数据进行了处理,这里有判断标签的类型是否CV_32SC1或者CV_32FC1

  r_type = CV_MAT_TYPE(responses->type);
    if( r_type != CV_32FC1 && r_type != CV_32SC1 )
        CV_ERROR( CV_StsUnsupportedFormat, "Unsupported response type" );

然后将返回的值传给函数cvPrepareTrainData中的responses,再返回到CvKNearest::train函数中,在该函数中所有的样本数据都被转为了单精度浮点型。

_rsize = _count*sizeof(float);
    CV_CALL( _samples = (CvVectors*)cvAlloc( sizeof(*_samples) + _rsize ));
    _samples->next = samples;
    _samples->type = CV_32F;//数据类型为float
    _samples->data.fl = _data;
    _samples->count = _count;
    total += _count;

    samples = _samples;
    memcpy( _samples + 1, responses->data.fl, _rsize );//标签也以float的形式copy到整个样本数据集

看了里面的源码,大部分地方的数据均用的是float,至于为什么在调用封装的dll时候传入int类型的标签,分类会错。看了官方的例程后发现,发现与这里的标签数据格式化有关。若格式化为一维的行向量的形式,则int型的标签会分类出错;若分类为一维的列向量,则int型的标签会分类正确;但是float型的标签,无论是行列向量均可。

CvMat responsesCvMat = cvMat(1, sample_num, CV_32SC1, lableData);

花时间看了一下OpenCV API Reference,里面关于KNN的CvStatModel::train的说明很详细
train说明这里明确说明了,训练数据必须是32bit的float型,标签数据通常是32bit的int型或float型。没看API文档,浪费了不少时间,以后还是要多看手册,不能一上来就百度!!!

参考资料:
1.[OpenCV K-Nearest Neighbors说明]:https://docs.opencv.org/2.4.13.6/modules/ml/doc/k_nearest_neighbors.html
2.[OpenCV Mat数据类型及位数总结]: http://blog.sina.com.cn/s/blog_662c7859010105za.html

如有不对的地方,欢迎大家批评指正

猜你喜欢

转载自blog.csdn.net/qq_18150255/article/details/84886510
今日推荐