前言
上一篇文章,我们来给矩阵添加一些常用方法,这篇文章将讲解图像的虚拟边缘。
虚拟边缘
虚拟边缘就是按照一定映射关系,给图像添加边缘。
那么虚拟边缘有什么用呢?比如可以很容易做一个倒影的效果:
当然这只是附带效果了,虚拟边缘主要用在图像卷积运算(例如平滑操作)时候,由于卷积运算的特点,需要将图片扩大才能对边角进行卷积运算,这时候就需要对图片进行预处理,添加虚拟边缘。
说白了,就是在一些图片处理前进行预处理。
边缘类型
这里参考opencv相关文档的边缘描述:
复制代码 代码如下:
/*
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'
*/
举个例子boder_reflect就是对于某一行或某一列像素点:
abcdefgh
其左的虚拟边缘对应为fedcba,右边对应为hgfedcb,也就是反射映射。上图就是通过对图片底部进行添加border_reflect类型的虚拟边缘得到的。
而border_constant则是所有边缘都是固定值i。
实现
因为border_constant比较特殊,所以和其他类型分开处理。
复制代码 代码如下:
function copymakeborder(__src, __top, __left, __bottom, __right, __bordertype, __value){
if(__src.type != cv_rgba){
console.error(不支持类型!);
}
if(__bordertype === cv_border_constant){
return copymakeconstborder_8u(__src, __top, __left, __bottom, __right, __value);
}else{
return copymakeborder_8u(__src, __top, __left, __bottom, __right, __bordertype);
}
};
这个函数接受一个输入矩阵src,每个方向要添加的像素大小top,left,bottom,right,边缘的类型bordertype,还有一个数组value,即如果是常数边缘时候添加的常数值。
然后我们引入一个边缘的映射关系函数borderinterpolate。
复制代码 代码如下:
function borderinterpolate(__p, __len, __bordertype){
if(__p = __len){
switch(__bordertype){
case cv_border_replicate:
__p = __p break;
case cv_border_reflect:
case cv_border_reflect_101:
var delta = __bordertype == cv_border_reflect_101;
if(__len == 1)
return 0;
do{
if(__p __p = -__p - 1 + delta;
else
__p = __len - 1 - (__p - __len) - delta;
}while(__p = __len)
break;
case cv_border_wrap:
if(__p __p -= ((__p - __len + 1) / __len) * __len;
if(__p >= __len)
__p %= __len;
break;
case cv_border_constant:
__p = -1;
default:
error(arguments.callee, unspport_border_type/* {line} */);
}
}
return __p;
};
这个函数的意义是对于原长度为len的某一行或者某一列的虚拟像素点p(p一般是负数或者大于或等于该行原长度的数,负数则表示该行左边的像素点,大于或等于原长度则表示是右边的像素点),映射成这一行的哪一个像素点。我们拿cv_border_replicate分析一下,其表达式是:
__p = __p 也就是说p为负数时(也就是左边)的时候映射为0,否则映射成len - 1。
然后我们来实现copymakeborder_8u函数:
复制代码 代码如下:
function copymakeborder_8u(__src, __top, __left, __bottom, __right, __bordertype){
var i, j;
var width = __src.col,
height = __src.row;
var top = __top,
left = __left || __top,
right = __right || left,
bottom = __bottom || top,
dstwidth = width + left + right,
dstheight = height + top + bottom,
bordertype = bordertype || cv_border_reflect;
var buffer = new arraybuffer(dstheight * dstwidth * 4),
tab = new uint32array(left + right);
for(i = 0; i tab[i] = borderinterpolate(i - left, width, __bordertype);
}
for(i = 0; i tab[i + left] = borderinterpolate(width + i, width, __bordertype);
}
var temparray, data;
for(i = 0; i temparray = new uint32array(buffer, (i + top) * dstwidth * 4, dstwidth);
data = new uint32array(__src.buffer, i * width * 4, width);
for(j = 0; j temparray[j] = data[tab[j]];
for(j = 0; j temparray[j + width + left] = data[tab[j + left]];
temparray.set(data, left);
}
var allarray = new uint32array(buffer);
for(i = 0; i j = borderinterpolate(i - top, height, __bordertype);
temparray = new uint32array(buffer, i * dstwidth * 4, dstwidth);
temparray.set(allarray.subarray((j + top) * dstwidth, (j + top + 1) * dstwidth));
}
for(i = 0; i j = borderinterpolate(i + height, height, __bordertype);
temparray = new uint32array(buffer, (i + top + height) * dstwidth * 4, dstwidth);
temparray.set(allarray.subarray((j + top) * dstwidth, (j + top + 1) * dstwidth));
}
return new mat(dstheight, dstwidth, new uint8clampedarray(buffer));
}
这里需要解释下,边缘的复制顺序是:先对每行的左右进行扩展,然后在此基础上进行上下扩展,如图所示。
然后我们根据arraybuffer的性质,将数据转成无符号32位整数来操作,这样每个操作单位就对应了每个像素点了。什么意思?
比如对于某个像素点:rgba,由于某个通道是用无符号8为整数来存储的,所以实际上一个像素点则对应了32位的存储大小,由于arraybuffer的性质,可以将数据转成任意类型来处理,这样我们就可以通过转成uint32array类型,将数据变成每个像素点的数据数组。
那么copymakeconstborder_8u就比较容易实现了:
复制代码 代码如下:
function copymakeconstborder_8u(__src, __top, __left, __bottom, __right, __value){
var i, j;
var width = __src.col,
height = __src.row;
var top = __top,
left = __left || __top,
right = __right || left,
bottom = __bottom || top,
dstwidth = width + left + right,
dstheight = height + top + bottom,
value = __value || [0, 0, 0, 255];
var constbuf = new arraybuffer(dstwidth * 4),
constarray = new uint8clampedarray(constbuf);
buffer = new arraybuffer(dstheight * dstwidth * 4);
for(i = 0; i for( j = 0; j constarray[i * 4 + j] = value[j];
}
}
constarray = new uint32array(constbuf);
var temparray;
for(i = 0; i temparray = new uint32array(buffer, (i + top) * dstwidth * 4, left);
temparray.set(constarray.subarray(0, left));
temparray = new uint32array(buffer, ((i + top + 1) * dstwidth - right) * 4, right);
temparray.set(constarray.subarray(0, right));
temparray = new uint32array(buffer, ((i + top) * dstwidth + left) * 4, width);
temparray.set(new uint32array(__src.buffer, i * width * 4, width));
}
for(i = 0; i temparray = new uint32array(buffer, i * dstwidth * 4, dstwidth);
temparray.set(constarray);
}
for(i = 0; i temparray = new uint32array(buffer, (i + top + height) * dstwidth * 4, dstwidth);
temparray.set(constarray);
}
return new mat(dstheight, dstwidth, new uint8clampedarray(buffer));
}
效果图
cv_border_replicate
cv_border_reflect
cv_border_wrap
cv_border_constant