在上次的筆記OpenCV4學習筆記(42)中,使用OpenCV自帶的HOG+SVM行人檢測模型來對圖像中的行人進行檢測,而在實際應用中我們更多的是需要針對自定義對象進行檢測,所以今天就來整理一下在OpenCV中如何基於HOG特徵與SVM線性分類器來實現對我們自定義對象的檢測。
首先我們需要訓練針對自定義對象的SVM模型,通過提取樣本圖像的HOG特徵描述子,來生成樣本的特徵數據,再通過SVM線性分類器對這些特徵數據進行分類學習與訓練,並且還可以將訓練結果保存爲 .xml 或 .yml 模型文件,這樣以後就可以通過加載這些模型來實現對同一種對象的檢測。
訓練自定義對象的SVM模型,其步驟可以大致分爲如下幾步:
(1)讀取我們用來訓練的正負樣本圖像,並組織成爲訓練樣本集;
(2)分別計算正負樣本中每張圖像的HOG特徵描述子,並進行標註,從而生成樣本數據的標籤集;
(3)對訓練樣本集進行格式轉化,變成SVM分類器所能接受的樣本格式;
(4)通過OpenCV的SVM分類器進行模型的訓練,並將訓練好的模型保存下來。
在讀取樣本圖像時,我們使用glob()
這個API來對同一文件夾中的多張圖像進行讀取並轉化爲string類型,注意讀取進來的是每張圖像的路徑。隨後就可以通過imread()
來循環讀取所有圖像,將string類型數據轉化爲Mat類型。
接着利用讀取進來的Mat類型數據來製作正負樣本圖像數據集,注意這裏可以把樣本圖像縮放爲Size(64,128)
的尺寸大小,剛好和計算HOG特徵描述子時所默認採用的開窗尺寸大小一致。
接着我們就需要對每一張樣本圖像進行HOG特徵描述子的計算,並且建立樣本數據集的標籤集。當我們獲取到所有樣本圖像的HOG特徵描述子後,再把這些特徵數據按行來進行組織,生成我們進行SVM模型訓練時所需要的包含樣本圖像HOG特徵描述子的數據集。
經過上述步驟,我們就得到了訓練所需的數據集和標籤集,接下來就可以通過創建SVM分類器、設置訓練參數,輸入訓練數據集和標籤集進行訓練。在進行參數設置時,如果手動設置則需要對很多參數進行調節,這個過程是相當麻煩的。還好在OpenCV中提供了一個自動尋找最優參數的方法,該方法通過測試參數C、 gamma、p、nu、coef0, degree等參數,尋找當測試集錯誤的交叉驗證估計值最小時這些參數的取值,這些參數取值會被認爲是最佳的參數設置,然後自動訓練SVM模型。
下面看一下代碼演示:
//定義加載正樣本數據和負樣本數據的文件路徑
string positive_path = "D:\\opencv_c++\\opencv_tutorial\\data\\dataset\\elec_watch\\positive\\";
string negative_path = "D:\\opencv_c++\\opencv_tutorial\\data\\dataset\\elec_watch\\negative\\";
//通過glob()將路徑下的所有圖像文件以string類型讀取進來
vector<string> positive_images_str, negative_images_str;
glob(positive_path, positive_images_str);
glob(negative_path, negative_images_str);
//將string類型的圖像數據轉換爲Mat類型
vector<Mat>positive_images, negative_images;
for (int i = 0; i < positive_images_str.size(); i++)
{
Mat positive_image = imread(positive_images_str[i]);
resize(positive_image, positive_image, Size(64, 128));
positive_images.push_back(positive_image);
}
for (int j = 0; j < negative_images_str.size(); j++)
{
Mat negative_image = imread(negative_images_str[j]);
resize(negative_image, negative_image, Size(64, 128));
negative_images.push_back(negative_image);
}
//分別獲取正負樣本中每張圖像的HOG特徵描述子,並進行標註
HOGDescriptor *hog_train = new HOGDescriptor;
vector<vector<float>> train_descriptors;
int positive_num = positive_images.size();
int negative_num = negative_images.size();
vector<int> labels;
for (int i = 0; i < positive_num; i++)
{
Mat gray;
cvtColor(positive_images[i], gray, COLOR_BGR2GRAY); //計算HOG描述子時需要使用灰度圖像
vector<float> descriptor;
hog_train->compute(gray, descriptor, Size(8, 8), Size(0, 0));
train_descriptors.push_back(descriptor);
labels.push_back(1);
}
for (int j = 0; j < negative_num; j++)
{
Mat gray;
cvtColor(negative_images[j], gray, COLOR_BGR2GRAY);
vector<float> descriptor;
hog_train->compute(gray, descriptor, Size(8, 8), Size(0, 0));
train_descriptors.push_back(descriptor);
labels.push_back(-1);
}
//將訓練數據vector轉換爲Mat對象,每一行爲一個描述子,行數即爲樣本數
int width = train_descriptors[0].size();
int height = train_descriptors.size();
Mat train_data = Mat::zeros(Size(width, height), CV_32F);
for (int r = 0; r < height; r++)
{
for (int c = 0; c < width; c++)
{
train_data.at<float>(r, c) = train_descriptors[r][c];
}
}
//使用最優參數訓練SVM分類器,並保存到與代碼同目錄下
auto train_svm = ml::SVM::create();
train_svm->trainAuto(train_data, ml::ROW_SAMPLE, labels);
train_svm->save("model.xml");
hog_train->~HOGDescriptor();
train_svm->clear();
至此,我們就針對自定義對象訓練出了我們所需的SVM分類器模型,接下來我們就通過加載這個 .xml
類型的模型來對測試圖像進行自定義對象檢測。
進行自定義對象檢測的思路如下:
(1)首先加載我們的測試圖像,並對其進行縮放、轉灰度圖像等相關的預處理操作;
(2)加載訓練好的自定義對象檢測模型;
(3)對測試圖像進行開窗操作,以尺寸大小爲(64,128)的窗口來對圖像進行遍歷開窗。注意在開窗時可以使窗口重疊,這樣做優點是提高檢測的準確度,但缺點是會帶來多個檢測框同時存在而降低運算速度。
在對測試圖像進行開窗操作、尋找匹配的窗口區域時,開窗移動步長可以選擇計算HOG特徵描述子時的窗口步長,這樣可以避免因爲步長太小而導致檢測框的數目過多而降低運行速度的情況發生,同時還可以避免步長太大而導致漏檢的情況發生。
(4)最後求取多個檢測框的平均值即可得到最終的檢測框,也即對象所在區域,從而實現自定義對象檢測。
下面來看一下代碼演示:
//測試分類器
Mat test_image = imread("D:\\opencv_c++\\opencv_tutorial\\data\\dataset\\elec_watch\\test\\scene_02.jpg");
resize(test_image, test_image, Size(0,0), 0.2,0.2);
Mat test_gray_image;
cvtColor(test_image, test_gray_image, COLOR_BGR2GRAY);
HOGDescriptor* hog_test = new HOGDescriptor;
//加載訓練好的SVM分類器
auto test_svm = ml::SVM::load("model.xml");
Rect winRect;
vector<float> descriptors;
vector<int>winRect_x, winRect_y;
for (int row = 64; row < test_image.rows -64; row += 8)
{
for (int col = 32; col < test_image.cols - 32; col += 8)
{
winRect.width = 64;
winRect.height = 128;
winRect.x = col - 32;
winRect.y = row - 64;
//計算當前窗口區域的HOG描述子,並轉換爲Mat對象
hog_test->compute(test_gray_image(winRect), descriptors);
Mat descriptor_mat = Mat::zeros(Size(descriptors.size(), 1), CV_32FC1);
for (int i = 0; i < descriptors.size(); i++)
{
descriptor_mat.at<float>(0, i) = descriptors[i];
}
//對當前窗口的描述子使用SVM模型進行預測,結果是1或-1
float result;
result = test_svm->predict(descriptor_mat);
if (result > 0)
{
//保存當前窗口左上角座標
winRect_x.push_back(col - 32);
winRect_y.push_back(row - 64);
}
}
}
//如果存在檢測出的對象框,則計算平均座標點並繪製目標框
if (winRect_x.size() != 0)
{
int x = 0, y = 0;
for (int k = 0; k < winRect_x.size(); k++)
{
x += winRect_x[k];
y += winRect_y[k];
}
x = x / winRect_x.size();
y = y / winRect_y.size();
Rect dected_rect(x, y, 64, 128);
rectangle(test_image, dected_rect, Scalar(0, 255, 0), 1, 8, 0);
}
imshow("test_image", test_image);
hog_test->~HOGDescriptor();
test_svm->clear();
接下來看一下我們實現的自定義對象檢測的效果怎樣,這裏使用的自定義對象是:
下面是在測試圖像中的檢測效果:
再來看另一個演示,所針對對象是“滿月”,如下圖(PS: 這裏面用的月亮照片都是我自己拍的~):
下面是效果圖:
從測試結果可以看出,對於我們自定義對象的檢測效果還是可以接受的,但還是會有些許偏差,仍然需要繼續完善。如果對這個演示感興趣的朋友可以在我的資源裏下載完整代碼以及測試圖像哦~
好的,今天的筆記就整理到此爲止啦,謝謝閱讀~
PS:本人的註釋比較雜,既有自己的心得體會也有網上查閱資料時摘抄下的知識內容,所以如有雷同,純屬我向前輩學習的致敬,如果有前輩覺得我的筆記內容侵犯了您的知識產權,請和我聯繫,我會將涉及到的博文內容刪除,謝謝!