OpenCV Shi-Tomasi角点解析笔记

1. Shi-Tomasi角点数学原理

1.1 角点的定义

首先通过固定大小的窗口观察图像,然后使窗口按任意方向滑动再次观察图像,比较滑动前后两种情况窗口中的像素灰度变化程度,如果在任意方向上滑动,像素灰度都有较大的变化,那么认为该窗口中存在角点(corner)。

下图给出了三种常见情况:

  • flat \text{flat} flat:该窗口在任何方向上滑动,灰度变化程度不大。
  • edge \text{edge} edge:该窗口在某个特定方向上滑动,灰度变化程度不大。
  • corner \text{corner} corner:该窗口在任何方向上滑动,灰度变化程度明显。

在这里插入图片描述

1.2 角点的数学推导

当窗口按 [ u , v ] [u,v] [u,v]方向进行移动时,滑动前后像素灰度变化程度可用下式表示:
E ( u , v ) = ∑ x , y w ( x , y ) [ I ( x + u , y + v ) − I ( x , y ) ] 2 E(u,v)=\sum_{x,y}w(x,y)[I(x+u,y+v)-I(x,y)]^2 E(u,v)=x,yw(x,y)[I(x+u,y+v)I(x,y)]2
其中 w ( ⋅ ) w(\cdot) w()表示窗口函数, I ( ⋅ ) I(\cdot) I()表示图像函数。

I ( x + u , y + v ) I(x+u,y+v) I(x+u,y+v)利用一阶泰勒展开有:
I ( x + u , y + v ) ≈ I ( x , y ) + I x u + I y v I(x+u,y+v)\approx I(x,y)+I_xu+I_yv I(x+u,y+v)I(x,y)+Ixu+Iyv
其中 I x I_x Ix I y I_y Iy表示沿图像沿 x x x y y y方向的一阶导数,可用 Sobel \text{Sobel} Sobel算子计算。

代入泰勒展开则有:
E ( u , v ) ≈ ∑ x , y w ( x , y ) [ I ( x , y ) + I x u + I y v − I ( x , y ) ] 2 ≈ ∑ x , y w ( x , y ) [ I x 2 u 2 + 2 I x I y u v + I y 2 v 2 ] ≈ ∑ x , y w ( x , y ) [ u v ] [ I x 2 I x I y I x I y I y 2 ] [ u v ] ≈ [ u v ] ∑ x , y w ( x , y ) [ I x 2 I x I y I x I y I y 2 ] [ u v ] ≈ [ u v ] [ ∑ x , y w ( x , y ) I x 2 ∑ x , y w ( x , y ) I x I y ∑ x , y w ( x , y ) I x I y ∑ x , y w ( x , y ) I y 2 ] [ u v ] ≈ [ u v ] M [ u v ] \begin{aligned} E(u,v)&\approx\sum_{x,y}w(x,y)[I(x,y)+I_xu+I_yv-I(x,y)]^2\\ &\approx\sum_{x,y}w(x,y)[I_x^2u^2+2I_xI_yuv+I_y^2v^2]\\ &\approx\sum_{x,y}w(x,y)\begin{bmatrix}u&v\end{bmatrix}\begin{bmatrix}I_x^2&I_xI_y\\I_xI_y&I_y^2\end{bmatrix}\begin{bmatrix}u\\v\end{bmatrix}\\ &\approx\begin{bmatrix}u&v\end{bmatrix}\sum_{x,y}w(x,y)\begin{bmatrix}I_x^2&I_xI_y\\I_xI_y&I_y^2\end{bmatrix}\begin{bmatrix}u\\v\end{bmatrix}\\ &\approx\begin{bmatrix}u&v\end{bmatrix}\begin{bmatrix}\sum_{x,y}w(x,y)I_x^2&\sum_{x,y}w(x,y)I_xI_y\\\sum_{x,y}w(x,y)I_xI_y&\sum_{x,y}w(x,y)I_y^2\end{bmatrix}\begin{bmatrix}u\\v\end{bmatrix}\\ &\approx\begin{bmatrix}u&v\end{bmatrix}M\begin{bmatrix}u\\v\end{bmatrix} \end{aligned} E(u,v)x,yw(x,y)[I(x,y)+Ixu+IyvI(x,y)]2x,yw(x,y)[Ix2u2+2IxIyuv+Iy2v2]x,yw(x,y)[uv][Ix2IxIyIxIyIy2][uv][uv]x,yw(x,y)[Ix2IxIyIxIyIy2][uv][uv][x,yw(x,y)Ix2x,yw(x,y)IxIyx,yw(x,y)IxIyx,yw(x,y)Iy2][uv][uv]M[uv]
上式右侧的表达式为一个二项式函数,其本质是一个椭圆函数:
A u 2 + 2 B u v + C v 2 = 1 Au^2+2Buv+Cv^2=1 Au2+2Buv+Cv2=1
其中 A A A表示 m 11 m_{11} m11 B B B表示 m 12 = m 21 m_{12}=m_{21} m12=m21 C C C表示 m 22 m_{22} m22。椭圆的扁率和尺寸由 M M M的特征值 λ 1 \lambda_1 λ1 λ 2 \lambda_2 λ2决定,椭圆的方向由 M M M的特征矢量决定。

特征根与椭圆关系

根据上述分析, M M M特征值大小比值可分为三种情况:

  • 一个特征值大,另一个特征值小, λ 1 ≫ λ 2 \lambda_1\gg\lambda_2 λ1λ2 λ 2 ≫ λ 1 \lambda_2\gg\lambda_1 λ2λ1。说明窗口在某个特定方向上滑动,灰度变化程度不大,在垂直方向上滑动,灰度变化程度明显,可以判断窗口存在** edge \text{edge} edge**。
  • 两个特征值都小,且近似相等。说明窗口在任何方向上滑动,灰度变化程度不大,可以判断窗口为 flat \text{flat} flat区域。
  • 两个特征值都大,且近似相等。说明窗口在任何方向上滑动,灰度变化程度明显,可以判断窗口存在 corner \text{corner} corner

特征根三种情况

1.3 Shi-Tomasi角点的判定

对于 Shi-Tomasi \text{Shi-Tomasi} Shi-Tomasi角点而言,其判定条件不要求两特征值近似相等,仅要求两特征值较小的一个大于特定的阈值。

根据 2 × 2 2\times2 2×2矩阵的特征值推导公式可知,较小的特征值为:
λ = ( A + C ) − ( A − C ) 2 + 4 B 2 2 = ( ∑ x , y w ( x , y ) I x 2 + ∑ x , y w ( x , y ) I y 2 ) − ( ∑ x , y w ( x , y ) I x 2 − ∑ x , y w ( x , y ) I y 2 ) + 4 ( ∑ x , y w ( x , y ) I x I y ) 2 2 \begin{aligned} \lambda&=\frac{(A+C)-\sqrt{(A-C)^2+4B^2}}{2}\\ &=\frac{(\sum_{x,y}w(x,y)I_x^2+\sum_{x,y}w(x,y)I_y^2)-\sqrt{(\sum_{x,y}w(x,y)I_x^2-\sum_{x,y}w(x,y)I_y^2)+4(\sum_{x,y}w(x,y)I_xI_y)^2}}{2} \end{aligned} λ=2(A+C)(AC)2+4B2 =2(x,yw(x,y)Ix2+x,yw(x,y)Iy2)(x,yw(x,y)Ix2x,yw(x,y)Iy2)+4(x,yw(x,y)IxIy)2

2. Shi-Tomasi角点代码解析

OpenCV \text{OpenCV} OpenCV中,关于 Shi-Tomasi \text{Shi-Tomasi} Shi-Tomasi算子的核心代码位于 corner.cpp \text{corner.cpp} corner.cpp文件中:

void cv::cornerMinEigenVal( InputArray _src, OutputArray _dst, int blockSize, int ksize, int borderType )
                                                                // _src:输入图像
                                                                // _dst:输出角点
                                                                // blockSize:观察窗尺寸
                                                                // ksize:一阶差分算子阶数
                                                                // borderType:边缘处理类型
{
    CV_INSTRUMENT_REGION();

    CV_OCL_RUN(_src.dims() <= 2 && _dst.isUMat(),
               ocl_cornerMinEigenValVecs(_src, _dst, blockSize, ksize, 0.0, borderType, MINEIGENVAL))
                                                                // OpenCL加速

/*#ifdef HAVE_IPP
    int kerSize = (ksize < 0)?3:ksize;
    bool isolated = (borderType & BORDER_ISOLATED) != 0;
    int borderTypeNI = borderType & ~BORDER_ISOLATED;
#endif
    CV_IPP_RUN(((borderTypeNI == BORDER_REPLICATE && (!_src.isSubmatrix() || isolated)) &&
            (kerSize == 3 || kerSize == 5) && (blockSize == 3 || blockSize == 5)) && IPP_VERSION_X100 >= 800,
        ipp_cornerMinEigenVal( _src, _dst, blockSize, ksize, borderType ));
    */

    Mat src = _src.getMat();
    _dst.create( src.size(), CV_32FC1 );
    Mat dst = _dst.getMat();

    cornerEigenValsVecs( src, dst, blockSize, ksize, MINEIGENVAL, 0, borderType );
}

static void
cornerEigenValsVecs( const Mat& src, Mat& eigenv, int block_size,
                     int aperture_size, int op_type, double k=0.,
                     int borderType=BORDER_DEFAULT )
                                                                // src:输入图像
                                                                // eigenv:输出特征根
                                                                // block_size:观察窗尺寸
                                                                // aperture_size:一阶差分算子阶数
                                                                // op_type:操作类型
                                                                // k = 0.:Harris 角点系数
                                                                // borderType = BORDER_DEFAULT:边缘处理类型
                                                                // ktype:算子数据类型
{
#if CV_TRY_AVX
    bool haveAvx = CV_CPU_HAS_SUPPORT_AVX;
#endif

    int depth = src.depth();                                    // 输入图像数据类型以及数据位数
    double scale = (double)(1 << ((aperture_size > 0 ? aperture_size : 3) - 1)) * block_size;
    if( aperture_size < 0 )
        scale *= 2.0;
    if( depth == CV_8U )
        scale *= 255.0;
    scale = 1.0/scale;

    CV_Assert( src.type() == CV_8UC1 || src.type() == CV_32FC1 );
                                                                // 参数检查

    Mat Dx, Dy;
    if( aperture_size > 0 )
    {
        Sobel( src, Dx, CV_32F, 1, 0, aperture_size, scale, 0, borderType );
                                                                // 生成x方向Sobel滤波结果(一阶差分)
        Sobel( src, Dy, CV_32F, 0, 1, aperture_size, scale, 0, borderType );
                                                                // 生成y方向Sobel滤波结果(一阶差分)
    }
    else
    {
        Scharr( src, Dx, CV_32F, 1, 0, scale, 0, borderType );
        Scharr( src, Dy, CV_32F, 0, 1, scale, 0, borderType );
    }

    Size size = src.size();
    Mat cov( size, CV_32FC3 );
    int i, j;

    for( i = 0; i < size.height; i++ )
    {
        float* cov_data = cov.ptr<float>(i);
        const float* dxdata = Dx.ptr<float>(i);
        const float* dydata = Dy.ptr<float>(i);

#if CV_TRY_AVX
        if( haveAvx )
            j = cornerEigenValsVecsLine_AVX(dxdata, dydata, cov_data, size.width);
        else
#endif // CV_TRY_AVX
            j = 0;

#if CV_SIMD128
        {
            for( ; j <= size.width - v_float32x4::nlanes; j += v_float32x4::nlanes )
            {
                v_float32x4 v_dx = v_load(dxdata + j);
                v_float32x4 v_dy = v_load(dydata + j);

                v_float32x4 v_dst0, v_dst1, v_dst2;
                v_dst0 = v_dx * v_dx;
                v_dst1 = v_dx * v_dy;
                v_dst2 = v_dy * v_dy;

                v_store_interleave(cov_data + j * 3, v_dst0, v_dst1, v_dst2);
            }
        }
#endif // CV_SIMD128

        for( ; j < size.width; j++ )
        {
            float dx = dxdata[j];
            float dy = dydata[j];

            cov_data[j*3] = dx*dx;                              // 卷积结果通道1保存x方向一阶差分平方结果
            cov_data[j*3+1] = dx*dy;                            // 卷积结果通道2保存xy方向一阶差分相乘结果
            cov_data[j*3+2] = dy*dy;                            // 卷积结果通道3保存y方向一阶差分平方结果
        }
    }

    boxFilter(cov, cov, cov.depth(), Size(block_size, block_size),
        Point(-1,-1), false, borderType );                      // 盒式滤波
                                                                // cov:输入图像
                                                                // cov:输出图像
                                                                // cov.depth():输出图像矩阵类型
                                                                // Size(block_size, block_size):核大小
                                                                // Point(-1, -1):内核中的锚点位置,默认值 (-1,-1) 表示锚点位于内核中心
                                                                // false:是否对核按面积进行归一化
                                                                // borderType:边缘处理类型

    if( op_type == MINEIGENVAL )
        calcMinEigenVal( cov, eigenv );                         // 计算最小特征根
    else if( op_type == HARRIS )
        calcHarris( cov, eigenv, k );
    else if( op_type == EIGENVALSVECS )
        calcEigenValsVecs( cov, eigenv );
}

static void calcMinEigenVal( const Mat& _cov, Mat& _dst )
                                                                // _cov:输入特征图,通道1保存x方向一阶差分平方结果,通道2保存xy方向一阶差分相乘结果,通道3保存y方向一阶差分平方结果
                                                                // _dst:输出特征根
{
    int i, j;
    Size size = _cov.size();
#if CV_TRY_AVX
    bool haveAvx = CV_CPU_HAS_SUPPORT_AVX;
#endif

    if( _cov.isContinuous() && _dst.isContinuous() )            // 如果_cov及_dst连续存储,则调整size大小以加速以下循环代码
    {
        size.width *= size.height;
        size.height = 1;
    }

    for( i = 0; i < size.height; i++ )
    {
        const float* cov = _cov.ptr<float>(i);
        float* dst = _dst.ptr<float>(i);
#if CV_TRY_AVX
        if( haveAvx )
            j = calcMinEigenValLine_AVX(cov, dst, size.width);
        else
#endif // CV_TRY_AVX
            j = 0;

#if CV_SIMD128
        {
            v_float32x4 half = v_setall_f32(0.5f);
            for( ; j <= size.width - v_float32x4::nlanes; j += v_float32x4::nlanes )
            {
                v_float32x4 v_a, v_b, v_c, v_t;
                v_load_deinterleave(cov + j*3, v_a, v_b, v_c);
                v_a *= half;
                v_c *= half;
                v_t = v_a - v_c;
                v_t = v_muladd(v_b, v_b, (v_t * v_t));
                v_store(dst + j, (v_a + v_c) - v_sqrt(v_t));
            }
        }
#endif // CV_SIMD128

        for( ; j < size.width; j++ )
        {
            float a = cov[j*3]*0.5f;
            float b = cov[j*3+1];
            float c = cov[j*3+2]*0.5f;
            dst[j] = (float)((a + c) - std::sqrt((a - c)*(a - c) + b*b));
                                                                // 求较小特征值
        }
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值