单目视觉(3):SFM之SIFT(三)

SFM之SIFT(三)


SIFT(scale-invariant-feature transform)

SIFT特性:

  • 局部特征,对旋转,尺度(缩放),平移,亮度(光照)保持不变性
  • 对视角变化(遮挡)、仿射/投影变换、噪声也较为稳定
  • 简单的图像(物体较少而不是模糊或者圆形图像)也能产生大量的特征数据
  • 优化的SIFT算法能达到实时性的要求(优化前实时性并不太好)
  • 可以和其他形式的特征进行组合

算法步骤

(依据不同的paper有不同的实现方式,OpenCV采用D. Lowe的方案)
- 尺度空间极值检测:搜索所有尺度上的图像,寻找候选点。可以通过高斯微分函数来实现。
- 关键点确定:在候选点的位置上,拟合模型来确定位置和尺度,依据其稳定程度来确定。
- 方向确定:基于图像的梯度方向,为关键点分配方向(可以有多个方向)。[对图像的各种变换可以保持不变性]
- 关键点描述:在其领域内计算图像局部梯度。[形变、光照鲁棒性]

opencv源码

/*!
 SIFT implementation.

 The class implements SIFT algorithm by D. Lowe.
*/
class CV_EXPORTS_W SIFT : public Feature2D
{
public:
    CV_WRAP explicit SIFT( int nfeatures=0, int nOctaveLayers=3,
          double contrastThreshold=0.04, double edgeThreshold=10,
          double sigma=1.6);

    //! returns the descriptor size in floats (128)
    CV_WRAP int descriptorSize() const;

    //! returns the descriptor type
    CV_WRAP int descriptorType() const;

    //! finds the keypoints using SIFT algorithm
    void operator()(InputArray img, InputArray mask,
                    vector<KeyPoint>& keypoints) const;
    //! finds the keypoints and computes descriptors for them using SIFT algorithm.
    //! Optionally it can compute descriptors for the user-provided keypoints
    void operator()(InputArray img, InputArray mask,
                    vector<KeyPoint>& keypoints,
                    OutputArray descriptors,
                    bool useProvidedKeypoints=false) const;

    AlgorithmInfo* info() const;

    void buildGaussianPyramid( const Mat& base, vector<Mat>& pyr, int nOctaves ) const;
    void buildDoGPyramid( const vector<Mat>& pyr, vector<Mat>& dogpyr ) const;
    void findScaleSpaceExtrema( const vector<Mat>& gauss_pyr, const vector<Mat>& dog_pyr,
                                vector<KeyPoint>& keypoints ) const;

protected:
    void detectImpl( const Mat& image, vector<KeyPoint>& keypoints, const Mat& mask=Mat() ) const;
    void computeImpl( const Mat& image, vector<KeyPoint>& keypoints, Mat& descriptors ) const;

    CV_PROP_RW int nfeatures;
    CV_PROP_RW int nOctaveLayers;
    CV_PROP_RW double contrastThreshold;
    CV_PROP_RW double edgeThreshold;
    CV_PROP_RW double sigma;
};

typedef SIFT SiftFeatureDetector;
typedef SIFT SiftDescriptorExtractor;


// 构建nOctaves组(每组nOctaves+3层)高斯金字塔  
void SIFT::buildGaussianPyramid( const Mat& base, vector<Mat>& pyr, int nOctaves ) const  
{  
    vector<double> sig(nOctaveLayers + 3);  
    pyr.resize(nOctaves*(nOctaveLayers + 3));  

    // precompute Gaussian sigmas using the following formula:  
    //  \sigma_{total}^2 = \sigma_{i}^2 + \sigma_{i-1}^2、  
    // 计算对图像做不同尺度高斯模糊的尺度因子  
    sig[0] = sigma;  
    double k = pow( 2., 1. / nOctaveLayers );  
    for( int i = 1; i < nOctaveLayers + 3; i++ )  
    {  
        double sig_prev = pow(k, (double)(i-1))*sigma;  
        double sig_total = sig_prev*k;  
        sig[i] = std::sqrt(sig_total*sig_total - sig_prev*sig_prev);  
    }  

    for( int o = 0; o < nOctaves; o++ )  
    {  
        // DoG金子塔需要nOctaveLayers+2层图像来检测nOctaves层尺度  
        // 所以高斯金字塔需要nOctaveLayers+3层图像得到nOctaveLayers+2层DoG金字塔  
        for( int i = 0; i < nOctaveLayers + 3; i++ )  
        {  
            // dst为第o组(Octave)金字塔  
            Mat& dst = pyr[o*(nOctaveLayers + 3) + i];  
            // 第0组第0层为原始图像  
            if( o == 0  &&  i == 0 )  
                dst = base;  

            // base of new octave is halved image from end of previous octave  
            // 每一组第0副图像时上一组倒数第三幅图像隔点采样得到  
            else if( i == 0 )  
            {  
                const Mat& src = pyr[(o-1)*(nOctaveLayers + 3) + nOctaveLayers];  
                resize(src, dst, Size(src.cols/2, src.rows/2),  
                       0, 0, INTER_NEAREST);  
            }  
            // 每一组第i副图像是由第i-1副图像进行sig[i]的高斯模糊得到  
            // 也就是本组图像在sig[i]的尺度空间下的图像  
            else  
            {  
                const Mat& src = pyr[o*(nOctaveLayers + 3) + i-1];  
                GaussianBlur(src, dst, Size(), sig[i], sig[i]);  
            }  
        }  
    }  
}  
/ 构建nOctaves组(每组nOctaves+2层)高斯差分金字塔  
void SIFT::buildDoGPyramid( const vector<Mat>& gpyr, vector<Mat>& dogpyr ) const  
{  
    int nOctaves = (int)gpyr.size()/(nOctaveLayers + 3);  
    dogpyr.resize( nOctaves*(nOctaveLayers + 2) );  

    for( int o = 0; o < nOctaves; o++ )  
    {  
        for( int i = 0; i < nOctaveLayers + 2; i++ )  
        {  
            // 第o组第i副图像为高斯金字塔中第o组第i+1和i组图像相减得到  
            const Mat& src1 = gpyr[o*(nOctaveLayers + 3) + i];  
            const Mat& src2 = gpyr[o*(nOctaveLayers + 3) + i + 1];  
            Mat& dst = dogpyr[o*(nOctaveLayers + 2) + i];  
            subtract(src2, src1, dst, noArray(), CV_16S);  
        }  
    }  
}  

// Detects features at extrema in DoG scale space.  Bad features are discarded  
// based on contrast and ratio of principal curvatures.  
// 在DoG尺度空间寻特征点(极值点)  
void SIFT::findScaleSpaceExtrema( const vector<Mat>& gauss_pyr, const vector<Mat>& dog_pyr,  
                                  vector<KeyPoint>& keypoints ) const  
{  
    int nOctaves = (int)gauss_pyr.size()/(nOctaveLayers + 3);  

    // The contrast threshold used to filter out weak features in semi-uniform  
    // (low-contrast) regions. The larger the threshold, the less features are produced by the detector.  
    // 过滤掉弱特征的阈值 contrastThreshold默认为0.04  
    int threshold = cvFloor(0.5 * contrastThreshold / nOctaveLayers * 255 * SIFT_FIXPT_SCALE);  
    const int n = SIFT_ORI_HIST_BINS; //36  
    float hist[n];  
    KeyPoint kpt;  

    keypoints.clear();  

    for( int o = 0; o < nOctaves; o++ )  
        for( int i = 1; i <= nOctaveLayers; i++ )  
        {  
            int idx = o*(nOctaveLayers+2)+i;  
            const Mat& img = dog_pyr[idx];  
            const Mat& prev = dog_pyr[idx-1];  
            const Mat& next = dog_pyr[idx+1];  
            int step = (int)img.step1();  
            int rows = img.rows, cols = img.cols;  

            for( int r = SIFT_IMG_BORDER; r < rows-SIFT_IMG_BORDER; r++)  
            {  
                const short* currptr = img.ptr<short>(r);  
                const short* prevptr = prev.ptr<short>(r);  
                const short* nextptr = next.ptr<short>(r);  

                for( int c = SIFT_IMG_BORDER; c < cols-SIFT_IMG_BORDER; c++)  
                {  
                    int val = currptr[c];  

                    // find local extrema with pixel accuracy  
                    // 寻找局部极值点,DoG中每个点与其所在的立方体周围的26个点比较  
                    // if (val比所有都大 或者 val比所有都小)  
                    if( std::abs(val) > threshold &&  
                       ((val > 0 && val >= currptr[c-1] && val >= currptr[c+1] &&  
                         val >= currptr[c-step-1] && val >= currptr[c-step] &&   
                         val >= currptr[c-step+1] && val >= currptr[c+step-1] &&   
                         val >= currptr[c+step] && val >= currptr[c+step+1] &&  
                         val >= nextptr[c] && val >= nextptr[c-1] &&   
                         val >= nextptr[c+1] && val >= nextptr[c-step-1] &&   
                         val >= nextptr[c-step] && val >= nextptr[c-step+1] &&   
                         val >= nextptr[c+step-1] && val >= nextptr[c+step] &&   
                         val >= nextptr[c+step+1] && val >= prevptr[c] &&   
                         val >= prevptr[c-1] && val >= prevptr[c+1] &&  
                         val >= prevptr[c-step-1] && val >= prevptr[c-step] &&   
                         val >= prevptr[c-step+1] && val >= prevptr[c+step-1] &&   
                         val >= prevptr[c+step] && val >= prevptr[c+step+1]) ||  
                        (val < 0 && val <= currptr[c-1] && val <= currptr[c+1] &&  
                         val <= currptr[c-step-1] && val <= currptr[c-step] &&   
                         val <= currptr[c-step+1] && val <= currptr[c+step-1] &&   
                         val <= currptr[c+step] && val <= currptr[c+step+1] &&  
                         val <= nextptr[c] && val <= nextptr[c-1] &&   
                         val <= nextptr[c+1] && val <= nextptr[c-step-1] &&   
                         val <= nextptr[c-step] && val <= nextptr[c-step+1] &&   
                         val <= nextptr[c+step-1] && val <= nextptr[c+step] &&   
                         val <= nextptr[c+step+1] && val <= prevptr[c] &&   
                         val <= prevptr[c-1] && val <= prevptr[c+1] &&  
                         val <= prevptr[c-step-1] && val <= prevptr[c-step] &&   
                         val <= prevptr[c-step+1] && val <= prevptr[c+step-1] &&   
                         val <= prevptr[c+step] && val <= prevptr[c+step+1])))  
                    {  
                        int r1 = r, c1 = c, layer = i;  

                        // 关键点精确定位  
                        if( !adjustLocalExtrema(dog_pyr, kpt, o, layer, r1, c1,  
                                                nOctaveLayers, (float)contrastThreshold,  
                                                (float)edgeThreshold, (float)sigma) )  
                            continue;  

                        float scl_octv = kpt.size*0.5f/(1 << o);  
                        // 计算梯度直方图  
                        float omax = calcOrientationHist(  
                            gauss_pyr[o*(nOctaveLayers+3) + layer],  
                            Point(c1, r1),  
                            cvRound(SIFT_ORI_RADIUS * scl_octv),  
                            SIFT_ORI_SIG_FCTR * scl_octv,  
                            hist, n);  
                        float mag_thr = (float)(omax * SIFT_ORI_PEAK_RATIO);  
                        for( int j = 0; j < n; j++ )  
                        {  
                            int l = j > 0 ? j - 1 : n - 1;  
                            int r2 = j < n-1 ? j + 1 : 0;  

                            if( hist[j] > hist[l]  &&  hist[j] > hist[r2]  &&  hist[j] >= mag_thr )  
                            {  
                                float bin = j + 0.5f * (hist[l]-hist[r2]) /   
                                (hist[l] - 2*hist[j] + hist[r2]);  
                                bin = bin < 0 ? n + bin : bin >= n ? bin - n : bin;  
                                kpt.angle = (float)((360.f/n) * bin);  
                                keypoints.push_back(kpt);  
                            }  
                        }  
                    }  
                }  
            }  
        }  
}  

// Interpolates a scale-space extremum's location and scale to subpixel  
// accuracy to form an image feature.  Rejects features with low contrast.  
// Based on Section 4 of Lowe's paper.  
// 特征点精确定位  
static bool adjustLocalExtrema( const vector<Mat>& dog_pyr, KeyPoint& kpt, int octv,  
                                int& layer, int& r, int& c, int nOctaveLayers,  
                                float contrastThreshold, float edgeThreshold, float sigma )  
{  
    const float img_scale = 1.f/(255*SIFT_FIXPT_SCALE);  
    const float deriv_scale = img_scale*0.5f;  
    const float second_deriv_scale = img_scale;  
    const float cross_deriv_scale = img_scale*0.25f;  

    float xi=0, xr=0, xc=0, contr;  
    int i = 0;  

    //三维子像元插值  
    for( ; i < SIFT_MAX_INTERP_STEPS; i++ )  
    {  
        int idx = octv*(nOctaveLayers+2) + layer;  
        const Mat& img = dog_pyr[idx];  
        const Mat& prev = dog_pyr[idx-1];  
        const Mat& next = dog_pyr[idx+1];  

        Vec3f dD((img.at<short>(r, c+1) - img.at<short>(r, c-1))*deriv_scale,  
                 (img.at<short>(r+1, c) - img.at<short>(r-1, c))*deriv_scale,  
                 (next.at<short>(r, c) - prev.at<short>(r, c))*deriv_scale);  

        float v2 = (float)img.at<short>(r, c)*2;  
        float dxx = (img.at<short>(r, c+1) +   
                img.at<short>(r, c-1) - v2)*second_deriv_scale;  
        float dyy = (img.at<short>(r+1, c) +   
                img.at<short>(r-1, c) - v2)*second_deriv_scale;  
        float dss = (next.at<short>(r, c) +   
                prev.at<short>(r, c) - v2)*second_deriv_scale;  
        float dxy = (img.at<short>(r+1, c+1) -   
                img.at<short>(r+1, c-1) - img.at<short>(r-1, c+1) +   
                img.at<short>(r-1, c-1))*cross_deriv_scale;  
        float dxs = (next.at<short>(r, c+1) -   
                next.at<short>(r, c-1) - prev.at<short>(r, c+1) +   
                prev.at<short>(r, c-1))*cross_deriv_scale;  
        float dys = (next.at<short>(r+1, c) -   
                next.at<short>(r-1, c) - prev.at<short>(r+1, c) +   
                prev.at<short>(r-1, c))*cross_deriv_scale;  

        Matx33f H(dxx, dxy, dxs,  
                  dxy, dyy, dys,  
                  dxs, dys, dss);  

        Vec3f X = H.solve(dD, DECOMP_LU);  

        xi = -X[2];  
        xr = -X[1];  
        xc = -X[0];  

        if( std::abs( xi ) < 0.5f  &&  std::abs( xr ) < 0.5f  &&  std::abs( xc ) < 0.5f )  
            break;  

        //将找到的极值点对应成像素(整数)  
        c += cvRound( xc );  
        r += cvRound( xr );  
        layer += cvRound( xi );  

        if( layer < 1 || layer > nOctaveLayers ||  
           c < SIFT_IMG_BORDER || c >= img.cols - SIFT_IMG_BORDER  ||  
           r < SIFT_IMG_BORDER || r >= img.rows - SIFT_IMG_BORDER )  
            return false;  
    }  

    /* ensure convergence of interpolation */  
    // SIFT_MAX_INTERP_STEPS:插值最大步数,避免插值不收敛,程序中默认为5  
    if( i >= SIFT_MAX_INTERP_STEPS )  
        return false;  

    {  
        int idx = octv*(nOctaveLayers+2) + layer;  
        const Mat& img = dog_pyr[idx];  
        const Mat& prev = dog_pyr[idx-1];  
        const Mat& next = dog_pyr[idx+1];  
        Matx31f dD((img.at<short>(r, c+1) - img.at<short>(r, c-1))*deriv_scale,  
                   (img.at<short>(r+1, c) - img.at<short>(r-1, c))*deriv_scale,  
                   (next.at<short>(r, c) - prev.at<short>(r, c))*deriv_scale);  
        float t = dD.dot(Matx31f(xc, xr, xi));  

        contr = img.at<short>(r, c)*img_scale + t * 0.5f;  
        if( std::abs( contr ) * nOctaveLayers < contrastThreshold )  
            return false;  

        /* principal curvatures are computed using the trace and det of Hessian */  
       //利用Hessian矩阵的迹和行列式计算主曲率的比值  
       float v2 = img.at<short>(r, c)*2.f;  
        float dxx = (img.at<short>(r, c+1) +   
                img.at<short>(r, c-1) - v2)*second_deriv_scale;  
        float dyy = (img.at<short>(r+1, c) +   
                img.at<short>(r-1, c) - v2)*second_deriv_scale;  
        float dxy = (img.at<short>(r+1, c+1) -   
                img.at<short>(r+1, c-1) - img.at<short>(r-1, c+1) +   
                img.at<short>(r-1, c-1)) * cross_deriv_scale;  
        float tr = dxx + dyy;  
        float det = dxx * dyy - dxy * dxy;  

        //这里edgeThreshold可以在调用SIFT()时输入;  
        //其实代码中定义了 static const float SIFT_CURV_THR = 10.f 可以直接使用  
        if( det <= 0 || tr*tr*edgeThreshold >= (edgeThreshold + 1)*(edgeThreshold + 1)*det )  
            return false;  
    }  

    kpt.pt.x = (c + xc) * (1 << octv);  
    kpt.pt.y = (r + xr) * (1 << octv);  
    kpt.octave = octv + (layer << 8) + (cvRound((xi + 0.5)*255) << 16);  
    kpt.size = sigma*powf(2.f, (layer + xi) / nOctaveLayers)*(1 << octv)*2;  

    return true;  
}  

// Computes a gradient orientation histogram at a specified pixel  
// 计算特定点的梯度方向直方图  
static float calcOrientationHist( const Mat& img, Point pt, int radius,  
                                  float sigma, float* hist, int n )  
{  
    //len:2r+1也就是以r为半径的圆(正方形)像素个数  
    int i, j, k, len = (radius*2+1)*(radius*2+1);  

    float expf_scale = -1.f/(2.f * sigma * sigma);  
    AutoBuffer<float> buf(len*4 + n+4);  
    float *X = buf, *Y = X + len, *Mag = X, *Ori = Y + len, *W = Ori + len;  
    float* temphist = W + len + 2;  

    for( i = 0; i < n; i++ )  
        temphist[i] = 0.f;  

    // 图像梯度直方图统计的像素范围  
    for( i = -radius, k = 0; i <= radius; i++ )  
    {  
        int y = pt.y + i;  
        if( y <= 0 || y >= img.rows - 1 )  
            continue;  
        for( j = -radius; j <= radius; j++ )  
        {  
            int x = pt.x + j;  
            if( x <= 0 || x >= img.cols - 1 )  
                continue;  

            float dx = (float)(img.at<short>(y, x+1) - img.at<short>(y, x-1));  
            float dy = (float)(img.at<short>(y-1, x) - img.at<short>(y+1, x));  

            X[k] = dx; Y[k] = dy; W[k] = (i*i + j*j)*expf_scale;  
            k++;  
        }  
    }  

    len = k;  

    // compute gradient values, orientations and the weights over the pixel neighborhood  
    exp(W, W, len);   
    fastAtan2(Y, X, Ori, len, true);   
    magnitude(X, Y, Mag, len);   

    // 计算直方图的每个bin  
    for( k = 0; k < len; k++ )  
    {  
        int bin = cvRound((n/360.f)*Ori[k]);  
        if( bin >= n )  
            bin -= n;  
        if( bin < 0 )  
            bin += n;  
        temphist[bin] += W[k]*Mag[k];  
    }  

    // smooth the histogram  
    // 高斯平滑  
    temphist[-1] = temphist[n-1];  
    temphist[-2] = temphist[n-2];  
    temphist[n] = temphist[0];  
    temphist[n+1] = temphist[1];  
    for( i = 0; i < n; i++ )  
    {  
        hist[i] = (temphist[i-2] + temphist[i+2])*(1.f/16.f) +  
            (temphist[i-1] + temphist[i+1])*(4.f/16.f) +  
            temphist[i]*(6.f/16.f);  
    }  

    // 得到主方向  
    float maxval = hist[0];  
    for( i = 1; i < n; i++ )  
        maxval = std::max(maxval, hist[i]);  

    return maxval;  
}  

// SIFT关键点特征描述  
// SIFT描述子是关键点领域高斯图像提取统计结果的一种表示  
static void calcSIFTDescriptor( const Mat& img, Point2f ptf, float ori, float scl,  
                               int d, int n, float* dst )  

{  
    Point pt(cvRound(ptf.x), cvRound(ptf.y));  
    //计算余弦,正弦,CV_PI/180:将角度值转化为幅度值  
    float cos_t = cosf(ori*(float)(CV_PI/180));  
    float sin_t = sinf(ori*(float)(CV_PI/180));  
    float bins_per_rad = n / 360.f;  
    float exp_scale = -1.f/(d * d * 0.5f); //d:SIFT_DESCR_WIDTH 4     
    float hist_width = SIFT_DESCR_SCL_FCTR * scl;  // SIFT_DESCR_SCL_FCTR: 3   
                                                   // scl: size*0.5f  
    // 计算图像区域半径mσ(d+1)/2*sqrt(2)  
    // 1.4142135623730951f 为根号2  
    int radius = cvRound(hist_width * 1.4142135623730951f * (d + 1) * 0.5f);  
    cos_t /= hist_width;  
    sin_t /= hist_width;  

    int i, j, k, len = (radius*2+1)*(radius*2+1), histlen = (d+2)*(d+2)*(n+2);  
    int rows = img.rows, cols = img.cols;  

    AutoBuffer<float> buf(len*6 + histlen);  
    float *X = buf, *Y = X + len, *Mag = Y, *Ori = Mag + len, *W = Ori + len;  
    float *RBin = W + len, *CBin = RBin + len, *hist = CBin + len;  

    //初始化直方图  
    for( i = 0; i < d+2; i++ )  
    {  
        for( j = 0; j < d+2; j++ )  
            for( k = 0; k < n+2; k++ )  
                hist[(i*(d+2) + j)*(n+2) + k] = 0.;  
    }  

    //计算采样区域点坐标旋转  
    for( i = -radius, k = 0; i <= radius; i++ )  
        for( j = -radius; j <= radius; j++ )  
        {  
            /* 
             Calculate sample's histogram array coords rotated relative to ori. 
             Subtract 0.5 so samples that fall e.g. in the center of row 1 (i.e. 
             r_rot = 1.5) have full weight placed in row 1 after interpolation. 
             */  
            float c_rot = j * cos_t - i * sin_t;  
            float r_rot = j * sin_t + i * cos_t;  
            float rbin = r_rot + d/2 - 0.5f;  
            float cbin = c_rot + d/2 - 0.5f;  
            int r = pt.y + i, c = pt.x + j;  

            if( rbin > -1 && rbin < d && cbin > -1 && cbin < d &&  
               r > 0 && r < rows - 1 && c > 0 && c < cols - 1 )  
            {  
                float dx = (float)(img.at<short>(r, c+1) - img.at<short>(r, c-1));  
                float dy = (float)(img.at<short>(r-1, c) - img.at<short>(r+1, c));  
                X[k] = dx; Y[k] = dy; RBin[k] = rbin; CBin[k] = cbin;  
                W[k] = (c_rot * c_rot + r_rot * r_rot)*exp_scale;  
                k++;  
            }  
        }  

    len = k;  
    fastAtan2(Y, X, Ori, len, true);  
    magnitude(X, Y, Mag, len);  
    exp(W, W, len);  


    //计算梯度直方图  
    for( k = 0; k < len; k++ )  
    {  
        float rbin = RBin[k], cbin = CBin[k];  
        float obin = (Ori[k] - ori)*bins_per_rad;  
        float mag = Mag[k]*W[k];  

        int r0 = cvFloor( rbin );  
        int c0 = cvFloor( cbin );  
        int o0 = cvFloor( obin );  
        rbin -= r0;  
        cbin -= c0;  
        obin -= o0;  

        //n为SIFT_DESCR_HIST_BINS:8,即将360°分为8个区间  
        if( o0 < 0 )  
            o0 += n;  
        if( o0 >= n )  
            o0 -= n;  


        // histogram update using tri-linear interpolation  
        // 双线性插值  
        float v_r1 = mag*rbin, v_r0 = mag - v_r1;  
        float v_rc11 = v_r1*cbin, v_rc10 = v_r1 - v_rc11;  
        float v_rc01 = v_r0*cbin, v_rc00 = v_r0 - v_rc01;  
        float v_rco111 = v_rc11*obin, v_rco110 = v_rc11 - v_rco111;  
        float v_rco101 = v_rc10*obin, v_rco100 = v_rc10 - v_rco101;  
        float v_rco011 = v_rc01*obin, v_rco010 = v_rc01 - v_rco011;  
        float v_rco001 = v_rc00*obin, v_rco000 = v_rc00 - v_rco001;  

        int idx = ((r0+1)*(d+2) + c0+1)*(n+2) + o0;  
        hist[idx] += v_rco000;  
        hist[idx+1] += v_rco001;  
        hist[idx+(n+2)] += v_rco010;  
        hist[idx+(n+3)] += v_rco011;  
        hist[idx+(d+2)*(n+2)] += v_rco100;  
        hist[idx+(d+2)*(n+2)+1] += v_rco101;  
        hist[idx+(d+3)*(n+2)] += v_rco110;  
        hist[idx+(d+3)*(n+2)+1] += v_rco111;  
    }  

    // finalize histogram, since the orientation histograms are circular  
    // 最后确定直方图,目标方向直方图是圆的  
    for( i = 0; i < d; i++ )  
        for( j = 0; j < d; j++ )  
        {  
            int idx = ((i+1)*(d+2) + (j+1))*(n+2);  
            hist[idx] += hist[idx+n];  
            hist[idx+1] += hist[idx+n+1];  
            for( k = 0; k < n; k++ )  
                dst[(i*d + j)*n + k] = hist[idx+k];  
        }  
    // copy histogram to the descriptor,  
    // apply hysteresis thresholding  
    // and scale the result, so that it can be easily converted  
    // to byte array  
    float nrm2 = 0;  
    len = d*d*n;  
    for( k = 0; k < len; k++ )  
        nrm2 += dst[k]*dst[k];  
    float thr = std::sqrt(nrm2)*SIFT_DESCR_MAG_THR;  
    for( i = 0, nrm2 = 0; i < k; i++ )  
    {  
        float val = std::min(dst[i], thr);  
        dst[i] = val;  
        nrm2 += val*val;  
    }  
    nrm2 = SIFT_INT_DESCR_FCTR/std::max(std::sqrt(nrm2), FLT_EPSILON);  
    for( k = 0; k < len; k++ )  
    {  
        dst[k] = saturate_cast<uchar>(dst[k]*nrm2);  
    }  
}  

实例

#include "opencv2/core/core.hpp"  
#include "highgui.h"  
#include "opencv2/imgproc/imgproc.hpp"  
#include "opencv2/features2d/features2d.hpp"  
#include "opencv2/nonfree/nonfree.hpp"  
#include "opencv2/legacy/legacy.hpp"  

using namespace cv;  
using namespace std;  

int main(int argc, char** argv)  
{  
   //待匹配的两幅图像,其中img1包括img2,也就是要从img1中识别出img2  
   Mat img1 = imread("box_in_scene.png");  
   Mat img2 = imread("box.png");  

   SIFT sift1, sift2;  

   vector<KeyPoint> key_points1, key_points2;  

   Mat descriptors1, descriptors2, mascara;  

   sift1(img1,mascara,key_points1,descriptors1);  
   sift2(img2,mascara,key_points2,descriptors2);  

   //实例化暴力匹配器——BruteForceMatcher  
   BruteForceMatcher<L2<float>> matcher;    
   //定义匹配器算子  
   vector<DMatch>matches;    
   //实现描述符之间的匹配,得到算子matches  
   matcher.match(descriptors1,descriptors2,matches);  

   //提取出前30个最佳匹配结果  
   std::nth_element(matches.begin(),     //匹配器算子的初始位置  
       matches.begin()+29,     // 排序的数量  
       matches.end());       // 结束位置  
   //剔除掉其余的匹配结果  
   matches.erase(matches.begin()+30, matches.end());  

   namedWindow("SIFT_matches");    
   Mat img_matches;    
   //在输出图像中绘制匹配结果  
   drawMatches(img1,key_points1,         //第一幅图像和它的特征点  
       img2,key_points2,      //第二幅图像和它的特征点  
       matches,       //匹配器算子  
       img_matches,      //匹配输出图像  
       Scalar(255,255,255));     //用白色直线连接两幅图像中的特征点  
   imshow("SIFT_matches",img_matches);    
   waitKey(0);  

   return 0;  
}  

Reference

[1]SIFT算法详解
[2]Opencv2.4.9源码分析——SIFT
[3]【OpenCV】SIFT原理与源码分析

猜你喜欢

转载自blog.csdn.net/xholes/article/details/79755519