OpenCV图像特征提取与检测C++(三)SURF特征检测、SIFT特征检测、HOG特征检测、LBP特征检测、Haar特征
**SURF(SURF是一种快速且稳定的特征检测方法, 不受环境因素影响)的核心特点:
-
- 特征识别
-
- 尺度空间 在多个尺度空间中进行缩放处理
-
- 不变量 在光照变化和物体旋转时具有不变特性
-
- 特征向量 将这些特性参数转化为特征向量形式,并通过此方法实现特征求解
- 尺度不變特性:人类在識別一個物體時 不管遠近 都能正確 辨识该物體 稱為其具有的尺度不變特性。
- 旋轉变换不變特性:當一個物體發生旋轉變化時仍能正確 辨识该物體 稱為其具有的旋轉不變特性。
SURF特征检测 工作原理:
- 选择图像中POI( Points of Interest) 通过 Hessian Matrix 寻找
- 在不同的尺度空间发现关键点,非最大信号压制(通过3*3矩阵做卷积,如果覆盖的中心像素值是局部最大值,保留,否则丢弃)
- 发现特征点方法、旋转不变性要求 (旋转不变性 可忽略)
- 光照不变性:假设{12,24,48} {6,12,24} 两组数据,将它们归一化到 0-1 之间,得到的结果都是 {0,0.5,1},这个就叫光照不变 性(随着光的变化,各像素值呈同比例增减)
- 生成特征向量 有了上述不变性特征,就可以生成特征描述子
通常情况下,标准SURF算子相比SIFT算子速度快了几个数量级,并且在处理多幅图像时展现出更高的稳定性。其主要特点在于采用了HARR特征以及积分图像的概念这一创新设计,在这种基础之上显著提升了程序运行效率。此外,该算法还可应用于计算机视觉领域的物体识别任务以及三维重建技术中。
static Ptr<cv::xfeatures2d::SURF> cv::xfeatures2d::SURF::create(
double hessianThreshold=100, // -HessianThreshold 最小的Hessian阈值 值可选300~500之间
int nOctaves = 4, // -Octaves – 4表示在四个尺度空间
int nOctaveLayers = 3, // OctaveLayers – 表示每个尺度的层数
bool extended = false,
bool upright = false // -upright 0- 表示计算选择不变性, 1表示不计算,速度更快
);
代码解读
代码:
#include <opencv2/opencv.hpp>
#include <opencv2/xfeatures2d.hpp>
#include<iostream>
#include<math.h>
#include <string>
#include<fstream>
using namespace cv;
using namespace std;
using namespace cv::xfeatures2d;
Mat src, graysrc;
int minHesssian = 100;
int maxhessian = 500;
void Surf(int, void*) {
Ptr<SURF> detector = SURF::create(minHesssian);// SURF特征检测类,Ptr 智能指针
vector<KeyPoint>keypoint;//特征点
detector->detect(src,keypoint);//特征检测
cout << "keypoint_size=" << keypoint.size() << endl;
Mat dst;
drawKeypoints(src, keypoint, dst, Scalar::all(-1), DrawMatchesFlags::DEFAULT);
imshow("output", dst);
}
int main() {
src = imread("C:/Users/Administrator/Desktop/pic/1-H.jpg");
imshow("input", src);
//cvtColor(src, graysrc, CV_BGR2GRAY);
Surf(0, 0);
createTrackbar("yuzhi", "output", &minHesssian, maxhessian, Surf);
waitKey(0);
}
代码解读
结果:

SIFT特征检测(斑点检测)
SIFT(即尺度不变性特征变换)是一种用于实现斑点检测的技术。其核心特点在于能够有效识别不同尺度下的图像关键点,并通过数学模型提取描述子以描述这些特征点的局部几何结构和纹理信息。基于此方法构建的系统在图像匹配、物体识别等领域展现出较高的鲁棒性和精确度。
- -构建尺度空间框架,并确定极值位置(最小或最大值);
- -进行关键点检测(确定关键点精确位置并剔除弱边缘);
- -定义各关键点的方向信息;
- -提取各关键点的描述特征
通过计算图像x和y方向上的梯度来确定每个像素位置处的梯度方向,并基于这些梯度值构建直方图来反映分布情况。随后,在此分布中找出出现频率最高的梯度值,并将其定义为关键点的方向。
算法特点:
- 该特征基于图像的局部特性,在面对旋转、尺度缩放、亮度变化等情况下仍能保持稳定的特性,在成像角度的变化、仿射变换以及噪声干扰下也展现出一定的鲁棒性。
- 该算法具有高度独立性和信息含量丰富,在与海量特征数据库中实现快速且精确的匹配过程。
- 即使仅有少数几个物体存在的情况下也能生成大量的SIFT特征向量。
- 该算法运行效率高
解决的问题:
- 目标执行旋转变换、缩放和平移操作
- 图像进行仿射变换/投影转换
- 光照变化的影响
- 目标出现部分遮挡
- 产生杂乱背景的场景
- 图像中存在噪声源的情况
SIFT算法的本质在于其在多尺度空间中定位关键区域(即特征描述子),并精确确定这些区域的方向信息。这些被发现的关键区域具有高度稳定性和独特性,在光照变化、几何变形以及噪声干扰等条件下依然能保持其不变性特性;具体而言包括但不限于角质层、边缘轮廓线以及暗光中的亮斑或明亮区域等典型形态。
然而尽管SIFT算法在不变异性提取方面表现优异却并非完美系统仍存在以下三点不足:
- 计算速度较慢;
- 在某些情况下可能出现关键区域数量不足的问题;
- 针对边缘平滑的目标对象难以实现精确描述
API:
static Ptr<cv::xfeatures2d::SIFT> cv::xfeatures2d::SIFT::create( // SIFT特征检测
int nfeatures = 0, // number of features 想求的特征的数量,越多计算量越大
int nOctaveLayers = 3, // 高斯金字塔的层级数,最小3,小于3就没有SIFT的尺度不变性的特点了
double contrastThreshold = 0.04, // 对比度,用于关键点定位
double edgeThreshold = 10, // 边缘阈值
double sigma = 1.6 // 高斯方差 σ
);
代码解读
代码:
#include <opencv2/opencv.hpp>
#include <opencv2/xfeatures2d.hpp>
#include<iostream>
#include<math.h>
#include <string>
#include<fstream>
using namespace cv;
using namespace std;
using namespace cv::xfeatures2d;
Mat src;
int numPoint = 100;
int max_count = 500;
void sift_detect(int, void*) {
Ptr<SIFT> detector = SIFT::create(numPoint);// SIFT特征检测类
vector<KeyPoint> keypoints;// 保存关键点
detector->detect(src, keypoints);// 检测
cout << "keypoint.size=" << keypoints.size() << endl;
Mat dst;
drawKeypoints(src, keypoints,dst,Scalar::all(-1));
imshow("output", dst);
// SIFT检测出来的特征点比SURF的检测结果看起来更准确些
}
int main() {
src = imread("C:/Users/Administrator/Desktop/pic/1-H.jpg");
imshow("input", src);
sift_detect(0, 0);
createTrackbar("yuzhi", "output", &numPoint, max_count, sift_detect);
waitKey(0);
}
代码解读
结果:

HOG特征提取方法
一种用于人体目标检测的图像描述子。其核心原理在于利用局部梯度强度分布特性来表征图像内容。即使在边缘具体位置未知的情况下,在不同边缘方向上的分布情况也能很好地反映行人目标外轮廓的关键形态特征。
步骤如下所示:
-
预处理阶段:首先对输入图像进行灰度化处理,并计算每个像素点的空间梯度。
-
细胞内梯度计算:将图像划分为大小相同的细胞,在每个细胞内计算各像素点的方向直方图。
-
块内归一化处理:将相邻的两个或多个细胞组成一个块,并对每个方向直方图进行L2范数归一化以增强鲁棒性。
-
特征向量构建:将所有块内的归一化直方图按顺序拼接成一个高维向量作为最终的人体目标特征描述。
-
颜色空间的归一化处理通常包括将图像转换为灰度图像以及对Grammar的修正。
-
梯队计算方法是一种常见的数据处理方式。
-
梯度方向的直方图是一种用于描述纹理特征的技术。
-
重叠块直方图的归一化处理有助于提高特征匹配的准确性。
-
HOG( Histogram of Oriented Gradients)特征在目标检测中表现出色。
API:
cv::HOGDescriptor HOGDescriptor( // HOG特征描述子提取
Size _winSize, // 窗口大小,对应上面举例的 64x128
Size _blockSize, // 2x2个_cellSize,也就是 16x16
Size _blockStride, // 每次移动步长,也就是 _cellSize的尺寸 8x8
Size _cellSize, // 8x8
int _nbins, // 对梯度方向直方图中的bins 9
int _derivAperture=1, // 求梯度的参数
double _winSigma=-1,
int _histogramNormType=HOGDescriptor::L2Hys, // 求直方图的参数
double _L2HysThreshold=0.2, // 归一化阈值参数
bool _gammaCorrection=false, // 对于灰度的输入图像是否要做伽马校正
int _nlevels=HOGDescriptor::DEFAULT_NLEVELS,
bool _signedGradient=false)
代码解读
代码:
#include <opencv2/opencv.hpp>
#include <opencv2/xfeatures2d.hpp>
#include<iostream>
#include<math.h>
#include <string>
#include<fstream>
using namespace cv;
using namespace std;
using namespace cv::xfeatures2d;
int main() {
Mat src = imread("C:/Users/Administrator/Desktop/pic/5.jpg");
imshow("input", src);
//HOG特征描述
Mat dst, dstgray;
resize(src, dst, Size(64, 128));
imshow("resize", dst);
cvtColor(dst, dstgray, COLOR_BGR2GRAY);
HOGDescriptor detector(Size(64, 128), Size(16, 16), Size(8, 8),Size(8,8), 9);
vector<float> descriptors;
vector<Point> locations;
detector.compute(dstgray, descriptors, Size(0, 0), Size(0, 0), locations);
cout << "descriptor.szie=" << descriptors.size() <<",locations.size"<<locations.size()<< endl;
//行人检测
Mat walkers = imread("C:/Users/Administrator/Desktop/pic/HOG2.jpg");
imshow("walkers", walkers);
HOGDescriptor hog = HOGDescriptor();
hog.setSVMDetector(hog.getDefaultPeopleDetector());
vector<Rect> foundLocations;
hog.detectMultiScale(walkers, foundLocations, 0,Size(8,8 ), Size(32, 32));
cout << "foundLocations.size=" << foundLocations.size() << endl;
Mat result = walkers.clone();
for (size_t t = 0; t < foundLocations.size(); t++) {
rectangle(result, foundLocations[t], Scalar(0, 0, 255), 2);
}
imshow("HOG", result);
waitKey(0);
}
代码解读
结果:

LBP特征:
(局部二值模式)作为一种衡量图像局部纹理信息的重要工具,在多个领域都展现出卓越的表现能力。它不仅具备旋转不变性和灰度不变性等显著优势,在人脸识别领域中被广泛认可为一种重要的特征提取方法。然而,在应对光照变化时表现得较为稳健;但其对姿态和表情变化却不够稳定。在实际应用中使用LBP进行人脸识别时,请注意以下几点:通常情况下,并不会直接使用LBP图像作为识别特征;相反地,则是通过统计其对应的直方图来构建更为可靠的分类依据
对LBP特征向量进行特征提取的步骤:
随后将输入图像划分为多个细小的区域单元(cell)。在每个区域单元内部对每一个像素而言,在其周围8个邻居像素中比较其灰度值:如果邻居像素值高于该中心像素,则标记该位置为1;否则标记为0。这样,在3×3领域内的8个点经比较可产生8位二进制数,并即得到该窗口下像素点的空间位置映射码(LBP值)。计算每个区域单元对应的直方图:统计每个可能的十进制LBP值出现的频率次数。将所有区域单元对应的统计直方图依次连接起来形成一个特征向量序列。通过预设的方法计算两张待识别图像LBP特征向量之间的相似度指标。
代码:
#include <opencv2/opencv.hpp>
#include <opencv2/xfeatures2d.hpp>
#include<iostream>
#include<math.h>
#include <string>
#include<fstream>
using namespace cv;
using namespace std;
using namespace cv::xfeatures2d;
Mat gray;
int current_radius = 3;
int max_radius = 20;
void m_lbp(int, void*) {
int offset = current_radius * 2;// 边缘未计算的像素行列数
Mat elbpImage = Mat::zeros(gray.rows - offset, gray.cols - offset, CV_8UC1);//减去边缘未计算的像素行列数
int width = gray.cols;
int height = gray.rows;
int numNeighbors = 8;//为了简化算法,不管LBP圆形半径多大,p值都取8
for (int n = 0; n < numNeighbors; n++) {
//从x坐标正方向开始,逆时针选取像素坐标点,
float x = static_cast<float>(current_radius) * cos(2.0 * CV_PI*n / static_cast<float>(numNeighbors));
float y = static_cast<float>(current_radius) * -sin(2.0 * CV_PI*n / static_cast<float>(numNeighbors));
//取当前点的周围四个点,做双线性插值
int fx = static_cast<int>(floor(x));//向下取整数,即不大于x的最大整数
int fy = static_cast<int>(floor(y));
int cx = static_cast<int>(ceil(x));//向上转整数
int cy = static_cast<int>(ceil(y));
//取小数部分
float ty = y - fy;
float tx = x - fx;
//当前点的周围四个点的权重,这个四个点的权重加起来等于1
float w1 = (1 - tx)*(1 - ty);//(0,0)
float w2 = tx * (1 - ty);//(1,0)
float w3 = (1 - tx)*ty;//(0,1)
float w4 = ty * tx;//(1,1)
for (int row = current_radius; row < (height - current_radius); row++) {
for (int col = current_radius; col < (width - current_radius); col++) {
float t = w1 * gray.at<uchar>(row + fy, col + fx) + w2 * gray.at<uchar>(row + fy, col + cx) +
w3 * gray.at<uchar>(row + cy, col + fx) + w4 * gray.at<uchar>(row + cy, col + cx);//计算当前点的像素值
uchar center = gray.at<uchar>(row, col);//中心像素值
// numeric_limits C++11 模板类 std::numeric_limits<float>::epsilon()表示的是最小非0浮点数
// 三元表达式就是在判断 t >= center , 然后移位
// 浮点数间判断等于: == 完全一样才相等, std::numeric_limits<float>::epsilon() 差值若在浮点数的精度范围内表示相等
elbpImage.at<uchar>(row - current_radius, col - current_radius) +=
((t > center) && (abs(t - center) > std::numeric_limits<float>::epsilon())) << n; // 移位相加,最终得到二进制转十进制的值
}
}
}
cout << std::numeric_limits<float>::epsilon() << endl;// 1.19209e-07 这里的e不是自然常数(2.71828),而是10,所以为 0.000000119209
imshow("output", elbpImage);
}
int main() {
gray = imread("C:/Users/Administrator/Desktop/pic/z2.jpg",0);
imshow("input",gray);
//LBP基本演示
int width = gray.cols;
int height = gray.rows;
Mat lbpImage_cw = Mat::zeros(gray.rows - 2, gray.cols - 2, CV_8UC1);//顺时针,减去2是因为卷积时上下左右两行像素忽略掉
Mat lbpImage_ccw = Mat::zeros(gray.rows - 2, gray.cols - 2, CV_8UC1);//逆时针
for (int row = 1; row < height - 1; row++) {
for (int col = 1; col < width - 1; col++) {
uchar center = gray.at<uchar>(row, col);
uchar code_cw = 0;
code_cw |= (gray.at<uchar>(row - 1, col - 1) >= center) << 7; // (0,0)位置最高位, 只判断大于 与 判断大于等于,图像结果差别很大
code_cw |= (gray.at<uchar>(row - 1, col) >= center) << 6;
code_cw |= (gray.at<uchar>(row - 1, col + 1) >= center) << 5;
code_cw |= (gray.at<uchar>(row, col + 1) >= center) << 4;
code_cw |= (gray.at<uchar>(row + 1, col + 1) >= center) << 3;
code_cw |= (gray.at<uchar>(row + 1, col) >= center) << 2;
code_cw |= (gray.at<uchar>(row + 1, col - 1) >= center) << 1;
code_cw |= (gray.at<uchar>(row, col - 1) >= center) << 0;
lbpImage_cw.at<uchar>(row - 1, col - 1) = code_cw;
uchar code_ccw = 0;
code_ccw |= (gray.at<uchar>(row - 1, col - 1) >= center) << 0; // (0,0)位置最低位
code_ccw |= (gray.at<uchar>(row - 1, col) >= center) << 1;
code_ccw |= (gray.at<uchar>(row - 1, col + 1) >= center) << 2;
code_ccw |= (gray.at<uchar>(row, col + 1) >= center) << 3;
code_ccw |= (gray.at<uchar>(row + 1, col + 1) >= center) << 4;
code_ccw |= (gray.at<uchar>(row + 1, col) >= center) << 5;
code_ccw |= (gray.at<uchar>(row + 1, col - 1) >= center) << 6;
code_ccw |= (gray.at<uchar>(row, col - 1) >= center) << 7;
lbpImage_ccw.at<uchar>(row - 1, col - 1) = code_ccw;
}
}
imshow("LBP base cw", lbpImage_cw);
imshow("LBP base ccw", lbpImage_ccw);
//LBP扩展与多尺度表达
m_lbp(0, 0);
createTrackbar("yuzhi", "output", ¤t_radius, max_radius, m_lbp);
waitKey(0);
}
代码解读
结果:

具有Haar-like特性的特征最初是由Papageorgiou及其团队开发用于人脸表示。随后Viola和Jones在此框架下开发了包括3种类型在内的4种不同形式的特征。
Haar特征分为四种类型:边缘型、直线型、中心型以及对角线型。这些不同类型的矩形区域通过组合形成特征模板。每个模板由白色矩形区域与黑色矩形区域构成,并定义模板的特征值为白色区域像素之和减去黑色区域像素之和。这种计算方式能够有效反映图像灰度变化的程度。例如,在脸部识别中一些简单的几何特性可以用矩形描述来简洁表达:如眼睛比脸颊颜色更深邃等现象;但这种基于矩形的描述方法对复杂图形结构则表现力有限。因此,在实际应用中该方法仅适用于较为简单的图形结构如水平边缘、垂直线条及对角线方向等
积分图是一种能够快速计算图像任意子区域像素总和的有效算法
从而显著提升了检测速度
void integral( // 积分图计算
InputArray src, // 输入图像,灰度图
OutputArray sum, // 求和,输出的尺寸比原图行列都要加1 , 不想行列变化的,自己实现积分图算法
OutputArray sqsum, // 求和的平方,输出的尺寸比原图行列都要加1
OutputArray tilted, // 旋转45°的积分图
int sdepth = -1,
int sqdepth = -1
);
代码解读
代码:
#include <opencv2/opencv.hpp>
#include <opencv2/xfeatures2d.hpp>
#include<iostream>
#include<math.h>
#include <string>
#include<fstream>
using namespace cv;
using namespace std;
using namespace cv::xfeatures2d;
int main() {
Mat src = imread("C:/Users/Administrator/Desktop/pic/1.jpg",0);
imshow("input", src);
Mat dst1 = Mat::zeros(src.rows + 1, src.cols + 1, CV_32FC1);
Mat dst2 = Mat::zeros(src.rows + 1, src.cols + 1, CV_64FC1);
integral(src, dst1, dst2);
Mat res1, res2;
normalize(dst1, res1, 0, 255, NORM_MINMAX,CV_8UC1);
imshow("dst1", dst1);
normalize(dst2, res2, 0, 255, NORM_MINMAX,CV_8UC1);
imshow("dst2", dst2);
waitKey(0);
}
代码解读
结果:

