初学机器学习,参考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;
}