前言
上一篇文章,我們來給矩陣添加一些常用方法,這篇文章將講解圖像的虛擬邊緣。
虛擬邊緣
虛擬邊緣就是按照一定映射關系,給圖像添加邊緣。
那么虛擬邊緣有什么用呢?比如可以很容易做一個倒影的效果:
當然這只是附帶效果了,虛擬邊緣主要用在圖像卷積運算(例如平滑操作)時候,由於卷積運算的特點,需要將圖片擴大才能對邊角進行卷積運算,這時候就需要對圖片進行預處理,添加虛擬邊緣。
說白了,就是在一些圖片處理前進行預處理。
邊緣類型
這里參考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 < 0 || __p >= __len){ switch(__borderType){ case CV_BORDER_REPLICATE: __p = __p < 0 ? 0 : __len - 1; 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 < 0) __p = -__p - 1 + delta; else __p = __len - 1 - (__p - __len) - delta; }while(__p < 0 || __p >= __len) break; case CV_BORDER_WRAP: if(__p < 0) __p -= (((__p - __len + 1) / __len) | 0) * __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 < 0 ? 0 : __len - 1;
也就是說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 < left; i++){ tab[i] = borderInterpolate(i - left, width, __borderType); } for(i = 0; i < right; i++){ tab[i + left] = borderInterpolate(width + i, width, __borderType); } var tempArray, data; for(i = 0; i < height; i++){ tempArray = new Uint32Array(buffer, (i + top) * dstWidth * 4, dstWidth); data = new Uint32Array(__src.buffer, i * width * 4, width); for(j = 0; j < left; j++) tempArray[j] = data[tab[j]]; for(j = 0; j < right; j++) tempArray[j + width + left] = data[tab[j + left]]; tempArray.set(data, left); } var allArray = new Uint32Array(buffer); for(i = 0; i < top; 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 < bottom; 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 < dstWidth; i++){ for( j = 0; j < 4; j++){ constArray[i * 4 + j] = value[j]; } } constArray = new Uint32Array(constBuf); var tempArray; for(i = 0; i < height; 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 < top; i++){ tempArray = new Uint32Array(buffer, i * dstWidth * 4, dstWidth); tempArray.set(constArray); } for(i = 0; i < bottom; 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
更多的例子
系列目錄
參考資料
在OpenCV中圖像邊界擴展 copyMakeBorder 的實現 . viewcode . 2012-12-13 09:28