欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

OpenCV源码解析:Jacobi法计算矩阵的特征值和特征向量

程序员文章站 2022-03-06 08:21:41
...

(注:CSDN不适合写公式,只好上传图片格式)

OpenCV源码解析:Jacobi法计算矩阵的特征值和特征向量

 

OpenCV源码解析:Jacobi法计算矩阵的特征值和特征向量

其中Pkk=Pll=cosθ, Plk=Pkl=sinθ,形式上就是这样,

A*PT

   Aik = Aik×Pkk+Ail×Pkl

   Ail = Aik×Plk+Ail×Pll

P*A

   Aki = Pkk×Aki+ Pkl×Ali

   Ali = Plk×Aki+ Pll×Ali

实际计算时,只计算那些必须计算的量,已经归0的量和不变的量都不用计算,比如,当k=1,l=3时,只计算了下面这6个量A01,A03, A12, A23, A14, A34,其中A13已经归0化(A03=0),

OpenCV源码解析:Jacobi法计算矩阵的特征值和特征向量

为了达到上述目的,OpenCV充分利用了矩阵的对称性(如Ali=Ail), 连续采用了三个for循环计算右上角的元素(对右上角的元素,OpenCV源码解析:Jacobi法计算矩阵的特征值和特征向量 一定成立),这样既可以避免左下角不必要的运算,同时也免除了向左下角拷贝元素值的必要。

第1个for循环,处理0到k-1行的第k,l个元素,这些元素在对角线的上面;

第2个for循环,k+1到l-1之间的元素,为了确保只用右上角的元素,后面写成了A[astep*i+l]

第3个for循环,l+1到n-1之间的元素,在对角线的右边;

        for( i = 0; i < k; i++ )
            rotate(A[astep*i+k], A[astep*i+l]); 
        for( i = k+1; i < l; i++ )
            rotate(A[astep*k+i], A[astep*i+l]);
        for( i = l+1; i < n; i++ )
            rotate(A[astep*k+i], A[astep*l+i]);

那些对角线的上元素(最后得到的就是特征值),则由下面的代码处理,

        if( V )
            for( i = 0; i < n; i++ )
                rotate(V[vstep*k+i], V[vstep*l+i]);

看到这里,不难明白,为什么OpenCV的eigen/Jacobi函数只适合实对称矩阵的处理,而不适合那些非对称的矩阵。

另外要说明一下源码中的最大值查找,它是冗余的,函数先实现了按行查找最大值,然后又实现了按列查找最大值,对于非对称矩阵,这种算法确保能找到最大值所在的位置;对于对称矩阵,这两种算法就完全重复了,两次查找得到的结果是一样的。

下面的我们看一下OpenCV中Jacob算法的具体实现,源码在lapack.cpp中

Hypot计算的是(a^2 + b^2)^0.5
template<typename _Tp> static inline _Tp hypot(_Tp a, _Tp b)
{
    a = std::abs(a);
    b = std::abs(b);
    if( a > b )
    {
        b /= a;
        return a*std::sqrt(1 + b*b);
    }
    if( b > 0 )
    {
        a /= b;
        return b*std::sqrt(1 + a*a);
    }
    return 0;
}

template<typename _Tp> bool
JacobiImpl_( _Tp* A, size_t astep, _Tp* W, _Tp* V, size_t vstep, int n, uchar* buf ) 
{
    const _Tp eps = std::numeric_limits<_Tp>::epsilon();  // 设置计算精度 
    int i, j, k, m; 

    astep /= sizeof(A[0]); 
    if( V ) 
    { 
        vstep /= sizeof(V[0]); 
        for( i = 0; i < n; i++ ) 
        {
            for( j = 0; j < n; j++ ) 
                V[i*vstep + j] = (_Tp)0; 	// 非对角线元素置0   
            V[i*vstep + i] = (_Tp)1;  		// 所有对角线元素置1 
        } 
    } 

    int iters, maxIters = n*n*30; // 最大允许叠代次数

    int* indR = (int*)alignPtr(buf, sizeof(int));
int* indC = indR + n;

    _Tp mv = (_Tp)0;

    for( k = 0; k < n; k++ )
    {
        W[k] = A[(astep + 1)*k];  // 取第k行对角线上的元素(第k行:astep*k,第k列:k)

        // 下面这个if,是对k行右边所有的元素逐个查找,找到绝对值最大的那个,并记录下其所在的位置
        if( k < n - 1 )
        {
		   //第k行中,从第k+1到第n-1个元素,逐个找
            for( m = k+1, mv = std::abs(A[astep*k + m]), i = k+2; i < n; i++ )  
            {
                _Tp val = std::abs(A[astep*k+i]);
                if( mv < val )
                    mv = val, m = i;
            }
            indR[k] = m;  // k行中最大值元素所在的位置
        }
		
	    // 下面这个if,是对k列上边所有的元素逐个查找,找到绝对值最大的那个,并记录下其所在的位置
        if( k > 0 ) 
        {
		   //第k列行中,从第0到第k-1个元素,逐个找
            for( m = 0, mv = std::abs(A[k]), i = 1; i < k; i++ )
            {
                _Tp val = std::abs(A[astep*i+k]);
                if( mv < val )
                    mv = val, m = i;
            }
            indC[k] = m;  // 第k列中最大值元素所在的位置(行数)
        }
    }

    if( n > 1 ) for( iters = 0; iters < maxIters; iters++ )
    {
        // find index (k,l) of pivot p
        // 从第0行到第n-2行,每行最大的那个值相互比对,逐行找最大值, 
        // 注意,最后一行是第n-1行,右边已经没有元素了,故而不参与计算
        for( k = 0, mv = std::abs(A[indR[0]]), i = 1; i < n-1; i++ )
        {
            _Tp val = std::abs(A[astep*i + indR[i]]); // 第i行的最大值
            if( mv < val )
                mv = val, k = i;
        }
        int l = indR[k];  // 右上角元素最大值所在的列		
	    
        // 从第1列到第n-1列,逐列找 
        for( i = 1; i < n; i++ )
        {
            _Tp val = std::abs(A[astep*indC[i] + i]);
            if( mv < val )
                mv = val, k = indC[i], l = i;
        }

        _Tp p = A[astep*k + l];
        if( std::abs(p) <= eps )
            break;    // 如果计算结果已经满足精度要求,直接退出
		
       // 下面的计算参考函数解释部分
        _Tp y = (_Tp)((W[l] - W[k])*0.5);
        _Tp t = std::abs(y) + hypot(p, y);  
        _Tp s = hypot(p, t);
        _Tp c = t/s;
        s = p/s; t = (p/t)*p;
        if( y < 0 )
            s = -s, t = -t;
        A[astep*k + l] = 0;

        W[k] -= t; // 更新对角线元素
        W[l] += t; // 更新对角线元素

        _Tp a0, b0;
        
#undef rotate
#define rotate(v0, v1) a0 = v0, b0 = v1, v0 = a0*c - b0*s, v1 = a0*s + b0*c 

        // rotate rows and columns k and l
        for( i = 0; i < k; i++ )
            rotate(A[astep*i+k], A[astep*i+l]); 
        for( i = k+1; i < l; i++ )
            rotate(A[astep*k+i], A[astep*i+l]);
        for( i = l+1; i < n; i++ )
            rotate(A[astep*k+i], A[astep*l+i]);

        // rotate eigenvectors
        if( V )
            for( i = 0; i < n; i++ )
                rotate(V[vstep*k+i], V[vstep*l+i]);

#undef rotate

        // 因为矩阵已经变更,所以第k,l行和第k,l列的最大,最小值可能已经变了,需要更新
        for( j = 0; j < 2; j++ )
        {
            int idx = j == 0 ? k : l;
            if( idx < n - 1 )
            {
                for( m = idx+1, mv = std::abs(A[astep*idx + m]), i = idx+2; i < n; i++ )
                {
                    _Tp val = std::abs(A[astep*idx+i]);
                    if( mv < val )
                        mv = val, m = i;
                }
                indR[idx] = m;
            }
            if( idx > 0 )
            {
                for( m = 0, mv = std::abs(A[idx]), i = 1; i < idx; i++ )
                {
                    _Tp val = std::abs(A[astep*i+idx]);
                    if( mv < val )
                        mv = val, m = i;
                }
                indC[idx] = m;
            }
        }
    }

    // sort eigenvalues & eigenvectors
    for( k = 0; k < n-1; k++ )
    {
        m = k;
        for( i = k+1; i < n; i++ )
        {
            if( W[m] < W[i] )
                m = i;
        }
        if( k != m )
        {
            std::swap(W[m], W[k]);
            if( V )
                for( i = 0; i < n; i++ )
                    std::swap(V[vstep*m + i], V[vstep*k + i]);
        }
    }

    return true;
}

static bool Jacobi( float* S, size_t sstep, float* e, float* E, size_t estep, int n, uchar* buf )
{
    return JacobiImpl_(S, sstep, e, E, estep, n, buf);
}

static bool Jacobi( double* S, size_t sstep, double* e, double* E, size_t estep, int n, uchar* buf )
{
    return JacobiImpl_(S, sstep, e, E, estep, n, buf);
}

解析完毕。

OpenCV源码解析:Jacobi法计算矩阵的特征值和特征向量