【OpenCV进阶】基于HOG+SVM自定义对象检测

📢:如果你也对机器人、人工智能感兴趣,看来我们志同道合✨
📢:不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852
📢:文章若有幸对你有帮助,可点赞 👍 收藏 ⭐不迷路🙉
📢:内容若有错误,敬请留言 📝指正!原创文,转载请注明出处


参考:
基于python的HOG+SVM目标检测算法实现
OpenCV4学习笔记(44)——基于HOG特征与SVM线性分类器的自定义对象检测
Opencv检测自定义目标
OpenCV3特征提取与目标检测之HOG(三)——使用HOG加SVM训练自己的行人检测模型

自定义对象检测的步骤

需要训练针对自定义对象的SVM模型,通过提取样本图像的HOG特征描述子,来生成样本的特征数据,再通过SVM线性分类器对这些特征数据进行分类学习与训练,并且还可以将训练结果保存为 .xml 或 .yml 模型文件,这样以后就可以通过加载这些模型来实现对同一种对象的检测。

1.确定提取特征点的算法和分类器。
2.收集数据集。采集检测对象的样本并打上标签,包括正样本和负样本。
3.编写代码,输入样本,获取每个样本的描述子和对应的标签,得到并保存训练模型。
4.模型验证。使用训练好的模型,对每张验证样本进行检测。

hog特征最早是由法国人使用
hog特征主要针对图像的梯度进行提取,从三通道转化为单通道,然后进行梯度计算,由于hog基于网格,因此在网格的基础上进行直方图的绘制,就会得到块描述子,再对描述子进行归一化。
就会得到正式的描述子数据。如果待检测对象的数据跟开窗的窗口区域的hog描述子数据,进行比对后,相似程度高于某一个阈值,那就是同一个物体。
svm对特征数据进行学习,得到一个二分类的分类器,具有很强的分类能力。对一些非实时性场景,准确性比较高。
图像灰度化后被分为一个有一个网格,每个网格就是一个8*8的像素块,在像素块上计算图像的梯度和角度,然后将梯度表述成直方图的形式。
一张图像有4608个描述子.
根据块的形状不同可以分为块和圆形hog特征
正负样本(图像)都会生成hog描述子,也就是说每张训练的图像都会有对应4608个描述子和标签
svm最小单元是感知机,感知机是二分类的,svm可以进行多种分类

改进:
1.不同分辨率的被检测图像,如何检测?图像金字塔?
2.如何提高检测速度,在开窗检测的时候优化,改为指针的方法
3.多个对象如何检测?
这种检测方法对一些刚体的检测特别有效果
其次,对特定应用场景的检测有很有效果

第一步:确定提取特征点的算法和分类器

本次选用HOG+SVM的方法对自定义对象进行检测。
HOG的详细介绍参考:
SVM的详细介绍参考:SVM学习(一):SVM概念
梯度直方图的学习:
归一化的学习:
缺点:计算量大,实时性差

第二步:采集检测对象的样本

图像样本的要求:尺寸最好大于64128(宽高)
对硬件的要求:

第三步:编写代码

代码中的参数含义:

1、pos文件夹 (正样本)
2、neg文件夹 (负样本 训练时所需文件)
3、xml (分类器保存的位置)
4、pos.txt (正样本图片路径和图片大小说明)
5、neg.txt (负样本图片路径说明 训练时所需文件)
6、pos.vec (pos.txt->pos.vec 训练时所需文件)
7、create_sample.bat (pos.txt->pos.vec的命令)
8、treain.bat (训练的命令)

1.训练模型,准备好正负样本进行训练

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace cv::ml;
using namespace std;

string positive_dir = "D:/images/person/positive";//文件夹内的图像名称和格式不限制
string negative_dir = "D:/images/person/negative";
void get_hog_descriptor(Mat &image, vector<float> &desc);
void generate_dataset(Mat &trainData, Mat &labels);
void svm_train(Mat &trainData, Mat &labels);

int main(int argc, char** argv) {
	// read data and generate dataset
	Mat trainData = Mat::zeros(Size(3780, 18983), CV_32FC1);//列数是描述子个数,3780指的是描述子的个数。行数是样本数,此处26指的是正负样本数之和,需要根据实际训练的样本数进行适当的修改
	Mat labels = Mat::zeros(Size(1, 18983), CV_32SC1);//标签只有1列,此处26指的对应所有26样本的标签数量,标签是1或是-1
	generate_dataset(trainData, labels);

	// SVM train, and save model  回调svm_train函数,进行svm训练和保存模型
	svm_train(trainData, labels);
}

 
void get_hog_descriptor(Mat &image, vector<float> &desc) {
	HOGDescriptor hog;
	int h = image.rows;
	int w = image.cols;
	float rate = 64.0 / w;
	Mat img, gray;
	resize(image, img, Size(64, int(rate*h)));//对图像进行等比例缩放,宽度设置为定长64,长度等比例缩放
	cvtColor(img, gray, COLOR_BGR2GRAY);//灰度化
	Mat result = Mat::zeros(Size(64, 128), CV_8UC1);//创建一个64*128黑色的图像
	result = Scalar(127);//对其余部分自动赋值,127即为灰色
	Rect roi;
	roi.x = 0;
	roi.width = 64;
	roi.y = (128 - gray.rows) / 2;
	roi.height = gray.rows;
	gray.copyTo(result(roi));
	hog.compute(result, desc, Size(8, 8), Size(0, 0));//8*8的像素块为一个cell,四个cell(2*2)为一个块描述子,128*64的图像一共有16*8个cell,
	//2*2窗口进行滑动后生成15*7块描述子,所有描述子从result中计算hog特征,保存在desc中
	printf("desc len : %d \n", desc.size());//打印出dsrc的长度,也就是描述子的个数
}

void generate_dataset(Mat &trainData, Mat &labels) {//生成数据集
	vector<string> images;
	//定义正样本
	glob(positive_dir, images);//扫描所有的正样本,保存在images中
	int pos_num = images.size();
	for (int i = 0; i < images.size(); i++) {
		Mat image = imread(images[i].c_str());//读取每一张图像
		vector<float> fv;
		get_hog_descriptor(image, fv);//获取已生成的描述子,存放在fv中
		for (int j = 0; j < fv.size(); j++) {
			trainData.at<float>(i, j) = fv[j];
		}
		labels.at<int>(i, 0) = 1;
	}
	images.clear();//清除上述的正样本
	//定义负样本
	glob(negative_dir, images);
	for (int i = 0; i < images.size(); i++) {
		Mat image = imread(images[i].c_str());
		vector<float> fv;
		get_hog_descriptor(image, fv);
		for (int j = 0; j < fv.size(); j++) {
			trainData.at<float>(i + pos_num, j) = fv[j];
		}
		labels.at<int>(i + pos_num, 0) = -1;
	}
}

//训练模型
void svm_train(Mat &trainData, Mat &labels) {
	printf("\n SVM training start... \n");
	Ptr<SVM> svm = SVM::create();
	svm->setC(2.67);//2.67是其默认值
	svm->setType(SVM::C_SVC);
	svm->setKernel(SVM::LINEAR);//设置核,SVM支持各种核,liner是线性核,比较简单,速度快
	svm->setGamma(5.383);//5.383是Gamma的参数
	svm->train(trainData, ROW_SAMPLE, labels);
	clog << "....[Done]" << endl;
	printf("train end...\n");

	// save xml
	svm->save("D:/images/person/hog_elec.xml");
}


2.检测测试集

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace cv::ml;
using namespace std;

string positive_dir = "D:/images/elec_watch/positive";
string negative_dir = "D:/images/elec_watch/negative";
void get_hog_descriptor(Mat &image, vector<float> &desc);
void generate_dataset(Mat &trainData, Mat &labels);

int main(int argc, char** argv) {
	//调用SVM训练好的模型,即hog_elec.xml文件 
	Ptr<SVM> svm = SVM::load("D:/images/elec_watch/hog_elec.xml");

   // 检测物体
	Mat test = imread("D:/images/elec_watch/test/scene_01.jpg");
	resize(test, test, Size(0, 0), 0.2, 0.2);//降低分辨率级别
	imshow("input", test);
	Rect winRect;//设置64X128的标准窗口
	winRect.width = 64;
	winRect.height = 128;
	int sum_x = 0;
	int sum_y = 0;
	int count = 0;

	// 开窗检测....,这段代码只能框选出一个物体
	for (int row = 64; row < test.rows - 64; row += 4) {
		for (int col = 32; col < test.cols - 32; col += 4) {
			winRect.x = col - 32;
			winRect.y = row - 64;
			vector<float> fv;
			get_hog_descriptor(test(winRect), fv);
			Mat one_row = Mat::zeros(Size(fv.size(), 1), CV_32FC1);
			for (int i = 0; i < fv.size(); i++) {
				one_row.at<float>(0, i) = fv[i];
			}
			float result = svm->predict(one_row);
			if (result > 0) {
				// rectangle(test, winRect, Scalar(0, 0, 255), 1, 8, 0);//显示出所有的框
				count += 1;
				sum_x += winRect.x;
				sum_y += winRect.y;
			}
		}
	}

	// 显示box,对所有框取平均。
	winRect.x = sum_x / count;
	winRect.y = sum_y / count;
	rectangle(test, winRect, Scalar(0, 0,255), 2, 8, 0);
	imshow("object detection result", test);
	imwrite("D:/case02.png", test);
	waitKey(0);
	return 0;
}

void get_hog_descriptor(Mat &image, vector<float> &desc) {
	HOGDescriptor hog;
	int h = image.rows;
	int w = image.cols;
	float rate = 64.0 / w;
	Mat img, gray;
	resize(image, img, Size(64, int(rate*h)));
	cvtColor(img, gray, COLOR_BGR2GRAY);
	Mat result = Mat::zeros(Size(64, 128), CV_8UC1);
	result = Scalar(127);
	Rect roi;
	roi.x = 0;
	roi.width = 64;
	roi.y = (128 - gray.rows) / 2;
	roi.height = gray.rows;
	gray.copyTo(result(roi));
	hog.compute(result, desc, Size(8, 8), Size(0, 0));
	// printf("desc len : %d \n", desc.size());
}

void generate_dataset(Mat &trainData, Mat &labels) {
	vector<string> images;
	glob(positive_dir, images);
	int pos_num = images.size();
	for (int i = 0; i < images.size(); i++) {
		Mat image = imread(images[i].c_str());
		vector<float> fv;
		get_hog_descriptor(image, fv);
		for (int j = 0; j < fv.size(); j++) {
			trainData.at<float>(i, j) = fv[j];
		}
		labels.at<int>(i, 0) = 1;
	}
	images.clear();
	glob(negative_dir, images);
	for (int i = 0; i < images.size(); i++) {
		Mat image = imread(images[i].c_str());
		vector<float> fv;
		get_hog_descriptor(image, fv);
		for (int j = 0; j < fv.size(); j++) {
			trainData.at<float>(i + pos_num, j) = fv[j];
		}
		labels.at<int>(i + pos_num, 0) = -1;
	}
}

训练时候出现的问题

在negative或是positive中添加图像都会增加训练负担,因此最好是在网络较为流畅的时候进行。
测试经验:之前在positive中有好几种上兆的图像,训练时很顺利;后面使用同样的数据集做训练,发现训练停止。比如出现下图的情况是由于网速过慢导致的。
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嵌小超

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值