• RobHess的SIFT代码解析步骤三


     平台:win10 x64 +VS 2015专业版 +opencv-2.4.11 + gtk_-bundle_2.24.10_win32

    主要参考:1.代码:RobHess的SIFT源码

    2.书:王永明 王贵锦 《图像局部不变性特征与描述》

     

    SIFT四步骤和特征匹配及筛选:

    步骤一:建立尺度空间,即建立高斯差分(DoG)金字塔dog_pyr

    步骤二:在尺度空间中检测极值点,并进行精确定位和筛选创建默认大小的内存存储器

    步骤三:特征点方向赋值,完成此步骤后,每个特征点有三个信息:位置、尺度、方向

    步骤四:计算特征描述子

    SIFT后特征匹配:KD树+BBF算法

    SIFT后特征匹配后错误点筛选:RANSAC算法

     

    步骤三:特征点方向赋值,完成此步骤后,每个特征点有三个信息:位置、尺度、方向

    问题及解答:

     

    (1)问题描述:怎么计算特征点的方向和幅值?思路是什么?

    答:为了实现图像旋转的不变性,需要根据检测到的特征点的局部图像结构求得一个方向基准。我们使用图像梯度的方法求取该局部结构的稳定方向。对于己经检测到特征点,我们知道该特征点的尺度值σ,因此根据这一尺度值,在GSS中得到最接近这一尺度值的高斯图像。然后使用有限差分,计算以特征点为中心,以3X1.5σ为半径的区域内图像梯度的幅角和幅值,如下图所示。幅角和幅值计算公式加下:

     

     参考书P130及图6-1

     

    (2)问题描述:为什么要计算特征梯度直方图?怎么计算?

    答:在完成特征点邻域的高斯图像的梯度计算后,使用直方图统计邻域内像素的梯度方向和幅值。梯度方向直方图的横轴是梯度方向角,纵轴是梯度方向角对应的(带高斯权重)梯度幅值累加值。梯度方向直方图将。0°~360°的范围,分为36个柱,每10°为一个柱。直方图的峰值代表了该特征点处邻域内图像梯度的主方向,也即该特征点的主方向,如下图所示:

     绿色格点代表邻域范围,蓝色圆圈代表格点的高斯权重(稍后介绍),黑色箭头指向代表梯度方向,箭头长度代表梯度幅值。右边为梯度方向直方图(36柱,每柱代表10°,上图只显示了8柱)。获得梯度方向直方图的步骤如下:

    1)生成领域各像元的高斯权重。其中高斯函数方差为该特征点的特征尺度σ的1.5倍。形式如下,其中(i,j)为该点距离特征点的相对位置,如上图,左上角点像元距离特征点(0,0)(即中心点)的相对位置坐标为(-4,-4),同理,右下角像元为(4,4)。


    2)遍历邻域(绿色)中每个点,判断其梯度方向,将其加入相应的梯度方向直方图中,加入量为其梯度幅值 * wi,j ,例如左上角(-4,-4)的点,其梯度为方向为25°,梯度幅值为mag,我们将其加入到hist[2]中(假设hist[0]为0°~10°的直方柱,hist[1]为10°~20°的直方柱,以此类推至hist[35]为350°~360°)。加入的量为mag* w(-4,-4),即hist[2] = hist[2] + mag* w(-4,-4)。直至遍历整个邻域,统计出该特征点出的梯度方向直方图。


    3)平滑直方图。对上一步得出的直方图进行平滑,得到最终的梯度方向直方图。OpenCV中使用的 (1/16) * [1,4,6,4,1] 的高斯卷积和对直方图进行平滑处理,而vlfeat中使用了6次,邻域大小为3的平均处理,即hist[i] = (hist[i-1]+hist[i]+hist[i+1])/3。


     参考书P131及图6-2

    (3)问题描述:为什么每个点梯度幅值要使用高斯权重?
    答:由于SIFT算法只考虑了尺度和旋转的不变性,并没有考虑仿射不变性。通过对各点梯度幅值进行高斯加权,使特征点附近的梯度幅值有较大的权重,这样可以部分弥补因没有仿射不变性而产生的特征点不稳定的问题。

     

    (4)问题描述:如何确定特征点方向?

    答:

    有了梯度方向直方图之后,找到直方图中最大的值,则认为该方向为该特征点的主方向,如存在另一个方向大于最大值的80%,则认为该方向为该特征点的辅方向。一个特征点可能会有多个方向(一个主方向,一个以上的辅方向),这可以增强匹配的鲁棒性。具体而言,就是将该特征点复制成多份特征点(除了方向θ不同外,x,y,σ都相同)。

    注意:在OpenCV中,若辅方向除了满足大于最大值80%外,还必须是局部最大值,即 hist[i] > hist[i-1] && hist[i] > hist[i+1]。

      通常离散的梯度方向直方图,可以通过插值拟合处理,这样可以得到更精确的方向角度值。

      经过上述过程,我们特征点的所有量(x,y,σ,θ)都已经求得,其中位置(x,y)、尺度σ都是在上一节中求得,而特征点方向θ是通过特征点邻域直方图求得。下一节,将介绍SIFT描述子的形成方式。

      参考书P132-P133及图6-5和图6-6

     

     (5)问题描述:RobHess的SIFT源码如何实现步骤三,大概思路是这样的?

    答:5.1)代码及说明:

    /*步骤三:特征点方向赋值,完成此步骤后,每个特征点有三个信息:位置、尺度、方向*/

     

    //计算每个特征点的梯度直方图,找出其主方向,若一个特征点有不止一个主方向,将其分为两个特征点

    calc_feature_oris( features, gauss_pyr );

     

    (5.2)calc_feature_oris代码及说明:

    确定关键点的大小和方向

     

    /*计算每个特征点的梯度直方图,找出其主方向,若一个特征点有不止一个主方向,将其分

    为两个特征点

    参数:

    features:特征点序列

    gauss_pyr:高斯金字塔*/

    static void calc_feature_oris( CvSeq* features, IplImage*** gauss_pyr )

    {

        struct feature* feat;

        struct detection_data* ddata;

        double* hist;//存放梯度直方图的数组

        double omax;

        int i, j, n = features->total;//特征点个数

        //遍历特征点序列

        for( i = 0; i < n; i++ )

        {

            //给每个特征点分配feature结构大小的内存

            feat = malloc( sizeof( struct feature ) );

            //移除列首元素,放到feat中

            cvSeqPopFront( features, feat );

            //调用宏feat_detection_data来提取参数feat中的feature_data成员并转换为

            //detection_data类型的指针

            //detection_data数据中存放有此特征点的行列坐标和尺度,以及所在的层和组

            ddata = feat_detection_data( feat );

            //计算指定像素点的梯度方向直方图,返回存放直方图的数组给hist

            hist = ori_hist( gauss_pyr[ddata->octv][ddata->intvl],       //特征点所在的图像

                ddata->r, ddata->c,                          //特征点的行列坐标

                SIFT_ORI_HIST_BINS,                          //默认的梯度直方图的bin(柱子)个数

                cvRound( SIFT_ORI_RADIUS * ddata->scl_octv ),//特征点方向赋值过程中,搜索邻域

                                                                      //的半径为:3 * 1.5 * σ

                SIFT_ORI_SIG_FCTR * ddata->scl_octv );       //计算直翻图时梯度幅值的高斯权重

                                                                      //的初始值

            //对梯度直方图进行高斯平滑,弥补因没有仿射不变性而产生的特征点不稳定的问题,

            //SIFT_ORI_SMOOTH_PASSES指定了平滑次数

            for( j = 0; j < SIFT_ORI_SMOOTH_PASSES; j++ )

                smooth_ori_hist( hist, SIFT_ORI_HIST_BINS );

     

            //查找梯度直方图中主方向的梯度幅值,即查找直方图中最大bin的值,返回给omax

            omax = dominant_ori( hist, SIFT_ORI_HIST_BINS );

            /*若当前特征点的直方图中某个bin的值大于给定的阈值,则新生成一个特征点并添

            加到特征点序列末尾传入的特征点指针feat是已经从特征点序列features中移除的,

            所以即使此特征点没有辅方向(第二个大于幅值阈值的方向),在函数

            add_good_ori_features中也会执行一次克隆feat,对其方向进行插值修正,并插入特征

            点序列的操作幅值阈值一般设置为当前特征点的梯度直方图的最大bin值的80%                   */

            add_good_ori_features( features, hist, SIFT_ORI_HIST_BINS,

                omax * SIFT_ORI_PEAK_RATIO, feat );

            //释放内存

            free( ddata );

            free( feat );

            free( hist );

        }

    }

     

    (5.2.1)cvSeqPopFront函数说明: 

    答:动态结构序列CvSeq是所有OpenCV动态数据结构的基础。
    分为两类:
         稠密序列
         稀疏序列
    (1) 稠密序列都派生自CvSeq,他们用来代表可扩展的一维数组 — 向量、栈、队列和双端队列。数据间不存在空隙(连续存储)。如果元素元素从序列中间被删除或插入新的元素到序列,那么此元素后边的相关元素全部被移动。
    (2)稀疏序列派生自CvSet,CvSet也是基于CvSeq的,他们都是由节点所组成,每一个节点要么被占用,那么为空,由标志位flag决定。这些序列作为无序数据结构被使用,如点集合、图、Hash表等。

    CvSeq的定义如下:

    CV_TREE_NODE_FIELDS(CvSeq);   
        int       total;          //total表示稠密序列的元素个数,或者稀疏序列被分配的节点数。
        int       elem_size;      //elem_size表示序列中每个元素占用的字节数。
        schar*    block_max;      //block_max是最近一个内存的最大边界指针。
        schar*    ptr;            //ptr表示当写指针。
        int       delta_elems;    //delta_elems表示序列间隔尺寸。
        CvMemStorage* storage;    //storage指向序列存储的内存块的指针
        CvSeqBlock* free_blocks;  //free_blocks表示空的块列表。
        CvSeqBlock* first;  //first指向第一个序列块。h_next和h_prev并不是指向CvSeq内部元素的指针,它们是指向其它CvSeq的。

    //头部添加
    Char* cvSeqPushFront(CvSeq* seq,void* element=NULL);
    
    //头部删除
    Void cvSeqPopFront(CvSeq* seq,void* element=NULL);

    参看:OpenCV——CvSeq动态结构序列——https://www.cnblogs.com/farewell-farewell/p/5999908.html

     

    (5.2.2)ori_hist代码及说明: 

    答:

    /*计算指定像素点的梯度方向直方图,返回存放直方图的数组

    参数:

    img:图像指针

    r:特征点所在的行

    c:特征点所在的列

    n:直方图中柱(bin)的个数,默认是36

    rad:区域半径,在此区域中计算梯度方向直方图

    sigma:计算直翻图时梯度幅值的高斯权重的初始值

    返回值:返回一个n元数组,其中是方向直方图的统计数据*/

    static double* ori_hist( IplImage* img, int r, int c, int n, int rad, double sigma)

    {

        double* hist;//直方图数组

        double mag, ori, w, exp_denom, PI2 = CV_PI * 2.0;

        int bin, i, j;

     

        //为直方图数组分配空间,共n个元素,n是柱的个数

        hist = calloc( n, sizeof( double ) );

        exp_denom = 2.0 * sigma * sigma;

     

        //遍历以指定点为中心的搜索区域

        for( i = -rad; i <= rad; i++ )  //范围为2*3*1.5σ,参看书P131图6-2

            for( j = -rad; j <= rad; j++ )

                //计算指定点的梯度的幅值mag和方向ori,返回值为1表示计算成功

                if( calc_grad_mag_ori( img, r + i, c + j, &mag, &ori ) )

                {

                    w = exp( -( i*i + j*j ) / exp_denom );//该点的梯度幅值权重

                    bin = cvRound( n * ( ori + CV_PI ) / PI2 );//计算梯度的方向对应的直方图中的bin下标

         //ori范围(-Π,Π],+Π后范围(0,2Π]

                    bin = ( bin < n )? bin : 0; //把bin的下标归到0~36

                    hist[bin] += w * mag;//在直方图的某个bin中累加加权后的幅值

                }

                return hist;//返回直方图数组

    }

     

    (5.2.2.1)calc_grad_mag_ori代码及说明: 

    答:

    /*计算指定点的梯度的幅值magnitude和方向orientation

    参数:

    img:图像指针

    r:特征点所在的行

    c:特征点所在的列

    img:输出参数,此点的梯度幅值

    ori:输出参数,此点的梯度方向

    返回值:如果指定的点是合法点并已计算出幅值和方向,返回1;否则返回0*/

    static int calc_grad_mag_ori( IplImage* img, int r, int c, double* mag, double* ori )

    {

        double dx, dy;

     

        //对输入的坐标值进行检查

        if( r > 0  &&  r < img->height - 1  &&  c > 0  &&  c < img->width - 1 )

        {

            //用差分近似代替偏导,来求梯度的幅值和方向

            dx = pixval32f( img, r, c+1 ) - pixval32f( img, r, c-1 );//x方向偏导

            dy = pixval32f( img, r-1, c ) - pixval32f( img, r+1, c );//y方向偏导

            *mag = sqrt( dx*dx + dy*dy );//梯度的幅值,即梯度的模

            *ori = atan2( dy, dx );//梯度的方向

            return 1;

        }

        //行列坐标值不合法,返回0

        else

            return 0;

    }

     

    问题1:具体如何求梯度?为什么一阶偏导没有二分之一的系数?

    答:差分代替偏导,一阶情况,具体参考:RobHess的SIFT代码解析步骤二(5.2.6.2.1.1)

    参看博客:https://blog.csdn.net/lingyunxianhe/article/details/79063547 3.1.1、梯度的计算

     

     

    问题2:为什么计算梯度的方向使用的是atan2,而不是(1)中介绍的arctan(atan)?

    答:

    说明:C 语言里 double atan2(double y,double x) 返回的是原点至点(x,y)的方位角,即与 x 轴的夹角。返回值的单位为弧度,取值范围为(-π, π]。结果为正表示从 X 轴逆时针旋转的角度,结果为负表示从 X 轴顺时针旋转的角度。

    atan2与atan的区别:
    atan接受的是一个正切值(直线的斜率)得到夹角,但是由于正切的规律性本可以有两个角度的但它却只返回一个,因为atan的值域是从-90~90 也就是它只处理一四象限,所以一般不用它。
    atan2(double y,double x) 其中y代表已知点的Y坐标 同理x ,返回值是此点与远点连线与x轴正方向的夹角,这样它就可以处理四个象限的任意情况了,它的值域相应的也就是-180~180了。
    例如:
    例1:斜率是1的直线的夹角
    cout<<atan(1.0)*180/PI;//45°
    cout<<atan2(1.0,1.0)*180/PI;//45° 第一象限
    cout<<atan2(-1.0,-1.0)*180/PI;//-135°第三象限
    后两个斜率都是1 但是atan只能求出一个45°

    例2:斜率是-1的直线的角度
    cout<<atan(-1.0)*180/PI;//-45°
    cout<<atan2(-1.0,1.0)*180/PI;//-45° y为负 在第四象限
    cout<<atan2(1.0,-1.0)*180/PI;//135° x为负 在第二象限
    后两个斜率都是-1 但是atan只能求出一个-45°

    此处与书P130页不同,atan的值域[ -Π/2,Π/2],而atan2的值域[ -Π,Π],涵盖了一周360度(2Π)的范围。

     

    参看博客:atan2相关知识汇总——https://blog.csdn.net/weixin_42142612/article/details/80972768

     

    (5.2.2.2)梯度直方图某点梯度的幅值怎么计算?不是直接累加的,而是乘以一个高斯权重1.5sigma。

    答:L为关键点所在的尺度空间值,按Lowe的建议,梯度的模值m(x,y)按 σ=1.5σ_oct 的高斯分布加成,按尺度采样的3σ原则,领域窗口半径为 3x1.5σ_oct。

    在完成关键点的梯度计算后,使用直方图统计领域内像素的梯度和方向。梯度直方图将0~360度的方向范围分为36个柱(bins),其中每柱10度。如图5.1所示,直方图的峰值方向代表了关键点的主方向,(为简化,图中只画了八个方向的直方图)。

     

    参看博客:https://blog.csdn.net/lingyunxianhe/article/details/79063547 3.1.1、梯度直方图

     

     

    (5.2.3)smooth_ori_hist代码及说明: 

    答:

    /*对梯度方向直方图进行高斯平滑,弥补因没有仿射不变性而产生的特征点不稳定的问题

    参数:

    hist:存放梯度直方图的数组

    n:梯度直方图中bin的个数*/

    static void smooth_ori_hist( double* hist, int n )

    {

        double prev, tmp, h0 = hist[0];

        int i;

     

        prev = hist[n-1];

        //类似均值漂移的一种邻域平滑,减少突变的影响

        for( i = 0; i < n; i++ )

        {

            tmp = hist[i];

            hist[i] = 0.25 * prev + 0.5 * hist[i] +

                0.25 * ( ( i+1 == n )? h0 : hist[i+1] );

            prev = tmp;

        }

    }

    问题1:为什么要进行高斯平滑?

    答:为了防止某个梯度方向角度因受到噪声的干扰而突变,我们还需要对梯度方向直方图进行平滑处理。Opencv  所使用的平滑公式为:

    1)今天的内容是是平滑,先介绍均值和高斯平滑,高斯平滑是均值平滑的一个扩展,或者是一个进化版本。均值的原理是,一个规定的邻域内,所有像素的平局值作为最终计算的结果,每个像素的权值相同,为总像素的倒数,而高斯平滑是上述的升级版本,邻域内每个像素的权值不同,而权值是由高斯函数确定的。

    均值平滑和高斯平滑都是线性的,也就是,一旦参数给定,模板就确定下来,不会因为位置和像素分布不同而改变,而线性模板的基本运算是卷积。

    线性模板的另一个性质就是可以进行频域分析,比如高斯平滑的频域仍然是高斯的,而且是低通的,这与前面讲到的平滑是消除尖锐的噪声(高频)的操作相互证明了其正确性,均值平滑是一个盒状滤波器,其频域为sinc函数,也是一个近似的低通,但sinc函数有旁瓣,所以,模板宽度选择不好可能会有一些不好的效果,比如有些高频会被保留,这一点也比较好理解。

    比较下两种均值(加权和不加权)。比如一维的一个序列{0,0,0,0,0,1000,0,0,0,0},明显1000是个边缘,如果使用3个宽度的均值平滑,结果是{0,0,0,0,333,333,333,0,0,0},边缘被完全模糊掉了。但如果使用{1,2,1}的近似高斯平滑模板,结果是{0,0,0,0,250,500,250,0,0,0},边缘被保留。所以,加权平均(高斯)可以保留一定的细节。

    参看博客:灰度图像--图像增强 平滑之均值滤波、高斯滤波——http://www.mamicode.com/info-detail-447178.html

    2)LOWE建议对直方图进行平滑,降低突变的影响,弥补因没有仿射不变性而产生的特征点不稳定的问题

     

    问题2:对梯度直方图进行高斯平滑,为什么用的是1/4{1,2,1}?盒子滤波器的模板吗?

    答:用的是1/4{1,2,1}不是盒子滤波器的模板1/4{1,2,1}是高斯加权平均。参看:https://www.cnblogs.com/liguangsunls/p/6785878.html 6.2 平滑直方图 lowe建议对直方图进行平滑,降低突变的影响。

     

    (5.2.4)dominant_ori代码及说明: 

    答:

    /*查找梯度直方图中主方向的梯度幅值,即查找直方图中最大bin的值

    参数:

    hist:存放直方图的数组

    n:直方图中bin的个数

    返回值:返回直方图中最大的bin的值*/

    static double dominant_ori( double* hist, int n )

    {

        double omax;

        int maxbin, i;

     

        omax = hist[0];

        maxbin = 0;

     

        //遍历直方图,找到最大的bin

        for( i = 1; i < n; i++ )

            if( hist[i] > omax )

            {

                omax = hist[i];

                maxbin = i;

            }

            return omax;//返回最大的bin的值

    }

     

    (5.2.5)add_good_ori_features代码及说明: 

    答:

    /*若当前特征点的直方图中某个bin的值大于给定的阈值,则新生成一个特征点并添加到特征点序列末尾

    传入的特征点指针feat是已经从特征点序列features中移除的,所以即使此特征点没有辅方向(第二个大于幅值阈值的方向)

    也会执行一次克隆feat,对其方向进行插值修正,并插入特征点序列的操作

    参数:          

    features:特征点序列

    hist:梯度直方图

    n:直方图中bin的个数

    mag_thr:幅值阈值,若直方图中有bin的值大于此阈值,则增加新特征点

    feat:一个特征点指针,新的特征点克隆自feat,但方向不同

    */

    static void add_good_ori_features( CvSeq* features, double* hist, int n,

                                      double mag_thr, struct feature* feat )

    {

        struct feature* new_feat;

        double bin, PI2 = CV_PI * 2.0;

        int l, r, i;

     

        //遍历直方图

        for( i = 0; i < n; i++ )

        {

            l = ( i == 0 )? n - 1 : i-1;//前一个(左边的)bin的下标

            r = ( i + 1 ) % n;//后一个(右边的)bin的下标

     

            //若当前的bin是局部极值(比前一个和后一个bin都大),并且值大于给定的幅值阈值,则新生成一个特征点并添加到特征点序列末尾

            if( hist[i] > hist[l]  &&  hist[i] > hist[r]  &&  hist[i] >= mag_thr )

            {

                //根据左、中、右三个bin的值对当前bin进行直方图插值

                bin = i + interp_hist_peak( hist[l], hist[i], hist[r] );

                bin = ( bin < 0 )? n + bin : ( bin >= n )? bin - n : bin;//将插值结果规范到[0,n]内

                new_feat = clone_feature( feat );//克隆当前特征点为新特征点

                new_feat->ori = ( ( PI2 * bin ) / n ) - CV_PI;//新特征点的方向

        //方向还原为(Π,Π],bin最大值36,2Π*36/36-Π=Π,bin最大值0,2Π*0/36-Π=-Π

                cvSeqPush( features, new_feat );//插入到特征点序列末尾

                free( new_feat );

            }

        }

    }

     

    问题1:插值时小柱子到边界怎么处理,怎么插值?因为抛物线插值需要利用三个点的坐标,如0之前和35之后怎么处理?

    答:由于角度是循环的,即00=3600,如果出现h(j),j超出了(0,…,35)的范围,那么可以通过圆周循环的方法找到它所对应的、在00=3600之间的值,如h(-1) = h(35)。把小柱子看成是循环的,角度的取值为0~360度是一个圆周,0之前利用标号为35的小柱子和35之后利用标号为0的小柱子。

    参看博客:https://blog.csdn.net/lingyunxianhe/article/details/79063547 3.2.1、梯度图像的平滑处理

     

    问题2:对直方图采用什么插值?插值需要利用几个点的坐标?

    答:采用抛物线插值,需要三个点的坐标,hist[l], hist[i], hist[r],传入interp_hist_peak函数。

     

    (5.2.5.1)interp_hist_peak代码及说明: 

    答:

    //根据左、中、右三个bin的值对当前bin进行直方图插值,以求取更精确的方向角度值

    #define interp_hist_peak( l, c, r ) ( 0.5 * ((l)-(r)) / ((l) - 2.0*(c) + (r)) )

     

    问题:梯度直方图抛物线插值 小柱子在直方图中的索引号的公式怎么计算?

     

    假设我们在第i个小柱子要找一个精确的方向,那么由上面分析知道:

    设插值抛物线方程为h(t)=at2+bt+c,其中a、b、c为抛物线的系数,t为自变量,t∈[-1,1],此抛物线求导并令它等于0。

    h(t)´=0 tmax=-b/(2a)

    现在把这三个插值点带入方程可得:

     

    参看博客:https://blog.csdn.net/lingyunxianhe/article/details/79063547 3.2.2、梯度直方图抛物线插值

     

     

     

    (5.2.5.2) clone_feature代码及说明: 

    答:

    /*对输入的feature结构特征点做深拷贝,返回克隆生成的特征点的指针

    参数:

    feat:将要被克隆的特征点的指针

    返回值:拷贝生成的特征点的指针

    */

    static struct feature* clone_feature( struct feature* feat )

    {

        struct feature* new_feat;

        struct detection_data* ddata;

     

        //为一个feature结构分配空间并初始化

        new_feat = new_feature();  //参看RobHess的SIFT代码解析步骤二(5.2.6.4)new_feature()代码及说明

        //调用宏feat_detection_data来提取参数feat中的feature_data成员并转换为detection_data类型的指针

        ddata = feat_detection_data( new_feat );

        //对内存空间进行赋值

        memcpy( new_feat, feat, sizeof( struct feature ) );

        memcpy( ddata, feat_detection_data(feat), sizeof( struct detection_data ) );

        new_feat->feature_data = ddata;

     

        return new_feat;//返回克隆生成的特征点的指针

    }

     

    问题:memcpy的函数功能?strcpy和memcpy的区别?

     答:所需头文件:C语言:#include<string.h>;C++:#include<cstring>

    函数原型:
    void *memcpy(void *dest, const void *src, size_t n);
    功能:
    从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中

    参看:百度百科——https://baike.baidu.com/item/memcpy/659918

     

  • 相关阅读:
    IOS开发之WIFI及IP相关
    Git使用教程
    git push到GitHub的时候遇到! [rejected] master -> master (non-fast-forward)的问题 【转载】
    Linux高并发web服务器开发 【转载】
    Linux下open函数、read函数、write函数 【转载】
    Linux epoll 详解 【转载】
    Linux Socket 编程简介 【转载】
    信号量sem_init,sem_wait,sem_post
    linux线程之pthread_join和pthread_detach 【转载】
    C++ static类成员,static类成员函数 【转载】
  • 原文地址:https://www.cnblogs.com/Alliswell-WP/p/SIFT_code3.html
Copyright © 2020-2023  润新知