js圖像處理 雙線性插值 雙三次插值法 實現(xiàn)

介紹

在網(wǎng)頁中利用canvas進行繪圖時,遇到一個問題,原始的數(shù)據(jù)分辨率很小,而圖片要放大到整個網(wǎng)頁,所以需要把數(shù)據(jù)進行插值放大。學習了雙線性插值和三次內(nèi)插法插值,兩種方式實現(xiàn)效果不同,雙線性插值放大后,會有明顯的馬賽克,而三次內(nèi)插法則比較平滑,當然耗時也長一點。
兩種方法,參考了其他的文章,我都用js代碼來實現(xiàn)了一下,下面給大家分享一下
下面的兩個方法,原始數(shù)據(jù),每個點都只有一個值,如果原始數(shù)據(jù)是一張圖片,要進行縮放,則可以用canvas取到每個點的rgb值,然后分別對rgb三個通道進行插值即可。

雙線性插值

原理

雙線性插值即在x和y兩個方向上,對數(shù)據(jù)各進行一次線性插值。
原始數(shù)據(jù)的矩陣,即一個二維數(shù)組,大小為a*b,目標矩陣大小為m*n,m、n比a、b可以大(放大),也可以?。s小),當然比例也可以不一樣, 取決于你插值后的數(shù)據(jù)需要多大。
基本思想為,遍歷目標矩陣的坐標,如x*y這個點,找到這個點在原始矩陣中對應的位置,稱為映射點P,然后找到這個映射點P在原始矩陣中周圍的四個點,然后根據(jù)映射點P到這個四個點的x和y方向上的坐標的距離,進行兩次線性插值,得到映射點的值即可。


雙線性插值

如上圖所示,p點為目標矩陣中x*y點在原始矩陣中映射的位置,它周圍最近的有Q12,Q11,Q21,Q22四個點,現(xiàn)在x方向進行線性插值,得到R1和R2兩個點的值,再在y方向進行一次線性插值,得到P點的值。
注意:用雙線性插值放大數(shù)據(jù)后,如果放大倍數(shù)過大,生成圖片后發(fā)現(xiàn)有著明顯的馬賽克現(xiàn)象
實現(xiàn)代碼參考后面js代碼

雙三次插值法

原理

雙三次插值又稱立方卷積插值。三次卷積插值是一種更加復雜的插值方式。該算法利用待采樣點周圍16個點的值作三次插值,不僅考慮到4 個直接相鄰點的灰度影響,而且考慮到各鄰點間值變化率的影響。具體的原理可參考下面博客:
參考這里的博客
基本原理就是,先找到目標矩陣中點在源數(shù)據(jù)矩陣中的映射點P,然后找到P點周圍16個點,然后根據(jù)P點坐標距離16個點的x和y方向的距離,利用BiCubic函數(shù)算出每個點的權(quán)重,最后每個點乘以權(quán)重后,加起來即可得到P的值。

示例

BiCubic函數(shù):


函數(shù)

其中,a取-0.5時,BiCubic函數(shù)具有如下形狀:


a=-0.5

取a=-0.5時,放大的數(shù)據(jù)挺好,生成的圖片非常平滑,也保留了很多細節(jié)。
具體為什么要用這個函數(shù),我也沒有深入研究,不過利用該方法放大數(shù)據(jù)后,生成圖片效果很好,沒有馬賽克現(xiàn)象

js實現(xiàn)

下面代碼中實現(xiàn)了,雙線性插值和雙三次插值法,scaleData 方法,根據(jù)傳入的type不同,使用不同的插值方法,該工具類,是完善的,可以直接拿來使用。

/**
 * 數(shù)據(jù)處理工具類(也可以自己直接定義方法,不用class)
 */
class DataUtil {
    constructor() {}
}

/**
 * 數(shù)據(jù)插值
 * @param w 目標矩陣寬度
 * @param h 目標矩陣高度
 * @param data 源數(shù)據(jù)矩陣(二維數(shù)組)
 * @param type 插值方式,1:雙線性插值,2:雙三次插值法
 */
DataUtil.scaleData = function(w, h, data, type = 2) {
    let t1 = new Date().getTime();
    let dw = data[0].length;
    let dh = data.length;
    
    let resData = new Array(h);
    
    for (let j = 0; j < h; j++) {
        let line = new Array(w);
        for (let i = 0; i < w; i++) {
            let v;
            if (type === 2) {
                // 雙三次插值法
                v = DataUtil.cubicInterpolation(w, h, i, j, data);
            } else if (type === 1) {
                // 雙線性插值
                v = DataUtil.interpolation(w, h, i, j, data);
            } else {
                throw new Error('scale data, type not supported(type must be 1 or 2)');
            }
            line[i] = v;
        }
        resData[j] = line;
    }
    
    let t2 = new Date().getTime();
    console.log("數(shù)據(jù)插值耗時:", (t2 - t1));
    
    return resData;
}

/**
 * 雙線性插值
 * @param sw 目標矩陣的寬度
 * @param sh 目標矩陣的高度
 * @param x_ 目標矩陣中的x坐標
 * @param y_ 目標矩陣中的y坐標
 * @param data 源數(shù)據(jù)矩陣(二維數(shù)組)
 */
DataUtil.interpolation = function(sw, sh, x_, y_, data) {
    let t1 = new Date().getTime();
    let w = data[0].length;
    let h = data.length;
    
    let x = (x_ + 0.5) * w / sw - 0.5;
    let y = (y_ + 0.5) * h / sh - 0.5;
    
    let x1 = Math.floor(x);
    let x2 = Math.floor(x + 0.5);
    let y1 = Math.floor(y);
    let y2 = Math.floor(y + 0.5);
    
    x1 = x1 < 0 ? 0 : x1;
    y1 = y1 < 0 ? 0 : y1;
    
    
    x1 = x1 < w - 1 ? x1 : w - 1;
    y1 = y1 < h - 1 ? y1 : h - 1;
    
    x2 = x2 < w - 1 ? x2 : w - 1;
    y2 = y2 < h - 1 ? y2 : h - 1;
    
    // 取出原矩陣中對應四個點的值
    let f11 = data[y1][x1];
    let f21 = data[y1][x2];
    let f12 = data[y2][x1];
    let f22 = data[y2][x2];
    // 計算該點的值
    let xm = x - x1;
    let ym = y - y1;
    let r1 = (1 - xm) * f11 + xm * f21;
    let r2 = (1 - xm) * f12 + xm * f22;
    let value = (1-ym) * r1 + ym * r2;
    
    return value;
}

/**
 * 雙三次插值法
 * @param sw 目標矩陣的寬度
 * @param sh 目標矩陣的高度
 * @param x_ 目標矩陣中的x坐標
 * @param y_ 目標矩陣中的y坐標
 * @param data 源數(shù)據(jù)矩陣(二維數(shù)組)
 */
DataUtil.cubicInterpolation = function (sw, sh, x_, y_, data) {
    let w = data[0].length;
    let h = data.length;
    // 計算縮放后坐標對應源數(shù)據(jù)上的坐標
    let x = x_ * w / sw;
    let y = y_ * h / sh;
    
    
    // 計算x和y方向的最近的4*4的坐標和權(quán)重
    let wcx = DataUtil.getCubicWeight(x);
    let wcy = DataUtil.getCubicWeight(y);
    
    // 權(quán)重
    let wx = wcx.weight;
    let wy = wcy.weight;
    
    // 坐標
    let xs = wcx.coordinate;
    let ys = wcy.coordinate;
    
    let val = 0;
    // 遍歷周圍4*4的點,根據(jù)權(quán)重相加
    for (let j = 0; j < 4; j++) {
        let py = ys[j];
        py = py < 0 ? 0 : py;
        py = py > h - 1 ? h - 1 : py;
        for (let i = 0; i < 4; i++) {
            let px = xs[i];
            px = px < 0 ? 0 : px;
            px = px > w - 1 ? w - 1 : px;
            // 該點的值
            let dv = data[py][px];
            // 該點的權(quán)重
            let w_x = wx[i];
            let w_y = wy[j];
            // 根據(jù)加權(quán)加起來
            val += (dv * w_x * w_y);
        }
    }
    
    return val;
}

/**
 * 雙三次插值法中,基于BiCubic基函數(shù),計算源坐標v,最近的4*4的坐標和坐標對應的權(quán)重
 * @param v 目標矩陣中坐標對應在源矩陣中坐標值
 */
DataUtil.getCubicWeight = function (v){
    let a = -0.5;
    
    // 取整
    let nv = Math.floor(v);
    
    // 坐標差值集合
    let xList = new Array(4);
    // 坐標集合
    let xs = new Array(4);
    
    // 最近的4個坐標差值
    xList[0] = nv - v - 1;
    xList[1] = nv - v
    xList[2] = nv - v + 1;
    xList[3] = nv - v + 2;
    // 
    xs[0] = nv - 1;
    xs[1] = nv;
    xs[2] = nv + 1;
    xs[3] = nv + 2;
    
    // 計算權(quán)重
    let ws = new Array(4);
    for (let i = 0; i < 4; i++) {
        let val = Math.abs(xList[i]);
        let w = 0;
        // 基于BiCubic基函數(shù)的雙三次插值
        if (val <= 1) {
            w = (a + 2) * val * val * val - (a + 3) * val * val + 1;
        } else if (val < 2) {
            w = a * val * val * val - 5 * a * val * val + 8 * a * val - 4 * a;
        }
        ws[i] = w;
    }
    
    return {
        weight: ws,
        coordinate: xs
    };
}

Git項目地址
項目中有上述源碼,和使用案例

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容