Javascript圖像處理——虛擬邊緣


前言

上一篇文章,我們來給矩陣添加一些常用方法,這篇文章將講解圖像的虛擬邊緣。

 

虛擬邊緣

虛擬邊緣就是按照一定映射關系,給圖像添加邊緣。

那么虛擬邊緣有什么用呢?比如可以很容易做一個倒影的效果:

當然這只是附帶效果了,虛擬邊緣主要用在圖像卷積運算(例如平滑操作)時候,由於卷積運算的特點,需要將圖片擴大才能對邊角進行卷積運算,這時候就需要對圖片進行預處理,添加虛擬邊緣。

說白了,就是在一些圖片處理前進行預處理。

 

邊緣類型

這里參考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

 

更多的例子

Javascript圖像處理之虛擬邊緣

 

系列目錄

Javascript圖像處理系列

 

參考資料

在OpenCV中圖像邊界擴展 copyMakeBorder 的實現 . viewcode . 2012-12-13 09:28


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM