OpenCV边缘填充
1、边缘填充类型
enum cv::BorderTypes
ORDER_CONSTANT iiiiii|abcdefgh|iiiiiii with some specified i -常量法,常熟值填充;
BORDER_REPLICATE aaaaaa|abcdefgh|hhhhhhh -复制法,复制边缘像素;
BORDER_REFLECT fedcba|abcdefgh|hgfedcb -反射法 ,反射最边缘的像素;
BORDER_WRAP cdefgh|abcdefgh|abcdefg -平铺法,让图像反复重复;
BORDER_REFLECT_101 gfedcb|abcdefgh|gfedcba -101反射法,对反射法进行改进,以最边缘像素为轴
BORDER_TRANSPARENT uvwxyz|abcdefgh|ijklmno -在OpenCV4中已经被取消了;
BORDER_REFLECT101 same as BORDER_REFLECT_101
BORDER_DEFAULT same as BORDER_REFLECT_101
BORDER_ISOLATED do not look outside of ROI
2、copyMakeBorder算子
void cv::copyMakeBorder(InputArray src, OutputArray dst, int top, int bottom, int left, int right, int borderType, const Scalar & value = Scalar())
Parameters
src 源图像;
dst 目标图像的类型与src相同,大小为size (src.cols +left+right, src.rows+top+bottom);
top 顶部像素;
bottom 底部像素;
left 左侧像素;
right 右侧像素;参数指定源图像矩形在每个方向上要外推的像素数。例如,top=1, bottom=1, left=1, right=1意味着需要构建1像素宽的边框。
borderType 边界类型,详情见borderInterpolate;
value 边界值,如果borderType==BORDER_CONSTANT;
该函数将源图像复制到目标图像的中间位置。复制源图像的左侧、右侧、上方和下方的区域将被外推像素填充。
3、borderInterpolate算子
int cv::borderInterpolate(int p, int len, int borderType)
Parameters
p 沿其中一个轴的基于0的外推像素坐标, 可能<0或者>len;
len 数组在对应轴上的长度;
borderType 边界类型,除了BORDER_TRANSPARENT和BORDER_ISOLATED之外的边界类型。当borderType==BORDER_CONSTANT时,无论p和len如何,函数总是返回-1;
计算外推像素的源位置。当使用指定的外推边界模式时,该函数计算并返回与指定的外推像素对应的供体像素的坐标。
OpenCV4.5源码分析
主接口函数
void cv::copyMakeBorder( InputArray _src, OutputArray _dst, int top, int bottom,
int left, int right, int borderType, const Scalar& value )
{
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();
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(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() );
}
}
内部填充函数
具体流程:先填充左右两侧,再填充上下两侧;
void copyMakeBorder_8u( const uchar* src, size_t srcstep, cv::Size srcroi,
uchar* dst, size_t dststep, cv::Size dstroi,
int top, int left, int cn, int borderType )
{
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;
}
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;
}
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);
}
}
//常量填充
void copyMakeConstBorder_8u( const uchar* src, size_t srcstep, cv::Size srcroi,
uchar* dst, size_t dststep, cv::Size dstroi,
int top, int left, int cn, const uchar* 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);
}
计算外推像素的源位置函数
/*
Various border types, image boundaries are denoted with '|'
* BORDER_REPLICATE: aaaaaa|abcdefgh|hhhhhhh
* BORDER_REFLECT: fedcba|abcdefgh|hgfedcb
* BORDER_REFLECT_101: gfedcb|abcdefgh|gfedcba
* BORDER_WRAP: cdefgh|abcdefgh|abcdefg
* BORDER_CONSTANT: iiiiii|abcdefgh|iiiiiii with some specified 'i'
*/
int cv::borderInterpolate( int p, int len, int borderType )
{
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;
}