opencv学习-HOG LOG DOG

1. HOG(Histogram of Oriented Gradients,方向梯度直方图)

1.1主要思想

    在一副图像中,局部目标的表象和形状(appearance and shape)能够被梯度或边缘的方向密度分布很好地描述。(本质:梯度的统计信息,而梯度主要存在于边缘的地方)。它与图像边缘特征的主要区别是图像边缘特征只识别像素是否是边缘(只计算梯度),而HOG特征还计算边缘(梯度)的方向。

1.2适用领域

    hog是一种在计算机视觉和图像处理中用来对物体检测的特征描述子,Hog特征结合SVM分类器已经被广泛应用于图像识别中,尤其在行人检测中获得了极大的成功。

1.3计算方向梯度直方图的过程

  输入图像-》图像灰度化-》Gamma校正-》计算图像梯度-》计算cell的描述子-》将cell组合为block,block内特征归一化-》将block串联起来,得到图像描述子

描述步骤之前,先总体介绍一些预备知识:

(1)在原论文中处理图像时,图像的尺寸缩放为64*128(64为宽,128为高),这个大小是为了方便之后划分为cell以及block。

(2)将图像划分为88的cell(对于88的网格已经足够大来表示有用的特征比如脸,头等等)
(3)每个cell都要计算一个直方图,该直方图是对幅值magnitude和方向direction的统计(每个像素都有一个幅值和方向),方向范围是0~180度,使用的是无符号梯度,此时一个梯度和它的负数是用同一个数字表示的,也就是说一个梯度的箭头以及它旋转180度之后的箭头方向被认为是一样的。那为什么不用0-360度的表示呢?在事件中发现unsigned gradients比signed gradients在行人检测任务中效果更好。

(4)block(16*16):由于局部光照的变化以及前景-背景对比度的变化,使得梯度强度的变化范围非常大。这就需要对梯度强度做归一化。在block内归一化是为了增强描述子对光照、阴影和边缘变化的鲁棒性。它能进一步对光照、阴影和边缘进行压缩。一个block内所有cell的特征向量串联起来便得到该block的HOG特征。这些区间是互有重叠的,这就意味着:每一个单元格的特征会以不同的结果多次出现在最后的特征向量中。

各步骤关键:

1.图像灰度化是可选操作,因为灰度图像和彩色图像都可以用于计算梯度图。对于彩色图像,先对三通道颜色值分别计算梯度,然后取梯度值最大的那个作为该像素的梯度。但在计算机视觉处理中一般都以灰度图作为处理对象(不包括深度学习)。

2.Gamma校正:调节图像对比度,减少光照对图像的影响,使过曝或者欠曝的图像恢复正常,更接近人眼看到的图像。

3.计算梯度:计算一个像素的x方向梯度及y方向的梯度。根据它们确定每个像素的大小和方向

梯度方向(角度的值):
atan(Gx/Gy)

梯度幅值为:
在这里插入图片描述

4.计算cell描述子:

按照角度不同,直方图分为9个bin,代表角度0,20,40,60,80,100,120,140,160;根据方向确定选择哪个bin,根据幅值确定bin的大小,如果角度值为介于上边9个值的值,那么需要根据距离9个值左右值的远近在幅值上分别乘以比例来确定在这两个bin上的大小。
在这里插入图片描述
5.组合为block并且归一化

一个block是由四个cell组成的,将每个cell的的9个Bin的直方图(即91的向量)组合成一个361的向量,之后对其归一化,接着,窗口再朝后面挪8个像素(看动图)。重复这个过程把整张图遍历一遍。如下图(该图源于参考链接4,为便于理解放于此处,如有侵权,联系笔者删除)
在这里插入图片描述
归一化方法有L1-norm、L2-norm、L1-sqrt(L1-norm后在平方)。通常使用L2-norm.

L2-norm的计算方法如下:

对于cell组合成的36维向量v=[a1,a2,a3,…,a36];
计算平方和的根:
在这里插入图片描述
并将向量v中的每个值除以此值k:

归一化后的向量=(a1/k,a2/k,a3/k,…,a36/k)
6.图像特征描述子

以5中图示华东,64128将有715个1616大小的block,这105个块中每一个有36个向量作为特征,所以该64128大小的图像描述子大小是105*36=3780

1.4opencv hog特征描述子的调用及计算

HogDescriptor: 构建一个Hog描述子及检测器

cv::HOGDescriptor::HOGDescriptor( Size winSize=Size(64,128), Size blockSize=Size(16,16), 
    Size blockStride=Size(8,8), Size cellSize=Size(8,8), int nbins=9,
    double winSigma = DEFAULT_WIN_SIGMA, double threshold_L2Hys = 0.2,
 bool gamma_Correction = true,int nlevels=DEFAULT_NLEVELS )
 
 
参数解释:
<1>win_size:检测窗口大小。
<2>block_size:块大小,目前只支持Size(16, 16)<3>block_stride:块的滑动步长,大小只支持是单元格cell_size大小的倍数。
<4>cell_size:单元格的大小,目前只支持Size(8, 8)<5>nbins:直方图bin的数量(投票箱的个数),目前每个单元格Cell只支持9个。
<6>win_sigma:高斯滤波窗口的参数。
<7>threshold_L2hys:块内直方图归一化类型L2-Hys的归一化收缩率
<8>gamma_correction:是否gamma校正
<9>nlevels:检测窗口的最大数量

compute函数:计算hog特征

void HOGDescriptor:: compute(const Mat&  img, 
                vector<float>&  descriptors,
                Size  winStride, 
                Size  padding,
                const vector<Point>&  locations
                ) const
(3)参数注释 
<1> img:源图像。只支持CV_8UC1和CV_8UC4数据类型。
<2> descriptors:返回的HOG特征向量,descriptors.size是HOG特征的维数。
<3> winStride:窗口移动步长。
<4> padding:扩充像素数。
<5> locations:对于正样本可以直接取(0,0),负样本为随机产生合理坐标范围内的点坐标。

getDescriptorSize函数:获取一个检测窗口HOG特征向量的位数

对百度下载一幅图像截取出人形做测试:
在这里插入图片描述
计算hog描述子代码:

#include <opencv2/opencv.hpp>
#include <opencv2/objdetect.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main()
{
	Mat srcImage = imread("oneperson.jpg");
	Mat grayImage, dstImage;
	cvtColor(srcImage, grayImage, COLOR_BGR2GRAY);
	resize(grayImage, dstImage, Size(64, 128));
 
	HOGDescriptor dectector(Size(64, 128), Size(16, 16), Size(8, 8), Size(8, 8), 9);
	cout <<"dectector.getDescriptorSize():"<< dectector.getDescriptorSize() << endl;
	// HOG的描述子
	vector<float> descriptors;
	vector<Point> locations;
	dectector.compute(dstImage, descriptors, Size(0, 0), Size(0, 0), locations);
	cout << "descriptors.size():" << descriptors.size() << endl;
	cout << "feature:" << endl;
	for (int j = 0; j < 3780; j++)
	{
		cout << descriptors[j] << ' ';
	}
	cout << endl;
	system("pause");
	return 0;
}

结果显示:
在这里插入图片描述

1.5 行人检测

OpenCV中自己带的训练模板里面有行人检测,可以直接调用,主要用到下面三个函数

setSVMDetector 函数:设置线性SVM分类器的系数

getDefaultPeopleDetector 函数:获取行人分类器(默认检测窗口大小)的系数(获得3780维检测算子)

detectMultiScale 函数(前边需有setSVMDetector):用多尺度的窗口进行物体检测

void HOGDescriptor::detectMultiScale(
    const Mat& img, vector<Rect>& foundLocations, vector<double>& foundWeights,
    double hitThreshold, Size winStride, Size padding,
    double scale0, double finalThreshold, bool useMeanshiftGrouping) 
参数注释
<1>img:源图像。
<2>foundlocations:检测出的物体的边缘。
<3>foundWeights: 检测窗口得分
<4>hit_threshold:阀值,特征向量和SVM划分超平面的距离,大于这个值的才作为目标返回。
<5>win_stride:窗口步长,必须是block步长的整数倍。
<6>padding:图片边缘补齐参数,gpu版本必须是(0,0)<7>scale0:检测窗口增长参数。
<8>finalThreshold:检测结果聚类参数
<9>useMeanshiftGrouping:聚类方式选择的参数

从百度图库下载一张行人的图像:
在这里插入图片描述
代码:

#include <opencv2/opencv.hpp>
#include <opencv2/objdetect.hpp>
#include <iostream>
using namespace std;
using namespace cv;
 
int main()
{
 
	Mat src, dst;
	src = imread("people.jpg", 1);
	if (src.empty())
	{
		printf("load image error...\n");
		return -1;
	}
	dst = src.clone();
	vector<Rect> findrects, findrect;
	HOGDescriptor HOG;
	//SVM分类器
	HOG.setSVMDetector(HOGDescriptor::getDefaultPeopleDetector());
	//多尺度检测
	HOG.detectMultiScale(src, findrects, 0, Size(4, 4), Size(0, 0), 1.05, 2);
 
	//框选出检测结果
	for (int i = 0; i<findrects.size(); i++)
	{
		RNG rng(i);
		Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
		rectangle(dst, findrects[i].tl(), findrects[i].br(), color, 2);
	}
 
	imshow("src", src);
	imshow("dst", dst);
	waitKey();
	return 0;
}

测试结果:
在这里插入图片描述

参考链接:https://blog.csdn.net/u013972657/article/details/118294795
另一篇值得参考的博文

2. LOG

2.1 简介

LoG边缘检测算子是David Courtnay Marr和Ellen Hildreth(1980)共同提出的。因此,也称为边缘检测算法或Marr & Hildreth算子。该算法首先对图像做高斯滤波,然后再求其拉普拉斯(Laplacian)二阶导数。即图像与 Laplacian of the Gaussian function 进行滤波运算。最后,通过检测滤波结果的零交叉(Zero crossings)可以获得图像或物体的边缘。因而,也被业界简称为Laplacian-of-Gaussian (LoG)算子。

算法描述:LoG算子也就是 Laplace of Gaussian function(高斯拉普拉斯函数)。常用于数字图像的边缘提取和二值化。LoG 算子源于D.Marr计算视觉理论中提出的边缘提取思想, 即首先对原始图像进行最佳平滑处理, 最大程度地抑制噪声, 再对平滑后的图像求取边缘。

由于噪声点(灰度与周围点相差很大的像素点)对边缘检测有一定的影响,所以效果更好的边缘检测器是LoG算子,也就是Laplacian-Gauss算子。它把Gauss平滑滤波器和Laplacian锐化滤波器结合了起来,先平滑掉噪声,再进行边缘检测,所以效果会更好。

高斯函数和一级、二阶导数如下图所示:
在这里插入图片描述
LoG算子到中心的距离与位置加权系数的关系曲线象墨西哥草帽的剖面,所以LoG算子也叫墨西哥草帽滤波器。
在这里插入图片描述
拉普拉斯算子是二阶差分算子,为什么要加入二阶的算子呢?试想一下,如果图像中有噪声,噪声在一阶导数处也会取得极大值从而被当作边缘。然而求解这个极大值也不方便,采用二阶导数后,极大值点就为0了,因此值为0的地方就是边界。

如下图所示,上面是一阶导数,下面是二阶导数
在这里插入图片描述

2.2 算法实现

int main()
{
	//使用LoG算子做边缘检测
	Mat src, src_gray;
	int kernel_size = 3;
	const char* window_name = "Laplacian-of-Gaussian Edeg Detection";

	src = imread("C:\\Users\\Administrator\\Downloads\\1.jpeg");
	//1.先通过高斯模糊去噪声
	GaussianBlur(src, src, Size(3, 3), 0, 0, BORDER_DEFAULT);	
	cvtColor(src, src_gray, CV_RGB2GRAY);
	namedWindow(window_name, WINDOW_AUTOSIZE);
	
	//2.通过拉普拉斯算子做边缘检测
	Mat dst, abs_dst;
	Laplacian(src_gray, dst, CV_16S, kernel_size);	
	convertScaleAbs(dst, abs_dst);

	imshow(window_name, abs_dst);

	//使用自定义滤波做边缘检测
	//自定义滤波算子 1  1  1
	//              1 -8  1
	//              1  1  1
	Mat custom_src, custom_gray, Kernel;
	custom_src = imread("C:\\Users\\Administrator\\Downloads\\1.jpeg");
	//1.先通过高斯模糊去噪声
	GaussianBlur(custom_src, custom_src, Size(3, 3), 0, 0, BORDER_DEFAULT);	
	cvtColor(custom_src, custom_gray, CV_RGB2GRAY);
	namedWindow("Custom Filter", WINDOW_AUTOSIZE);
	//2.自定义滤波算子做边缘检测
	Kernel = (Mat_<double>(3, 3) << 1, 1, 1, 1, -8, 1, 1, 1, 1);	
	Mat custdst, abs_custdst;
	filter2D(custom_gray, custdst, CV_16S, Kernel, Point(-1, -1));
	convertScaleAbs(custdst, abs_custdst);

	imshow("Custom Filter", abs_custdst);
	waitKey(0);

	return 0;
}

运行结果:
在这里插入图片描述
参考链接:链接地址

3. DOG

3.1 简介

DoG(Difference of Gaussian) 常用于边缘检测、特征检测, 是高斯函数的差分。我们已经知道可以通过将图像与高斯函数进行卷积得到一幅图像的低通滤波结果,即去噪过程,为一个正态分布函数,而 某一尺度上的特征检测可以通过对两个相邻高斯尺度空间的图像相减,得到 DoG 的响应值图像。

DoG 算子不仅实现简单、计算速度快,而且对噪声、尺度、仿射变化和旋转等具有很强的鲁棒性,能够提供丰富的局部特征信息。

首先,高斯函数表示定义为:
在这里插入图片描述
具体图像处理中,就是将两幅图像在不同参数下的高斯滤波结果相减,得到 DoG 图

3.2 具体步骤

3.2.1 将同一图像在不同的参数下进行高斯滤波计算,并相减,得到三个DOG图

在这里插入图片描述
图(1):原始图
在这里插入图片描述
图(2) 一个高斯平滑参数为0.3,另一个高斯平滑参数为0.4

在这里插入图片描述
图(3) 一个高斯平滑参数为0.6,另一个高斯平滑参数为0.7

在这里插入图片描述
图(4) 一个高斯平滑参数为0.7,另一个高斯平滑参数为0.8

3.2.2 根据第一步求出的三张DOG 假设第一张图为f1(x,y),第二张图为f2(x,y),第三张图为f3(x,y);根据下面的模型,求出f2(x,y)(f2为中间标记红色的图)即中间)的最大值和最小值点

在这里插入图片描述
注:这里,一些童鞋们可以根据上面模型理解整个DOG特征提取的过程

  1:左边的四张图(包括原始图)是不同参数下高斯滤波结果的

  2:对高斯滤波后的图片(相邻状态下)依次进行两两相减可得到右边的三个高斯函数的差分图(简称DOG)。然后根据上面的模型,依次求中间图片每个像素与该像素同尺度的8个相邻点以及上下相邻尺度对应的9*2共26个点的极值。一个点如果在DOG空间本层以及上下两层的26个领域中是最大值和最小值时,就认为该点是图像在该尺度下的一个特征点。

  假设右边的三张图从上到下依次排列顺序为图f1,f2,f3。f2图中的红色标记为当前像素点,黄色对应的像素点表示其邻接的点公26个,如果该点是所有邻接像素点的最大值和最小值,则红色标记对应的点为特征点。如此可以求出所有f2图像中的所有极值点,即完成为了特征点提取。对应最大值标记为1,最小值标记为-1。如下图表示

在这里插入图片描述
图(5) f2中的所有极值点,黑色表示极小值,白色表示最大值

最后,在原始图像上标记出所提取的DOG检测出的特征点。如下图所示;
在这里插入图片描述

3.3 代码实现

typedef  std::vector<cv::Point> keypoints_type;

cv::Mat faster_2_gaussi_filter_channel(const cv::Mat& src, int ksize, double sigx, double sigy)
{
	cv::Mat ret;
	cv::GaussianBlur(src, ret, Size(ksize, ksize), sigx, sigy);

	return ret;
}

void cv_show(cv::Mat& src)
{
	imshow("result", src);
	cv::waitKey(0);
}

cv::Mat cv_concat(const std::vector<cv::Mat> &imgs)
{
	cv::Mat ret;
	cv::hconcat(imgs, ret);

	return ret;
}

// 单通道关键点检测
std::pair< keypoints_type, keypoints_type > difference_of_gaussi_keypoints_detection(
	const cv::Mat& source, const int radius,
	const std::vector< double > sigma_list,
	const double threshold) {
	const int C = source.channels();
	if (C != 1) {
		std::cout << "只支持单通道图像 !" << std::endl;
		return std::pair< keypoints_type, keypoints_type >();
	}
	// 四次高斯模糊, 3 张 DOG 图
	assert(sigma_list.size() == 4);

	// 根据 sigma_list 的方差分别做高斯模糊
	const int kernel_size = (radius << 1) + 1;
	const auto gaussi_1 = faster_2_gaussi_filter_channel(source, kernel_size, sigma_list[0], sigma_list[0]);
	const auto gaussi_2 = faster_2_gaussi_filter_channel(source, kernel_size, sigma_list[1], sigma_list[1]);
	const auto gaussi_3 = faster_2_gaussi_filter_channel(source, kernel_size, sigma_list[2], sigma_list[2]);
	const auto gaussi_4 = faster_2_gaussi_filter_channel(source, kernel_size, sigma_list[3], sigma_list[3]);

	// 分别求 DOG
	const int H = source.rows;
	const int W = source.cols;
	const int length = H * W;
	std::vector<double> DOG_down(length), DOG_mid(length), DOG_up(length);
	for (int i = 0; i < length; ++i) DOG_down[i] = gaussi_1.data[i] - gaussi_2.data[i];
	for (int i = 0; i < length; ++i) DOG_mid[i] = gaussi_2.data[i] - gaussi_3.data[i];
	for (int i = 0; i < length; ++i) DOG_up[i] = gaussi_3.data[i] - gaussi_4.data[i];

	// 准备结果, 返回极大值与极小值的点
	keypoints_type max_points;
	keypoints_type min_points;

	// 三层之间找极值
	for (int i = 1; i < H - 1; ++i) {
		double* const down = DOG_down.data() + i * W;
		double* const mid = DOG_mid.data() + i * W;
		double* const up = DOG_up.data() + i * W;
		for (int j = 1; j < W - 1; ++j) {
			// 中间这个点的值, 和最近的 26 个点比较大小
			const auto center = mid[j];
			// 极大值
			if (center > mid[j - 1] && center > mid[j + 1] &&
				center > mid[j - 1 - W] && center > mid[j - W] && center > mid[j + 1 - W] &&
				center > mid[j - 1 + W] && center > mid[j + W] && center > mid[j + 1 + W] &&

				center > down[j - 1] && center > down[j] && center > down[j + 1] &&
				center > down[j - 1 - W] && center > down[j - W] && center > down[j + 1 - W] &&
				center > down[j - 1 + W] && center > down[j + W] && center > down[j + 1 + W] &&

				center > up[j - 1] && center > up[j] && center > up[j + 1] &&
				center > up[j - 1 - W] && center > up[j - W] && center > up[j + 1 - W] &&
				center > up[j - 1 + W] && center > up[j + W] && center > up[j + 1 + W]) {
				//保留响应值比较强的点作为特征点
				if (std::abs(center) > threshold) max_points.emplace_back(i, j);
			}
			// 极小值
			if (center < mid[j - 1] && center < mid[j + 1] &&
				center < mid[j - 1 - W] && center < mid[j - W] && center < mid[j + 1 - W] &&
				center < mid[j - 1 + W] && center < mid[j + W] && center < mid[j + 1 + W] &&

				center < down[j - 1] && center < down[j] && center < down[j + 1] &&
				center < down[j - 1 - W] && center < down[j - W] && center < down[j + 1 - W] &&
				center < down[j - 1 + W] && center < down[j + W] && center < down[j + 1 + W] &&

				center < up[j - 1] && center < up[j] && center < up[j + 1] &&
				center < up[j - 1 - W] && center < up[j - W] && center < up[j + 1 - W] &&
				center < up[j - 1 + W] && center < up[j + W] && center < up[j + 1 + W]) {
				if (std::abs(center) > threshold) min_points.emplace_back(i, j);
			}
		}
	}
	return std::make_pair(max_points, min_points);
}


void main() {
	// 读取图像
	const std::string image_path("C:\\Users\\Administrator\\Desktop\\1.jpg");
	auto origin_image = cv::imread(image_path);

	cv::Mat detected_result = origin_image.clone();
	// BGR -> Gray 支持灰度图像
	cv::cvtColor(origin_image, origin_image, cv::COLOR_BGR2GRAY);

	// LOG 检测边缘
	std::pair<keypoints_type, keypoints_type > key_points;
	key_points = difference_of_gaussi_keypoints_detection(origin_image, 3, { 0.3, 0.4, 0.5, 0.6 }, 6);

	// 极大值
	for (const auto point : key_points.first) 
		cv::circle(detected_result, cv::Point(point.y, point.x), 3, CV_RGB(255, 0, 0));

	// 极小值
	for (const auto point : key_points.second) 
		cv::circle(detected_result, cv::Point(point.y, point.x), 3, CV_RGB(0, 255, 0));

	// 展示结果, 保存
	auto comparison_results = cv_concat({ detected_result });
	cv_show(comparison_results);
	//cv_write(comparison_results, "./images/output/LOG_keypoints_detection.png");
}

参考:
https://blog.csdn.net/qq_32211827/article/details/72758090?spm=1001.2101.3001.6650.3&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-3-72758090-blog-7639488.pc_relevant_3mothn_strategy_and_data_recovery&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-3-72758090-blog-7639488.pc_relevant_3mothn_strategy_and_data_recovery&utm_relevant_index=6
https://zhuanlan.zhihu.com/p/446286009

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值