在 ”光流跟踪“ 中,使用了 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. vue - 搭建 webapp 自适应项目-使用 vant 组件库 并 可自动调节大小

    1.创建个vue 项目,这里不详细写怎么创建,参考 vue - 指令创建 vue工程 - 岑惜 - 博客园 (cnblogs.com) https://www.cnblogs.com/c2g52013 ...

  2. List<FieldModelBase> 转 DataTable

    // List<FieldModelBase> 转 DataTable private DataTable ListToDataTable(List<FieldModelBase&g ...

  3. Centos7 查看文件命令总结

    ls命令 ls -d --文件或者目录是否存在 ls -l 或者ll --显示详细信息 ls -lt --文件按时间顺序排序(升序) ls -ltr --按时间倒叙排序 ls -i --显示索引节点 ...

  4. hisql ORM 查询语句使用教程

    HiSql 提供一个可以适合多种数据库的中间查询语法,不需要关注各个数据库的语法特性,通过HiSql写语句可以在常用的不同类型数据库中执行,且语法与Sql语境类似一看就懂一学就会 hisql.net ...

  5. Python面向对象时最常见的3类方法

    为了节省读友的时间,先上结论(对于过程和细节感兴趣的读友可以继续往下阅读,一探究竟): [结论] 类中定义的方法类型 关键词 本质含义 如何定义 如何调用 使用场景举例 实例方法 一般无任何修饰时,默 ...

  6. 数组内sizeof与strlen的区别

    1.数组在内存中是连续存放的,地址呈4个字节递增 2.数组的定义需要初始化,否则输出会已随机值输出 3.strlen()和sizeof()之间无关联:strlen():是求字符串长度的----只能针对 ...

  7. JavaScript输出的两种方式

    var a="Hello World" document.write(a) //在网页上输出:Hello World var a="Hello World" c ...

  8. wordcount报错:org.apache.hadoop.mapreduce.lib.input.InvalidInputException: Input path does not exist:

    Exception in thread "main" org.apache.hadoop.mapreduce.lib.input.InvalidInputException: In ...

  9. ArrayList实现类

    特点 数组结构实现,查询快,增删慢 运行效率高,线程不安全 可重复 常用方法 Modifier and Type Method and Description boolean add(E e) 将指定 ...

  10. 146_LRU cache | LRU缓存设计

    题目: Design and implement a data structure for Least Recently Used (LRU) cache. It should support the ...