LineMod源码梳理

LineMod算法
代码来源:https://github.com/meiqua/shape_based_matching

一.过程:

制作模板:

1.首先计算模板的梯度信息,如利用sobel算子计算x方向和y方向模板每个像素梯度值Gx,Gy。梯度的方向θ,还有每个像素点的梯度幅值。
2.梯度幅值大于某个阈值的点,我们认为是边缘点。并只保存边缘点的位置信息(x,y),梯度方向,梯度幅值。
3 .梯度方向的量化。实际用到了8个方向,《Gradient Response Maps for Real-TimeDetection of Textureless Objects》,这篇文章中量化为5个方向(拿5个方向举例)。这篇论文里提到量化梯度方向可以增加鲁棒性。我们拿论文里的梯度量化图举例子。

0-180°被量化成5个方向,且5个方向分别用二进制来表示。0-10°和180-190°被归为一个梯度方向。
4 .对图像金字塔下采样后的图像进行梯度方向量化的操作
5分别对通过旋转和缩放制作生成变换后的模板进行,1,2,3,4步骤操作。并保存。

二.特征文件
                     

1.%s_templ2.yaml

这个文件存储了通过 line2Dup::Detector 添加的模板数据,包含特征点的信息和其他模板相关的元数据。具体而言,文件名中 %s 会被替换为类标识符(class_id),在代码中为 "test",最终生成的文件名为 test_templ2.yaml

作用
  • 存储模板信息,包括模板ID、特征点位置、特征描述符等。
  • 用于在测试模式下加载这些模板,进行匹配操作。

2. test_info2.yaml

这个文件存储了在训练过程中生成的变换后模板的信息。具体内容包括模板的旋转角度、变换后的图像数据及其掩码。

  • %YAML:1.0: 指示文件使用的 YAML 版本。

  • class_id: test: 表示模板的类标识符。在训练和测试过程中,这个类标识符用于区分不同的模板类别。

  • pyramid_levels: 2: 指定用于模板匹配的图像金字塔层数。这意味着在不同的尺度上创建模板,以便在多尺度上进行匹配。

  • template_pyramids: 包含模板金字塔的信息,每个金字塔层包含多个模板。

    • template_id: 0: 模板的唯一标识符。

    • templates: 包含具体模板的详细信息。

      • width: 319: 模板的宽度。

      • height: 220: 模板的高度。

      • tl_x: 346: 模板左上角在原图像中的 x 坐标。

      • tl_y: 314: 模板左上角在原图像中的 y 坐标。

      • pyramid_level: 0: 该模板所在的金字塔层级。

      • features: 模板的特征点列表,每个特征点包含 [x, y, label] 三个值:

        • x: 特征点的 x 坐标。
        • y: 特征点的 y 坐标。
        • label: 特征点的标签,通常表示特征点的类型或其描述符信息。这个标签可以帮助算法区分不同的特征点,并在匹配过程中进行更精确的特征点对比。
作用
  • 保存变换后模板的详细信息,用于分析和调试。
  • 记录了每个模板的角度及其对应的图像,方便验证模板的生成过程

3. template_infos2.yaml

这个文件保存了每个模板的ID和对应的旋转角度。这些信息在匹配过程中用于确定模板的旋转角度,以便在测试图像中绘制正确的旋转矩形框。

作用
  • 保存模板ID和角度的映射关系。
  • 用于在测试过程中查找模板的角度信息,以便准确绘制匹配结果。

具体参考:https://blog.csdn.net/ALZFterry/article/details/108694869

三、总体结构说明
1、Feature结构体描述了一个特征点,即(x,y)位置,以及其量化梯度的方向(在代码中,将方向量化为8个)

2、Template结构体描述了一个模板,保存了模板的缩放大小(width,height),对应的金字塔层级(pyramid_level),以及特征序列(features)。

3、shapeInfo_producer类描述了模板的预处理,用于对模板图像进行旋转、缩放等操作,并记录每次操作的具体信息(旋转角度、缩放步长、类别等)。

4、ColorGradientPyramid类描述了制作模板的必要步骤,提供了“量化”接口、“提取模板”接口,“金字塔下采样”接口,特征点选择策略。该类分别被ColorGradientPyramid和DepthNormalPyramid继承,外部都不可见。

5、Match类描述了一个有效的模板匹配,包含了匹配的位置,类别(代码一般类别只有一个,暂时还没有多个类别的检测)、特征点相似度排序算法以及相应的模板信息(id)。

6、Detector类是最重要的类,包含添加模板、匹配等一系列操作。其中匹配函数现在底层金字塔进行全局匹配,后来在顶层金字塔局部细化匹配。即先低分辨率匹配,高分辨率局部相对精细匹配。

7.static void computeResponseMaps(const Mat& src, std::vector<Mat>& response_maps)

Mat 输入的是量化并且扩散的梯度图,response_maps是8张不同方向梯度的响应图,顾名思义就是这些图的值越大,匹配度就越高。

Mat 输入的是量化并且扩散的梯度图,response_maps是8张不同方向梯度的响应图,顾名思义就是这些图的值越大,匹配度就越高。

四.细节结构说明

1.

//目的:像素存储在 linearized 矩阵中
static void linearize(const Mat& response_map, Mat& linearized, int T)
{
    if (response_map.rows % T != 0 || response_map.cols % T != 0) {
        std::cerr << "response_map 的行数或列数不能被 T 整除。" << std::endl;
        return;
    }
    CV_Assert(response_map.rows % T == 0);
    CV_Assert(response_map.cols % T == 0);
    // 计算每个子区域的宽度和高度
    // linearized has T^2 rows, where each row is a linear memory
    int mem_width = response_map.cols / T;
    int mem_height = response_map.rows / T;
     创建线性化矩阵,行数为 T*T,列数为每个子区域的总像素数
    linearized.create(T * T, mem_width * mem_height, CV_8U);

    // Outer two for loops iterate over top-left T^2 starting pixels
    // 初始化索引
   // 外部的两个循环遍历 response_map 中 T x T 个子区域的左上角像素。
   // 这些子区域被称为“starting pixels”,因为每个子区域从这些位置开始,
   // 按一定的步长(即 T)跳跃以提取子区域的像素。
    int index = 0;
    // 遍历每个子区域的起始位置
    for (int r_start = 0; r_start < T; ++r_start)
    {
        for (int c_start = 0; c_start < T; ++c_start)
        {
            //在 linearized 矩阵中获取当前线性化内存行的指针。
            uchar* memory = linearized.ptr(index);
            ++index;
            // 遍历每个子区域的像素,将其值填充到线性化矩阵
            // Inner two loops copy every T-th pixel into the linear memory

            for (int r = r_start; r < response_map.rows; r += T)
            {
                const uchar* response_data = response_map.ptr(r);
                for (int c = c_start; c < response_map.cols; c += T)
                    *memory++ = response_data[c];
            }
        }
    }
}

response_map 是一个 OpenCV Mat 对象,表示一个二维矩阵,用于存储检测或匹配过程中生成的响应值(例如特征点检测、模板匹配的响应值)。在这种情况下,linearize 函数的作用是将二维 response_map 转换为一个线性化的表示方式,每一行包含从原始 response_map 中提取的子区域。

为什么行和列必须被 T 整除

函数中要求 response_map 的行数和列数必须能被 T 整除的原因是为了确保可以均匀地划分 response_mapT x T 个子区域。每个子区域的大小为 (response_map.rows / T) x (response_map.cols / T),这样才能进行线性化操作并正确地填充 linearized 矩阵。为了确保每个子区域的大小是一致的,并且可以均匀地线性化 response_map,需要 response_map 的行数和列数都能够被 T 整除。如果不能整除,就会导致子区域大小不一致,从而破坏线性化操作。

注意:

2.特征值和T该怎么选。

  • 选择适当的 T 值:

    • T 值决定了网格的大小,即每个线性化存储器包含的区域大小。选择合适的 T 值可以提高算法的效率和匹配精度。
    • 较大的 T 值可以减少存储器的数量,从而减少内存使用,但会降低匹配的精度。
    • 较小的 T 值可以提高匹配的精度,但会增加存储器的数量和内存使用。
  • 确定 T 的层数:

    • 层数决定了图像金字塔的深度,每层的 T 值通常是前一层的 T 值的倍数。
    • 较多的层数可以提高算法对尺度变化的鲁棒性,但会增加计算复杂度。
    • 较少的层数可以提高计算效率,但可能无法处理大尺度变化。

3

void ColorGradientPyramid::pyrDown(int T)
{
    // Some parameters need to be adjusted
    num_features /= 2; /// @todo Why not 4?
    ++pyramid_level;

    // Downsample the current inputs
    Size size(src.cols / 2, src.rows / 2);
    Mat next_src;
    cv::pyrDown(src, next_src, size);
    
    // 确保填充后的图像尺寸能被 T 整除
    padImageToMultipleOfT(next_src, T);
    src = next_src;

    if (!mask.empty())
    {
        Mat next_mask;
        resize(mask, next_mask, size, 0.0, 0.0, INTER_NEAREST);
        padImageToMultipleOfT(next_mask, T);
        mask = next_mask;
    }

    update();
}中的num_features /= 2;是什么意思

在图像金字塔中,每一层的特征点数量会随着金字塔层数的增加而减少。num_features /= 2; 这行代码的作用是将特征点的数量减少到之前的一半。这是为了确保在金字塔的每一层中,特征点的密度保持相对一致。具体来说:

金字塔层的特征点减少:在图像金字塔中,每一层图像的分辨率都会减半(宽度和高度分别减半),因此每一层的图像面积会是上一层的四分之一。如果我们保持特征点的密度不变,理论上每一层的特征点数量应该减少到原来的四分之一。然而,为了确保在降采样过程中不会丢失过多的特征点,通常我们会选择将特征点数量减少到原来的一半,而不是四分之一。这样做是为了保留更多的特征点,提高匹配的准确性。

平衡计算复杂度和准确性:减少特征点的数量可以减少计算复杂度,同时保留足够的特征点以确保匹配的准确性。通过将特征点数量减少到原来的二分之一,可以在降低计算复杂度的同时,尽量保持匹配的可靠性。

总结起来,这行代码是用来调整每一层金字塔的特征点数量,以适应图像分辨率的变化,从而平衡计算复杂度和特征匹配的准确性。

问题:

问题1:有些T值或层数有问题会报错。特征点数量选取不对会有些模板生不出来,所以该怎么处理这个才能更好的应用呢?

  • 通常选择能够平衡计算效率和匹配精度的 T 值。
  • 确保图像的宽度和高度能够被 T 整除,以及图像金字塔第i层 的图像尺寸被 T 整除。

   同时图像金字塔第i层 的图像尺寸无法被 T 整除,调整图像尺寸或 T 值。

解决:扩充每一层图片或许会有帮助,但不知道会不会对其他步骤有影响。

每一层的周围填一层黑色的像素点理论上不会影响到模板匹配效果。

实际测试:

  • 特征值保证能生成所有模板,所以设置少一点,并尽可能多一点。

在每层图像中检测特征点数量过少,可能影响匹配效果.

  • 造成的原因:分辨率降低,特征丢失。

解决:实际应用时可以统计生成模板的数量,不达要求就降低特征点数量重新生成模型文件。

问题2:特征图中模板的宽度和高度一直在变化。

金字塔层次不同:

不同的 pyramid_level 对应于不同分辨率的图像金字塔层次。较高的层次通常会是原始图像的缩小版本,导致不同的模板宽度和高度。
模板匹配区域的大小:

在某些应用中,可能需要对不同区域进行模板匹配,这些区域的大小和形状可能不同,从而导致不同的模板宽度和高度。
检测到的特征区域不同:

特征点检测器可能在不同的金字塔层次上检测到不同的特征区域,导致模板的大小不同。

问题3:特征void ColorGradientPyramid::pyrDown(int T)中的num_features /= 2;是什么意思

是指从底层金字塔(原图像)提取到的特征点数量减少一半,还是说对每层要提取的特征数量限制减少一半。

正在采用的解决:扩充每一层图片使其可以被T整除,但不知道会不会对其他步骤有影响。

调整:

1

+

2

3

调整后时间测试:

1.std::vector<int> T = { 4 ,8,12 }; [-90,90],相当于用了3层金字塔

2.std::vector<int> T = { 4 ,8 }; [-90,90]

3. std::vector<int> T = { 4 ,4,8,8 };

测试二:

改成release运行

有些模板没那么多特征点,建议目标图像分辨率比较大,模板图就选大点

同样的图模板选大了点

问题2:特征图中模板的宽度和高度一直在变化。

金字塔层次不同:

不同的 pyramid_level 对应于不同分辨率的图像金字塔层次。较高的层次通常会是原始图像的缩小版本,导致不同的模板宽度和高度。
模板匹配区域的大小:

在某些应用中,可能需要对不同区域进行模板匹配,这些区域的大小和形状可能不同,从而导致不同的模板宽度和高度。
检测到的特征区域不同:

特征点检测器可能在不同的金字塔层次上检测到不同的特征区域,导致模板的大小不同。

问题3:特征void ColorGradientPyramid::pyrDown(int T)中的num_features /= 2;是什么意思

是指从底层金字塔(原图像)提取到的特征点数量减少一半,还是说对每层要提取的特征数量限制减少一半。

 问题4:构建响应图的时间好长啊,用不了一点。

                        
参考1:https://blog.csdn.net/Jinxiaoyu886/article/details/118994670

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值