HOG+SVM行人檢測的兩種方法
關於HOG+SVM,CSDN上有一些非常好的文章,這裡給出我覺得寫的比較好的幾篇,僅供大家參考
以上就是個人覺得寫的比較好的部落格,基本上將上面的部落格看懂了,HOG也比較理解了,如果還想輸入瞭解HOG,建議直接看OpenCV HOG的原始碼
下面,就說說使用OpenCV 中的HOG+SVM實現行人檢測的兩種方式
說明:程式執行環境為VS2013+OpenCV3.0
第一種
先說第一種方式,直接上程式碼:
///////////////////////////////////HOG+SVM識別方式2///////////////////////////////////////////////////
void Train()
{
////////////////////////////////讀入訓練樣本圖片路徑和類別///////////////////////////////////////////////////
//影象路徑和類別
vector<string> imagePath;
vector<int > imageClass;
int numberOfLine = 0;
string buffer;
ifstream trainingData(string(FILEPATH)+"TrainData.txt");
unsigned long n;
while (!trainingData.eof())
{
getline(trainingData, buffer);
if (!buffer.empty())
{
++numberOfLine;
if (numberOfLine % 2 == 0)
{
//讀取樣本類別
imageClass.push_back(atoi(buffer.c_str()));
}
else
{
//讀取影象路徑
imagePath.push_back(buffer);
}
}
}
//關閉檔案
trainingData.close();
/// /////////////////////////////獲取樣本的HOG特徵///////////////////////////////////////////////////
//樣本特徵向量矩陣
int numberOfSample = numberOfLine / 2;
Mat featureVectorOfSample(numberOfSample, 3780, CV_32FC1);//矩陣中每行為一個樣本
//樣本的類別
Mat classOfSample(numberOfSample, 1, CV_32SC1);
Mat convertedImage;
Mat trainImage;
// 計算HOG特徵
for (vector<string>::size_type i = 0; i <= imagePath.size() - 1; ++i)
{
//讀入圖片
Mat src = imread(imagePath[i], -1);
if (src.empty())
{
cout << "can not load the image:" << imagePath[i] << endl;
continue;
}
//cout << "processing:" << imagePath[i] << endl;
// 歸一化
resize(src, trainImage, Size(64, 128));
// 提取HOG特徵
HOGDescriptor hog(cvSize(64, 128), cvSize(16, 16), cvSize(8, 8), cvSize(8, 8), 9);
vector<float> descriptors;
double time1 = getTickCount();
hog.compute(trainImage, descriptors);//這裡可以設定檢測視窗步長,如果圖片大小超過64×128,可以設定winStride
double time2 = getTickCount();
double elapse_ms = (time2 - time1) * 1000 / getTickFrequency();
//cout << "HOG dimensions:" << descriptors.size() << endl;
//cout << "Compute time:" << elapse_ms << endl;
//儲存到特徵向量矩陣中
for (vector<float>::size_type j = 0; j <= descriptors.size() - 1; ++j)
{
featureVectorOfSample.at<float>(i, j) = descriptors[j];
}
//儲存類別到類別矩陣
//!!注意類別型別一定要是int 型別的
classOfSample.at<int>(i, 0) = imageClass[i];
}
///////////////////////////////////使用SVM分類器訓練///////////////////////////////////////////////////
//設定引數,注意Ptr的使用
Ptr<SVM> svm = SVM::create();
svm->setType(SVM::C_SVC);
svm->setKernel(SVM::LINEAR);//注意必須使用線性SVM進行訓練,因為HogDescriptor檢測函式只支援線性檢測!!!
svm->setTermCriteria(TermCriteria(CV_TERMCRIT_ITER, 1000, FLT_EPSILON));
//使用SVM學習
svm->train(featureVectorOfSample, ROW_SAMPLE, classOfSample);
//儲存分類器(裡面包括了SVM的引數,支援向量,α和rho)
svm->save(string(FILEPATH) + "Classifier.xml");
/*
SVM訓練完成後得到的XML檔案裡面,有一個數組,叫做support vector,還有一個數組,叫做alpha,有一個浮點數,叫做rho;
將alpha矩陣同support vector相乘,注意,alpha*supportVector,將得到一個行向量,將該向量前面乘以-1。之後,再該行向量的最後新增一個元素rho。
如此,變得到了一個分類器,利用該分類器,直接替換opencv中行人檢測預設的那個分類器(cv::HOGDescriptor::setSVMDetector()),
*/
//獲取支援向量機:矩陣預設是CV_32F
Mat supportVector = svm->getSupportVectors();//
//獲取alpha和rho
Mat alpha;//每個支援向量對應的引數α(拉格朗日乘子),預設alpha是float64的
Mat svIndex;//支援向量所在的索引
float rho = svm->getDecisionFunction(0, alpha, svIndex);
//轉換型別:這裡一定要注意,需要轉換為32的
Mat alpha2;
alpha.convertTo(alpha2, CV_32FC1);
//結果矩陣,兩個矩陣相乘
Mat result(1, 3780, CV_32FC1);
result = alpha2*supportVector;
//乘以-1,這裡為什麼會乘以-1?
//注意因為svm.predict使用的是alpha*sv*another-rho,如果為負的話則認為是正樣本,在HOG的檢測函式中,使用rho+alpha*sv*another(another為-1)
for (int i = 0; i < 3780; ++i)
result.at<float>(0, i) *= -1;
//將分類器儲存到檔案,便於HOG識別
//這個才是真正的判別函式的引數(ω),HOG可以直接使用該引數進行識別
FILE *fp = fopen((string(FILEPATH) + "HOG_SVM.txt").c_str(), "wb");
for (int i = 0; i<3780; i++)
{
fprintf(fp, "%f \n", result.at<float>(0,i));
}
fprintf(fp, "%f", rho);
fclose(fp);
}
// 使用訓練好的分類器識別
void Detect()
{
Mat img;
FILE* f = 0;
char _filename[1024];
// 獲取測試圖片檔案路徑
f = fopen((string(FILEPATH) + "TestData.txt").c_str(), "rt");
if (!f)
{
fprintf(stderr, "ERROR: the specified file could not be loaded\n");
return;
}
//載入訓練好的判別函式的引數(注意,與svm->save儲存的分類器不同)
vector<float> detector;
ifstream fileIn(string(FILEPATH) + "HOG_SVM.txt", ios::in);
float val = 0.0f;
while (!fileIn.eof())
{
fileIn >> val;
detector.push_back(val);
}
fileIn.close();
//設定HOG
HOGDescriptor hog;
hog.setSVMDetector(detector);// 使用自己訓練的分類器
//hog.setSVMDetector(HOGDescriptor::getDefaultPeopleDetector());//可以直接使用05 CVPR已訓練好的分類器,這樣就不用Train()這個步驟了
namedWindow("people detector", 1);
// 檢測圖片
for (;;)
{
// 讀取檔名
char* filename = _filename;
if (f)
{
if (!fgets(filename, (int)sizeof(_filename)-2, f))
break;
//while(*filename && isspace(*filename))
// ++filename;
if (filename[0] == '#')
continue;
//去掉空格
int l = (int)strlen(filename);
while (l > 0 && isspace(filename[l - 1]))
--l;
filename[l] = '\0';
img = imread(filename);
}
printf("%s:\n", filename);
if (!img.data)
continue;
fflush(stdout);
vector<Rect> found, found_filtered;
double t = (double)getTickCount();
// run the detector with default parameters. to get a higher hit-rate
// (and more false alarms, respectively), decrease the hitThreshold and
// groupThreshold (set groupThreshold to 0 to turn off the grouping completely).
//多尺度檢測
hog.detectMultiScale(img, found, 0, Size(8, 8), Size(32, 32), 1.05, 2);
t = (double)getTickCount() - t;
printf("detection time = %gms\n", t*1000. / cv::getTickFrequency());
size_t i, j;
//去掉空間中具有內外包含關係的區域,保留大的
for (i = 0; i < found.size(); i++)
{
Rect r = found[i];
for (j = 0; j < found.size(); j++)
if (j != i && (r & found[j]) == r)
break;
if (j == found.size())
found_filtered.push_back(r);
}
// 適當縮小矩形
for (i = 0; i < found_filtered.size(); i++)
{
Rect r = found_filtered[i];
// the HOG detector returns slightly larger rectangles than the real objects.
// so we slightly shrink the rectangles to get a nicer output.
r.x += cvRound(r.width*0.1);
r.width = cvRound(r.width*0.8);
r.y += cvRound(r.height*0.07);
r.height = cvRound(r.height*0.8);
rectangle(img, r.tl(), r.br(), cv::Scalar(0, 255, 0), 3);
}
imshow("people detector", img);
int c = waitKey(0) & 255;
if (c == 'q' || c == 'Q' || !f)
break;
}
if (f)
fclose(f);
return;
}
void HOG_SVM2()
{
//如果使用05 CVPR提供的預設分類器,則不需要Train(),直接使用Detect檢測圖片
Train();
Detect();
}
int main()
{
//HOG+SVM識別方式1:直接輸出類別
//HOG_SVM1();
//HOG+SVM識別方式2:輸出圖片中的存在目標的矩形
HOG_SVM2();
}
這裡我想說明一下TrainData.txt,這個檔案放置了所有樣本的路徑和類別,如下:
關
於如何讀取正負樣本的路徑到txt檔案,可以使用批處理檔案,批處理檔案我上傳到了CSDN,大家可以去下載
點選下載
正負樣本至少保證有1000,不能太少,否則效果就不好了,其中HOG_SVM.txt裡面包含了判別函式的引數,這個引數可以直接給HOG用
下面就是我的測試效果:
檢測效果還可以.
測試圖片我也上傳到網上了
點選下載
當然你也可以不用自己訓練分類器,直接使用OpenCV自帶的分類器,OpenCV自帶的分類器使用的是05年CVPR那篇文章中作者訓練好的分類器,下面我們就來看看效果:
圖中可以看出,OpenCV自帶的分類器效果要比自己訓練的好,主要原因大概有以下幾點
1.訓練樣本不足,我的正負樣本才900多
2.正樣本圖片不夠清晰,導致特徵提取有比較大的誤差
最近有人在執行部落格中程式的時候出現了問題,讓我看看程式,我也不太清楚什麼問題,所以我將整個程式和程式測試資料打包了一下,上傳到了CSDN上。
點選下載
解壓後,將”Pedestrians64x128”資料夾放置在D盤根目錄,然後使用HOG.cpp新建一個工程,直接可以執行。
注:環境是VS2013+OpenCV3.0.0,Release版本
第二種
下面說說第二種方式,第二種方式就是傳統的方式了,就是對於測試樣本,提取特徵,然後使用訓練好的分類器進行識別,程式碼
///////////////////////////////////HOG+SVM識別方式1///////////////////////////////////////////////////
void HOG_SVM1()
{
////////////////////////////////讀入訓練樣本圖片路徑和類別///////////////////////////////////////////////////
//影象路徑和類別
vector<string> imagePath;
vector<int> imageClass;
int numberOfLine = 0;
string buffer;
ifstream trainingData(string(FILEPATH) + "TrainData.txt", ios::in);
unsigned long n;
while (!trainingData.eof())
{
getline(trainingData, buffer);
if (!buffer.empty())
{
++numberOfLine;
if (numberOfLine % 2 == 0)
{
//讀取樣本類別
imageClass.push_back(atoi(buffer.c_str()));
}
else
{
//讀取影象路徑
imagePath.push_back(buffer);
}
}
}
trainingData.close();
////////////////////////////////獲取樣本的HOG特徵///////////////////////////////////////////////////
//樣本特徵向量矩陣
int numberOfSample = numberOfLine / 2;
Mat featureVectorOfSample(numberOfSample, 3780, CV_32FC1);//矩陣中每行為一個樣本
//樣本的類別
Mat classOfSample(numberOfSample, 1, CV_32SC1);
//開始計算訓練樣本的HOG特徵
for (vector<string>::size_type i = 0; i <= imagePath.size() - 1; ++i)
{
//讀入圖片
Mat src = imread(imagePath[i], -1);
if (src.empty())
{
cout << "can not load the image:" << imagePath[i] << endl;
continue;
}
cout << "processing" << imagePath[i] << endl;
//縮放
Mat trainImage;
resize(src, trainImage, Size(64, 128));
//提取HOG特徵
HOGDescriptor hog(Size(64, 128), Size(16, 16), Size(8, 8), Size(8, 8), 9);
vector<float> descriptors;
hog.compute(trainImage, descriptors);//這裡可以設定檢測視窗步長,如果圖片大小超過64×128,可以設定winStride
cout << "HOG dimensions:" << descriptors.size() << endl;
//儲存特徵向量矩陣中
for (vector<float>::size_type j = 0; j <= descriptors.size() - 1; ++j)
{
featureVectorOfSample.at<float>(i, j) = descriptors[j];
}
//儲存類別到類別矩陣
//!!注意類別型別一定要是int 型別的
classOfSample.at<int>(i, 0) = imageClass[i];
}
///////////////////////////////////使用SVM分類器訓練///////////////////////////////////////////////////
//設定引數
//參考3.0的Demo
Ptr<SVM> svm = SVM::create();
svm->setKernel(SVM::RBF);
svm->setType(SVM::C_SVC);
svm->setC(10);
svm->setCoef0(1.0);
svm->setP(1.0);
svm->setNu(0.5);
svm->setTermCriteria(TermCriteria(CV_TERMCRIT_EPS, 1000, FLT_EPSILON));
//使用SVM學習
svm->train(featureVectorOfSample, ROW_SAMPLE, classOfSample);
//儲存分類器
svm->save("Classifier.xml");
///////////////////////////////////使用訓練好的分類器進行識別///////////////////////////////////////////////////
vector<string> testImagePath;
ifstream testData(string(FILEPATH) + "TestData.txt", ios::out);
while (!testData.eof())
{
getline(testData, buffer);
//讀取
if (!buffer.empty())
testImagePath.push_back(buffer);
}
testData.close();
ofstream fileOfPredictResult(string(FILEPATH) + "PredictResult.txt"); //最後識別的結果
for (vector<string>::size_type i = 0; i <= testImagePath.size() - 1; ++i)
{
//讀取測試圖片
Mat src = imread(testImagePath[i], -1);
if (src.empty())
{
cout << "Can not load the image:" << testImagePath[i] << endl;
continue;
}
//縮放
Mat testImage;
resize(src, testImage, Size(64, 64));
//測試圖片提取HOG特徵
HOGDescriptor hog(cvSize(64, 64), cvSize(16, 16), cvSize(8, 8), cvSize(8, 8), 9);
vector<float> descriptors;
hog.compute(testImage, descriptors);
cout << "HOG dimensions:" << descriptors.size() << endl;
Mat featureVectorOfTestImage(1, descriptors.size(), CV_32FC1);
for (int j = 0; j <= descriptors.size() - 1; ++j)
{
featureVectorOfTestImage.at<float>(0, j) = descriptors[j];
}
//對測試圖片進行分類並寫入檔案
int predictResult = svm->predict(featureVectorOfTestImage);
char line[512];
//printf("%s %d\r\n", testImagePath[i].c_str(), predictResult);
std::sprintf(line, "%s %d\n", testImagePath[i].c_str(), predictResult);
fileOfPredictResult << line;
}
fileOfPredictResult.close();
}
int main()
{
//HOG+SVM識別方式1:直接輸出類別
HOG_SVM1();
//HOG+SVM識別方式2:輸出圖片中的存在目標的矩形
//HOG_SVM2();
}
大家可以分別使用自己的資料集測試一下上面的兩種方式,如果有上面疑問,歡迎留言討論
非常感謝您的閱讀,如果您覺得這篇文章對您有幫助,歡迎掃碼進行讚賞。