OpenCV copyMakeBorder

copyMakeBorder 将源图像复制到目标图像的中间并在图像周围形成边框。
src已经在dst中间时,该函数不会复制src本身,而只是构造边框。在执行时函数会尝试使用 ROI 之外的像素来形成边界。若要禁用此功能并始终执行外推,就像src不是 ROI 一样,需要设置borderType | BORDER_ISOLATED
copyMakeBorder 是 OpenCV 中较为简单的一个函数,然而由于其浅拷贝和 ROI 机制的存在,实现也并不简单。

cv::copyMakeBorder

SameSzie
Interpolate
Constant
cv::copyMakeBorder
Mat::copyTo
copyMakeBorder_8u
copyMakeConstBorder_8u
borderInterpolate

cv::copyMakeBorder 在调用时分为4种情况:

CV_INSTRUMENT_REGION() 属于 OpenCV 性能测试系统的实现工具框架。
首先 CV_Assert 检查输入参数是否合法。
CV_OCL_RUN 在第一个参数满足条件时调用 OpenCL 的实现。
_InputArray::getMat

    CV_INSTRUMENT_REGION();

    CV_Assert( top >= 0 && bottom >= 0 && left >= 0 && right >= 0 && _src.dims() <= 2);

    CV_OCL_RUN(_dst.isUMat(),
               ocl_copyMakeBorder(_src, _dst, top, bottom, left, right, borderType, value))

    Mat src = _src.getMat();
    int type = src.type();

Mat::isSubmatrix 判断矩阵的属性是否为子矩阵。
Mat::locateROI 获取整个矩阵大小及子矩阵偏移。
如果源是子矩阵,并且边界格式未设置BORDER_ISOLATED,则将src向外扩。
_OutputArray::create 根据输入的类型调用相应的创建函数。后者仅在必要的情况下才开辟内存。
如果源与目的地址不一致或者步长不等则调用 Mat::copyTo

    if( src.isSubmatrix() && (borderType & BORDER_ISOLATED) == 0 )
    {
        Size wholeSize;
        Point ofs;
        src.locateROI(wholeSize, ofs);
        int dtop = std::min(ofs.y, top);
        int dbottom = std::min(wholeSize.height - src.rows - ofs.y, bottom);
        int dleft = std::min(ofs.x, left);
        int dright = std::min(wholeSize.width - src.cols - ofs.x, right);
        src.adjustROI(dtop, dbottom, dleft, dright);
        top -= dtop;
        left -= dleft;
        bottom -= dbottom;
        right -= dright;
    }

    _dst.create( src.rows + top + bottom, src.cols + left + right, type );
    Mat dst = _dst.getMat();

    if(top == 0 && left == 0 && bottom == 0 && right == 0)
    {
        if(src.data != dst.data || src.step != dst.step)
            src.copyTo(dst);
        return;
    }

    borderType &= ~BORDER_ISOLATED;

CV_IPP_RUN_FAST 尝试调用 IPP 的实现。
如果不是常量填充则调用 copyMakeBorder_8u,否则调用 copyMakeConstBorder_8u
scalarToRawData 将标量值赋到原始数据。

    CV_IPP_RUN_FAST(ipp_copyMakeBorder(src, dst, top, bottom, left, right, borderType, value))

    if( borderType != BORDER_CONSTANT )
        copyMakeBorder_8u( src.ptr(), src.step, src.size(),
                           dst.ptr(), dst.step, dst.size(),
                           top, left, (int)src.elemSize(), borderType );
    else
    {
        int cn = src.channels(), cn1 = cn;
        AutoBuffer<double> buf(cn);
        if( cn > 4 )
        {
            CV_Assert( value[0] == value[1] && value[0] == value[2] && value[0] == value[3] );
            cn1 = 1;
        }
        scalarToRawData(value, buf.data(), CV_MAKETYPE(src.depth(), cn1), cn);
        copyMakeConstBorder_8u( src.ptr(), src.step, src.size(),
                                dst.ptr(), dst.step, dst.size(),
                                top, left, (int)src.elemSize(), (uchar*)buf.data() );
    }

scalarToRawData

调用 scalarToRawData_ 将标量值赋到原始数据。

    CV_INSTRUMENT_REGION();

    const int depth = CV_MAT_DEPTH(type), cn = CV_MAT_CN(type);
    CV_Assert(cn <= 4);
    switch(depth)
    {
    case CV_8U:
        scalarToRawData_<uchar>(s, (uchar*)_buf, cn, unroll_to);
        break;
    case CV_8S:
        scalarToRawData_<schar>(s, (schar*)_buf, cn, unroll_to);
        break;
    case CV_16U:
        scalarToRawData_<ushort>(s, (ushort*)_buf, cn, unroll_to);
        break;
    case CV_16S:
        scalarToRawData_<short>(s, (short*)_buf, cn, unroll_to);
        break;
    case CV_32S:
        scalarToRawData_<int>(s, (int*)_buf, cn, unroll_to);
        break;
    case CV_32F:
        scalarToRawData_<float>(s, (float*)_buf, cn, unroll_to);
        break;
    case CV_64F:
        scalarToRawData_<double>(s, (double*)_buf, cn, unroll_to);
        break;
    case CV_16F:
        scalarToRawData_<float16_t>(s, (float16_t*)_buf, cn, unroll_to);
        break;
    default:
        CV_Error(CV_StsUnsupportedFormat,"");
    }

scalarToRawData_

    int i = 0;
    for(; i < cn; i++)
        buf[i] = saturate_cast<T>(s.val[i]);
    for(; i < unroll_to; i++)
        buf[i] = buf[i-cn];

copyMakeBorder_8u

检查数据指针及步长是否满足整型对齐,如果满足则按intMode执行。

    const int isz = (int)sizeof(int);
    int i, j, k, elemSize = 1;
    bool intMode = false;

    if( (cn | srcstep | dststep | (size_t)src | (size_t)dst) % isz == 0 )
    {
        cn /= isz;
        elemSize = isz;
        intMode = true;
    }

AutoBuffer 为函数的临时缓冲区。
_tab对应一行中边界元素的数量。
borderInterpolate 计算插值所用元素索引。

    cv::AutoBuffer<int> _tab((dstroi.width - srcroi.width)*cn);
    int* tab = _tab.data();
    int right = dstroi.width - srcroi.width - left;
    int bottom = dstroi.height - srcroi.height - top;

    for( i = 0; i < left; i++ )
    {
        j = cv::borderInterpolate(i - left, srcroi.width, borderType)*cn;
        for( k = 0; k < cn; k++ )
            tab[i*cn + k] = j + k;
    }

    for( i = 0; i < right; i++ )
    {
        j = cv::borderInterpolate(srcroi.width + i, srcroi.width, borderType)*cn;
        for( k = 0; k < cn; k++ )
            tab[(i+left)*cn + k] = j + k;
    }

dstInner定位到目的矩阵中与对应源矩阵原点的位置:

  • 逐行处理,如果地址不相同则拷贝;
  • 逐个拷贝左右边界元素。为什么不整体拷贝两段呢?
    srcroi.width *= cn;
    dstroi.width *= cn;
    left *= cn;
    right *= cn;

    uchar* dstInner = dst + dststep*top + left*elemSize;

    for( i = 0; i < srcroi.height; i++, dstInner += dststep, src += srcstep )
    {
        if( dstInner != src )
            memcpy(dstInner, src, srcroi.width*elemSize);

        if( intMode )
        {
            const int* isrc = (int*)src;
            int* idstInner = (int*)dstInner;
            for( j = 0; j < left; j++ )
                idstInner[j - left] = isrc[tab[j]];
            for( j = 0; j < right; j++ )
                idstInner[j + srcroi.width] = isrc[tab[j + left]];
        }
        else
        {
            for( j = 0; j < left; j++ )
                dstInner[j - left] = src[tab[j]];
            for( j = 0; j < right; j++ )
                dstInner[j + srcroi.width] = src[tab[j + left]];
        }
    }

处理顶部和低部的行。

    dstroi.width *= elemSize;
    dst += dststep*top;

    for( i = 0; i < top; i++ )
    {
        j = cv::borderInterpolate(i - top, srcroi.height, borderType);
        memcpy(dst + (i - top)*dststep, dst + j*dststep, dstroi.width);
    }

    for( i = 0; i < bottom; i++ )
    {
        j = cv::borderInterpolate(i + srcroi.height, srcroi.height, borderType);
        memcpy(dst + (i + srcroi.height)*dststep, dst + j*dststep, dstroi.width);
    }

borderInterpolate

CV_TRACE_FUNCTION_VERBOSE 为追踪日志函数。
p为目的点在源矩阵坐标系上的位置,所以会有负值。len标识源矩阵的行长度。
函数内仅处理 p < 0 ∥ l e n ≤ p p < 0 \| \mathrm{len}\le p p<0∥lenp 的情况:

  • BORDER_REPLICATE仅重复首尾元素即可;
  • BORDER_REFLECTBORDER_REFLECT_101为镜像;
  • BORDER_WRAP为平铺效果。
    CV_TRACE_FUNCTION_VERBOSE();

    CV_DbgAssert(len > 0);

#ifdef CV_STATIC_ANALYSIS
    if(p >= 0 && p < len)
#else
    if( (unsigned)p < (unsigned)len )
#endif
        ;
    else if( borderType == BORDER_REPLICATE )
        p = p < 0 ? 0 : len - 1;
    else if( borderType == BORDER_REFLECT || borderType == BORDER_REFLECT_101 )
    {
        int delta = borderType == BORDER_REFLECT_101;
        if( len == 1 )
            return 0;
        do
        {
            if( p < 0 )
                p = -p - 1 + delta;
            else
                p = len - 1 - (p - len) - delta;
        }
#ifdef CV_STATIC_ANALYSIS
        while(p < 0 || p >= len);
#else
        while( (unsigned)p >= (unsigned)len );
#endif
    }
    else if( borderType == BORDER_WRAP )
    {
        CV_Assert(len > 0);
        if( p < 0 )
            p -= ((p-len+1)/len)*len;
        if( p >= len )
            p %= len;
    }
    else if( borderType == BORDER_CONSTANT )
        p = -1;
    else
        CV_Error( CV_StsBadArg, "Unknown/unsupported border type" );
    return p;

copyMakeConstBorder_8u

按照目的行跨度准备一个_constBuf,以value填充。

    int i, j;
    cv::AutoBuffer<uchar> _constBuf(dstroi.width*cn);
    uchar* constBuf = _constBuf.data();
    int right = dstroi.width - srcroi.width - left;
    int bottom = dstroi.height - srcroi.height - top;

    for( i = 0; i < dstroi.width; i++ )
    {
        for( j = 0; j < cn; j++ )
            constBuf[i*cn + j] = value[j];
    }

遍历源兴趣区域的每一行,如果和目的兴趣区域地址不同则复制。
左右填充,最后上下填充。

    srcroi.width *= cn;
    dstroi.width *= cn;
    left *= cn;
    right *= cn;

    uchar* dstInner = dst + dststep*top + left;

    for( i = 0; i < srcroi.height; i++, dstInner += dststep, src += srcstep )
    {
        if( dstInner != src )
            memcpy( dstInner, src, srcroi.width );
        memcpy( dstInner - left, constBuf, left );
        memcpy( dstInner + srcroi.width, constBuf, right );
    }

    dst += dststep*top;

    for( i = 0; i < top; i++ )
        memcpy(dst + (i - top)*dststep, constBuf, dstroi.width);

    for( i = 0; i < bottom; i++ )
        memcpy(dst + (i + srcroi.height)*dststep, constBuf, dstroi.width);

参考资料:

  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值