opencv学习笔记

这里写自定义目录标题

数据结构cv::Matcv::Matx (小型矩阵)cv::Pointcv::Rectcv::Scalarcv::Sizecv::Veccv::KeyPointcv::DMatchcv::RotatedRectcv::InputArray / OutputArray
图像变换色彩空间几何变换1. 仿射变换 (Affine Transformation)平移变换 (Translation)旋转变换 (Rotation)缩放变换 (Scaling)
2. 投影变换 (Perspective Transformation)3. 插值方法 (Interpolation)INTER_NEAREST – 最近邻插值INTER_LINEAR – 双线性插值INTER_CUBIC – 双三次插值INTER_LANCZOS4 – Lanczos插值

图像滤波线性滤波方框滤波 (Box Filter)高斯滤波 (Gaussian Blur)
非线性滤波中值滤波 (Median Blur)双边滤波 (Bilateral Filter)
形态学滤波腐蚀 (Erosion)膨胀 (Dilation)开运算 (Opening)闭运算 (Closing)
边缘检测滤波器Sobel算子Scharr算子Laplacian算子Canny边缘检测边缘检测滤波器比较选择建议
关键参数说明滤波方法选择指南
图像分割基于阈值的分割简单阈值分割自适应阈值分割
基于边缘的分割Canny边缘检测边缘连接与轮廓提取
基于区域的分割区域生长法分水岭算法 (Watershed)
基于图论的分割GrabCut算法
基于聚类的分割K-means聚类分割Mean-Shift分割

特征提取颜色特征颜色直方图 (Color Histogram)颜色矩 (Color Moments)
纹理特征灰度共生矩阵 (GLCM – Gray Level Co-occurrence Matrix)LBP (Local Binary Pattern)HOG (Histogram of Oriented Gradients)
形状特征轮廓特征Hu矩不变量 (Hu Moments)
关键点与描述符SIFT (Scale-Invariant Feature Transform)SURF (Speeded-Up Robust Features)ORB (Oriented FAST and Rotated BRIEF)BRISK (Binary Robust Invariant Scalable Keypoints)关键点与描述符的应用
深度特征使用预训练深度学习模型
特征提取方法选择指南特征提取流程总结

数据结构

cv::Mat

用于存储多维数组(如图像、矩阵),是 OpenCV 最核心的数据结构。


#include <opencv2/opencv.hpp>
cv::Mat img = cv::imread("image.jpg"); // 读取图像为 Mat 对象
cv::Mat gray;
cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY); // 转换为灰度图

cv::Matx (小型矩阵)

固定大小的轻量矩阵,常用于小规模运算。


cv::Matx33f matx(1, 0, 0, 0, 1, 0, 0, 0, 1);  // 3x3 单位矩阵
float val = matx(1, 1);  // 访问元素

cv::Point

表示二维点,模板类支持多种数据类型(如
int
,
float
)。


cv::Point pt1(10, 20); // 整数坐标点
cv::Point2f pt2(5.5f, 3.2f); // 浮点坐标点
cv::circle(img, pt1, 5, cv::Scalar(0, 255, 0), -1); // 绘制点

cv::Rect

表示矩形区域,包含左上角坐标和宽高。


cv::Rect roi(50, 50, 100, 100); // x, y, width, height
cv::Mat cropped = img(roi); // 裁剪图像区域
cv::rectangle(img, roi, cv::Scalar(255, 0, 0), 2); // 绘制矩形

cv::Scalar

存储 1-4 个通道的数值,常用于表示颜色。


cv::Scalar color(0, 0, 255); // BGR 颜色 (红色)
cv::Mat blank(300, 300, CV_8UC3, color); // 创建纯色图像

cv::Size

表示尺寸(宽度和高度)。


cv::Size sz(640, 480);
cv::resize(img, img, sz); // 调整图像尺寸

cv::Vec

模板类表示固定长度的向量,常用于像素值访问。


cv::Vec3b pixel = img.at<cv::Vec3b>(10, 10); // 获取 BGR 像素值
pixel[0] = 255; // 修改蓝色通道值

cv::KeyPoint

存储特征点检测结果(如 SIFT、ORB)。


std::vector<cv::KeyPoint> keypoints;
cv::Ptr<cv::FeatureDetector> detector = cv::ORB::create();
detector->detect(img, keypoints); // 检测特征点
cv::drawKeypoints(img, keypoints, img); // 绘制特征点

cv::DMatch

存储特征匹配结果。


std::vector<cv::DMatch> matches;
cv::BFMatcher matcher(cv::NORM_HAMMING);
matcher.match(descriptors1, descriptors2, matches); // 特征匹配

cv::RotatedRect

表示旋转矩形(中心点、尺寸、旋转角度)。


cv::RotatedRect rRect(cv::Point2f(100, 100), cv::Size2f(50, 30), 45);
cv::Point2f vertices[4];
rRect.points(vertices); // 获取四个顶点

cv::InputArray / OutputArray

通用输入/输出接口,支持 Mat、vector 等。


void processImage(cv::InputArray input, cv::OutputArray output) {
    cv::Mat img = input.getMat();
    cv::cvtColor(img, output, cv::COLOR_BGR2GRAY);
}

图像变换

色彩空间

特性 RGB HSV YUV/YCbCr
核心组成 红、绿、蓝 色相、饱和度、明度 亮度、色度(蓝色差、红色差)
模型类型 加色模型 (用于发光设备) 用户直观模型 (基于感知) 亮度-色度分离模型
设计目的 匹配显示器硬件 方便人类理解和选择颜色 高效压缩、兼容黑白电视
优点 硬件原生支持,处理快 非常直观,易于颜色操作和分割 利用人眼特性实现高效压缩
主要缺点 不符合直觉,亮色耦合 需要与RGB转换才能显示 不直观,不适合直接编辑
典型应用 所有显示器、数码成像、游戏图形 颜色选择器、图像处理、颜色分割 视频编码 (H.264/HEVC)、电视广播、JPEG

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

using namespace cv;
using namespace std;

// 创建测试图像 - 彩色渐变
Mat createTestImage(int width, int height) {
    Mat image(height, width, CV_8UC3);
    
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            // 创建从红到蓝的渐变
            Vec3b& pixel = image.at<Vec3b>(y, x);
            pixel[0] = static_cast<uchar>(255 * x / width);        // Blue
            pixel[1] = static_cast<uchar>(255 * y / height);       // Green  
            pixel[2] = static_cast<uchar>(255 * (1 - x / width));  // Red
        }
    }
    
    return image;
}

// 显示RGB图像及其通道
void displayRGB(const Mat& image) {
    cout << "显示RGB色彩空间..." << endl;
    
    // 分离RGB通道
    vector<Mat> rgbChannels;
    split(image, rgbChannels);
    
    // 创建各个通道的显示图像(单通道显示为灰度)
    Mat redChannel, greenChannel, blueChannel;
    
    // 创建纯黑图像作为背景
    Mat black = Mat::zeros(image.size(), CV_8UC1);
    
    // 合并通道来显示单个颜色通道
    vector<Mat> channels;
    
    // 红色通道
    channels = {black, black, rgbChannels[2]}; // B, G, R
    merge(channels, redChannel);
    
    // 绿色通道
    channels = {black, rgbChannels[1], black};
    merge(channels, greenChannel);
    
    // 蓝色通道
    channels = {rgbChannels[0], black, black};
    merge(channels, blueChannel);
    
    // 显示图像
    imshow("1. RGB - 原始图像", image);
    imshow("2. RGB - 红色通道", redChannel);
    imshow("3. RGB - 绿色通道", greenChannel);
    imshow("4. RGB - 蓝色通道", blueChannel);
    
    waitKey(0);
    destroyAllWindows();
}

// 显示HSV图像及其通道
void displayHSV(const Mat& image) {
    cout << "显示HSV色彩空间..." << endl;
    
    Mat hsvImage;
    cvtColor(image, hsvImage, COLOR_BGR2HSV);
    
    // 分离HSV通道
    vector<Mat> hsvChannels;
    split(hsvImage, hsvChannels);
    
    // 显示各个通道
    imshow("5. HSV - 原始BGR图像", image);
    imshow("6. HSV - 色相 (Hue) 通道", hsvChannels[0]);       // 色相
    imshow("7. HSV - 饱和度 (Saturation) 通道", hsvChannels[1]); // 饱和度
    imshow("8. HSV - 明度 (Value) 通道", hsvChannels[2]);      // 明度
    
    // 解释:色相通道看起来像灰度图,但实际上每个灰度值代表一个颜色角度
    cout << "注意:色相通道中,不同的灰度值代表不同的颜色角度(0-180°,OpenCV中H范围是0-180)" << endl;
    
    waitKey(0);
    destroyAllWindows();
}

// 显示YUV图像及其通道
void displayYUV(const Mat& image) {
    cout << "显示YUV色彩空间..." << endl;
    
    Mat yuvImage;
    cvtColor(image, yuvImage, COLOR_BGR2YUV);
    
    // 分离YUV通道
    vector<Mat> yuvChannels;
    split(yuvImage, yuvChannels);
    
    // 显示各个通道
    imshow("9. YUV - 原始BGR图像", image);
    imshow("10. YUV - 亮度 (Y) 通道", yuvChannels[0]);     // 亮度
    imshow("11. YUV - 色度 U (Cb) 通道", yuvChannels[1]);  // 蓝色差
    imshow("12. YUV - 色度 V (Cr) 通道", yuvChannels[2]);  // 红色差
    
    // 解释YUV通道的含义
    cout << "Y通道:亮度信息,相当于黑白图像" << endl;
    cout << "U通道:蓝色差信息(B-Y)" << endl;
    cout << "V通道:红色差信息(R-Y)" << endl;
    
    waitKey(0);
    destroyAllWindows();
}

// 显示颜色分割示例(使用HSV)
void displayColorSegmentation(const Mat& image) {
    cout << "演示HSV在颜色分割中的应用..." << endl;
    
    Mat hsvImage;
    cvtColor(image, hsvImage, COLOR_BGR2HSV);
    
    // 定义要检测的红色范围(在HSV空间中)
    Scalar lower_red1(0, 70, 50);    // 红色范围1(0-10°)
    Scalar upper_red1(10, 255, 255);
    Scalar lower_red2(170, 70, 50);  // 红色范围2(170-180°)
    Scalar upper_red2(180, 255, 255);
    
    // 创建掩码
    Mat mask1, mask2, redMask;
    inRange(hsvImage, lower_red1, upper_red1, mask1);
    inRange(hsvImage, lower_red2, upper_red2, mask2);
    redMask = mask1 | mask2;
    
    // 应用掩码到原图
    Mat result;
    image.copyTo(result, redMask);
    
    // 显示结果
    imshow("13. 原始图像", image);
    imshow("14. 红色区域掩码", redMask);
    imshow("15. 颜色分割结果 - 只保留红色", result);
    
    cout << "HSV颜色分割完成!可以看到只有红色区域被保留下来。" << endl;
    
    waitKey(0);
    destroyAllWindows();
}

int main() {
    cout << "开始演示不同色彩空间的图像表示..." << endl;
    cout << "=========================================" << endl;
    
    // 创建测试图像
    Mat testImage = createTestImage(400, 300);
    
    // 显示各个色彩空间
    displayRGB(testImage);
    displayHSV(testImage);
    displayYUV(testImage);
    
    // 创建另一个测试图像(包含明显的红色区域)
    Mat colorImage(300, 400, CV_8UC3, Scalar(100, 100, 100));
    
    // 添加一些红色区域
    rectangle(colorImage, Point(50, 50), Point(150, 150), Scalar(0, 0, 255), -1);   // 红色矩形
    rectangle(colorImage, Point(200, 100), Point(300, 200), Scalar(0, 255, 0), -1); // 绿色矩形
    circle(colorImage, Point(100, 250), 40, Scalar(255, 0, 0), -1);                 // 蓝色圆形
    
    displayColorSegmentation(colorImage);
    
    cout << "演示结束!按任意键退出..." << endl;
    waitKey(0);
    
    return 0;
}

几何变换

几何变换的本质是坐标映射。对于原图像中的每个像素点 (x, y),通过某种变换函数找到它在目标图像中对应的新位置 (x’, y’)。
数学表示为:


[x']   = f([x])
[y']      [y]

1. 仿射变换 (Affine Transformation)

仿射变换是最常用的几何变换,它保持直线的平行性。包括平移、旋转、缩放、错切等。

变换矩阵形式:


[x']   = [a11 a12] [x]   + [b1]
[y']     [a21 a22] [y]     [b2]

或者用齐次坐标表示:


[x']   = [a11 a12 b1] [x]
[y']     [a21 a22 b2] [y]
[1 ]     [0  0  1 ]   [1]
平移变换 (Translation)

Mat translationMatrix = (Mat_<double>(2,3) << 1, 0, tx, 
                                              0, 1, ty);
warpAffine(src, dst, translationMatrix, src.size());
旋转变换 (Rotation)

Point2f center(src.cols/2.0, src.rows/2.0);
Mat rotationMatrix = getRotationMatrix2D(center, angle, scale);
warpAffine(src, dst, rotationMatrix, src.size());
缩放变换 (Scaling)

// 方法1:使用resize函数
resize(src, dst, Size(), scaleX, scaleY, INTER_LINEAR);

// 方法2:使用仿射变换矩阵
Mat scaleMatrix = (Mat_<double>(2,3) << scaleX, 0, 0,
                                        0, scaleY, 0);
warpAffine(src, dst, scaleMatrix, src.size());

2. 投影变换 (Perspective Transformation)

投影变换也称为透视变换,它不保持平行性,用于模拟透视效果。

变换矩阵形式:


[x']   = [a11 a12 a13] [x]
[y']     [a21 a22 a23] [y]
[w']     [a31 a32 1 ]  [1]

3×3的投影变换矩阵有8个自由度(9个元素,但可以归一化,所以少一个自由度),需要4对对应点来唯一确定。

实际坐标:
x'' = x'/w', y'' = y'/w'


// 定义原图像和目标图像的4个对应点
Point2f srcPoints[4] = {Point2f(0,0), Point2f(src.cols,0), 
                        Point2f(src.cols,src.rows), Point2f(0,src.rows)};
Point2f dstPoints[4] = {Point2f(0,0), Point2f(dst.cols,0), 
                        Point2f(dst.cols-100,dst.rows), Point2f(100,dst.rows)};

Mat perspectiveMatrix = getPerspectiveTransform(srcPoints, dstPoints);
warpPerspective(src, dst, perspectiveMatrix, dst.size());

3. 插值方法 (Interpolation)

当进行几何变换时,目标像素的位置可能对应原图像中的非整数坐标。插值就是根据周围已知像素值来估算这些”中间位置”像素值的方法。

INTER_NEAREST – 最近邻插值

含义:选择距离目标位置最近的原始像素值。

计算流程:

计算目标位置在原图像中的坐标 (x, y)四舍五入到最近的整数坐标:(round(x), round(y))直接取该位置的像素值

数学表示:


I(x,y) = I(round(x), round(y))

示例:


原图像像素:I(1,1)=10, I(1,2)=20, I(2,1)=30, I(2,2)=40
目标位置:(1.3, 1.7)
最近邻:round(1.3)=1, round(1.7)=2 → I(1,2)=20

特点:计算简单快速,但会产生锯齿状边缘。

INTER_LINEAR – 双线性插值

图片[1] - opencv学习笔记 - 鹿快
数学公式:


I(x,y) = (1-a)(1-b)·I(i,j) + a(1-b)·I(i+1,j) + 
         (1-a)b·I(i,j+1) + a·b·I(i+1,j+1)

示例:


I(1,1)=10, I(2,1)=20, I(1,2)=30, I(2,2)=40
目标位置:(1.3, 1.7)
a = 0.3, b = 0.7

计算:
(1-0.3)(1-0.7)·10 = 0.7×0.3×10 = 2.1
0.3×(1-0.7)·20 = 0.3×0.3×20 = 1.8
(1-0.3)×0.7·30 = 0.7×0.7×30 = 14.7
0.3×0.7·40 = 0.21×40 = 8.4

总和:2.1 + 1.8 + 14.7 + 8.4 = 27

特点:速度和质量平衡,是OpenCV的默认选择。

INTER_CUBIC – 双三次插值

图片[2] - opencv学习笔记 - 鹿快
权重函数(常用):


W(x) = { 
    (a+2)|x|³ - (a+3)|x|² + 1,    for |x| ≤ 1
    a|x|³ - 5a|x|² + 8a|x| - 4a, for 1 < |x| < 2
    0,                           otherwise
}
其中 a 通常取 -0.5 或 -0.75

计算复杂度:每个像素需要16次乘加运算
特点:质量更好,边缘更平滑,但计算量较大。

INTER_LANCZOS4 – Lanczos插值

含义:使用sinc函数作为插值核,考虑8×8邻域。
计算流程:

使用Lanczos窗口函数:


L(x) = { 
    sinc(x) · sinc(x/a),  for |x| < a
    0,                    otherwise
}

其中 sinc(x) = sin(πx)/(πx),a通常取2、3、4

对于INTER_LANCZOS4,a=4,使用8×8邻域
数学公式:


I(x,y) = ΣΣ I(i+k, j+l) · L(k - dx) · L(l - dy)
其中 k,l = -3,-2,-1,0,1,2,3,4

特点:质量最好,能很好保留细节,但计算最复杂,可能产生振铃效应。

图像滤波

滤波的本质:通过一个小的滤波器(或称为核、模板)在图像上滑动,对每个像素邻域进行数学运算,得到新的像素值。

数学表示:


I'(x,y) = ΣΣ K(i,j) · I(x+i, y+j)
其中 K 是滤波器核,I 是原图像

线性滤波

方框滤波 (Box Filter)

含义:计算邻域内像素的平均值。

核示例(3×3):


[1 1 1]
[1 1 1] × (1/9)
[1 1 1]

void boxFilter(InputArray src, OutputArray dst, int ddepth,
               Size ksize, Point anchor = Point(-1,-1),
               bool normalize = true, int borderType = BORDER_DEFAULT);

高斯滤波 (Gaussian Blur)

含义:使用高斯函数作为权重进行加权平均,中心像素权重最大。
核示例(σ=1的3×3高斯核):


[1 2 1]
[2 4 2] × (1/16)
[1 2 1]

void GaussianBlur(InputArray src, OutputArray dst, Size ksize,
                  double sigmaX, double sigmaY = 0,
                  int borderType = BORDER_DEFAULT);

非线性滤波

中值滤波 (Median Blur)

含义:取邻域内像素值的中位数。
特点:

有效去除椒盐噪声保留边缘信息计算量较大


void medianBlur(InputArray src, OutputArray dst, int ksize);

双边滤波 (Bilateral Filter)

含义:同时考虑空间距离和像素值相似性的滤波方法。
权重计算:


权重 = 空间权重 × 亮度权重
空间权重:基于像素距离(类似高斯)
亮度权重:基于像素值差异

特点:

保边去噪计算复杂度高


 void bilateralFilter(InputArray src, OutputArray dst, int d,
                     double sigmaColor, double sigmaSpace,
                     int borderType = BORDER_DEFAULT);

形态学滤波

腐蚀 (Erosion)

含义:用核的最小值替换中心像素,使亮区缩小。
效果:去除小白点,分离相连物体。


void erode(InputArray src, OutputArray dst, InputArray kernel,
           Point anchor = Point(-1,-1), int iterations = 1,
           int borderType = BORDER_CONSTANT,
           const Scalar& borderValue = morphologyDefaultBorderValue());

膨胀 (Dilation)

含义:用核的最大值替换中心像素,使亮区扩大。
效果:填充小孔洞,连接断裂部分。


void dilate(InputArray src, OutputArray dst, InputArray kernel,
            Point anchor = Point(-1,-1), int iterations = 1,
            int borderType = BORDER_CONSTANT,
            const Scalar& borderValue = morphologyDefaultBorderValue());

开运算 (Opening)

含义:先腐蚀后膨胀。
效果:去除小物体,平滑轮廓。


morphologyEx(src, dst, MORPH_OPEN, kernel);

闭运算 (Closing)

含义:先膨胀后腐蚀。
效果:填充小孔洞,连接邻近物体。


morphologyEx(src, dst, MORPH_CLOSE, kernel);

边缘检测滤波器

边缘的本质是图像灰度值发生剧烈变化的地方。数学上,这种变化可以用导数或梯度来表示:

一阶导数:检测边缘的存在和方向二阶导数:检测边缘的位置和类型(阶跃边缘、屋顶边缘等)

Sobel算子

含义:计算图像梯度,检测边缘。
基本原理:Sobel算子使用两个3×3的卷积核(水平核和垂直核)分别计算图像在x方向和y方向的梯度近似值。
卷积核定义
水平方向核(检测垂直边缘):


Gx = [-1  0  1]
     [-2  0  2]
     [-1  0  1]

垂直方向核(检测水平边缘):


Gy = [-1 -2 -1]
     [ 0  0  0]
     [ 1  2  1]

梯度计算


梯度幅值:G = √(Gx² + Gy²)
梯度方向:θ = arctan(Gy / Gx)

void Sobel(InputArray src, OutputArray dst, int ddepth,
           int dx, int dy, int ksize = 3, double scale = 1,
           double delta = 0, int borderType = BORDER_DEFAULT);

参数说明
dx, dy:x和y方向的导数阶数
ksize:核大小,必须是1, 3, 5, 7
scale:缩放因子
delta:添加到结果的偏移量

Scharr算子

含义:对Sobel的改进,更精确的边缘检测。
基本原理:Scharr算子是Sobel算子的改进版本,具有更高的旋转对称性和更好的边缘检测精度。
卷积核定义
水平方向核:


Gx = [-3   0   3]
     [-10  0  10]
     [-3   0   3]

垂直方向核:


Gy = [-3 -10 -3]
     [ 0   0  0]
     [ 3  10  3]

void Scharr(InputArray src, OutputArray dst, int ddepth,
            int dx, int dy, double scale = 1, double delta = 0,
            int borderType = BORDER_DEFAULT);

Laplacian算子

含义:二阶微分算子,检测边缘和角点。
基本原理:Laplacian是二阶微分算子,直接计算图像的拉普拉斯变换,对噪声更敏感但能检测更细的边缘。
卷积核定义
常用的3×3拉普拉斯核:


[ 0 -1  0]
[-1  4 -1]
[ 0 -1  0]

或者:


[-1 -1 -1]
[-1  8 -1]
[-1 -1 -1]

数学表示


∇²f = ∂²f/∂x² + ∂²f/∂y²

void Laplacian(InputArray src, OutputArray dst, int ddepth,
               int ksize = 1, double scale = 1, double delta = 0,
               int borderType = BORDER_DEFAULT);

Canny边缘检测

Canny是边缘检测的重要方法。

Canny算法步骤

高斯滤波:去除噪声计算梯度:使用Sobel算子非极大值抑制:细化边缘双阈值检测:连接边缘

步骤1:高斯滤波 – 去除噪声

目的:消除图像中的噪声,因为边缘检测算法对噪声非常敏感。

原理:使用高斯滤波器对图像进行平滑处理。高斯核的权重从中心向四周递减,符合正态分布。

高斯核示例(5×5, σ=1.4):


[2  4  5  4  2]
[4  9  12 9  4]
[5  12 15 12 5] × (1/159)
[4  9  12 9  4]
[2  4  5  4  2]

数学表示


G(x,y) = (1/(2πσ²)) · exp(-(x²+y²)/(2σ²))
I_smooth(x,y) = G(x,y) ∗ I(x,y)

效果 :减少高频噪声 ;轻微模糊图像,使边缘更加连续

步骤2:计算梯度 – 使用Sobel算子
目的:计算图像中每个像素点的梯度强度和方向。

使用Sobel算子
水平方向核Gx:


[-1  0  1]
[-2  0  2]
[-1  0  1]

垂直方向核Gy:


[-1 -2 -1]
[ 0  0  0]
[ 1  2  1]

梯度计算
对于每个像素点(i,j):


Gx = Sobel_x ∗ I_smooth
Gy = Sobel_y ∗ I_smooth
梯度幅值:G = √(Gx² + Gy²)
梯度方向:θ = arctan(Gy / Gx) (取值范围:-π/2 到 π/2)

方向量化
将梯度方向量化为4个主要方向:


0°(水平方向)
45°(正对角线方向)
90°(垂直方向)
135°(反对角线方向)

步骤3:非极大值抑制 – 细化边缘
目的:去除非边缘像素,使边缘变得更细(理想情况下只有一个像素宽)。
原理:检查每个像素点,如果它在梯度方向上的幅值不是局部最大值,则将其抑制(设为0)。

具体步骤

对于每个像素点,根据其梯度方向θ,确定要比较的相邻像素比较当前像素的梯度幅值与梯度方向上的两个相邻像素如果当前像素的幅值不是最大,则将其抑制

比较方向选择
根据量化后的梯度方向:


0°:比较左右像素
45°:比较右上和左下像素
90°:比较上下像素
135°:比较左上和右下像素

示例


假设某像素梯度方向为90°(垂直方向)
比较:当前像素 vs 上方像素 vs 下方像素
只有当当前像素梯度值 > 上方像素 且 > 下方像素时,才保留

步骤4:双阈值检测 – 连接边缘
目的:识别和连接真正的边缘,去除虚假边缘。
原理
使用两个阈值:


高阈值(T_high):强边缘阈值
低阈值(T_low):弱边缘阈值

具体步骤

强边缘判断:梯度值 > T_high → 确定为强边缘弱边缘判断:T_low < 梯度值 ≤ T_high → 标记为弱边缘边缘连接:弱边缘只有在与强边缘相连时才被保留为真正边缘

边缘连接算法

从强边缘像素开始检查8邻域内的像素如果邻域像素是弱边缘,将其提升为强边缘递归地进行这个过程,直到没有新的弱边缘被连接

阈值选择原则

高阈值:通常设置为梯度幅值直方图的70-80百分位低阈值:通常为高阈值的0.4-0.5倍经验公式:T_low = 0.4 × T_high 或 T_low = 0.5 × T_high


void Canny(InputArray image, OutputArray edges,
           double threshold1, double threshold2,
           int apertureSize = 3, bool L2gradient = false);

边缘检测滤波器比较

方法 原理 优点 缺点 适用场景
Sobel 一阶导数 计算快,实现简单 边缘较粗,对噪声敏感 实时应用,初步边缘检测
Scharr 改进的一阶导数 精度高,旋转对称性好 计算稍复杂 需要精确边缘检测
Laplacian 二阶导数 能检测更细的边缘,各向同性 对噪声非常敏感 需要检测细微边缘
Canny 多阶段算法 综合性能最好,抗噪声 计算复杂,参数敏感 高质量的边缘检测

选择建议

实时应用:选择Sobel算子
精确边缘检测:选择Scharr算子
细微边缘检测:选择Laplacian算子(配合降噪)
高质量边缘检测:选择Canny算法
噪声环境:先进行高斯滤波再进行边缘检测

关键参数说明

核大小 (ksize):通常选择奇数,如3, 5, 7等
Sigma值:高斯滤波的标准差,控制平滑程度
迭代次数:形态学操作的重复次数
核形状:矩形(MORPH_RECT)、椭圆(MORPH_ELLIPSE)、十字(MORPH_CROSS)

滤波方法选择指南

滤波类型 适用场景 优点 缺点
高斯滤波 一般性平滑,高斯噪声 计算快,效果好 模糊边缘
中值滤波 椒盐噪声,脉冲噪声 有效去噪,保边 计算较慢
双边滤波 保边去噪,细节保留 优秀保边效果 计算复杂
方框滤波 快速平均,简单平滑 计算最快 效果一般
形态学滤波 二值图像处理,形状分析 改变物体形状 仅适用二值图
Sobel滤波 边缘检测,梯度计算 边缘定位准 对噪声敏感

图像分割

图像分割的目的是将图像划分为多个有意义的区域或对象。

基于阈值的分割

原理:基于像素的灰度值设置一个或多个阈值,将像素分为不同的类别。

简单阈值分割

流程:

选择初始阈值T(通常为128或图像平均灰度值)根据阈值将图像分为两部分:
前景:像素值 > T
背景:像素值 ≤ T计算两部分的平均灰度值μ1和μ2计算新阈值:T_new = (μ1+μ2) / 2重复步骤2-4直到T收敛


double threshold(InputArray src, OutputArray dst, 
                 double thresh, double maxval, int type);

类型参数:

THRESH_BINARY: 二值化
THRESH_BINARY_INV: 反二值化
THRESH_TRUNC: 截断
THRESH_TOZERO: 零处理
THRESH_TOZERO_INV: 反零处理

自适应阈值分割

原理:为图像的不同区域计算不同的阈值
流程:

将图像分成多个小区域对每个区域计算局部阈值(均值或高斯加权均值)应用局部阈值进行二值化


void adaptiveThreshold(InputArray src, OutputArray dst,
                       double maxValue, int adaptiveMethod,
                       int thresholdType, int blockSize, double C);

自适应方法:

ADAPTIVE_THRESH_MEAN_C: 局部均值ADAPTIVE_THRESH_GAUSSIAN_C: 局部高斯加权

基于边缘的分割

原理:利用图像中灰度值急剧变化的区域(边缘)来划分边界。
流程

边缘检测:使用Canny、Sobel等算子检测边缘边缘连接:将断裂的边缘连接起来轮廓提取:找到闭合的边界区域填充:根据边界填充区域


Mat edgeBasedSegmentation(const Mat& src) {
    Mat gray, edges, binary;
    cvtColor(src, gray, COLOR_BGR2GRAY);
    
    // 边缘检测
    Canny(gray, edges, 50, 150);
    
    // 形态学操作连接边缘
    Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
    morphologyEx(edges, edges, MORPH_CLOSE, kernel);
    
    // 查找轮廓并填充
    vector<vector<Point>> contours;
    findContours(edges, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
    
    binary = Mat::zeros(src.size(), CV_8UC1);
    drawContours(binary, contours, -1, Scalar(255), FILLED);
    
    return binary;
}

Canny边缘检测


void Canny(InputArray image, OutputArray edges,
           double threshold1, double threshold2,
           int apertureSize = 3, bool L2gradient = false);

边缘连接与轮廓提取


void findContours(InputArray image, OutputArrayOfArrays contours,
                  OutputArray hierarchy, int mode,
                  int method, Point offset = Point());

基于区域的分割

区域生长法

原理:从种子点开始,将具有相似性质的像素合并到同一区域。
流程:

选择种子像素检查邻域像素,如果满足相似性准则则合并重复直到没有新的像素可以合并


Mat regionGrowing(const Mat& gray, Point seed, int threshold) {
    Mat segmented = Mat::zeros(gray.size(), CV_8UC1);
    queue<Point> pointsQueue;
    pointsQueue.push(seed);
    
    uchar seedValue = gray.at<uchar>(seed);
    segmented.at<uchar>(seed) = 255;
    
    while (!pointsQueue.empty()) {
        Point p = pointsQueue.front();
        pointsQueue.pop();
        
        for (int dy = -1; dy <= 1; dy++) {
            for (int dx = -1; dx <= 1; dx++) {
                Point neighbor(p.x + dx, p.y + dy);
                
                if (neighbor.x >= 0 && neighbor.x < gray.cols &&
                    neighbor.y >= 0 && neighbor.y < gray.rows) {
                    
                    if (segmented.at<uchar>(neighbor) == 0) {
                        uchar neighborValue = gray.at<uchar>(neighbor);
                        if (abs(neighborValue - seedValue) <= threshold) {
                            segmented.at<uchar>(neighbor) = 255;
                            pointsQueue.push(neighbor);
                        }
                    }
                }
            }
        }
    }
    return segmented;
}

分水岭算法 (Watershed)

原理:将图像看作地形表面,亮度值表示海拔高度。
流程:

计算梯度图像生成标记(确定的前景、背景区域)应用分水岭算法标记不同的区域


Mat watershedSegmentation(const Mat& src) {
    Mat gray, binary;
    cvtColor(src, gray, COLOR_BGR2GRAY);
    
    // 预处理
    GaussianBlur(gray, gray, Size(5, 5), 2);
    threshold(gray, binary, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
    
    // 形态学操作
    Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
    morphologyEx(binary, binary, MORPH_OPEN, kernel, Point(-1, -1), 2);
    
    // 距离变换
    Mat dist;
    distanceTransform(binary, dist, DIST_L2, 5);
    normalize(dist, dist, 0, 1.0, NORM_MINMAX);
    
    // 创建标记
    threshold(dist, dist, 0.4, 1.0, THRESH_BINARY);
    Mat markers = Mat::zeros(dist.size(), CV_32S);
    
    // 查找轮廓作为前景标记
    vector<vector<Point>> contours;
    findContours(dist.clone(), contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
    
    for (size_t i = 0; i < contours.size(); i++) {
        drawContours(markers, contours, static_cast<int>(i), 
                    Scalar(static_cast<int>(i) + 1), -1);
    }
    
    // 背景标记
    circle(markers, Point(5, 5), 3, Scalar(255), -1);
    
    // 应用分水岭
    watershed(src, markers);
    
    // 可视化结果
    Mat result;
    markers.convertTo(result, CV_8U, 255.0 / (contours.size() + 1));
    
    return result;
}

分水岭步骤:

计算梯度图像生成标记(前景、背景、未知区域)应用分水岭算法

基于图论的分割

GrabCut算法

原理:基于图割理论,通过最小化能量函数实现分割。
流程:

用户指定前景和背景区域建立高斯混合模型(GMM)构建图并计算最小割迭代优化直到收敛


void grabCut(InputArray img, InputOutputArray mask, Rect rect,
             InputOutputArray bgdModel, InputOutputArray fgdModel,
             int iterCount, int mode = GC_EVAL);

基于聚类的分割

K-means聚类分割

原理:将像素根据颜色特征聚类到K个簇中。
流程:

随机选择K个中心点将每个像素分配到最近的中心点重新计算中心点位置重复步骤2-3直到收敛


double kmeans(InputArray data, int K, InputOutputArray bestLabels,
              TermCriteria criteria, int attempts,
              int flags, OutputArray centers = noArray());

Mean-Shift分割

原理:基于密度估计的聚类方法,自动确定簇的数量。


void pyrMeanShiftFiltering(InputArray src, OutputArray dst,
                           double sp, double sr,
                           int maxLevel = 1,
                           TermCriteria termcrit = TermCriteria(
                               TermCriteria::MAX_ITER+TermCriteria::EPS, 5, 1));

特征提取

特征提取是从图像中提取有意义的信息(特征)的过程,这些特征能够描述图像的内容,用于后续的图像识别、匹配、分类等任务。

颜色特征

颜色直方图 (Color Histogram)

原理:统计图像中每种颜色值的像素数量分布。
流程:

将颜色空间量化(如256级)统计每个颜色级别的像素数量归一化直方图


Mat computeColorHistogram(const Mat& image, int bins = 256) {
    vector<Mat> bgr_planes;
    split(image, bgr_planes);
    
    int histSize = bins;
    float range[] = {0, 256};
    const float* histRange = {range};
    
    Mat b_hist, g_hist, r_hist;
    calcHist(&bgr_planes[0], 1, 0, Mat(), b_hist, 1, &histSize, &histRange);
    calcHist(&bgr_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRange);
    calcHist(&bgr_planes[2], 1, 0, Mat(), r_hist, 1, &histSize, &histRange);
    
    // 归一化
    normalize(b_hist, b_hist, 0, 1, NORM_MINMAX);
    normalize(g_hist, g_hist, 0, 1, NORM_MINMAX);
    normalize(r_hist, r_hist, 0, 1, NORM_MINMAX);
    
    // 合并特征向量
    Mat feature;
    hconcat(b_hist, g_hist, feature);
    hconcat(feature, r_hist, feature);
    
    return feature;
}

颜色矩 (Color Moments)

原理:用统计矩来描述颜色分布。
三个主要颜色矩:

均值:平均颜色标准差:颜色变化程度偏度:颜色分布的不对称性


vector<double> computeColorMoments(const Mat& image) {
    Scalar mean, stddev;
    meanStdDev(image, mean, stddev);
    
    // 计算偏度
    Mat mean_mat = Mat(image.size(), image.type(), mean);
    Mat diff = image - mean_mat;
    Mat skewness_mat = mean(diff.mul(diff).mul(diff));
    
    vector<double> moments;
    for (int i = 0; i < 3; i++) { // BGR三个通道
        moments.push_back(mean[i]);        // 均值
        moments.push_back(stddev[i]);      // 标准差
        moments.push_back(skewness_mat.at<double>(i)); // 偏度
    }
    
    return moments;
}

纹理特征

灰度共生矩阵 (GLCM – Gray Level Co-occurrence Matrix)

原理:统计在特定空间关系中具有特定灰度值的像素对出现的频率。
流程:

定义空间关系(距离和方向)构建共生矩阵从矩阵中提取统计特征


Mat computeGLCM(const Mat& gray, int dx = 1, int dy = 0) {
    Mat glcm = Mat::zeros(256, 256, CV_32F);
    
    for (int y = 0; y < gray.rows - abs(dy); y++) {
        for (int x = 0; x < gray.cols - abs(dx); x++) {
            int i = gray.at<uchar>(y, x);
            int j = gray.at<uchar>(y + dy, x + dx);
            glcm.at<float>(i, j) += 1.0;
        }
    }
    
    // 归一化
    glcm /= sum(glcm)[0];
    return glcm;
}

vector<double> extractGLCMFeatures(const Mat& glcm) {
    double energy = 0.0, contrast = 0.0, homogeneity = 0.0, correlation = 0.0;
    
    // 计算均值
    Scalar mean_val = mean(glcm);
    
    for (int i = 0; i < glcm.rows; i++) {
        for (int j = 0; j < glcm.cols; j++) {
            float p = glcm.at<float>(i, j);
            energy += p * p;
            contrast += (i - j) * (i - j) * p;
            homogeneity += p / (1 + abs(i - j));
            correlation += (i - mean_val[0]) * (j - mean_val[0]) * p;
        }
    }
    
    return {energy, contrast, homogeneity, correlation};
}

LBP (Local Binary Pattern)

原理:将每个像素与其邻域像素比较,生成二进制模式。
流程:

对每个像素,比较其与邻域像素的灰度值生成8位二进制编码统计LBP模式的直方图


Mat computeLBP(const Mat& gray) {
    Mat lbp = Mat::zeros(gray.size(), CV_8UC1);
    
    for (int y = 1; y < gray.rows - 1; y++) {
        for (int x = 1; x < gray.cols - 1; x++) {
            uchar center = gray.at<uchar>(y, x);
            uchar code = 0;
            
            // 8邻域比较
            code |= (gray.at<uchar>(y-1, x-1) > center) << 7;
            code |= (gray.at<uchar>(y-1, x)   > center) << 6;
            code |= (gray.at<uchar>(y-1, x+1) > center) << 5;
            code |= (gray.at<uchar>(y,   x+1) > center) << 4;
            code |= (gray.at<uchar>(y+1, x+1) > center) << 3;
            code |= (gray.at<uchar>(y+1, x)   > center) << 2;
            code |= (gray.at<uchar>(y+1, x-1) > center) << 1;
            code |= (gray.at<uchar>(y,   x-1) > center) << 0;
            
            lbp.at<uchar>(y, x) = code;
        }
    }
    return lbp;
}

Mat computeLBPHistogram(const Mat& lbp, int bins = 256) {
    Mat hist;
    float range[] = {0, 256};
    const float* histRange = {range};
    calcHist(&lbp, 1, 0, Mat(), hist, 1, &bins, &histRange);
    normalize(hist, hist, 0, 1, NORM_MINMAX);
    return hist;
}

HOG (Histogram of Oriented Gradients)

原理:统计局部区域内的梯度方向分布。
流程:

计算图像的梯度幅值和方向将图像分成小的细胞单元统计每个细胞单元内的梯度方向直方图对细胞单元进行块归一化


Mat computeHOGFeatures(const Mat& gray, Size cellSize = Size(8, 8)) {
    // 计算梯度
    Mat grad_x, grad_y;
    Sobel(gray, grad_x, CV_32F, 1, 0);
    Sobel(gray, grad_y, CV_32F, 0, 1);
    
    Mat mag, angle;
    cartToPolar(grad_x, grad_y, mag, angle, true);
    
    // 计算HOG特征
    int bins = 9;
    Mat hog_features;
    
    for (int y = 0; y < gray.rows; y += cellSize.height) {
        for (int x = 0; x < gray.cols; x += cellSize.width) {
            Rect cell_rect(x, y, cellSize.width, cellSize.height);
            if (x + cellSize.width > gray.cols || y + cellSize.height > gray.rows)
                continue;
                
            Mat cell_mag = mag(cell_rect);
            Mat cell_angle = angle(cell_rect);
            
            Mat hist = Mat::zeros(1, bins, CV_32F);
            
            for (int i = 0; i < cell_mag.rows; i++) {
                for (int j = 0; j < cell_mag.cols; j++) {
                    float ang = cell_angle.at<float>(i, j);
                    float mg = cell_mag.at<float>(i, j);
                    
                    int bin = static_cast<int>(ang * bins / 360) % bins;
                    hist.at<float>(bin) += mg;
                }
            }
            
            // 归一化细胞单元
            normalize(hist, hist, 1, 0, NORM_L2);
            hog_features.push_back(hist);
        }
    }
    
    return hog_features.reshape(0, 1);
}

形状特征

轮廓特征

原理:从物体轮廓中提取几何特征。


vector<double> extractContourFeatures(const vector<Point>& contour) {
    double area = contourArea(contour);
    double perimeter = arcLength(contour, true);
    double circularity = 4 * CV_PI * area / (perimeter * perimeter);
    
    // 最小外接矩形
    RotatedRect rect = minAreaRect(contour);
    double aspect_ratio = max(rect.size.width, rect.size.height) / 
                         min(rect.size.width, rect.size.height);
    
    // 凸包
    vector<Point> hull;
    convexHull(contour, hull);
    double hull_area = contourArea(hull);
    double solidity = area / hull_area;
    
    return {area, perimeter, circularity, aspect_ratio, solidity};
}

Hu矩不变量 (Hu Moments)

原理:Hu矩不变量是由M.K. Hu在1962年提出的7个基于图像矩的特征,具有平移、缩放和旋转不变性,用于描述物体的形状特征。
Hu基于二阶和三阶归一化中心矩,推导出了7个绝对正交的不变量:

第一个Hu矩:类似于转动惯量,描述图像强度的分布
第二个Hu矩:描述图像的偏心度
第三个Hu矩:衡量矩的斜度(不对称性)
第四个Hu矩:另一个斜度度量
第五个Hu矩:复杂的形状描述符
第六个Hu矩:描述形状的对称性
第七个Hu矩:另一个复杂的形状描述符


vector<double> computeHuMoments(const Mat& image) {
    Moments m = moments(image);
    double hu[7];
    HuMoments(m, hu);
    
    // 对数变换增强小值
    vector<double> hu_moments;
    for (int i = 0; i < 7; i++) {
        hu_moments.push_back(-1 * copysign(1.0, hu[i]) * log10(abs(hu[i])));
    }
    
    return hu_moments;
}

关键点与描述符

关键点(Keypoints):图像中具有显著性的点,如角点、边缘点等
描述符(Descriptors):描述关键点周围区域特征的向量

SIFT (Scale-Invariant Feature Transform)

原理:SIFT通过在不同尺度空间中检测极值点,并计算旋转不变的描述符,实现尺度、旋转、光照不变性。
流程:

尺度空间极值检测关键点定位方向分配关键点描述符生成

步骤1:尺度空间极值检测
目的:在尺度空间中寻找稳定的关键点
流程:

构建高斯金字塔:


# 高斯模糊
G(x,y,σ) = (1/2πσ²) · exp(-(x²+y²)/2σ²)
# 图像与高斯核卷积
L(x,y,σ) = G(x,y,σ) ∗ I(x,y)

构建高斯差分金字塔(DoG):


D(x,y,σ) = L(x,y,kσ) - L(x,y,σ)

DoG是Laplacian的近似,计算更高效

极值检测:
在每个尺度层,比较每个像素与其26个邻居(8+9+9)
如果是极大值或极小值,则作为候选关键点

步骤2:关键点精确定位
目的:去除低对比度和边缘响应点
流程:

泰勒展开精确定位:


D(x) = D + (∂D/∂x)ᵀx + ½xᵀ(∂²D/∂x²)x

求解极值点偏移量

去除低对比度点:


if |D(x̂)| < 0.03: 删除该点

去除边缘响应点:
计算Hessian矩阵
检查主曲率比率


if (Tr(H)²/Det(H)) > (r+1)²/r: 删除该点
# 通常 r = 10

步骤3:方向分配
目的:为关键点分配主导方向,实现旋转不变性
流程:

在关键点尺度σ上计算梯度幅值和方向:


m(x,y) = √((L(x+1,y)-L(x-1,y))² + (L(x,y+1)-L(x,y-1))²)
θ(x,y) = atan2(L(x,y+1)-L(x,y-1), L(x+1,y)-L(x-1,y))

创建36bin的方向直方图(每10°一个bin)找到直方图峰值,大于80%峰值的作为辅助方向

步骤4:关键点描述符生成
目的:生成128维的旋转不变描述符
流程:

将关键点邻域旋转到主导方向划分4×4的子区域(共16个子区域)在每个子区域内计算8方向的梯度直方图拼接成4×4×8=128维特征向量归一化处理,增强光照不变性


void extractSIFTFeatures(const Mat& image, 
                        vector<KeyPoint>& keypoints, 
                        Mat& descriptors) {
    Ptr<SIFT> sift = SIFT::create(
        nfeatures = 0,      // 特征点数量(0表示不限制)
        nOctaveLayers = 3,  // 金字塔每层尺度数
        contrastThreshold = 0.04, // 对比度阈值
        edgeThreshold = 10,       // 边缘阈值
        sigma = 1.6        // 高斯核σ
    );
    sift->detectAndCompute(image, noArray(), keypoints, descriptors);
}

SURF (Speeded-Up Robust Features)

原理:SURF是SIFT的加速版本,使用积分图像和Hessian矩阵检测器,速度比SIFT快数倍。
算法流程
步骤1:Hessian矩阵检测
目的:快速检测斑点状特征
流程:

计算积分图像:


I_Σ(x,y) = Σᵢ=0ˣΣⱼ=0ʸ I(i,j)

积分图像使得矩形区域求和为O(1)操作

近似Hessian矩阵:


H(x,σ) = [L_xx(x,σ)  L_xy(x,σ)]
         [L_xy(x,σ)  L_yy(x,σ)]

使用盒式滤波器近似二阶高斯导数

尺度空间构建:
通过增大滤波器尺寸而不是下采样图像
计算Hessian矩阵行列式作为斑点响应

步骤2:关键点定位
流程:

在3×3×3邻域内进行非极大值抑制使用插值精确定位关键点计算Hessian矩阵迹符号确定亮暗斑点

步骤3:方向分配
流程:

在关键点周围计算Haar小波响应统计60°扇形区域内的响应和选择最长向量的方向作为主导方向

步骤4:描述符生成
流程:

将区域旋转到主导方向划分4×4的子区域在每个子区域计算25个采样点的Haar小波响应形成4×4×4=64维特征向量


void extractSURFFeatures(const Mat& image,
                        vector<KeyPoint>& keypoints,
                        Mat& descriptors) {
    Ptr<SURF> surf = SURF::create(
        hessianThreshold = 400, // Hessian阈值
        nOctaves = 4,           // 金字塔层数
        nOctaveLayers = 3,      // 每层尺度数
        extended = false,       // 是否使用扩展描述符(128维)
        upright = false         // 是否忽略方向
    );
    surf->detectAndCompute(image, noArray(), keypoints, descriptors);
}

ORB (Oriented FAST and Rotated BRIEF)

原理:ORB结合了FAST关键点检测和BRIEF描述符,并添加了方向补偿,具有良好的实时性能。

ORB = FAST关键点检测 + BRIEF描述符 + 方向补偿

算法流程
步骤1:oFAST关键点检测
目的:快速检测角点并计算方向
流程:

FAST角点检测:
在半径为3的圆周上比较16个像素
如果有连续n个像素比中心点亮或暗,则为角点
通常n=9(FAST-9)或n=12(FAST-12)Harris角点响应排序:
计算每个FAST角点的Harris响应值
选择前N个响应最强的点方向计算:
计算质心方向:


m_pq = Σx,y x^p y^q I(x,y)
θ = atan2(m01, m10)

其中m10和m01是一阶矩

步骤2:rBRIEF描述符生成
目的:生成旋转不变的二进制描述符
流程:

BRIEF描述符:
在31×31的patch内随机选择256对点
比较每对点的亮度:1 if I§ > I(q) else 0
生成256位的二进制字符串旋转补偿:
根据关键点方向旋转BRIEF采样模式
使用旋转矩阵:


R = [cosθ -sinθ]
    [sinθ  cosθ]

学习优化:
使用机器学习方法选择相关性低的点对
提高描述符的判别性


void extractORBFeatures(const Mat& image,
                       vector<KeyPoint>& keypoints,
                       Mat& descriptors) {
    Ptr<ORB> orb = ORB::create(
        nfeatures = 500,     // 特征点数量
        scaleFactor = 1.2,   // 金字塔尺度因子
        nlevels = 8,         // 金字塔层数
        edgeThreshold = 31,  // 边缘阈值
        firstLevel = 0,      // 第一层索引
        WTA_K = 2,           // 点对比较数
        scoreType = ORB::HARRIS_SCORE, // 评分类型
        patchSize = 31,      // patch大小
        fastThreshold = 20   // FAST阈值
    );
    orb->detectAndCompute(image, noArray(), keypoints, descriptors);
}

BRISK (Binary Robust Invariant Scalable Keypoints)

原理:BRISK使用 AGAST 检测器和二进制描述符,具有尺度和旋转不变性。
算法流程
步骤1:关键点检测
流程:

使用AGAST(Adaptive and Generic Accelerated Segment Test)检测器在尺度金字塔中进行非极大值抑制使用Laplacian确定尺度(亮/暗斑点)

步骤2:描述符生成
流程:

采样模式:
在关键点周围定义60个采样点
采样点分布在4个同心圆上短距离对和长距离对:
短距离对:用于亮度比较(生成描述符)
长距离对:用于方向估计方向估计:


g = (1/L) · Σ(所有长距离对) · (p_j - p_i) · (I(p_j) - I(p_i)) / ||p_j - p_i||²

其中L是长距离对的数量

二进制描述符生成:
旋转采样模式到主导方向
比较512对短距离采样点的亮度
生成512位的二进制描述符


void extractBRISKFeatures(const Mat& image,
                         vector<KeyPoint>& keypoints,
                         Mat& descriptors) {
    Ptr<BRISK> brisk = BRISK::create(
        thresh = 30,        // AGAST阈值
        octaves = 3,        // 金字塔层数
        patternScale = 1.0f // 采样模式缩放
    );
    brisk->detectAndCompute(image, noArray(), keypoints, descriptors);
}

关键点与描述符的应用

图像匹配:通过特征匹配实现图像配准目标识别:通过特征匹配识别特定物体三维重建:多视图几何中的特征对应图像检索:基于内容的图像检索视觉SLAM:同时定位与地图构建

深度特征

使用预训练深度学习模型

原理:使用在大型数据集上预训练的CNN模型提取深度特征。


Mat extractDeepFeatures(const Mat& image, const string& model_path) {
    // 加载预训练模型
    Net net = readNetFromTensorflow(model_path);
    
    // 预处理图像
    Mat blob;
    blobFromImage(image, blob, 1.0/255, Size(224, 224), Scalar(0,0,0), true, false);
    
    // 前向传播
    net.setInput(blob);
    Mat features = net.forward("fc7"); // 提取全连接层特征
    
    return features;
}

特征提取方法选择指南

特征类型 优点 缺点 适用场景
颜色特征 计算简单,对旋转缩放不变 对光照敏感 颜色丰富的图像
纹理特征 描述表面 patterns 计算复杂 纹理分析、材质识别
形状特征 几何不变性 需要好的分割 物体识别、形状分析
局部特征 尺度旋转不变,鲁棒性强 计算量大 图像匹配、目标识别
深度特征 表征能力强,自动学习 需要大量数据 复杂视觉任务

特征提取流程总结

预处理:图像归一化、去噪特征选择:根据任务选择合适的特征类型特征计算:使用相应算法提取特征特征归一化:标准化特征向量特征降维:PCA、LDA等方法减少维度特征存储:保存特征用于后续任务

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
夏雨冬阳的头像 - 鹿快
评论 抢沙发

请登录后发表评论

    暂无评论内容