HOG + SVM 做目標檢測、車輛檢測

所屬知識點:Computer Vision:Feature Extraction;Classifier;Object Detection
微信公衆號:“RoboticsCV”(微信號:ModernRobotics)即將運營
歸納和總結機器學習技術的庫:ViolinLee/ML_notes


關鍵概念:梯度計算;直方圖統計的方向單元劃分(Orientation binning);描述器區塊(Descripter blocks);
                  區間歸一化(Block normalization);HOG 特徵;SVM 分類器——目標識別;

1. HOG 特徵
       方向梯度直方圖英語:Histogram of oriented gradient,簡稱HOG)是應用在計算機視覺和圖像處理領域,用於目標檢測特徵描述器(feature descriptor)。這項技術是用來計算局部圖像梯度的方向信息的統計值。這種方法跟邊緣方向直方圖(edge orientation histograms)、尺度不變特徵變換(scale-invariant feature transform descriptors,簡稱 SIFT)以及形狀上下文方法( shape contexts)有很多相似之處,但與它們的不同點是:HOG描述器是在一個網格密集的大小統一的細胞單元(dense grid of uniformly spaced cells)上計算,而且爲了提高性能,還採用了重疊的局部對比度歸一化(overlapping local contrast normalization)技術。HOG 特徵結合 SVM 分類器已經被廣泛應用於圖像識別中,尤其在行人檢測中獲得了極大的成功。
       HOG 特徵的基礎:圖像梯度直方圖,具體來說就是梯度方向的分佈圖,因爲我們更加關注圖像上的形狀和紋理。爲了觀察這些梯度的空間分佈,需要把圖像分成網格,並由此計算多個直方圖。

       HOG 的基本思路:首先將圖像分成小的連通區域,我們把它叫細胞單元。然後採集細胞單元中各像素點的梯度的或邊緣的方向直方圖。最後把這些直方圖組合起來就可以構成特徵描述器。

       HOG 提升性能的方式:把局部直方圖在圖像的更大的範圍內(稱爲 block)進行對比度歸一化(contrast-normalized)。通過這個歸一化後,能對光照變化和陰影獲得更好的效果。

       HOG 特徵提取算法的實現過程:

  • 1)圖像灰度化;
  • 2)採用 Gamma 校正法對輸入圖像進行顏色空間的標準化(歸一化)。目的是調節圖像的對比度,降低圖像局部的陰影和光照變化所造成的影響,同時可以抑制噪音的干擾;
  • 3)計算圖像每個像素的梯度(包括大小和方向)。主要是爲了捕獲輪廓信息,同時進一步弱化光照的干擾。
  • 4)Spatial / Orientation Binning:將圖像劃分成小 cells(例如:8像素×8像素/cell);
  • 5)統計每個 cell 的梯度直方圖,即可形成每個 cell 的 descriptor;
  • 6)將每幾個 cell 組成一個 block(例如3*3個cell/block),一個 block 內所有 cell 的特徵 descriptor 串聯起來便得到該 block 的 HOG feature descriptor。
  • 7)將圖像內的所有 block 的 HOG feature descriptor 串聯起來就可以得到該圖像的 HOG feature descriptor,這個就是最終可供分類使用的特徵向量了。

       圖像的 HOG 特徵維度:假設圖像尺寸爲 64×64;每個 cell 爲 8×8;每個 block 包含 2×2 的 cell,即爲 16×16;步長爲 1 個 cell,共有 7×7=49 個 block;最終得到的特徵向量的維度是:49×(2×2×9)=1764,這就是圖像的 HOG feature descriptor。
       (注:上面 2×2×9 的由來?我們計算方向梯度直方圖是針對每個 cell 進行計算的。方向的值會被分割成多個 bin。通常只考慮梯度的方向而不考慮正負。這裏的方向值範圍是 0°~180°。採用 9 個 bin 的直方圖,即方向值得分割間距爲 20°。每個 cell 的梯度向量產生一個 bin,該 bin 的權重對應梯度的幅值。)
       由此可見,圖像 HOG 模型的向量的維度非常高。這個向量就代表了圖像的特徵,可用於各種物體的分類。爲此,我們需要一種能處理這種高維向量的機器學習方法。

       HOG 特徵的可視化:HOG 是根據單元格創建的,這些單元格組合成區塊,並且區塊之間可以重疊,因此很難對它進行直觀顯示。不過可以通過顯示每個單元格的直方圖來表示 HOG。顯示方向直方圖時,不使用柱狀圖,而是採用更加直觀的星形圖,每個線條的方向與 bin 對應,長度與 bin 的數量成正比。可以用這種方法在圖像上繪製 HOG。
在 Matlab 中提取 HOG 特徵並進行可視化:

                                                                                                 原圖像
Matlab 代碼:

img = imread('cameraman.tif');

[featureVector,hogVisualization] = extractHOGFeatures(img);

figure;
imshow(img); 
hold on;
plot(hogVisualization);

在 OpenCV 中進行可視化:

                                                                                                 原圖像
OpenCV 代碼(C++):

int main()
{
	cv::Mat image = imread("E:/CV/OpenCV_zhaizhigang/code_hog_svm/girl.jpg", cv::IMREAD_GRAYSCALE);
	cv::imshow("Original image", image);

	cv::HOGDescriptor hog(cv::Size((image.cols / 16) * 16, (image.rows / 16) * 16), // size of the window
		cv::Size(16, 16),    // block size
		cv::Size(16, 16),    // block stride
		cv::Size(4, 4),      // cell size
		9);                  // number of bins

	std::vector<float> descriptors;

	// Draw a representation of HOG cells
	cv::Mat hogImage = image.clone();
	drawHOGDescriptors(image, hogImage, cv::Size(16, 16), 9);
	cv::imshow("HOG image", hogImage);

    cv::waitKey(0);
}


2. SVM 簡述
       SVM 基於強大的數學工具,在處理超高維空間的特徵效果很好。實踐表明,當特徵空間的維度超過樣本數量時,SVM 的效果是最好的。此外,SVM 佔用內存很少,因爲它只需要存放支持向量(而最近鄰法等算法則需要將全部樣本點存放在內存中)。構建分類器時,將方向梯度直方圖和 SVM 結合使用的效果很好。原因之一是 HOG 可以看作是一個魯棒的高維描述子,能準確反映一個類別的本質特徵。
       現在介紹一個提取 STOP 較通標誌 HOG 特徵,並訓練 SVM 的例子,例子摘自《OpenCV 3 Computer Vision Applicatioin Programming Cookbook》第14章節。

代碼如下(trainSVM.cpp):

int main()
{
	// generate the filename
	std::vector<std::string> imgs;
	std::string prefix = "E:/CV/OpenCV_quick_learning/projects/object_detection_hog_svm/stopsamples/stop";
	std::string ext = ".png";

	// loading 8 positive samples
	std::vector<cv::Mat> positives;

	for (long i = 0; i < 8; i++) {

		std::string name(prefix);
		std::ostringstream ss; ss << std::setfill('0') << std::setw(2) << i; name += ss.str();
		name += ext;

		positives.push_back(cv::imread(name, cv::IMREAD_GRAYSCALE));
	}

	// the first 8 positive samples
	cv::Mat posSamples(2 * positives[0].rows, 4 * positives[0].cols, CV_8U);
	for (int i = 0; i < 2; i++)
		for (int j = 0; j < 4; j++) {

			positives[i * 4 + j].copyTo(posSamples(cv::Rect(j*positives[i * 4 + j].cols, i*positives[i * 4 + j].rows, positives[i * 4 + j].cols, positives[i * 4 + j].rows)));

		}

	cv::imshow("Positive samples", posSamples);


	// loading 8 negative samples
	std::string nprefix = "E:/CV/OpenCV_quick_learning/projects/object_detection_hog_svm/stopSamples/neg";
	std::vector<cv::Mat> negatives;

	for (long i = 0; i < 8; i++) {

		std::string name(nprefix);
		std::ostringstream ss; ss << std::setfill('0') << std::setw(2) << i; name += ss.str();
		name += ext;

		negatives.push_back(cv::imread(name, cv::IMREAD_GRAYSCALE));
	}

	// the first 8 negative samples
	cv::Mat negSamples(2 * negatives[0].rows, 4 * negatives[0].cols, CV_8U);
	for (int i = 0; i < 2; i++)
		for (int j = 0; j < 4; j++) {

			negatives[i * 4 + j].copyTo(negSamples(cv::Rect(j*negatives[i * 4 + j].cols, i*negatives[i * 4 + j].rows, negatives[i * 4 + j].cols, negatives[i * 4 + j].rows)));
		}

	cv::imshow("Negative samples", negSamples);

	// The HOG descriptor for stop sign detection
	cv::HOGDescriptor hogDesc(positives[0].size(), // size of the window
		cv::Size(8, 8),    // block size
		cv::Size(4, 4),    // block stride
		cv::Size(4, 4),    // cell size
		9);                // number of bins

						   // compute first descriptor 
	std::vector<float> desc;
	hogDesc.compute(positives[0], desc);

	std::cout << "Positive sample size: " << positives[0].rows << "x" << positives[0].cols << std::endl;
	std::cout << "HOG descriptor size: " << desc.size() << std::endl;

	// the matrix of sample descriptors
	int featureSize = desc.size();
	int numberOfSamples = positives.size() + negatives.size();
	// create the matrix that will contain the samples HOG
	cv::Mat samples(numberOfSamples, featureSize, CV_32FC1);

	// fill first row with first descriptor
	for (int i = 0; i < featureSize; i++)
		samples.ptr<float>(0)[i] = desc[i];

	// compute descriptor of the positive samples
	for (int j = 1; j < positives.size(); j++) {
		hogDesc.compute(positives[j], desc);
		// fill the next row with current descriptor
		for (int i = 0; i < featureSize; i++)
			samples.ptr<float>(j)[i] = desc[i];
	}

	// compute descriptor of the negative samples
	for (int j = 0; j < negatives.size(); j++) {
		hogDesc.compute(negatives[j], desc);
		// fill the next row with current descriptor
		for (int i = 0; i < featureSize; i++)
			samples.ptr<float>(j + positives.size())[i] = desc[i];
	}

	// Create the labels
	cv::Mat labels(numberOfSamples, 1, CV_32SC1);
	// labels of positive samples
	labels.rowRange(0, positives.size()) = 1.0;
	// labels of negative samples
	labels.rowRange(positives.size(), numberOfSamples) = -1.0;

	// create SVM classifier
	cv::Ptr<cv::ml::SVM> svm = cv::ml::SVM::create();
	svm->setType(cv::ml::SVM::C_SVC);
	svm->setKernel(cv::ml::SVM::LINEAR);

	// prepare the training data
	cv::Ptr<cv::ml::TrainData> trainingData =
		cv::ml::TrainData::create(samples, cv::ml::SampleTypes::ROW_SAMPLE, labels);

	// SVM training
	svm->train(trainingData);

	cv::Mat queries(4, featureSize, CV_32FC1);

	// fill the rows with query descriptors
	hogDesc.compute(cv::imread("E:/CV/OpenCV_quick_learning/projects/object_detection_hog_svm/stopSamples/stop08.png", cv::IMREAD_GRAYSCALE), desc);
	for (int i = 0; i < featureSize; i++)
		queries.ptr<float>(0)[i] = desc[i];
	hogDesc.compute(cv::imread("E:/CV/OpenCV_quick_learning/projects/object_detection_hog_svm/stopSamples/stop09.png", cv::IMREAD_GRAYSCALE), desc);
	for (int i = 0; i < featureSize; i++)
		queries.ptr<float>(1)[i] = desc[i];
	hogDesc.compute(cv::imread("E:/CV/OpenCV_quick_learning/projects/object_detection_hog_svm/stopSamples/neg08.png", cv::IMREAD_GRAYSCALE), desc);
	for (int i = 0; i < featureSize; i++)
		queries.ptr<float>(2)[i] = desc[i];
	hogDesc.compute(cv::imread("E:/CV/OpenCV_quick_learning/projects/object_detection_hog_svm/stopSamples/neg09.png", cv::IMREAD_GRAYSCALE), desc);
	for (int i = 0; i < featureSize; i++)
		queries.ptr<float>(3)[i] = desc[i];
	cv::Mat predictions;

	// Test the classifier with the last two pos and neg samples
	svm->predict(queries, predictions);

	for (int i = 0; i < 4; i++)
		std::cout << "query: " << i << ": " << ((predictions.at<float>(i) < 0.0) ? "Negative" : "Positive") << std::endl;

    cv::waitKey(0);
}

訓練集正樣本包含八張 STOP 較通標誌圖片:


訓練集負樣本包含八張其他類型圖片:

預測結果(分別預測正、負樣本的最後兩張圖片):


3. HOG 優缺點
HOG 的優點:

  • 核心思想是所檢測的局部物體外形能夠被梯度或邊緣方向的分佈所描述,HOG 能較好地捕捉局部形狀信息,對幾何和光學變化都有很好的不變性
  • HOG 是在密集採樣的圖像塊中求取的,在計算得到的 HOG 特徵向量中隱含了該塊與檢測窗口之間的空間位置關係

HOG 的缺點:

  • 很難處理遮擋問題,人體姿勢動作幅度過大或物體方向改變也不易檢測(這個問題後來在DPM中採用可變形部件模型的方法得到了改善); 
  • 跟SIFT相比,HOG 沒有選取主方向,也沒有旋轉梯度方向直方圖,因而本身不具有旋轉不變性,其旋轉不變性是通過採用不同旋轉方向的訓練樣本來實現的; 
  • 跟SIFT相比,HOG 本身不具有尺度不變性,其尺度不變性是通過縮放檢測窗口圖像的大小來實現的; 
  • 由於梯度的性質,HOG 對噪點相當敏感,在實際應用中,在 Block 和 Cell 劃分之後,對於得到各個像區域中,有時候還會做一次高斯平滑去除噪點。


4. HOG 用於目標檢測
4.1 人物檢測(靜態圖像)

OpenCV 代碼如下(直接使用 cv::HOGDescriptor peopleHog 中的 SVM 分類器:peopleHog.setSVMDetector()):

int main() {
	// People detection
	cv::Mat myImage = imread("E:/CV/OpenCV_quick_learning/projects/object_detection_hog_svm/person.jpg", cv::IMREAD_GRAYSCALE);

	// create the detector
	std::vector<cv::Rect> peoples;
	cv::HOGDescriptor peopleHog;
	peopleHog.setSVMDetector(cv::HOGDescriptor::getDefaultPeopleDetector());
	// detect peoples oin an image
	peopleHog.detectMultiScale(myImage, // input image
		peoples, // ouput list of bounding boxes 
		0,       // threshold to consider a detection to be positive 
		cv::Size(4, 4),   // window stride 
		cv::Size(32, 32), // image padding
		1.1,              // scale factor
		2);               // grouping threshold (0 means no grouping) 

						  // draw detections on image
	std::cout << "Number of peoples detected: " << peoples.size() << std::endl;
	for (int i = 0; i < peoples.size(); i++)
		cv::rectangle(myImage, peoples[i], cv::Scalar(255, 255, 255), 2);

	cv::imshow("People detection", myImage);
	cv::imwrite("E:/CV/OpenCV_quick_learning/projects/object_detection_hog_svm/people_detection.jpg", myImage);

	cv::waitKey(0);
}


4.2 車輛檢測(視頻流)
       該例子來自 Udacity 自動駕駛納米學位的一個車輛檢測的項目,在文章末尾放有鏈接,有興趣的同學可以自行探索解決。下圖是解決結果:

       項目的流程非常具有借鑑性,大致如下:

  • 提取有標籤圖像訓練集的 HOG 特徵,並訓練線性支持向量機分類器;
  • 實現滑動窗口技術,並使用訓練的 SVM 分類器搜索圖像中的車輛;
  • 在視頻流上運行上述過程,並逐幀創建循環檢測的熱圖,以移除異常值並跟蹤檢測到的車輛;
  • 最後,估計檢測到的車輛的邊框(bounding box)。


5. 參考和推薦
HOG 特徵學習總結
Histogram of Oriented Gradients:HOG 的 OpenCV 教程
CarND-Vehicle-Detection:Udacity 車輛檢測項目實現
OpenCV3CookbookOpenCV 3 Computer Vision Applicatioin Programming Cookbook 代碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章