1. 程式人生 > >hog+svm 實現行人檢測(C++ opencv3.4)

hog+svm 實現行人檢測(C++ opencv3.4)

最近想學下傳統機器學習方法來實現目標檢測,從頭到尾下來記錄下自己的程式碼過程行人檢測加粗樣式 資料集準備:INRIA行人檢測資料集百度雲下載,http://pan.baidu.com/s/1eSdlw7g 下載完之後我們解壓可以看到資料集檔案分佈

INRIADATA{**加粗樣式**
    normalized_images{
        train{
            pos:96x160大小,訓練正樣本,需要crop中間的64x128大小。已經做過flip,即包含左右對稱的圖
            neg:大小不一,通常是幾百乘幾百,訓練負樣本,需要從每張圖中隨機crop 10個區域作為訓練負樣本
        }
    }
    original_images{
        train{
            pos:訓練正樣本,大小不一
            neg:訓練負樣本,大小不一
            annotations:標註資訊
        }
        test{
            pos:大小不一
            neg:大小不一
            annotations:標註資訊
        }
    }
}

進入資料夾,開啟Train,有neg負樣本,pos正樣本。負樣本的尺寸是437X292的,所以需要先處理一下,才能夠進行訓練。(傳統方法就是特麼煩),首先先將圖片名寫到一個txt檔案裡,方便讀取。

#encoding: UTF - 8
import os
import re
def createFileList(images_path, txt_save_path) : # 開啟圖片列表清單txt檔案
fw = open(txt_save_path, "w")    # 檢視圖片目錄下的檔案, 相當於shell指令ls
images_name = os.listdir(images_path)    # 遍歷所有檔名
for eachname in images_name : # 按照規則將內容寫入txt檔案中
fw.write(eachname + '\n')    # 列印成功資訊
print "生成txt檔案成功"    # 關閉fw
fw.close()  # 下面是相關變數定義的路徑
if __name__ == '__main__':     # txt存放目錄, 並且注意這邊的路徑有中文,所以要做一些變換。
	txt_path = u"F:\\train\\".encode('gbk')    # 圖片存放目錄
	images_path = u'F:\\cuhk03'.encode('gbk')    # 生成的圖片列表清單txt檔名
	txt_name = 'train.txt'    # 生成的圖片列表清單txt檔案的儲存目錄
	txt_save_path = txt_path + txt_name    # 生成txt檔案
	createFileList(images_path, txt_save_path)

裁剪影象 下面是處理程式碼(這裡預設你已經配置好了opencv環境)。

#include <iostream>  
#include <fstream> 
#include <stdlib.h> //srand()和rand()函式  
#include <time.h> //time()函式  
#include <opencv2/core/core.hpp> 
#include <opencv2/highgui/highgui.hpp> 
#include <opencv2/imgproc/imgproc.hpp> 
#include <opencv2/objdetect/objdetect.hpp> 
#include <opencv2/ml/ml.hpp>    
using namespace std;  
using namespace cv;  
int CropImageCount = 0; //裁剪出來的負樣本圖片個數  
int main() 
{   
	Mat src;     
	string ImgName;    
	char saveName[256];//裁剪出來的負樣本圖片檔名    
	ifstream fin("F:\\BaiduNetdiskDownload\\INRIADATA\\normalized_images\\train\\neg\\1.txt");//開啟原始負樣本圖片檔案列表     
	//一行一行讀取檔案列表     
	
	while(getline(fin,ImgName))     
	{       
		cout<<"處理:"<<ImgName<<endl;        
		ImgName = "F:\\BaiduNetdiskDownload\\INRIADATA\\normalized_images\\train\\neg\\" + ImgName;        
		src = imread(ImgName,1);//讀取圖片        
		//cout<<"寬:"<<src.cols<<",高:"<<src.rows<<endl;      
		//圖片大小應該能能至少包含一個64*128的視窗       
		if(src.cols >= 64 && src.rows >= 128)          
		{              
			srand(time(NULL));//設定隨機數種子  time(NULL)表示當前系統時間     
			//從每張圖片中隨機取樣10個64*128大小的不包含人的負樣本        
			for(int i=0; i<10; i++)           
			{              
				int x = ( rand() % (src.cols-64) ); //左上角x座標          
				int y = ( rand() % (src.rows-128) ); //左上角y座標     
				//cout<<x<<","<<y<<endl;                
				Mat imgROI = src(Rect(x,y,64,128));     
				sprintf(saveName,"D:/INRIAPerson/negphoto/noperson%06d.jpg",++CropImageCount);//生成裁剪出的負樣本圖片的檔名   
				imwrite(saveName, imgROI);//儲存檔案        
			}      
		}    
	}    
	return 0;
} 

訓練測試

接下來就是訓練分類器以及生成yml 對測試樣本進行測試了。

#include <iostream> 
#include <fstream>  
#include <stdlib.h> //srand()和rand()函式 
#include <time.h> //time()函式 
#include <opencv2/core/core.hpp> 
#include <opencv2/highgui/highgui.hpp> 
#include <opencv2/imgproc/imgproc.hpp>  
#include <opencv2/objdetect/objdetect.hpp> 
#include <opencv2/ml/ml.hpp>  
#include<opencv2\opencv.hpp>
using namespace std;
using namespace cv;
using namespace ml;
void get_svm_detector(const Ptr< SVM > & svm, vector< float > & hog_detector); 
void convert_to_ml(const std::vector< Mat > & train_samples, Mat& trainData);
void load_images(const String & dirname, vector< Mat > & img_lst, bool showImages);
void sample_neg(const vector< Mat > & full_neg_lst, vector< Mat > & neg_lst, const Size & size); 
void computeHOGs(const Size wsize, const vector< Mat > & img_lst, vector< Mat > & gradient_lst);
//函式定義
///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////
void get_svm_detector(const Ptr< SVM >& svm, vector< float > & hog_detector)
{	// get the support vectors	
	Mat sv = svm->getSupportVectors();	
	const int sv_total = sv.rows;	// get the decision function	
	Mat alpha, svidx;	
	double rho = svm->getDecisionFunction(0, alpha, svidx);
	CV_Assert(alpha.total() == 1 && svidx.total() == 1 && sv_total == 1);	//括號中的條件不滿足時,返回錯誤	
	CV_Assert((alpha.type() == CV_64F && alpha.at<double>(0) == 1.)||(alpha.type() == CV_32F && alpha.at<float>(0) == 1.f));
	CV_Assert(sv.type() == CV_32F);	hog_detector.clear(); 	
	hog_detector.resize(sv.cols + 1);	
	memcpy(&hog_detector[0], sv.ptr(), sv.cols * sizeof(hog_detector[0]));	//memcpy指的是c和c++使用的記憶體拷貝函式,memcpy函式的功能是從源src所指的記憶體地址的起始位置開始拷貝n個位元組到目標dest所指的記憶體地址的起始位置中。	
	hog_detector[sv.cols] = (float)-rho;
}
///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////


/** Convert training/testing set to be used by OpenCV Machine Learning algorithms.*
TrainData is a matrix of size (#samples x max(#cols,#rows) per samples), in 32FC1.*
Transposition of samples are made if needed.*/
///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////

void convert_to_ml(const vector< Mat > & train_samples, Mat& trainData)
{	
	//--Convert data	
	const int rows = (int)train_samples.size();	//行數等於訓練樣本個數	
	const int cols = (int)std::max(train_samples[0].cols, train_samples[0].rows);	//列數取樣本圖片中寬度與高度中較大的那一個	
	Mat tmp(1, cols, CV_32FC1); //< used for transposition if needed	
	trainData = Mat(rows, cols, CV_32FC1); 	
	for (size_t i = 0; i < train_samples.size(); ++i)	
	{		
		CV_Assert(train_samples[i].cols == 1 || train_samples[i].rows == 1); 	
		if (train_samples[i].cols == 1)		
		{			
			transpose(train_samples[i], tmp);			
			tmp.copyTo(trainData.row((int)i));		
		}	
		else if (train_samples[i].rows == 1)		
		{			
			train_samples[i].copyTo(trainData.row((int)i));		
		}	
	}
}	
///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////
void load_images(const String & dirname, vector< Mat > & img_lst, bool showImages = false)
{	
	//載入目錄下的圖片樣本	
	vector< String > files;	glob(dirname, files);		
	//返回一個包含有匹配檔案/目錄的陣列。出錯則返回false 	
	for (size_t i = 0; i < files.size(); ++i)	
	{		
		Mat img = imread(files[i]); // load the image		
		if (img.empty())            // invalid image, skip it.		
		{			
			cout << files[i] << " is invalid!" << endl;			
			continue;		
		} 		
		if (showImages)		
		{			
			imshow("image", img);			
			waitKey(1);		
		}		
		img_lst.push_back(img); //將Img壓入img_lst	
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////

void sample_neg(const vector< Mat > & full_neg_lst, vector< Mat > & neg_lst, const Size & size)
{	
	//該函式對每一個負樣本取樣出一個隨機的64*128尺寸的樣本,由於之前已經取樣過了,所以main函式中沒有使用該函式
	Rect box;	box.width = size.width;	//等於檢測器寬度	
	box.height = size.height;	//等於檢測器高度 	
	const int size_x = box.width;	
	const int size_y = box.height; 	
	srand((unsigned int)time(NULL));		//生成隨機數種子 
	for (size_t i = 0; i < full_neg_lst.size(); i++)	
	{	
		//對每個負樣本進行裁剪,隨機指定x,y,裁剪一個尺寸為檢測器大小的負樣本	
		box.x = rand() % (full_neg_lst[i].cols - size_x);		
		box.y = rand() % (full_neg_lst[i].rows - size_y);	
		Mat roi = full_neg_lst[i](box);		
		neg_lst.push_back(roi.clone());
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////


void computeHOGs(const Size wsize, const vector< Mat > & img_lst, vector< Mat > & gradient_lst)
{	
	//計算HOG特徵	
	HOGDescriptor hog;	
	hog.winSize = wsize; 
	Rect r = Rect(0, 0, wsize.width, wsize.height);
	r.x += (img_lst[0].cols - r.width) / 2;	//正樣本圖片的尺寸減去檢測器的尺寸,再除以2	
	r.y += (img_lst[0].rows - r.height) / 2; 	
	Mat gray;	vector< float > descriptors; 	
	for (size_t i = 0; i< img_lst.size(); i++)	
	{		
		cvtColor(img_lst[i](r), gray, COLOR_BGR2GRAY);		
		hog.compute(gray, descriptors, Size(8, 8), Size(0, 0));	//Size(8,8)為視窗移動步長,		
		gradient_lst.push_back(Mat(descriptors).clone());
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////

int test_trained_detector(String obj_det_filename, String test_dir, String videofilename)
{	
	//當videofilename為空,則只檢測圖片中的行人	
	cout << "Testing trained detector..." << endl;	
	HOGDescriptor hog;	hog.load(obj_det_filename); 	
	vector< String > files;	glob(test_dir, files); 
	int delay = 0;	VideoCapture cap; 
	if (videofilename != "")	
	{		
		cap.open(videofilename);	
	} 	
	obj_det_filename = "testing " + obj_det_filename;	
	namedWindow(obj_det_filename, WINDOW_NORMAL); 
	for (size_t i = 0;; i++)
	{		
		Mat img; 		
		if (cap.isOpened())		
		{			
			cap >> img;		
			delay = 1;		
		}		
		else if (i < files.size())		
		{			
			img = imread(files[i]);		
		} 	
		if (img.empty())		
		{			
			return 0;		
		} 		
		vector< Rect > detections;		
		vector< double > foundWeights; 		
		hog.detectMultiScale(img, detections, foundWeights);	
		for (size_t j = 0; j < detections.size(); j++)		
		{			
			if (foundWeights[j] < 0.5) 
				continue;	//清楚權值較小的檢測視窗			
			Scalar color = Scalar(0, foundWeights[j] * foundWeights[j] * 200, 0);		
			rectangle(img, detections[j], color, img.cols / 400 + 1);		
		} 		
		imshow(obj_det_filename, img); 		
		if (27 == waitKey(delay))		
		{			
			return 0;	
		}	
	}	
	return 0;
}
///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////


int main(int argc, char** argv)
{
	cout << "HELLO WORLD" << endl;
	const char* keys = 
	{
		"{help h|     | show help message}"		
		"{pd    |  D:/INRIAPerson/96X160H96/Train/pos   | path of directory contains possitive images}"		
		"{nd    |  D:/INRIAPerson/negphoto   | path of directory contains negative images}"		
		"{td    |  D:/INRIAPerson/Test/pos   | path of directory contains test images}"		
		"{tv    |     | test video file name}"		
		"{dw    |  64   | width of the detector}"		
		"{dh    |  128   | height of the detector}"		
		"{d     |false| train twice}"		
		"{t     |true| test a trained detector}"		
		"{v     |false| visualize training steps}"		
		"{fn    |D:/my_detector.yml| file name of trained SVM}" };

		CommandLineParser parser(argc, argv, keys);	//命令列函式,讀取keys中的字元, 其中key的格式為:名字 簡稱| 內容 |提示字元。 
		if (parser.has("help"))
		{		
			parser.printMessage();		
			exit(0);	
		} 	
		String pos_dir = parser.get< String >("pd");	//正樣本目錄	
		cout << pos_dir << endl;
		String neg_dir = parser.get< String >("nd");	//負樣本目錄	
		String test_dir = parser.get< String >("td");	//測試樣本目錄	
		String obj_det_filename = parser.get< String >("fn");	//訓練好的SVM檢測器檔名	
		String videofilename = parser.get< String >("tv");	//測試視訊	
		int detector_width = parser.get< int >("dw");	//檢測器寬度		
		int detector_height = parser.get< int >("dh");	//檢測器高度	
		bool test_detector = parser.get< bool >("t");	//測試訓練好的檢測器	
		bool train_twice = parser.get< bool >("d");		//訓練兩次	
		bool visualization = parser.get< bool >("v");	//訓練過程視覺化(建議false,不然爆炸)       
		//根據評論,以下5行程式碼在初次執行時,請註釋掉。該段程式碼是為了對已經訓練好的模型進行測試的,初次執行時,因為還未有任何模型引數,所以可能會報錯。	
		//if (test_detector)	//若為true,測對測試集進行測試	
		//{	
		//	test_trained_detector(obj_det_filename, test_dir, videofilename);	
		//	exit(0);	
		//}
		if (pos_dir.empty() || neg_dir.empty())	//檢測非空	
		{		
			parser.printMessage();	
			cout << "Wrong number of parameters.\n\n"		
				<< "Example command line:\n" 
				<< argv[0] 
				<< " -pd=/INRIAPerson/96X160H96/Train/pos -nd=/INRIAPerson/neg -td=/INRIAPerson/Test/pos -fn=HOGpedestrian96x160.yml -d\n"		
				<< "\nExample command line for testing trained detector:\n" 
				<< argv[0] 
				<< " -t -dw=96 -dh=160 -fn=HOGpedestrian96x160.yml -td=/INRIAPerson/Test/pos";	
			exit(1);	
		} 	
		vector< Mat > pos_lst,	//正樣本圖片向量		
			full_neg_lst,		//負樣本圖片向量		
			neg_lst,			//取樣後的負樣本圖片向量		
			gradient_lst;		//HOG描述符存入到該梯度資訊裡面
		vector< int > labels;	//標籤向量
		clog << "Positive images are being loaded...";
		load_images(pos_dir, pos_lst, visualization);	//載入圖片 pos正樣本的尺寸為96*160	
		if (pos_lst.size() > 0)	
		{		
			clog << "...[done]" << endl;	
		}	else	
		{	
			clog << "no image in " << pos_dir << endl;	
			return 1;	
		} 
		Size pos_image_size = pos_lst[0].size(); 
		//令尺寸變數pos_image_size=正樣本尺寸 
		//檢測所有正樣本是否具有相同尺寸	
		for (size_t i = 0; i < pos_lst.size(); ++i)	
		{	
			if (pos_lst[i].size() != pos_image_size)		
			{			
				cout << "All positive images should be same size!" << endl;		
				exit(1);	
			}	
		}
		pos_image_size = pos_image_size / 8 * 8; 	
		//令pos_image_size的尺寸為檢測器的尺寸	
		if (detector_width && detector_height)	
		{		
			pos_image_size = Size(detector_width, detector_height);	
		} 	labels.assign(pos_lst.size(), +1);             
		//assign()為labels分配pos_lst.size()大小的容器,用+1填充 表示為正樣本	
		const unsigned int old = (unsigned int)labels.size();	//舊標籤大小 	
		clog << "Negative images are being loaded...";	
		load_images(neg_dir, neg_lst, false);	//載入負樣本圖片	
		//sample_neg(full_neg_lst, neg_lst, pos_image_size);  	
		clog << "...[done]" << endl; 	
		labels.insert(labels.end(), neg_lst.size(), -1);	
		//在labels向量的尾部新增neg_lst.size()大小的容器,用-1填充 表示為負樣本	
		CV_Assert(old < labels.size());		//CV_Assert()作用:CV_Assert()若括號中的表示式值為false,則返回一個錯誤資訊。 	
		clog << "Histogram of Gradients are being calculated for positive images...";	
		computeHOGs(pos_image_size, pos_lst, gradient_lst);	//計算正樣本圖片的HOG特徵	
		clog << "...[done]" << endl; 	
		clog << "Histogram of Gradients are being calculated for negative images...";	
		computeHOGs(pos_image_size, neg_lst, gradient_lst);	//計算負樣本圖片的HOG特徵	
		clog << "...[done]" << endl;
		Mat train_data;	
		convert_to_ml(gradient_lst, train_data);	//轉化為ml所需的訓練資料形式 	
		clog << "Training SVM...";	
		Ptr< SVM > svm = SVM::create();	/* Default values to train SVM */	
		svm->setCoef0(0.0);	svm->setDegree(3);	
		svm->setTermCriteria(TermCriteria(CV_TERMCRIT_ITER + CV_TERMCRIT_EPS, 1000, 1e-3));
		svm->setGamma(0);	
		svm->setKernel(SVM::LINEAR);	
		//採用線性核函,其他的sigmoid 和RBF 可自行設定,其值由0-5。
		svm->setNu(0.5);	
		svm->setP(0.1); // for EPSILON_SVR, epsilon in loss function?	
		svm->setC(0.01); // From paper, soft classifier	
		svm->setType(SVM::EPS_SVR); // C_SVC; // EPSILON_SVR; // may be also NU_SVR; // do regression task	
		svm->train(train_data, ROW_SAMPLE, Mat(labels));	
		clog << "...[done]" << endl;
		//訓練兩次	if (train_twice)	
		{
			clog << "Testing trained detector on negative images. This may take a few minutes...";	
			HOGDescriptor my_hog;		
			my_hog.winSize = pos_image_size; 		// Set the trained svm to my_hog	
			vector< float > hog_detector;	
			get_svm_detector(svm, hog_detector);		
			my_hog.setSVMDetector(hog_detector); 		
			vector< Rect > detections;		
			vector< double > foundWeights; 	
			for (size_t i = 0; i < full_neg_lst.size(); i++)		
			{			
				my_hog.detectMultiScale(full_neg_lst[i], detections, foundWeights);		
				for (size_t j = 0; j < detections.size(); j++)			
				{			
					Mat detection = full_neg_lst[i](detections[j]).clone();			
					resize(detection, detection, pos_image_size);				
					neg_lst.push_back(detection);		
				}			
				if (visualization)			
				{				
					for (size_t j = 0; j < detections.size(); j++)		
					{					
						rectangle(full_neg_lst[i], detections[j], Scalar(0, 255, 0), 2);			
					}				
					imshow("testing trained detector on negative images", full_neg_lst[i]);			
					waitKey(5);			
				}		
			}	
			clog << "...[done]" << endl;
			labels.clear();		
			labels.assign(pos_lst.size(), +1);		
			labels.insert(labels.end(), neg_lst.size(), -1); 		
			gradient_lst.clear();		
			clog << "Histogram of Gradients are being calculated for positive images...";		
			computeHOGs(pos_image_size, pos_lst, gradient_lst);		
			clog << "...[done]" << endl; 	
			clog << "Histogram of Gradients are being calculated for negative images...";		
			computeHOGs(pos_image_size, neg_lst, gradient_lst);		
			clog << "...[done]" << endl; 		
			clog << "Training SVM again...";		
			convert_to_ml(gradient_lst, train_data);	
			svm->train(train_data, ROW_SAMPLE, Mat(labels));	
			clog << "...[done]" << endl;	
		}
		vector< float > hog_detector;	//定義hog檢測器	
		get_svm_detector(svm, hog_detector);	//得到訓練好的檢測器	
		HOGDescriptor hog;	hog.winSize = pos_image_size;	//視窗大小	
		hog.setSVMDetector(hog_detector);	
		hog.save(obj_det_filename);		//儲存分類器 	
		test_trained_detector(obj_det_filename, test_dir, videofilename);	//檢測訓練集 
		return 0;

}