Steger算法的基本原理

光条中心提取(Steger 方法)


1. 线条模型

光条截面的光强分布类似服从高斯分布,那么光条的中心就是高斯分布的顶点。通过将图像函数 z ( x , y ) z(x,y) z(x,y)进行泰勒展开,再用多项式进行函数逼近,从而找到这个顶点

1.1 一维线条模型与检测

许多线条检测方法认为,一维情况下线条是条形的,设理想线宽度为 2 w 2w 2w,高度为 h h h,以线条中心为原点建立坐标系,则线条轮廓可以表示为:

f b ( x ) = { h , ∣ x ∣ < w 0 , ∣ x ∣ > w f_b(x)= \left \{ \begin{array}{} h&,|x|<w\\ 0&,|x|>w \end {array} \right. fb(x)={ h0,x<w,x>w

考虑传感器采样等影响,实际的线条表面假设成抛物线(结合上图的情况),同样设宽度为 2 ω 2\omega 2ω,建立坐标系,则高度可以表示为:

f p ( x ) = { h ( 1 − ( x w ) 2 ) , ∣ x ∣ < w 0 , ∣ x ∣ > w f_p(x)= \left \{ \begin{array}{} h(1-(\frac{x}{w})^2)&,|x|<w\\ 0&,|x|>w \end {array} \right. fp(x)={ h(1(wx)2)0,x<w,x>w

无噪声的条件下,在一维图像 z ( x ) z(x) z(x)中找到这条线,只需要找到为 z ′ ( x ) = 0 z'(x)=0 z(x)=0的点。通常采用显著线的标准来找这条线。判断是否为显著线的标准为看在 z ′ ( x ) = 0 z'(x)=0 z(x)=0 z ′ ′ ( x ) z''(x) z′′(x)的大小,对于暗背景下的亮线, z ′ ′ ( x ) ≤ 0 z''(x) \le 0 z′′(x)0,对于亮背景下的暗线, z ′ ′ ( x ) ≥ 0 z''(x) \ge 0 z′′(x)0。但真实图像中包含大量噪声,需要通过高斯平滑核的导数与图像卷积来估计 z ( x ) z(x) z(x)的一阶和二阶导数,达到减噪的目的。

卷积过程中,考虑到线条两侧的对比度有可能不同,简单起见,采用不对称的线条模型:

f a ( x ) = { 0 , x < − w 1 , ∣ x ∣ > w h , x > w f_a(x)= \left \{ \begin{array}{} 0&,x<-w\\ 1&,|x|>w\\ h&,x>w \end {array} \right. fa(x)= 01h,x<w,x>w,x>w

(有另外的讨论)这种情况对中心的检测会存在偏移,但只要 σ \sigma σ设置得当,偏移量在可接受范围之内,不会超出实际线条范围,而Facet模型,就会存在较大的偏移。

1.2 二维线条模型与检测

Steger论文原文中这样解释:二维的曲线结构可以建模为曲线 s ( t ) s(t) s(t),该曲线在其垂直方向上呈现典型的一维线轮廓。设该法方向为 n → ( t ) \overrightarrow{\mathbf n}(t) n (t),则 n → ( t ) \overrightarrow{\mathbf n}(t) n (t)方向上的一阶方向导数应为零,二阶方向导数应具有较大的绝对值。

二维线条模型是在一维线条模型的拓展,与一维线条不同的是需要在每个局部图像点上计算线的方向,这里引入Hessian矩阵来确定方向,这在第3章的子像素级边缘检测中有提到过,在此我们进一步加深对Hessian矩阵的理解。

H ( x , y ) = [ I x x I x y I x y I y y ] \mathbf H(x, y)=\begin{bmatrix} I_{xx}&I_{xy}\\I_{xy}&I_{yy} \end{bmatrix} H(x,y)=[IxxIxyIxyIyy]

该矩阵由图像某点沿不同方向的四个偏导组成,Hessian矩阵的最大绝对特征值对应线条法向二阶导数,其最大绝对特征值的特征向量对应线条的法向向量。接下来的思路就是:首先进行高斯滤波,然后计算Hessian矩阵得到光条的法线方向,最后在法线方向利用泰勒展开得到亚像素位置。

( x 0 , y 0 ) (x_0,y_0) (x0,y0)为基准点,图像上每个点可以计算Hessian矩阵,线中心点的Hessian矩阵特征值比其他地方都要大,可以设置阈值进行筛选,则光条中心的亚像素坐标为:

( p x , p y ) = ( x 0 + t n x , y 0 + t n y ) (p_x,p_y)=(x_0+tn_x,y_0+tn_y) (px,py)=(x0+tnx,y0+tny)

构造出当前像素 ( x 0 , y 0 ) (x_0,y_0) (x0,y0)法线方向上光强分布的多项式(泰勒展开)如下:

I ( x 0 + t n x , y 0 + t n y ) = I ( x 0 , y 0 ) + t [ n x n y ] [ I x I y ] + t 2 2 ! [ n x n y ] [ I x x I x y I x y I y y ] [ n x n y ] I(x_0+tn_x,y_0+tn_y) = I(x_0,y_0) + t \begin{bmatrix} n_x&n_y \end{bmatrix} \begin{bmatrix} I_x\\I_y \end{bmatrix} + \frac{t^2}{2!} \begin{bmatrix} n_x&n_y \end{bmatrix} \begin{bmatrix} I_{xx}&I_{xy}\\I_{xy}&I_{yy} \end{bmatrix} \begin{bmatrix} n_x\\n_y \end{bmatrix} I(x0+tnx,y0+tny)=I(x0,y0)+t[nxny][IxIy]+2!t2[nxny][IxxIxyIxyIyy][nxny]

其中, I ( x 0 , y 0 ) I(x_0,y_0) I(x0,y0)代表当前像素的光强值, [ I x I y ] \begin{bmatrix} I_x&I_y \end{bmatrix} [IxIy]代表一阶导数的值, H ( x , y ) \mathbf H(x, y) H(x,y)代表二阶导数的值, t t t是一个未知参数。对 t t t求导,导数为0的地方就是光强最强的地方(极值),也就是激光中心点。

∂ I ∂ t = [ n x n y ] [ I x I y ] + t [ n x n y ] [ I x x I x y I x y I y y ] [ n x n y ] = 0 \frac{\partial I}{\partial t} = \begin{bmatrix} n_x&n_y \end{bmatrix} \begin{bmatrix} I_x\\I_y \end{bmatrix} + t \begin{bmatrix} n_x&n_y \end{bmatrix} \begin{bmatrix} I_{xx}&I_{xy}\\I_{xy}&I_{yy} \end{bmatrix} \begin{bmatrix} n_x\\n_y \end{bmatrix} = 0 tI=[nxny][IxIy]+t[nxny][IxxIxyIxyIyy][nxny]=0

可以计算出 t t t

t = − n x I x + n y I y n x 2 I x x + 2 n x n y I x y + n y 2 I y y t = -\frac{n_xI_x+n_yI_y}{n_x^2I_{xx}+2n_xn_yI_{xy}+n_y^2I_{yy}} t=nx2Ixx+2nxnyIxy+ny2IyynxIx+nyIy

泰勒展开在很小的范围内适用,如果 t n x tn_x tnx t n y tn_y tny都小于0.5说明这个极值点就位于当前像素内。附近光强分布函数适用。如果 t n x tn_x tnx t n y tn_y tny都很大,则说明这个光强最强点有点距离(同时附近光强分布函数不适用,不可以用t计算最强点),要跳过当前点,继续扫描距离激光中心更近的点。

2. 线条连接算法

文献中提出了一种点的连接算法,并表示优于使用Facet模型检测的效果。

首先由第一部分的方法获得每个像素的以下数据:线的方向 ( n x , n y ) = ( c o s α , s i n α ) (n_x,n_y)=(cos \alpha ,sin \alpha) (nx,ny)=(cosα,sinα),线的强度度量( α \alpha α方向上的二阶方向导数),以及线的亚像素位置 ( p x , p y ) (p_x,p_y) (px,py)。然后从具有最大二阶导数的像素开始,通过向当前线条添加适当的相邻点来连接线条。相邻点的选择基于与相应亚像素位置之间的距离和两个点的角度差。令 d = ∣ ∣ p 2 − p 1 ∣ ∣ 2 , β = α 2 − α 1 d=||p_2-p_1||_2,\beta = \alpha_2-\alpha_1 d=∣∣p2p12,β=α2α1,则添加的相邻线点为 d + w β d+w \beta d+的最小值,文献中令 w = 1 w=1 w=1。按顺序选择每个线点,直到在当前邻域中找不到更多的线点,或者直到最佳匹配候选点是已添加到另一条线的点。如果发生这种情况,则该点将标记为连接点,并且包含该点的线将在连接点处拆分为两条线。在交叉点处,选择一个要跟随的分支,然后继续添加线点。

迟滞阈值处理,双阈值处理

只要选定的点的二阶方向导数大于用户指定的上阈值,就可以成为新线条的起点。而只要邻接候选点的二阶方向导数大于用户指定的下阈值就可以标记成为该线条上的点。文献中,上限被设置为零,即所有的线,无论有多弱,都被选择中。如果使用的上限为5,则只选择突出的线。

3. 算法实现

void StegerLine()
{ 
    Mat img0 = imread("image_0.png", 1);
    Mat img;
    cvtColor(img0, img0, CV_BGR2GRAY);
    img = img0.clone();

    //高斯滤波
    img.convertTo(img, CV_32FC1);
    GaussianBlur(img, img, Size(0, 0), 6, 6);

    //一阶偏导数
    Mat m1, m2;
    m1 = (Mat_<float>(1, 2) << 1, -1);  //x偏导
    m2 = (Mat_<float>(2, 1) << 1, -1);  //y偏导

    Mat dx, dy;
    filter2D(img, dx, CV_32FC1, m1);
    filter2D(img, dy, CV_32FC1, m2);

    //二阶偏导数
    Mat m3, m4, m5;
    m3 = (Mat_<float>(1, 3) << 1, -2, 1);   //二阶x偏导
    m4 = (Mat_<float>(3, 1) << 1, -2, 1);   //二阶y偏导
    m5 = (Mat_<float>(2, 2) << 1, -1, -1, 1);   //二阶xy偏导

    Mat dxx, dyy, dxy;
    filter2D(img, dxx, CV_32FC1, m3);
    filter2D(img, dyy, CV_32FC1, m4);
    filter2D(img, dxy, CV_32FC1, m5);

    //hessian矩阵
    double maxD = -1;
    int imgcol = img.cols;
    int imgrow = img.rows;
    vector<double> Pt;
    for (int i=0;i<imgcol;i++)
    {
        for (int j=0;j<imgrow;j++)
        {
            if (img0.at<uchar>(j,i)>200)
            {
                Mat hessian(2, 2, CV_32FC1);
                hessian.at<float>(0, 0) = dxx.at<float>(j, i);
                hessian.at<float>(0, 1) = dxy.at<float>(j, i);
                hessian.at<float>(1, 0) = dxy.at<float>(j, i);
                hessian.at<float>(1, 1) = dyy.at<float>(j, i);

                Mat eValue;
                Mat eVectors;
                eigen(hessian, eValue, eVectors);

                double nx, ny;
                double fmaxD = 0;
                if (fabs(eValue.at<float>(0,0))>= fabs(eValue.at<float>(1,0)))  //求特征值最大时对应的特征向量
                {
                    nx = eVectors.at<float>(0, 0);
                    ny = eVectors.at<float>(0, 1);
                    fmaxD = eValue.at<float>(0, 0);
                }
                else
                {
                    nx = eVectors.at<float>(1, 0);
                    ny = eVectors.at<float>(1, 1);
                    fmaxD = eValue.at<float>(1, 0);
                }

                double t = -(nx*dx.at<float>(j, i) + ny*dy.at<float>(j, i)) / (nx*nx*dxx.at<float>(j,i)+2*nx*ny*dxy.at<float>(j,i)+ny*ny*dyy.at<float>(j,i));

                if (fabs(t*nx)<=0.5 && fabs(t*ny)<=0.5)
                {
                    Pt.push_back(i);
                    Pt.push_back(j);
                }
            }
        }
    }

    for (int k = 0;k<Pt.size()/2;k++)
    {
        Point rpt;
        rpt.x = Pt[2 * k + 0];
        rpt.y = Pt[2 * k + 1];
        circle(img0, rpt, 1, Scalar(0, 0, 255));
    }

    imshow("result", img0);
    waitKey(0);
}

4.Steger方法与Harris角点检测的联系

Harris法使用的矩阵为图像自相关函数一次泰勒展开系数的实对称矩阵表示,而Steger方法使用的Hessian矩阵为函数的二阶偏导。这两种矩阵有一定的相似性,Harris矩阵实对称在合同对角化之后,特征值表示的是朝某个方向移动单位距离,图像灰度的变化大小,最小特征值大于一定范围时可认为在x,y方向的灰度变化都比较大,可以认为是角点;而Hessian矩阵的特征值对应的特征向量指示函数变化最大的方向,特征值为二阶导数值,也能反映函数的变化情况。

猜你喜欢

转载自blog.csdn.net/Ucarrot/article/details/127082528