copyMakeBorder 将源图像复制到目标图像的中间并在图像周围形成边框。
当src
已经在dst
中间时,该函数不会复制src
本身,而只是构造边框。在执行时函数会尝试使用 ROI 之外的像素来形成边界。若要禁用此功能并始终执行外推,就像src
不是 ROI 一样,需要设置borderType | BORDER_ISOLATED
。
copyMakeBorder 是 OpenCV 中较为简单的一个函数,然而由于其浅拷贝和 ROI 机制的存在,实现也并不简单。
cv::copyMakeBorder
cv::copyMakeBorder 在调用时分为4种情况:
src
与dst
尺寸相等且同源,直接返回;src
与dst
尺寸相等且不同源则调用 Mat::copyTo;- 边界类型需要插值则调用 copyMakeBorder_8u;
- 边界为常量则调用 copyMakeConstBorder_8u。
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∥len≤p 的情况:
BORDER_REPLICATE
仅重复首尾元素即可;BORDER_REFLECT
和BORDER_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);
参考资料:
- OpenCV-扩充图像的边界
- Getting the source ROI coordinates of a cv::Mat
- locateROI
- 【OpenCV3】cv::Mat类成员函数详解
- OpenCV笔记(一)——Mat的引用计数机制
- OpenCV3.4.0中String类构造函数使用的CV_XADD问题
- atomic_fetch_add
- 多线程无锁实践
- atomic_fetch_add, atomic_fetch_add_explicit
- copyMakeBorder
- opencv中高斯模糊(滤波器)的源码解析(c++版)
- 手撕OpenCV源码之filter2D(一)
- what’s the meaning of CV_INSTRUMENT_REGION()?
- Profiling OpenCV Applications
- 【朝花夕拾】Android Log篇
- Thoughts - OpenCV Logger - 2019-01-14
- Logging facilities
- 关于写作那些事之快速上手Mermaid流程图