【机器学习】LBP+SVM实现特征检测

初学机器学习,参考HOG SVM 车辆检测(https://www.cnblogs.com/louyihang-loves-baiyan/p/4658478.html)、LBP特征原理(https://blog.csdn.net/q1007729991/article/details/52995734)及LBP特征的实现及LBP+SVM分类 (https://blog.csdn.net/qianqing13579/article/details/49406563)的一些理论知识内容,实现基于LBP特征加上其他约束特征,结合SVM模型训练正负样本,实现二分类、对感兴趣特征的检测。

#include<opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/gpu/gpu.hpp>
#include<opencv2/ml/ml.hpp>
#include<opencv2/objdetect/objdetect.hpp>
#include<iostream>
#include<fstream>
#include<string>
#include<vector>

using namespace cv;
using namespace std;

#define TRAIN    //开关控制是否训练还是直接载入训练好的模型

class MySVM: public CvSVM
{
public:
	double * get_alpha_data()
	{
		return this->decision_func->alpha;
	}
	double  get_rho_data()
	{
		return this->decision_func->rho;
	}
};


//计算输入图片的最大灰度差、平均灰度、平均梯度
int calAverageGary(const Mat &inImg, int &maxGaryDiff, int &averageGrad_xy)
{
	float averageGary;
	int garySum = 0;
	int i, j;

	//求平均灰度值
	for (i=0; i<inImg.cols; i++)
	{
		for (j=0; j<inImg.rows; j++)
		{
			garySum += inImg.at<uchar>(j, i);
		}
	}
	averageGary = (int)(garySum*1.0f/(inImg.rows*inImg.cols));

	//求滑窗内的最大灰度差值
	double minGary, maxGary; 
	minMaxLoc(inImg, &minGary, &maxGary, NULL, NULL);
	maxGaryDiff = (int)(maxGary-minGary);

	//求滑窗内的平均梯度值
	Mat grad_x, grad_y, abs_grad_x, abs_grad_y, grad_xy; 
	Sobel( inImg, grad_x, CV_16S, 1, 0, 3, 1, 1, BORDER_DEFAULT );  	//求X方向梯度 
	convertScaleAbs( grad_x, abs_grad_x );  
	Sobel( inImg, grad_y, CV_16S, 0, 1, 3, 1, 1, BORDER_DEFAULT );  	//求Y方向梯度  
	convertScaleAbs( grad_y, abs_grad_y );  
	addWeighted( abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad_xy);  	       //合并梯度(近似)  
	//cout<<"gary_xy"<<grad_xy<<endl;

	int grad_xy_sum = 0;
	for (i=0; i<inImg.cols; i++)
	{
		for (j=0; j<inImg.rows; j++)
		{
			grad_xy_sum += grad_xy.at<uchar>(j, i);
		}
	}
	averageGrad_xy = (int)(grad_xy_sum*1.0f/(inImg.rows*inImg.cols));
	return averageGary;
}


// 计算等价模式LBP特征图
static void ComputeLBPImage_Uniform(const Mat &srcImage, Mat &LBPImage)
{
	// 参数检查,内存分配
	CV_Assert(srcImage.depth() == CV_8U&&srcImage.channels() == 1);
	LBPImage.create(srcImage.size(), srcImage.type());

	// 计算LBP图
	Mat extendedImage;
	copyMakeBorder(srcImage, extendedImage, 1, 1, 1, 1, BORDER_DEFAULT);

	// LUT(256种每一种模式对应的等价模式)
	static const int table[256] = { 1, 2, 3, 4, 5, 0, 6, 7, 8, 0, 0, 0, 9, 0, 10, 11, 12, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 14, 0, 15, 16, 17, 0, 0, 0,
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 0, 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 20, 0, 21, 22, 23, 0, 0, 0, 0, 0, 0, 0, 0,
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25,
		0, 0, 0, 0, 0, 0, 0, 26, 0, 0, 0, 27, 0, 28, 29, 30, 31, 0, 32, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0
		, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
		0, 0, 0, 0, 36, 37, 38, 0, 39, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42
		, 43, 44, 0, 45, 0, 0, 0, 46, 0, 0, 0, 0, 0, 0, 0, 47, 48, 49, 0, 50, 0, 0, 0, 51, 52, 53, 0, 54, 55, 56, 57, 58 };

	// 计算LBP
	int heightOfExtendedImage = extendedImage.rows;
	int widthOfExtendedImage = extendedImage.cols;
	int widthOfLBP=LBPImage.cols;
	uchar *rowOfExtendedImage = extendedImage.data+widthOfExtendedImage+1;
	uchar *rowOfLBPImage = LBPImage.data;

	int pixelDiff = 5;

	for (int y = 1; y <= heightOfExtendedImage - 2; ++y,rowOfExtendedImage += widthOfExtendedImage, rowOfLBPImage += widthOfLBP)
	{
		// 列
		uchar *colOfExtendedImage = rowOfExtendedImage;
		uchar *colOfLBPImage = rowOfLBPImage;
		for (int x = 1; x <= widthOfExtendedImage - 2; ++x, ++colOfExtendedImage, ++colOfLBPImage)
		{
			// 计算LBP值
			int LBPValue = 0;
			if (colOfExtendedImage[0 - widthOfExtendedImage - 1] >= colOfExtendedImage[0]+pixelDiff)
				LBPValue += 128;
			if (colOfExtendedImage[0 - widthOfExtendedImage] >= colOfExtendedImage[0]+pixelDiff)
				LBPValue += 64;
			if (colOfExtendedImage[0 - widthOfExtendedImage + 1] >= colOfExtendedImage[0]+pixelDiff)
				LBPValue += 32;
			if (colOfExtendedImage[0 + 1] >= colOfExtendedImage[0]+pixelDiff)
				LBPValue += 16;
			if (colOfExtendedImage[0 + widthOfExtendedImage + 1] >= colOfExtendedImage[0]+pixelDiff)
				LBPValue += 8;
			if (colOfExtendedImage[0 + widthOfExtendedImage] >= colOfExtendedImage[0]+pixelDiff)
				LBPValue += 4;
			if (colOfExtendedImage[0 + widthOfExtendedImage - 1] >= colOfExtendedImage[0]+pixelDiff)
				LBPValue += 2;
			if (colOfExtendedImage[0 - 1] >= colOfExtendedImage[0]+pixelDiff)
				LBPValue += 1;

			colOfLBPImage[0] = table[LBPValue];
		}
	}
}

//计算归一化的LBP特征矩阵
static void ComputeLBPFeatureVector_Uniform(const Mat &srcImage, Size cellSize, Mat &featureVector)
{
	// 参数检查,内存分配
	CV_Assert(srcImage.depth() == CV_8U&&srcImage.channels() == 1);

	Mat LBPImage;
	ComputeLBPImage_Uniform(srcImage, LBPImage);

	//cout<<"LBPImage_uniform:"<<endl<<LBPImage<<endl<<endl;

	// 计算cell个数
	int widthOfCell = cellSize.width;
	int heightOfCell = cellSize.height;
	int numberOfCell_X = srcImage.cols / widthOfCell;// X方向cell的个数
	int numberOfCell_Y = srcImage.rows / heightOfCell;

	// 特征向量的个数
	int numberOfDimension = 58 * numberOfCell_X*numberOfCell_Y;
	featureVector.create(1, numberOfDimension, CV_32FC1);
	featureVector.setTo(Scalar(0));

	// 计算LBP特征向量
	int stepOfCell=srcImage.cols;
	int index = -58;// cell的特征向量在最终特征向量中的起始位置
	float *dataOfFeatureVector=(float *)featureVector.data;
	for (int y = 0; y <= numberOfCell_Y - 1; ++y)
	{
		for (int x = 0; x <= numberOfCell_X - 1; ++x)
		{
			index+=58;

			// 计算每个cell的LBP直方图
			Mat cell = LBPImage(Rect(x * widthOfCell, y * heightOfCell, widthOfCell, heightOfCell));
			uchar *rowOfCell=cell.data;
			int sum = 0; // 每个cell的等价模式总数
			for(int y_Cell=0;y_Cell<=cell.rows-1;++y_Cell,rowOfCell+=stepOfCell)
			{
				uchar *colOfCell=rowOfCell;
				for(int x_Cell=0;x_Cell<=cell.cols-1;++x_Cell,++colOfCell)
				{
					if(colOfCell[0]!=0)
					{
						// 在直方图中转化为0~57,所以是colOfCell[0] - 1
						++dataOfFeatureVector[index + colOfCell[0]-1];
						++sum;
					}
				}
			}

			for (int i = 0; i <= 57; ++i)
				dataOfFeatureVector[index + i] /= sum;
		}
	}
}

//计算扩展LBP特征矩阵,在原LBP特征上增加了3维
void calExtendLBPFeature(const Mat &srcImage, Size cellSize, Mat &extendLBPFeature)
{
	// 参数检查,内存分配
	CV_Assert(srcImage.depth() == CV_8U&&srcImage.channels() == 1);

	Mat LBPImage;
	int i,j, height, width;
	height = srcImage.rows;
	width = srcImage.cols;

	ComputeLBPFeatureVector_Uniform(srcImage, cellSize, LBPImage);   //求归一化后的LBP特征
	//cout<<"LBPImage"<<LBPImage<<endl;   //取值范围[0,58]

	//把LBPImage折算到[0,255]之间
	Mat LBPImage_255(1, LBPImage.cols, CV_8UC1, Scalar(0));
	for (i=0; i<LBPImage.cols; i++)
	{
		LBPImage_255.at<uchar>(0,i) = (uchar)(LBPImage.at<float>(0,i) * 255.0f);
	}
	//cout<<"LBPImage_255"<<endl<<LBPImage_255<<endl;

	int maxGaryDiff, averageGrad_xy;
	int averageGary = calAverageGary(srcImage, maxGaryDiff, averageGrad_xy);
	//cout<<"averageGary="<<averageGary<<",   maxGrayDiff="<<maxGaryDiff<<endl<<endl;

	int descriptorDim;
	descriptorDim = LBPImage.cols + 3;
	Mat extendLBPFeature_255 = Mat::zeros(1, descriptorDim, CV_8UC1); 

	for (i=0; i<LBPImage.cols; i++)
	{
		extendLBPFeature_255.at<uchar>(0,i) = LBPImage_255.at<uchar>(0,i);
	}
	extendLBPFeature_255.at<uchar>(0,LBPImage.cols) = averageGary;       //增加维度,存放平均像素
	extendLBPFeature_255.at<uchar>(0,LBPImage.cols+1) = maxGaryDiff;     //增加维度,存放最大灰度差
	extendLBPFeature_255.at<uchar>(0,LBPImage.cols+2) = averageGrad_xy;  //增加维度,存放平均梯度

	//把扩展LBP特征矩阵归一化
	extendLBPFeature = Mat(1, descriptorDim, CV_32FC1, Scalar(0)); 
	for(i=0; i<descriptorDim; i++)
	{
		extendLBPFeature.at<float>(0,i) = extendLBPFeature_255.at<uchar>(0,i)*1.0f/255;
	}
	//cout<<"extendLBPFeature: "<<endl<<extendLBPFeature<<endl;
}


int main(int argc, char ** argv)
{
	MySVM SVM;
	int descriptorDim;

	string buffer;
	string trainImg;
	vector<string> posSamples;
	vector<string> negSamples;
	vector<string> testSamples;
	int posSampleNum;
	int negSampleNum;
	int testSampleNum;
	double rho;

#ifdef TRAIN   //此开关开启,使用正负样本进行训练
	ifstream fInPos("..\\Inputs\\PositiveSample.txt");  //读取正样本
	ifstream fInNeg("..\\Inputs\\NegtiveSample.txt");   //读取负样本

	while (fInPos) //正样本读入imgPathList中
	{
		if(getline(fInPos, buffer))
			posSamples.push_back(buffer);
	}
	posSampleNum = posSamples.size();
	fInPos.close();

	while(fInNeg) //读取负样本
	{
		if (getline(fInNeg, buffer))
			negSamples.push_back(buffer);
	}
	negSampleNum = negSamples.size();
	fInNeg.close();

	Mat sampleFeatureMat; //样本特征向量矩阵
	Mat sampleLabelMat;   //样本标签

	//1、处理正样本
	for(int i = 0 ; i < posSampleNum; i++) 
	{
		Mat inputImg = imread(posSamples[i]);
		GaussianBlur(inputImg, inputImg, Size(3,3), 0);
		cout<<"processing "<<i<<"/"<<posSampleNum<<" "<<posSamples[i]<<endl;

		Size dsize = Size(12,30);
		Mat trainImg = Mat(dsize, CV_8UC1);
		resize(inputImg, trainImg, dsize);
		if (trainImg.channels()>1)	cvtColor(trainImg, trainImg, CV_BGR2GRAY);

		Mat dstImg(trainImg.rows, trainImg.cols, CV_8UC1, Scalar(0));
		vector<int> descriptor;

		//等价模式LBP特征计算
		Mat sigleFeatureMat;
		calExtendLBPFeature(trainImg, Size(3, 3), sigleFeatureMat);

		descriptorDim = sigleFeatureMat.cols;

		if(i == 0)//首次特殊处理根据检测到的维数确定特征矩阵的尺寸
		{
			sampleFeatureMat = Mat::zeros(posSampleNum + negSampleNum, descriptorDim, CV_32FC1);
			sampleLabelMat = Mat::zeros(posSampleNum + negSampleNum, 1, CV_32SC1);
		}

		sigleFeatureMat.row(0).copyTo(sampleFeatureMat.row(i));   //把sigleFeatureMat的第一列赋给sampleFeatureMat的第i列
		sampleLabelMat.at<int>(i, 0) = 1;
	}
	cout<<"extract posSampleFeature done"<<endl; 

	//2、处理负样本
	for(int i = 0 ; i < negSampleNum; i++)
	{
		Mat inputImg = imread(negSamples[i]);
		GaussianBlur(inputImg, inputImg, Size(3,3), 0);
		cout<<"processing "<<i<<"/"<<negSampleNum<<" "<<negSamples[i]<<endl;
		Size dsize = Size(12,30);
		Mat trainImg = Mat(dsize, CV_8UC1);
		resize(inputImg, trainImg, dsize);
		if (trainImg.channels()>1)	cvtColor(trainImg, trainImg, CV_BGR2GRAY);

		Mat dstImg(trainImg.rows, trainImg.cols, CV_8UC1, Scalar(0));
		Mat sigleFeatureMat;  //单行矩阵

		//12×30负样本,等价模式LBP特征计算(增加3维)
		calExtendLBPFeature(trainImg, Size(3, 3), sigleFeatureMat);

		sigleFeatureMat.row(0).copyTo(sampleFeatureMat.row(posSampleNum + i));   //把sigleFeatureMat赋给sampleFeatureMat的第i列
		sampleLabelMat.at<int>(posSampleNum + i, 0) = 0;                         //标签保存样本被划分的类型
	}
	cout<<"extract negSampleFeature done"<<endl; 

	ofstream foutFeature("SampleLBPFeatureMat.txt");   //保存特征向量文件
	for(int i = 0; i <  posSampleNum + negSampleNum; i++)
	{
		for(int j = 0; j < descriptorDim; j++)
		{
			foutFeature<<sampleFeatureMat.at<float>(i, j)<<" ";
		}
		foutFeature<<"\n";
	}
	foutFeature.close();
	cout<<"output posSample and negSample Feature done"<<endl; 

	CvTermCriteria criteria = cvTermCriteria(CV_TERMCRIT_ITER, 1000, FLT_EPSILON);
	CvSVMParams params(CvSVM::C_SVC, CvSVM::LINEAR, 0, 0.5, 0, 0.01, 0, 0, 0, criteria);  
	cout<<"SVM Training Start..."<<endl;
	SVM.train_auto(sampleFeatureMat, sampleLabelMat, Mat(), Mat(), params);
	SVM.save("SVM_Model.xml");
	cout<<"SVM Training Complete"<<endl;
#endif  //TRAIN


#ifndef TRAIN  //没定义TRAIN,直接使用训练好的模型
	SVM.load("SVM_Model.xml");//加载模型文件
#endif

	descriptorDim = SVM.get_var_count();
	int supportVectorNum = SVM.get_support_vector_count();
	cout<<"support vector num: "<< supportVectorNum <<endl;

	Mat alphaMat = Mat::zeros(1, supportVectorNum, CV_32FC1);
	Mat supportVectorMat = Mat::zeros(supportVectorNum, descriptorDim, CV_32FC1);
	Mat resultMat = Mat::zeros(1, descriptorDim, CV_32FC1);

	for (int i = 0; i < supportVectorNum; i++)  //复制支持向量矩阵
	{
		const float * pSupportVectorData = SVM.get_support_vector(i);
		for(int j = 0 ;j < descriptorDim; j++)
		{
			supportVectorMat.at<float>(i,j) = pSupportVectorData[j];
		}
	}

	double *pAlphaData = SVM.get_alpha_data();
	for (int i = 0; i < supportVectorNum; i++)   //复制函数中的alpha 记住决策公式Y= wx+b
	{
		alphaMat.at<float>(0, i) = pAlphaData[i];
	}

	resultMat = -1 * alphaMat * supportVectorMat; //alphaMat就是权重向量

	cout<<"描述子维数 "<<descriptorDim<<endl;
	vector<float> myDetector;
	for (int i = 0 ;i < descriptorDim; i++)
	{
		myDetector.push_back(resultMat.at<float>(0, i));
	}

	rho = SVM.get_rho_data();
	myDetector.push_back(rho);
	cout<<"检测子维数 "<<myDetector.size()<<endl;

	//保存检测子
	int minusNum = 0;
	int posNum = 0;

	ofstream foutDetector("HogDetectorForCarFace.txt");
	for (int i = 0 ;i < myDetector.size(); i++)
	{
		foutDetector<<myDetector[i]<<" ";	//cout<<myDetector[i]<<" ";
	}
	//cout<<endl<<"posNum "<<posNum<<endl;
	//cout<<endl<<"minusNum "<<minusNum<<endl;
	foutDetector.close();

	//测试部分
	ifstream fInTest("..\\Inputs\\testSample.txt");  //加载测试样本
	while (fInTest)
	{
		if(getline(fInTest, buffer))
		{
			testSamples.push_back( buffer);
		}
	}
	testSampleNum = testSamples.size();
	fInTest.close(); 

	VideoWriter outputVideo;
	char saveNameExt[1200], dispParams[1200];
	sprintf(saveNameExt, "..\\Demo.avi");
	outputVideo.open(saveNameExt, CV_FOURCC('M','P','4','2'),10,Size(300, 720),true); //保存处理结果成视频
	for (int i = 0; i < testSamples.size(); i++)
	{
		Mat testImg = imread(testSamples[i]);
		if(testImg.empty()) return -1;
		if(testImg.channels()>1)  cvtColor(testImg, testImg, CV_BGR2GRAY);

		imshow("原图", testImg);
		GaussianBlur(testImg, testImg, Size(3,3), 0);   //高斯滤波
		//imshow("高斯滤波后", testImg);
		//equalizeHist(testImg, testImg);  //直方图均衡
		//imshow("直方图均衡后", testImg);

		Mat copyImg = testImg.clone();
		if(copyImg.channels()<3)  cvtColor(copyImg, copyImg, CV_GRAY2BGR);

		Mat testImgNorm; 
		resize(testImg, testImgNorm, Size(300, 720));

		vector<Rect> found, foundFiltered;

		//设计滑窗遍历测试样本:计算每个滑窗内的LBP特征,给SVM预测,把输出为1的框画出来。
		int m, n;
		Mat slideWinImg(30, 12, CV_8UC1, Scalar(0));
		Mat dstImg(30, 12, CV_8UC1, Scalar(0));   //LBP图,可不输出
		Mat lideWinImgFeatureMat;
		Mat sigleFeatureMat = Mat::zeros(1, descriptorDim, CV_32FC1);  
		vector<int> descriptor;

		for(n=0; n<=testImgNorm.rows-30; n+=30) 
		{
			for(m=0; m<=testImgNorm.cols-12; m+=4)
			{
				slideWinImg = testImgNorm(Rect(m, n, 12, 30));
				if (slideWinImg.channels()>1)	cvtColor(slideWinImg, slideWinImg, CV_BGR2GRAY);
				
				Mat extendLBPFeature;
				calExtendLBPFeature(slideWinImg, Size(3, 3), extendLBPFeature);

				//使用训练的SVM模型预测测试样本
				int predictResult = SVM.predict(extendLBPFeature);
				//cout<<"SVM训练模型预测结果:"<<predictResult<<endl;

				if (predictResult==1)
				{
					rectangle(copyImg,Point(m,n),Point(m+12,n+30),Scalar(0,255,0),1,1,0);
				}
			}
		}
		outputVideo <<copyImg; 
		waitKey(100);
	}
	system("pause");
	return 0;
}

猜你喜欢

转载自blog.csdn.net/lyq_12/article/details/80711776