其实在深度学习中我们已经介绍了目标检测和目标识别的概念、为了照顾一些没有学过深度学习的童鞋,这里我重新说明一次:目标检测是用来确定图像上某个区域是否有我们要识别的对象,目标识别是用来判断图片上这个对象是什么。识别通常只处理已经检测到对象的区域,例如,人们总是会在已有的人脸图像的区域去识别人脸。
传统的目标检测方法与识别不同于深度学习方法,后者主要利用神经网络来实现分类和回归问题。在这里我们主要介绍如何利用OpecnCV来实现传统目标检测和识别,在计算机视觉中有很多目标检测和识别的技术,这里我们主要介绍下面几块内容:
HOG(Histograms of Oriented Gradients)梯度方向直方图
HOG不是基于颜色值而是基于梯度来计算直方图的,它通过计算和统计图像局部区域的梯度方向直方图来构建特征
主要思想:
局部目标的外表和形状可以被局部梯度或边缘方向的分布很好的描述,即使我们不知道对应的梯度和边缘的位置。(本质:梯度的统计信息,梯度主要存在于边缘的地方)
实施方法
首先将图像分成很多小的连通区域,我们把它叫做细胞单元,然后采集细胞单元中各像素点的梯度和边缘方向,然后在每个细胞单元中累加出一个一维的梯度方向直方图。
为了对光照和阴影有更好的不变性,需要对直方图进行对比度归一化,这可以通过把这些直方图在图像的更大的范围内(我们把它叫做区间或者block)进行对比度归一化。首先我们计算出各直方图在这个区间中的密度,然后根据这个密度对区间中的各个细胞单元做归一化。我们把归一化的块描述符叫作HOG描述子。
1)灰度图像 转换(将图像看做一个x,y,z(灰度)的三维图像);
2)采用Gamma校正法对输入图像进行颜色空间的标准化(归一化);目的是调节图像的对比度,降低图像局部的阴影和光照变化所造成的影响,同时可以抑制噪音的干扰;
首先采用Gamma校正法对输入图像的颜色空间进行标准化(或者说是归一化)。
所谓的Gamma校正可以理解为提高图像中偏暗或者偏亮部分的图像对比效果,能够有效地降低图像局部的阴影和光照变化。更详细的内容可以点击这里查看图像处理之gamma校正。
γ<1在低灰度值区域内,动态范围变大,图像对比度增加强;在高灰度值区域,动态范围变小,图像对比度降低,同时,图像的整体灰度值变大;
γ>1在低灰度值区域内,动态范围变小,图像对比度降低;在高灰度值区域,动态范围变大,图像对比度提高,同时,图像的整体灰度值变小;
#if 1 // 图像增强算法 --gamma
int Gamma = 2;
int main(int args, char* arg)
{
Mat src = imread("C:\\Users\\19473\\Desktop\\opencv_images\\88.jpg");
if (!src.data)
{
printf("could not load image....\n");
}
imshow("原图像", src);
// 注意点 : CV_32FC3
Mat dst(src.size(), CV_32FC3);
for (int i = 0; i < src.rows; i++)
{
for (int j = 0; j < src.cols; j++)
{
// 对bgr 的每个通道都进行计算
dst.at(i, j)[0] = pow(src.at(i, j)[0], Gamma);
dst.at(i, j)[1] = pow(src.at(i, j)[1], Gamma);
dst.at(i, j)[2] = pow(src.at(i, j)[2], Gamma);
}
}
// 归一化
normalize(dst, dst, 0, 255, CV_MINMAX);
convertScaleAbs(dst, dst);
imshow("增强后的图像", dst);
waitKey(0);
return -1;
}
#endif
图像平滑(具体视情况而定)
对于灰度图像,一般为了去除噪点,所以会先利用高斯函数进行平滑:高斯函数在不同的平滑尺度下对灰度图像进行平滑操作
3)计算图像每个像素的梯度(包括大小和方向);主要是为了捕获轮廓信息,同时进一步弱化光照的干扰
Mat non_max_supprusion(Mat dx, Mat dy) //传进来的是两个方向上的差分矩阵 3*3 的掩膜
{
//边缘强度=sqrt(dx 的平方+dy 的平方)
Mat edge;
magnitude(dx, dy, edge);// 计算幅度值
int rows = dx.rows;
int cols = dy.cols;
//边缘强度的非极大值抑制
Mat edgemag_nonMaxSup = Mat::zeros(dx.size(), dx.type());
// 用两个循序计算出 和梯度方向 并且转换成angleMatrix
for (int row = 1; row < rows - 1; row++)
{
for (int col = 1; col < cols - 1; col++)
{
float x = dx.at(row, col);
float y = dx.at(row, col);
// 梯度的方向---atan2f
float angle = atan2f(y, x) / CV_PI * 180;
// 当前位置的边缘强度
float mag = edge.at(row, col);
// 找到左右两个方向
if (abs(angle) < 22.5 || abs(angle) > 157.5)
{
float left = edge.at(row, col - 1);
float right = edge.at(row, col + 1);
// 判断两个方向上的
if (mag > left && mag > right) {
edgemag_nonMaxSup.at(row, col) = mag;
}
}
// 左上和右下两个方向
if ((abs(angle) >= 22.5 && abs(angle) < 67.5) || (abs(angle) < -112.5 && abs(angle) > 157.5))
{
float lefttop = edge.at(row - 1, col - 1);
float rightbottom = edge.at(row + 1, col + 1);
// 判断两个方向上的
if (mag > lefttop && mag > rightbottom) {
edgemag_nonMaxSup.at(row, col) = mag;
}
}
// 上 下 方向
if ((abs(angle) >= 67.5 && abs(angle) <= 112.5) || (abs(angle) >= -112.5 && abs(angle) <= -67.5))
{
float top = edge.at(row - 1, col);
float down = edge.at(row + 1, col);
// 判断两个方向上的
if (mag > top && mag > down) {
edgemag_nonMaxSup.at(row, col) = mag;
}
}
// 右上 左下 方向
if ((abs(angle) > 122.5 && abs(angle) < 157.5) || (abs(angle) > -67.5 && abs(angle) <= -22.5))
{
float leftdown = edge.at(row - 1, col + 1);
float rightup = edge.at(row + 1, col - 1);
// 判断两个方向上的
if (mag > leftdown && mag > rightup) {
edgemag_nonMaxSup.at(row, col) = mag;
}
}
}
}
return edgemag_nonMaxSup;
}
如果cell中某一个像素的梯度方向是20~40°,直方图第2个bin的计数就要加1,这样对cell中的每一个像素用梯度方向在直方图中进行加权投影(权值大小等于梯度幅值),将其映射到对应的角度范围块内,就可以得到这个cell的梯度方向直方图了,就是该cell对应的9维特征向量。对于梯度方向位于相邻bin的中心之间(如20°、40°等)需要进行方向和位置上的双线性插值。
采用梯度幅值量级本身得到的检测效果最佳,而使用二值的边缘权值表示会严重降低效果。采用梯度幅值作为权重,可以使那些比较明显的边缘的方向信息对特征表达影响增大,这样比较合理,因为HOG特征主要就是依靠这些边缘纹理。
根据Dalal等人的实验,在行人目标检测中,在无符号方向角度范围并将其平均分成9份(bins)能取得最好的效果,当bin的数目继续增大效果改变不明显,故一般在人体目标检测中使用bin数目为9范围0~180°的度量方式。
5)统计每个cell的梯度直方图(不同梯度的个数),即可形成每个cell的descriptor; 快描述籽
6)对block归一化 将每几个cell组成一个block(例如3 * 3个cell / block),一个block内所有cell的特征descriptor串联起来便得到该block的HOG特征descriptor。快描述籽归一化
最终的描述子是检测窗口内所有块内的细胞单元的直方图构成的向量,事实上,块之间是有重叠的,也就是说,每个细胞单元的直方图都会被多次用于最终的描述子的计算,块之间的重叠看起来有冗余,但可以显著的提升性能 。
通常使用的HOG结构大致有三种:矩形HOG(简称为R-HOG),圆形HOG和中心环绕HOG。它们的单位都是Block(即块)。Dalal的试验证明矩形HOG和圆形HOG的检测效果基本一致,而环绕形HOG效果相对差一些
如上图,一个块由2×2个cell组成,每一个cell包含8×8个像素点,每个cell提取9个直方图通道,因此一个块的特征向量长度为2×2×9
7)将图像image内的所有block的HOG特征descriptor串联起来就可以得到该image(你要检测的目标)的HOG特征descriptor了。这个就是最终的可供分类使用的特征向量了 特征数据与检测窗口
对于大小为128×64大小的图像,采用8*8像素的sell,2×2个cell组成的16×16像素的block,采用8像素的block移动步长,这样检测窗口block的数量有((128-16)/8+1)×((64-16)/8+1)=15×7.则HOG特征描述符的维数为15×7×4×9.
8) 匹配方法
速度慢,实时性差;难以处理遮挡问题
int main(int args, char* arg)
{
//目标图像
src = imread("C:\\Users\\19473\\Desktop\\opencv_images\\153.jpg");
if (!src.data)
{
printf("could not load image....\n");
}
namedWindow(INPUT_TITLE, CV_WINDOW_AUTOSIZE);
//namedWindow(OUT_TITLE, CV_WINDOW_AUTOSIZE);
imshow(INPUT_TITLE, src);
/*
// 将图像重新规定大小
resize(src, dst,Size(64,128));
cvtColor(dst, src_gary, CV_BGR2GRAY);
HOGDescriptor detector(Size(64,128), Size(16,16), Size(8,8),Size(8,8),9);
vector descripers;
vector locations;
detector.compute(src_gary, descripers, Size(0,0), Size(0, 0), locations);
printf("num of HOG: %d\n", descripers.size());
*/
//SVM分类器 --描述子
HOGDescriptor hog = HOGDescriptor();
hog.setSVMDetector(hog.getDefaultPeopleDetector());
vector foundloactions;
// 多尺度检测
hog.detectMultiScale(src, foundloactions, 0, Size(8, 8), Size(32, 32), 1.05, 2, false);
//若rects有嵌套,则取最外面的矩形存入rect
for (size_t i = 0; i < foundloactions.size(); i++)
{
rectangle(src, foundloactions[i], Scalar(0, 0, 255), 2, 8.0);
}
namedWindow(OUT_TITLE, CV_WINDOW_AUTOSIZE);
imshow(OUT_TITLE, src);
waitKey(0);
return 0;
}