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_map
为 T
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