在 ”光流跟踪“ 中,使用了 Harris 角点作为 LK 光流跟踪输入点。角点定义为在两个方向上均有较大梯度变化的小区域,使用自相关函数描述。

自相关函数为为图像平移前后某一个区域的相似度度量。图像可以看作二维平面上的连续函数,使用泰勒级数可以将自相关函数转换为自相关矩阵。

通过分析自相关矩阵的特征值,可以判断在该区域内各个方向上梯度变化情况,从而决定是否为角点。

在 opencv 中,cv::goodFeatrueToTrack 函数可以提取 Harris 角点,其中参数 useHarrisDetector 决定使用 Harris 判定依据还是 Shi-Tomasi 判定依据。

使用 cv::GFTTDetector 可以实现与 cv::goodFeatrueToTrack 基本一致的功能。然而,cv::GFTTDetector 继承自 cv::Feature2D,将角点视为通用关键点,可以对角点做更多后续处理(如特征点描述)。

以下是 cv::GFTTDetector 的构造函数,其参数与 cv::goodFeatrueToTrack 基本一致,如下:

static Ptr<GFTTDetector> create( int maxCorners=1000, double qualityLevel=0.01, double minDistance=1,
                                             int blockSize=3, bool useHarrisDetector=false, double k=0.04 );

其中,maxCorners 决定提取关键点最大个数;

qualityLevel 表示关键点强度阈值,只保留大于该阈值的关键点(阈值 = 最强关键点强度 * qualityLevel);

minDistance 决定关键点间的最小距离;

blockSize 决定自相关函数累加区域,也决定了 Sobel 梯度时窗口尺寸;

useHarrisDetector 决定使用 Harris 判定依据还是 Shi-Tomasi 判定依据;

k 仅在使用 Harris 判定依据时有效;

以下代码使用 cv::GFTTDetector 检测角点并绘制角点:

 1 // 检测 keypoints
2 std::vector<cv::KeyPoint> key_points;
3 cv::Ptr<cv::GFTTDetector> gftt = cv::GFTTDetector::create(1000, .3, 5., 3, false);
4 gftt->detect(img, key_points);
5
6 // 使用 opencv 内置函数绘制 keypoints
7 cv::Mat key_points_img;
8 cv::drawKeypoints(img, key_points, key_points_img,
9 cv::Scalar(255, 0, 0), cv::DrawMatchesFlags::DEFAULT);
10
11 // 读取 cv::KeyPoint 绘制 keypoints
12 cv::Mat key_points_img2;
13 cv::cvtColor(img, key_points_img2, CV_GRAY2BGR);
14 for (int i = 0; i < key_points.size(); ++i)
15 {
16 cv::Point c(key_points[i].pt.x, key_points[i].pt.y);
17 float r = key_points[i].size / 2.;
18 int draw_r = r * 2.;
19 cv::circle(key_points_img2, c, draw_r, cv::Scalar(255, 0, 0));
20 }

cv::GFTTDetector 内部如何实现角点检测呢,opencv 提供了以下几个基本函数:

1)void cornerEigenValsAndVecs( InputArray src, OutputArray dst,
                                          int blockSize, int ksize,
                                          int borderType = BORDER_DEFAULT );

2)void cornerHarris( InputArray src, OutputArray dst, int blockSize,
                                int ksize, double k,
                                int borderType = BORDER_DEFAULT );

3) void cornerMinEigenVal( InputArray src, OutputArray dst,
                                     int blockSize, int ksize = 3,
                                     int borderType = BORDER_DEFAULT );

其中,函数1 仅仅级数图像上每个点上自相关函数的特征值与特征向量,函数2 在 函数1 基础上应用 Harris 判定依据检测角点,函数3 在 函数1 基础上应用 Shi-Tomasi 判定依据检测角点。

以上函数参数含义与 cv::GFTTDetector create 函数基本一致,其中 blockSize 表示自相关函数计算窗口,ksize 表示 Sobel 函数计算窗口。

通过以上函数,可以自己构造一个角点检测算法:

  1 void harrisKeyPoints(cv::Mat& img,
2 std::vector<cv::Point>& key_points, std::vector<cv::Point2f>& eigen_vals)
3 {
4 cv::Mat smt_img;
5 cv::GaussianBlur(img, smt_img, cv::Size(3, 3), 0);
6
7 // 使用 cornerEigenValsAndVecs 提取特征值与特征向量
8 cv::Mat eigen_img = cv::Mat::zeros(img.size(), CV_32FC(6));
9 cv::cornerEigenValsAndVecs(smt_img, eigen_img, 3, 3);
10
11 // 计算每个点上最小特征值
12 cv::Mat min_eigen_img = cv::Mat::zeros(eigen_img.size(), CV_32FC1);
13 cv::Mat max_eigen_img = cv::Mat::zeros(eigen_img.size(), CV_32FC1);
14 for (int y = 0; y < eigen_img.rows; ++y)
15 {
16 float *eigen_data = eigen_img.ptr<float>(y);
17 float *min_eigen_data = min_eigen_img.ptr<float>(y);
18 float *max_eigen_data = max_eigen_img.ptr<float>(y);
19 for (int x = 0; x < eigen_img.cols; ++x)
20 {
21 float eigen_val1 = eigen_data[x * 6];
22 float eigen_val2 = eigen_data[x * 6 + 1];
23 if (eigen_val1 > eigen_val2)
24 std::swap(eigen_val1, eigen_val2);
25
26 min_eigen_data[x] = eigen_val1;
27 max_eigen_data[x] = eigen_val2;
28 }
29 }
30
31 // 计算特征值阈值
32 double min_eigen = 0.;
33 double max_eigen = 0.;
34 minMaxIdx(min_eigen_img, &min_eigen, &max_eigen);
35 float eigen_threshold = max_eigen * .15; // 保留 15% 最大强度特征点
36
37 // 保留满足条件的特征点
38 const int min_dist_permitted = 10 * 10; // 特征点间最小距离
39 const int max_size_permitted = 50; // 特征点最大数量
40
41 key_points.clear();
42 eigen_vals.clear();
43 int key_points_size = 0;
44
45 float max_eigen_val = 0.;
46 float min_eigen_val = 10000.;
47 int min_eigen_val_idx = -1;
48
49 for (int y = 0; y < min_eigen_img.rows; ++y)
50 {
51 float *data = min_eigen_img.ptr<float>(y);
52 for (int x = 0; x < min_eigen_img.cols; ++x)
53 {
54 if (data[x] > eigen_threshold)
55 {
56 if (key_points_size < max_size_permitted)
57 {
58 bool already_exists = false;
59 for (int i = 0; i < key_points_size; ++i)
60 {
61 int dist = (x - key_points[i].x) * (x - key_points[i].x) +
62 (y - key_points[i].y) * (y - key_points[i].y);
63 if (dist < min_dist_permitted)
64 {
65 already_exists = true;
66 if (eigen_vals[i].x < data[x])
67 {
68 // 列表中存在邻近点且特征值较小,使用新值替代
69 key_points[i].x = x;
70 key_points[i].y = y;
71 eigen_vals[i].x = data[x];
72 eigen_vals[i].y = max_eigen_img.ptr<float>(y)[x];
73
74 if (max_eigen_val < data[x])
75 max_eigen_val = data[x];
76 if (min_eigen_val > data[x])
77 {
78 min_eigen_val = data[x];
79 min_eigen_val_idx = i;
80 }
81 }
82 }
83 }
84
85 if (!already_exists)
86 {
87 // 列表中不存在邻近点,添加新值到列表中
88 key_points.push_back(cv::Point(x, y));
89 eigen_vals.push_back(cv::Point2f(data[x], max_eigen_img.ptr<float>(y)[x]));
90 ++key_points_size;
91
92 if (max_eigen_val < data[x])
93 max_eigen_val = data[x];
94 if (min_eigen_val > data[x])
95 {
96 min_eigen_val = data[x];
97 min_eigen_val_idx = key_points_size - 1;
98 }
99 }
100 }
101 else
102 {
103 // 特征值小于列表中最小值,不更新
104 if (data[x] <= min_eigen_val)
105 continue;
106
107 // 是否存在距离较近元素
108 bool already_exists = false;
109 for (int i = 0; i < key_points_size; ++i)
110 {
111 int dist = (x - key_points[i].x) * (x - key_points[i].x) +
112 (y - key_points[i].y) * (y - key_points[i].y);
113 if (dist < min_dist_permitted)
114 {
115 already_exists = true;
116 if (eigen_vals[i].x < data[x])
117 {
118 key_points[i].x = x;
119 key_points[i].y = y;
120 eigen_vals[i].x = data[x];
121 eigen_vals[i].y = max_eigen_img.ptr<float>(y)[x];
122
123 if (max_eigen_val < data[x])
124 max_eigen_val = data[x];
125 }
126 }
127 }
128
129 // 当不存在较近元素时替换最小元素
130 if (!already_exists)
131 {
132 key_points[min_eigen_val_idx].x = x;
133 key_points[min_eigen_val_idx].y = y;
134 eigen_vals[min_eigen_val_idx].x = data[x];
135 eigen_vals[min_eigen_val_idx].y = max_eigen_img.ptr<float>(y)[x];
136
137 // 重新搜索最大最小元素
138 max_eigen_val = 0.;
139 min_eigen_val = 10000.;
140 min_eigen_val_idx = -1;
141
142 for (int i = 0; i < key_points_size; ++i)
143 {
144 float val = eigen_vals[i].x;
145 if (max_eigen_val < val)
146 max_eigen_val = val;
147 if (min_eigen_val > val)
148 {
149 min_eigen_val = val;
150 min_eigen_val_idx = i;
151 }
152 }
153 }
154 }
155 }
156 }
157 }
158
159 // 绘制特征点
160 cv::Mat color_img;
161 cv::cvtColor(img, color_img, CV_GRAY2BGR);
162 for (int i = 0; i < key_points.size(); ++i)
163 cv::circle(color_img, key_points[i], 5, cv::Scalar(255, 0, 0), 1, cv::LINE_AA);
164
165 }

由于每一个关键点对应一对特征值,这里使用特征值作为关键点的简单描述符,尝试关键点匹配(效果较差),代码如下:

 1 void matchKeypoints(std::vector<cv::Point>& prev_key_points, std::vector<cv::Point2f>& prev_eigen_vals,
2 std::vector<cv::Point>& next_key_points, std::vector<cv::Point2f>& next_eigen_vals, std::vector<cv::Point>& match)
3 {
4
5 std::vector<int> prev_used;
6 std::vector<int> next_used;
7 for (int i = 0; i < prev_eigen_vals.size(); ++i)
8 prev_used.push_back(0);
9 for (int i = 0; i < next_eigen_vals.size(); ++i)
10 next_used.push_back(0);
11
12 match.clear();
13 for (int i = 0; i < prev_eigen_vals.size(); ++i)
14 {
15 if (prev_used[i] > 0)
16 continue;
17
18 float min_dist = .0001 * .0001;
19 int min_dist_idx = -1;
20 for (int j = 0; j < next_eigen_vals.size(); ++j)
21 {
22 if (next_used[j] > 0)
23 continue;
24
25 float dist = (prev_eigen_vals[i].x - next_eigen_vals[j].x) *
26 (prev_eigen_vals[i].x - next_eigen_vals[j].x) +
27 (prev_eigen_vals[i].y - next_eigen_vals[j].y) *
28 (prev_eigen_vals[i].y - next_eigen_vals[j].y);
29 if (dist < min_dist)
30 {
31 min_dist = dist;
32 min_dist_idx = j;
33 }
34 }
35
36 if (min_dist_idx < 0)
37 continue;
38
39 float min_dist2 = .0001 * .0001;
40 int min_dist_idx2 = -1;
41 for (int k = 0; k < prev_eigen_vals.size(); ++k)
42 {
43 if (prev_used[k] > 0)
44 continue;
45
46 float dist = (prev_eigen_vals[k].x - next_eigen_vals[min_dist_idx].x) *
47 (prev_eigen_vals[k].x - next_eigen_vals[min_dist_idx].x) +
48 (prev_eigen_vals[k].y - next_eigen_vals[min_dist_idx].y) *
49 (prev_eigen_vals[k].y - next_eigen_vals[min_dist_idx].y);
50 if (dist < min_dist2)
51 {
52 min_dist2 = dist;
53 min_dist_idx2 = k;
54 }
55 }
56
57 if (min_dist < min_dist2)
58 {
59 if (min_dist_idx >= 0)
60 {
61 match.push_back(cv::Point(i, min_dist_idx));
62 prev_used[i] = 1;
63 next_used[min_dist_idx] = 1;
64 }
65 }
66 else
67 {
68 if (min_dist_idx2 >= 0)
69 {
70 match.push_back(cv::Point(min_dist_idx2, min_dist_idx));
71 prev_used[min_dist_idx2] = 1;
72 next_used[min_dist_idx] = 1;
73 }
74 }
75 }
76 }
 1     // 分别求两幅图像关键点,将对应特征值向量作为关键点描述符
2 std::vector<cv::Point> key_points;
3 std::vector<cv::Point2f> eigen_vals;
4 harrisKeyPoints(img, key_points, eigen_vals, 1);
5
6 std::vector<cv::Point> key_points2;
7 std::vector<cv::Point2f> eigen_vals2;
8 harrisKeyPoints(img2, key_points2, eigen_vals2, 0);
9
10 // 简单匹配
11 std::vector<cv::Point> match;
12 matchKeypoints(key_points, eigen_vals, key_points2, eigen_vals2, match);
13
14 // 输出匹配结果
15 cv::Mat match_img = cv::Mat::zeros(img.rows, img.cols * 2 + 4, CV_8UC1);
16 for (int y = 0; y < match_img.rows; ++y)
17 {
18 uchar *dst_data1 = match_img.ptr<uchar>(y);
19 uchar *dst_data2 = dst_data1 + img.cols + 4;
20 uchar *src_data1 = img.ptr<uchar>(y);
21 uchar *src_data2 = img2.ptr<uchar>(y);
22
23 memcpy(dst_data1, src_data1, img.cols);
24 memcpy(dst_data2, src_data2, img2.cols);
25 }
26
27 for (int i = 0; i < match.size(); ++i)
28 {
29 cv::Point pt1(key_points[match[i].x]);
30 cv::Point pt2(key_points2[match[i].y]);
31 pt2.x += img.cols + 4;
32 cv::line(match_img, pt1, pt2, cv::Scalar(255), 2);
33 }
34

参考资料 Learning OpenCV 3   Adrian Kaehler & Gary Bradski

opencv笔记-GFTTDetector的更多相关文章

  1. OpenCV笔记大集锦(转载)

    整理了我所了解的有关OpenCV的学习笔记.原理分析.使用例程等相关的博文.排序不分先后,随机整理的.如果有好的资源,也欢迎介绍和分享. 1:OpenCV学习笔记 作者:CSDN数量:55篇博文网址: ...

  2. opencv笔记6:角点检测

    time:2015年10月09日 星期五 23时11分58秒 # opencv笔记6:角点检测 update:从角点检测,学习图像的特征,这是后续图像跟踪.图像匹配的基础. 角点检测是什么鬼?前面一篇 ...

  3. opencv笔记5:频域和空域的一点理解

    time:2015年10月06日 星期二 12时14分51秒 # opencv笔记5:频域和空域的一点理解 空间域和频率域 傅立叶变换是f(t)乘以正弦项的展开,正弦项的频率由u(其实是miu)的值决 ...

  4. opencv笔记4:模板运算和常见滤波操作

    time:2015年10月04日 星期日 00时00分27秒 # opencv笔记4:模板运算和常见滤波操作 这一篇主要是学习模板运算,了解各种模板运算的运算过程和分类,理论方面主要参考<图像工 ...

  5. opencv笔记3:trackbar简单使用

    time:2015年 10月 03日 星期六 13:54:17 CST # opencv笔记3:trackbar简单使用 当需要测试某变量的一系列取值取值会产生什么结果时,适合用trackbar.看起 ...

  6. opencv笔记2:图像ROI

    time:2015年 10月 03日 星期六 12:03:45 CST # opencv笔记2:图像ROI ROI ROI意思是Region Of Interests,感兴趣区域,是一个图中的一个子区 ...

  7. opencv笔记1:opencv的基本模块,以及环境搭建

    opencv笔记1:opencv的基本模块,以及环境搭建 安装系统 使用fedora22-workstation-x86_64 安装opencv sudo dnf install opencv-dev ...

  8. OpenCV基本架构[OpenCV 笔记0]

    最近正在系统学习OpenCV,将不定期发布笔记,主要按照毛星云的<OpenCV3编程入门>的顺序学习,会参考官方教程和文档.学习工具是Xcode+CMake,会对书中一部分内容更正,并加入 ...

  9. 查找并绘制轮廓[OpenCV 笔记XX]

    好久没有更新了,原谅自己放了个假最近又在赶进度,所以...更新的内容是很靠后的第八章,因为最近工作要用就先跳了,后面会更新笔记编号...加油加油! 在二值图像中寻找轮廓 void cv::findCo ...

随机推荐

  1. 日志通过脚本导入到HDFS当中

    可以关注公众号:分享电脑学习回复"百度云盘" 可以免费获取所有学习文档的代码(不定期更新) 利用shell脚本定时备份日志数据到HDFS上(适合日志数据比较少的时候) 时间命令 d ...

  2. 基于rabbitmq延迟插件实现分布式延迟任务

    承接上文基于redis,redisson的延迟队列实践,今天介绍下基于rabbitmq延迟插件rabbitmq_delayed_message_exchange实现延迟任务. 一.延迟任务的使用场景 ...

  3. [µC/GUI 学习]µC/GUI移植

    一.什么是µC/GUI µC/GUI为任何需要图形显示器的嵌入式应用提供了一种灵活的图形用户界面(GUI).µC/GUI允许软件工程师在使用了LCD显示器的产品上增加美轮美奂的用户界面,从简单的2D黑 ...

  4. Win7升级Win11升级记录及教程 【错误码(0×8004242d)】

    hellow,大家好,我是公众号棱镜Prism K的[K君].家中电脑因为一些原因不得不进行升级,下面是我对这次电脑升级所进行的记录. step 1.打开微软官网,找到对应的WIN11下载模块,这里注 ...

  5. INFO client.RMProxy: Connecting to ResourceManager at hadoop

    1.查看防火墙是否没关闭. 2.用jps 命令查看是否没有启动resourcemanager

  6. WebGPU图形编程(1):建立开发环境 <学习引自徐博士教程>

    首先感谢徐博士提供的视频教程,我的博客记录也是学习徐博士进行的自我总结,老徐B站学习视频链接网址:WebGPU图形编程 - 免费视频教程(1):建立开发环境_哔哩哔哩_bilibili 创建之前你需要 ...

  7. 【webpack4.0】---webpack的基本使用(三)

    一.webpack-dev-server 1.安装 cnpm   install  webpack-dev-server  -D 2.作用 开启一个web服务,监听文件的变化并自动刷新网页,做到实时预 ...

  8. 大厂面试:一个四年多经验程序员的BAT面经(字节、阿里、腾讯)

    前言 上次写了篇欢聚时代的面经,公众号后台有些读者反馈说看的意犹未尽,希望我尽快更新其他大厂的面经,这里先说声抱歉,不是我太懒,而是项目组刚好有个活动要赶在春节前上线,所以这几天经常加班,只能工作之余 ...

  9. Android开发----Button组件的使用与练习

    Button 学习目标: 文字大小.颜色 自定义背景形状 自定义按压效果 点击事件 创建一个新的Activity以增加控件 1.文字大小.颜色 直接在xml文件中定义即可 <Button and ...

  10. 挂载的卸载与运行 关闭selinux

    目录 一:系统目录结构介绍 1 定义挂载设备信息 光驱设备 /cd/cdrom 2 完成设备挂载操作 运行挂载 mount/dev/cdrom /mnt/ 3 检查测试挂载结果 挂载检查 df -h ...