zoukankan      html  css  js  c++  java
  • opencv笔记-GFTTDetector

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

  • 相关阅读:
    Excel教程集个人珍藏版
    【转】UML中几种类间关系:继承、实现 ...
    【转】UML用户指南(一)入门
    【转】软件项目经理素质能力的必备要求
    【转】类与类之间的常见关系,uml图表示
    【转】UML用户指南(二)类、关系、图、接口、包、实例
    Oracle建立DBLINK的详细步骤记录
    oracle中的默认帐户详解 .
    pro*c
    oracle type类型
  • 原文地址:https://www.cnblogs.com/luofeiju/p/12768682.html
Copyright © 2011-2022 走看看