美文网首页
CV03_04:角点位置校正

CV03_04:角点位置校正

作者: 杨强AT南京 | 来源:发表于2020-02-12 00:23 被阅读0次

  角点的位置校正也称亚像素角点检测算法,这个算法思想是基于经验与假设的。
  (1)经验是:在角点所在的足够小的区域内的像素,要么在平坦区域(梯度为0),要么在角点所在的边缘上(梯度垂直于边缘,因为角点与边缘上的点连成的直线也垂直于梯度方向,这需要足够小的条件支持)。这导致如下一个公式成立I_i \cdot (q- p_i) = 0,p_i是角点领域内的任一像素点位置,q是角点位置。该算法的就是根据这种理想的假设找到最佳角点位置。
  (2)假设是:角点的精确位置应该角点附近,也就是领域满足足够小的假设条件。这样理想角点到邻域内任一点的连线才能拟合角点的某个边缘,从而直线才能垂直与这个像素的梯度方向。


注意:这个假设是调用cornerSubPix函数的时候取最佳参数的依据。


亚像素级的角点精准位置校正

  • 亚像素级角点位置校正检测提供更高精度的坐标定位。强角点检测出来的角点位置实际存在偏移。就是从一个整数坐标,求出一个位置更加精准的坐标。该坐标因为计算的关系,最后的结果用小数表示。

  • 这个算法是对已经存在的角点进行位置调整。

    • 所以需要输入原图像,已经需要调整的角点。然后输出新的调整后的角点。
  • 注意:

    • 输入的角点,只需要坐标位置即可,不需要判定值。

OpenCV函数cornerSubPix的体验

  1. 函数定义
void cv::cornerSubPix(
    InputArray          image,       // 输入图像
    InputOutputArray    corners,     // 输入需要精细化的角点,输出精细化后的角点
                                     // 类型:vector<cv::Point<float>>类型,也可以使用cv::Mat处理                    
    Size                winSize,     // 亚像素的滑动窗体,就是高斯平滑窗体,使用半径
    Size                zeroZone,    // 死区,在该区域不计算所有角点的求和。用来防止奇异点(没有可逆矩阵,因为算法采用的是最小二乘法)。
                                     // 设置(-1,-1)表示没有这样的区域
    TermCriteria        criteria     // 算法迭代的停止条件:一般俩个:(1)迭代到指定次数结束,(2)误差在可以接受范围内结束
)
  1. TermCriteria类介绍

    • TermCriteria (int type, int maxCount, double epsilon)
      • 三个public属性:type, maxCount,epsilon
      • type是枚举类型 enmu Type:
        1. COUNT =1,
        2. MAX_ITER =COUNT,
        3. EPS =2
      • 取值为三种形式:
        • COUNT,
        • EPS
        • COUNT + EPS
      • 使用方式记得使用命名空间与类前缀
        • cv::TermCriteria::COUNT
        • cv::TermCriteria::EPS
        • cv::TermCriteria::COUNT + cv::TermCriteria::EPS
  2. 代码实现

#include <iostream>
#include <cmath>
#include <climits>

#include <opencv2/opencv.hpp>
/*
 * 亚像素角点检测
 */


void call_opencv(){
    cv::Mat img = cv::imread("corner.jpg");
    cv::Mat img_src;
    cv::cvtColor(img, img_src, cv::COLOR_BGR2GRAY);  // OpenCV读取图像的颜色是BGR,灰度是8位图像

    /*
     * OpenCV标准的强角点检测算法
     */
    // 1. 首先计算角点
    cv::Mat corners;
    // std::vector<cv::Point2f> corners;
    cv::goodFeaturesToTrack(img_src, corners, 200, 0.01, 10, cv::noArray(), 9, true, 0.04);
    std::cout << corners.dims << "," << corners.rows << "," << corners.cols << std::endl;
    // 2. 然后计算精细化角点(就是焦点精细化)
    cv::cornerSubPix(img_src, corners, cv::Size(3, 3), cv::Size(-1, -1), cv::TermCriteria(cv::TermCriteria::COUNT + cv::TermCriteria::EPS, 100, 0.001));
    std::cout << corners.dims << "," << corners.rows << "," << corners.cols << std::endl;
    for(int i = 0; i < corners.rows; i++){
        cv::circle(img, corners.at<cv::Point2f>(i), 4, cv::Scalar(255, 0, 0, 255), 2, 8, 0);
    }
    cv::imwrite("corner_cv.jpg", img);
}


int main(int argc, char **argv){
    call_opencv();
    std::cout << "------------------------------" << std::endl;
    return 0;
}

  1. 效果
精细化后的强角点

亚像素角点位置精细校正算法

  1. 亚像素角点位置校正算法的目的:
    • 对一个使用Harris或者Shi-tomasi算法检测出的角点,通过一个算法,得到精确的角点位置。见下面示意图。
      1. 红色是通过Harris算法或者Shi-Tomasi算法检测出来的角点;这些算法计算的位置不是特别准确(差分本身的性质决定的)
      2. 绿色点是我们需要找出来的精确角点位置。
亚像素角点位置校正目的示意图
  1. 算法的理论依据

    • 精确的角点位置与周围邻域像素存在如下已知信息:
      1. 因为角点是边缘的交叉点,所以角点肯定在边缘上。
      2. 周围的像素点,要么在边缘上,要么在平坦区域。
  2. 最精准的角点满足如下等式:

    • 0 = \sum \limits _{p_i \in \text{\{q的所有领域像素\}}} I_i \cdot (q - p_i)
      • I_i表示邻域像素的梯度(像素变化的方向)
      • q- p_i是邻域像素与角点够的位置向量。
  3. 上述公式的分析

    1. p_i如果不在边缘,因为没有梯度变化,所以I_i = 0
    2. p_i如果在边缘上,根据梯度的描述,在足够小的范围内,I_i \cdot (q- p_i) = 0,因为正交。(足够大的情况下,这个不成立的,但是因为我们在焦点周围查找最精准角点位置,这个假设条件是可以满足的)
    3. 但是因为计算缘故,上述公式不可能精确等于0从而存在误差,这个公式可以表述为:\epsilon = \sum \limits _{p_i \in \text{\{q的所有领域像素\}}} I_i \cdot (q - p_i),这是所有领域点的误差的和。
  4. 问题的转换

    • 位置最精准角点肯定是误差最小的q。问题就转换为求\epsilon最小的q角点。
    • 因为我们根据已知角点,来划分区域,则该区域是可能存在最精准位置的角点的。
    • 如果已知角点与找到的最精准角点位置非常接近,则我们可以判定已知角点本身就是位置精准的角点。
    • 根据上述描述,也可能不存在最精准位置的角点,这个我们可以
  5. 算法求解

    • \epsilon = \sum _i I_{p_i} \cdot q - \sum _i I_{p_i} \cdot p_i
    • 设置\epsilon = 0,则
      1. 0 = \sum _i I_{p_i} \cdot q - \sum _i I_{p_i} \cdot p_i
      2. \sum _i I_{p_i} \cdot q = \sum _i I_{p_i} \cdot p_i
      3. \sum _i(I_{p_i}^T \cdot {I_{p_i}}) \cdot q = \sum _i(I_{p_i} ^ T \cdot {I_{p_i}}) \cdot p_i
      4. q = (\sum _i(I_{p_i}^T \cdot {I_{p_i}})) ^ {-1} \cdot (\sum _i(I_{p_i} ^ T \cdot {I_{p_i}}) \cdot p_i)
  6. 算法说明

    1. I_{p_i} = \begin{bmatrix}{I_{x_i}} & {I_{y_i} } \end{bmatrix}
    2. I_{p_i} ^ T = \begin{bmatrix}{I_{x_i}} \\ {I_{y_i}} \end{bmatrix}
    3. I_{p_i} ^ T \cdot I_{p_i} = \begin{bmatrix}{ I_{x_i} } \\ { I_{y_i} } \end{bmatrix} \cdot \begin{bmatrix}{ I_{x_i} }&{ I_{y_i} } \end{bmatrix}
    4. I_{p_i} ^ T \cdot I_{ p_i } = \begin{bmatrix}{ I_{x_i} I_{x_i} } & { I_{x_i} I_{y_i} } \\ {I_{x_i } I_{y_i} } & { I_{y_i} I_{y_i} } \end{bmatrix} = M_i
  7. 使用梯度的协方差矩阵表示

    • q = (\sum \limits _i M_i) ^{-1} \cdot (\sum \limits _i M_i) \cdot p_i
  8. 算法步骤

    1. 已知一个角点 q_0;
    2. 设置 i=1
    3. 使用q_{i-1}位置得到一个邻域W_i
    4. 根据上面公式得到q_i
    5. 判定是否(1)|q_i-q_{i-1} <= \epsilon|或者(2)循环次数是否是最大次数或者(3)q_i位置不在图像范围内或者(4)矩阵奇异,满足一个,则终止循环;否则i++,继续下一次循环:步骤3)
    6. 如果结束循环得到的角点位置为q_n,则计算|q_0 - q_n| <邻域窗口大小,则q_n就是最精确角点位置,否则q_0就是最精确角点位置。
  9. 提示:

    • 这个计算量目测比较大。
    • 上述算法没有考虑死区的设置,死区就是不参与q计算。这个可以设置一个参数m来控制死区,死区参数设置为0
    • q = (\sum \limits _i m_i M_i) ^{-1} \cdot (\sum \limits _i m_i M_i) \cdot p_i
    • 可以考虑m_i是高斯核,与死区重叠那部分设置为0即可。(后面我们实现的算法不考虑死区了,如果发生奇异情况再说)
  10. 算法实现:

    1. 其中逆矩阵对二阶方阵来说使用如下公式:M = \begin{bmatrix} { a } & { b } \\ { c } & { d } \end{bmatrix}

      • M^{-1} =\dfrac{ \begin{bmatrix}{d} & {-b} \\ {-c} & {a} \end{bmatrix}}{|M|}
      • | M | = ab -cd
    2. 其中的差分计算直接对整个图像计算(这样没有参与的像素也要计算,效率会低,但容易理解)

    3. 把取滑动窗口的函数单独封装

  11. 实现的核心代码代码

  • 算法我们参考了OpenCV的源代码,但源代码写的晦涩难懂,效率是没有说的,依然使用连续内存块,用指针访问数据,这种方式主要是充分利用L2,L3缓存,可以参考我们写的关于GPU运算效率相关的主题。

  • 获取窗口位置的函数

bool getROI(cv::Mat image, cv::Point2f center, cv::Size size, cv::Mat &roi){
    // 感兴趣区域大小
    int h = size.height;
    int w = size.width;
    // 计算中心坐标的整数值
    int x = int(center.x + 0.5f);
    int y = int(center.y + 0.5f);
    // 计算ROI区域的左上角点
    int lt_x = x - w / 2;
    int lt_y = y - h / 2;

    // ROI区域不包含在图像,则直接返回false,表示没有这个区域
    if (lt_x < 0 || lt_y < 0 || (lt_x + w) >= image.cols || (lt_y + h) >= image.rows){
        return false;
    } 
    // 构造ROI区域的Rect描述
    cv::Rect rect_roi(lt_x, lt_y, w, h);
    roi = image(rect_roi).clone();
    return true;
}

void yq_subpixel(cv::Mat image, cv::Mat &corners, cv::Size neighbor_size, int max_count, float epsilon){
    // 构造一个Gauss核,大小为邻域大小, 因为getGaussianKernel函数返回的必须是方阵,所以下面我们自己编写一个
    // 高斯函数的参数:方差窗体的半径, 均值为窗体中心
    //搜索窗口大小
    int w = neighbor_size.width * 2 + 1;
    int h = neighbor_size.height * 2 + 1;

    cv::Mat m_gauss = cv::Mat(h, w, CV_32FC1);   // 浮点数float类型
    for (int y = 0; y < h; y++){
        for (int x = 0; x < w; x++){
            float px = (float)(x - neighbor_size.width) / neighbor_size.width;
            float py = (float)(y - neighbor_size.height) / neighbor_size.height;
            float ex = exp(-px*px);
            float ey = exp(-py*py);
            m_gauss.at<float>(y, x) = (float)(ex*ey);
        }
    }

    // 对每个角点进行精确位置校正
    // 因为误差计算使用的是平方,所以参数的误差限制也平方
    epsilon *= epsilon;
    // 一次性把图像梯度计算好(可以考虑使用GPU计算)
    cv::Mat  I_x, I_y;
    cv::Sobel(image, I_x, CV_32FC1, 1, 0, 3);
    cv::Sobel(image, I_y, CV_32FC1, 0, 1, 3);

    for(int i = 0; i < corners.rows; i++){
        // 开始迭代找到精准位置点
        // 下面五个量可以计算出新的q点
        float a, b, c;     // 协方差矩阵的四个元素,反对角线上的一样都是b
        float c_x, c_y;    // 协方差矩阵与邻域内点的内积后的向量的两个分量

        float err;         // 用来存放初始角点与新角点的距离误差,这是循环的终止条件
        int   n_iter;      // 存放迭代的次数,这也是终止的判定条件 

        cv::Point2f init_corner = corners.at<cv::Point2f>(i);   // 得到需要校正的角点
        cv::Point2f new_corner = init_corner;                   // 用来存放新计算出来的角点,第一次就是初始角点,这个点用来决定滑动窗体的位置

        n_iter = 0 ;   // 初始化迭代次数
        do{
            // 使用角点的位置,与滑动窗体大小,取差分领域(两个)
            cv::Mat window_x;
            cv::Mat window_y;

            // 取滑动窗口(没有就结束,意味着精确角点就是原角点)
            if ( !getROI(I_x, new_corner, cv::Size(w, h), window_x)) break;
            if ( !getROI(I_y, new_corner, cv::Size(w, h), window_y)) break;
            
            // 计算M矩阵
            a = b = c = 0.0f;
            c_x = c_y = 0.0f;
            for (int y = 0; y < h; y++){
                for(int x = 0; x < w; x++){
                    // 取高斯值
                    float m = m_gauss.at<float>(y, x);
                    // 取梯度(差分)
                    float i_x = window_x.at<float>(y, x);
                    float i_y = window_y.at<float>(y, x);
                    // 计算IxIx , IxIy, IyIy
                    double i_xx = i_x * i_x * m;
                    double i_xy = i_x * i_y * m;
                    double i_yy = i_y * i_y * m;
                    a += i_xx;
                    b += i_xy;
                    c += i_yy;

                    // p_i的坐标(因为滑动窗口的中心点是初始角点位置,所以窗口内每个点的位置实际是相对位置,不是在原图像中的位置)
                    int p_i_x = x - neighbor_size.width;
                    int p_i_y = y - neighbor_size.height;
                    // 计算c_x,c_y
                    c_x += i_xx * p_i_x + i_xy * p_i_y;
                    c_y += i_xy * p_i_x + i_yy * p_i_y; 
                }
            }

            // 通过上面的循环,累加的M矩阵计算完毕
            // 判定是否奇异(计算行列式的值是否接近0)
            
            float det = a*c - b*b;
            // 矩阵奇异就退出精确位置校正的循环
            if (fabs(det) <= DBL_EPSILON * DBL_EPSILON)
                break;

            // 不奇异就计算逆矩阵(参考二阶方阵的逆矩阵计算公式)
            double inv_a = c / det;
            double inv_c = a / det;
            double inv_b = -b / det;

            // 最后计算出新的q角点位置(因为坐标以初始角点为中心,所以需要做平移)
            cv::Point2f q;
            q.x = (float)(new_corner.x + inv_a * c_x + inv_b * c_y);
            q.y = (float)(new_corner.y + inv_b * c_x + inv_c * c_y);

            // 计算误差
            err = (q.x - new_corner.x)*(q.x - new_corner.x) + (q.y - new_corner.y)*(q.y - new_corner.y);
            // 把新的点放入new_corner,开始下一次循环
            new_corner = q;
            // 迭代次数累加
            n_iter ++;

            // 判定新的角点位置是否在图像内(放在最后,前面一定要替换新角点,循环结束后,这种情况要做异常处理的,意味着角点位置还是原来的位置)
            if (new_corner.x < 0 || new_corner.x >= image.cols || new_corner.y < 0 || new_corner.y >= image.rows){
                break;
            }
        }while(n_iter < max_count && err > epsilon);
        // 决定新的角点位置是否是合理的
        if (fabs(new_corner.x - init_corner.x) > neighbor_size.width || fabs(new_corner.y - init_corner.y) > neighbor_size.height){
            // 新的角点位置如果在窗体外面,则使用原来的角点位置(注意,图像外面的位置肯定在窗体外面)
            new_corner = init_corner;
        }
        //保存算出的亚像素角点
        corners.at<cv::Point2f>(i) = new_corner;
    }
}

  1. 效果
    • 计算的效果与OpenCV的计算效果一致。但我们的算法中明显牺牲计算效率,来换取对算法的理解。这里不贴图看效果,可以自己运行代码体验。完整代码见下面。
#include <iostream>
#include <cmath>
#include <climits>

#include <opencv2/opencv.hpp>
/*
 * 亚像素角点检测
 */
void yq_subpixel(cv::Mat image, cv::Mat &corners, cv::Size neighbor_size, int max_count, float epsilon);
// 在图像取一个子区域
bool getROI(cv::Mat image, cv::Point2f center, cv::Size size, cv::Mat &roi){
    // 感兴趣区域大小
    int h = size.height;
    int w = size.width;
    // 计算中心坐标的整数值
    int x = int(center.x + 0.5f);
    int y = int(center.y + 0.5f);
    // 计算ROI区域的左上角点
    int lt_x = x - w / 2;
    int lt_y = y - h / 2;

    // ROI区域不包含在图像,则直接返回false,表示没有这个区域
    if (lt_x < 0 || lt_y < 0 || (lt_x + w) >= image.cols || (lt_y + h) >= image.rows){
        return false;
    } 
    // 构造ROI区域的Rect描述
    cv::Rect rect_roi(lt_x, lt_y, w, h);
    roi = image(rect_roi).clone();
    return true;
}

void call_opencv(){
    cv::Mat img = cv::imread("corner.jpg");
    cv::Mat img_src;
    cv::cvtColor(img, img_src, cv::COLOR_BGR2GRAY);  // OpenCV读取图像的颜色是BGR,灰度是8位图像

    /*
     * OpenCV标准的强角点检测算法
     */
    // 1. 首先计算角点
    cv::Mat corners;
    // std::vector<cv::Point2f> corners;
    cv::goodFeaturesToTrack(img_src, corners, 200, 0.01, 10, cv::noArray(), 9, true, 0.04);
    std::cout << corners.dims << "," << corners.rows << "," << corners.cols << std::endl;
    // 2. 然后计算精细化角点(就是焦点精细化)
    cv::cornerSubPix(img_src, corners, cv::Size(3, 3), cv::Size(-1, -1), cv::TermCriteria(cv::TermCriteria::COUNT + cv::TermCriteria::EPS, 100, 0.001));
    std::cout << corners.dims << "," << corners.rows << "," << corners.cols << std::endl;
    for(int i = 0; i < corners.rows; i++){
        cv::circle(img, corners.at<cv::Point2f>(i), 4, cv::Scalar(255, 0, 0, 255), 2, 8, 0);
    }
    cv::imwrite("corner_cv.jpg", img);
}

void call_subpix(){
    cv::Mat img = cv::imread("corner.jpg");
    cv::Mat img_src;
    cv::cvtColor(img, img_src, cv::COLOR_BGR2GRAY);  // OpenCV读取图像的颜色是BGR,灰度是8位图像

    /*
     * OpenCV标准的强角点检测算法
     */
    // 1. 首先计算角点
    cv::Mat corners;
    // std::vector<cv::Point2f> corners;
    cv::goodFeaturesToTrack(img_src, corners, 200, 0.01, 10, cv::noArray(), 9, true, 0.04);
    std::cout << corners.dims << "," << corners.rows << "," << corners.cols << std::endl;
    // 2. 然后计算精细化角点(就是焦点精细化)
    yq_subpixel(img_src, corners, cv::Size(3, 3),  100, 0.001);
    std::cout << corners.dims << "," << corners.rows << "," << corners.cols << std::endl;
    for(int i = 0; i < corners.rows; i++){
        cv::circle(img, corners.at<cv::Point2f>(i), 4, cv::Scalar(255, 0, 0, 255), 2, 8, 0);
    }
    cv::imwrite("corner_px.jpg", img);
}

int main(int argc, char **argv){
    call_opencv();
    std::cout << "------------------------------" << std::endl;
    call_subpix();
    return 0;
}


void yq_subpixel(cv::Mat image, cv::Mat &corners, cv::Size neighbor_size, int max_count, float epsilon){
    // 构造一个Gauss核,大小为邻域大小, 因为getGaussianKernel函数返回的必须是方阵,所以下面我们自己编写一个
    // 高斯函数的参数:方差窗体的半径, 均值为窗体中心
    //搜索窗口大小
    int w = neighbor_size.width * 2 + 1;
    int h = neighbor_size.height * 2 + 1;

    cv::Mat m_gauss = cv::Mat(h, w, CV_32FC1);   // 浮点数float类型
    for (int y = 0; y < h; y++){
        for (int x = 0; x < w; x++){
            float px = (float)(x - neighbor_size.width) / neighbor_size.width;
            float py = (float)(y - neighbor_size.height) / neighbor_size.height;
            float ex = exp(-px*px);
            float ey = exp(-py*py);
            m_gauss.at<float>(y, x) = (float)(ex*ey);
        }
    }

    // 对每个角点进行精确位置校正
    // 因为误差计算使用的是平方,所以参数的误差限制也平方
    epsilon *= epsilon;
    // 一次性把图像梯度计算好
    cv::Mat  I_x, I_y;
    cv::Sobel(image, I_x, CV_32FC1, 1, 0, 3);
    cv::Sobel(image, I_y, CV_32FC1, 0, 1, 3);

    for(int i = 0; i < corners.rows; i++){
        // 开始迭代找到精准位置点
        // 下面五个量可以计算出新的q点
        float a, b, c;     // 协方差矩阵的四个元素,反对角线上的一样都是b
        float c_x, c_y;    // 协方差矩阵与邻域内点的内积后的向量的两个分量

        float err;         // 用来存放初始角点与新角点的距离误差,这是循环的终止条件
        int   n_iter;      // 存放迭代的次数,这也是终止的判定条件 

        cv::Point2f init_corner = corners.at<cv::Point2f>(i);   // 得到需要校正的角点
        cv::Point2f new_corner = init_corner;                   // 用来存放新计算出来的角点,第一次就是初始角点,这个点用来决定滑动窗体的位置

        n_iter = 0 ;   // 初始化迭代次数
        do{
            // 使用角点的位置,与滑动窗体大小,取差分领域(两个)
            cv::Mat window_x;
            cv::Mat window_y;

            // 取滑动窗口(没有就结束,意味着精确角点就是原角点)
            if ( !getROI(I_x, new_corner, cv::Size(w, h), window_x)) break;
            if ( !getROI(I_y, new_corner, cv::Size(w, h), window_y)) break;
            
            // 计算M矩阵
            a = b = c = 0.0f;
            c_x = c_y = 0.0f;
            for (int y = 0; y < h; y++){
                for(int x = 0; x < w; x++){
                    // 取高斯值
                    float m = m_gauss.at<float>(y, x);
                    // 取梯度(差分)
                    float i_x = window_x.at<float>(y, x);
                    float i_y = window_y.at<float>(y, x);
                    // 计算IxIx , IxIy, IyIy
                    double i_xx = i_x * i_x * m;
                    double i_xy = i_x * i_y * m;
                    double i_yy = i_y * i_y * m;
                    a += i_xx;
                    b += i_xy;
                    c += i_yy;

                    // p_i的坐标(因为滑动窗口的中心点是初始角点位置,所以窗口内每个点的位置实际是相对位置,不是在原图像中的位置)
                    int p_i_x = x - neighbor_size.width;
                    int p_i_y = y - neighbor_size.height;
                    // 计算c_x,c_y
                    c_x += i_xx * p_i_x + i_xy * p_i_y;
                    c_y += i_xy * p_i_x + i_yy * p_i_y; 
                }
            }

            // 通过上面的循环,累加的M矩阵计算完毕
            // 判定是否奇异(计算行列式的值是否接近0)
            
            float det = a*c - b*b;
            // 矩阵奇异就退出精确位置校正的循环
            if (fabs(det) <= DBL_EPSILON * DBL_EPSILON)
                break;

            // 不奇异就计算逆矩阵(参考二阶方阵的逆矩阵计算公式)
            double inv_a = c / det;
            double inv_c = a / det;
            double inv_b = -b / det;

            // 最后计算出新的q角点位置(因为坐标以初始角点为中心,所以需要做平移)
            cv::Point2f q;
            q.x = (float)(new_corner.x + inv_a * c_x + inv_b * c_y);
            q.y = (float)(new_corner.y + inv_b * c_x + inv_c * c_y);

            // 计算误差
            err = (q.x - new_corner.x)*(q.x - new_corner.x) + (q.y - new_corner.y)*(q.y - new_corner.y);
            // 把新的点放入new_corner,开始下一次循环
            new_corner = q;
            // 迭代次数累加
            n_iter ++;

            // 判定新的角点位置是否在图像内(放在最后,前面一定要替换新角点,循环结束后,这种情况要做异常处理的,意味着角点位置还是原来的位置)
            if (new_corner.x < 0 || new_corner.x >= image.cols || new_corner.y < 0 || new_corner.y >= image.rows){
                break;
            }
        }while(n_iter < max_count && err > epsilon);
        // 决定新的角点位置是否是合理的
        if (fabs(new_corner.x - init_corner.x) > neighbor_size.width || fabs(new_corner.y - init_corner.y) > neighbor_size.height){
            // 新的角点位置如果在窗体外面,则使用原来的角点位置(注意,图像外面的位置肯定在窗体外面)
            new_corner = init_corner;
        }
        //保存算出的亚像素角点
        corners.at<cv::Point2f>(i) = new_corner;
    }
}


相关文章

网友评论

      本文标题:CV03_04:角点位置校正

      本文链接:https://www.haomeiwen.com/subject/riaffhtx.html