在 ”光流跟踪“ 中,使用了 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. less 循环模拟sass的for循环效果

    // 输入框部分宽度 从10px到600px 相隔10像素 .generate-widths(600); .generate-widths(@n, @i: 10) when (@i =< @n) ...

  2. Java安全之Spring内存马

    Java安全之Spring内存马 基础知识 Bean bean 是 Spring 框架的一个核心概念,它是构成应用程序的主干,并且是由 Spring IoC 容器负责实例化.配置.组装和管理的对象. ...

  3. List转换Map的三种方式

    1.for循环 ... 2.使用guava Map<Long, User> maps = Maps.uniqueIndex(userList, new Function<User, ...

  4. Android官方文档翻译 七 2.Adding the Action Bar

    Adding the Action Bar 增加一个Action Bar(工具栏) The action bar is one of the most important design element ...

  5. github与gitlab创建新仓库

    github创建新仓库 然后根据下一页的命令提示进行即可 gitlab创建新仓库 git init git remote add origin git@***.***.**.**:user/proje ...

  6. 【pwn】攻防世界 pwn新手区wp

    [pwn]攻防世界 pwn新手区wp 前言 这几天恶补pwn的各种知识点,然后看了看攻防世界的pwn新手区没有堆题(堆才刚刚开始看),所以就花了一晚上的时间把新手区的10题给写完了. 1.get_sh ...

  7. Centos下安装Scala(1)

    1.登录centos至root角色下 配置环境变量 2.执行下述命令 scala -version 出现结果如下 输入 'y' 3.开始安装 4.安装完成界面 5.启动成功以及测试程序

  8. Ajax_axios发送ajax请求

    Ajax_axios发送ajax请求 这篇笔记主要讲一下axios基本的发送ajax请求的方法 axios在当前的前端行业里面是用的比较热门的一个 下面给大家分享一下它axios的一个基本用法 这段代 ...

  9. JuiceFS 在理想汽车的使用和展望

    理想汽车是中国新能源汽车制造商,设计.研发.制造和销售豪华智能电动汽车,于 2015 年 7 月创立,总部位于北京,已投产的自有生产基地位于江苏常州,通过产品创新及技术研发,为家庭用户提供安全及便捷的 ...

  10. 什么是XSS攻击?什么是SQL注入攻击?什么是CSRF攻击?

    1. XSS(Cross Site Script,跨站脚本攻击) 是向网页中注入恶意脚本在用户浏览网页时在用户浏览器中执行恶意脚本的攻击方式. 1.1跨站脚本攻击分有两种形式: 反射型攻击(诱使用户点 ...