分享

opencv笔记

 昵称70747151 2020-07-03

 在 ”光流跟踪“ 中,使用了 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

语言 方法
4461 7A0Ob
pnPYz
  • 利用抖音封面吸粉的技巧
  • 6133 2009/06/15 08:15:51

      本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
      转藏 分享 献花(0

      0条评论

      发表

      请遵守用户 评论公约

      类似文章 更多