js图像处理 双线性插值 双三次插值法 实现

介绍

在网页中利用canvas进行绘图时,遇到一个问题,原始的数据分辨率很小,而图片要放大到整个网页,所以需要把数据进行插值放大。学习了双线性插值和三次内插法插值,两种方式实现效果不同,双线性插值放大后,会有明显的马赛克,而三次内插法则比较平滑,当然耗时也长一点。
两种方法,参考了其他的文章,我都用js代码来实现了一下,下面给大家分享一下
下面的两个方法,原始数据,每个点都只有一个值,如果原始数据是一张图片,要进行缩放,则可以用canvas取到每个点的rgb值,然后分别对rgb三个通道进行插值即可。

双线性插值

原理

双线性插值即在x和y两个方向上,对数据各进行一次线性插值。
原始数据的矩阵,即一个二维数组,大小为a*b,目标矩阵大小为m*n,m、n比a、b可以大(放大),也可以小(缩小),当然比例也可以不一样, 取决于你插值后的数据需要多大。
基本思想为,遍历目标矩阵的坐标,如x*y这个点,找到这个点在原始矩阵中对应的位置,称为映射点P,然后找到这个映射点P在原始矩阵中周围的四个点,然后根据映射点P到这个四个点的x和y方向上的坐标的距离,进行两次线性插值,得到映射点的值即可。


双线性插值

如上图所示,p点为目标矩阵中x*y点在原始矩阵中映射的位置,它周围最近的有Q12,Q11,Q21,Q22四个点,现在x方向进行线性插值,得到R1和R2两个点的值,再在y方向进行一次线性插值,得到P点的值。
注意:用双线性插值放大数据后,如果放大倍数过大,生成图片后发现有着明显的马赛克现象
实现代码参考后面js代码

双三次插值法

原理

双三次插值又称立方卷积插值。三次卷积插值是一种更加复杂的插值方式。该算法利用待采样点周围16个点的值作三次插值,不仅考虑到4 个直接相邻点的灰度影响,而且考虑到各邻点间值变化率的影响。具体的原理可参考下面博客:
参考这里的博客
基本原理就是,先找到目标矩阵中点在源数据矩阵中的映射点P,然后找到P点周围16个点,然后根据P点坐标距离16个点的x和y方向的距离,利用BiCubic函数算出每个点的权重,最后每个点乘以权重后,加起来即可得到P的值。

示例

BiCubic函数:


函数

其中,a取-0.5时,BiCubic函数具有如下形状:


a=-0.5

取a=-0.5时,放大的数据挺好,生成的图片非常平滑,也保留了很多细节。
具体为什么要用这个函数,我也没有深入研究,不过利用该方法放大数据后,生成图片效果很好,没有马赛克现象

js实现

下面代码中实现了,双线性插值和双三次插值法,scaleData 方法,根据传入的type不同,使用不同的插值方法,该工具类,是完善的,可以直接拿来使用。

/**
 * 数据处理工具类(也可以自己直接定义方法,不用class)
 */
class DataUtil {
    constructor() {}
}

/**
 * 数据插值
 * @param w 目标矩阵宽度
 * @param h 目标矩阵高度
 * @param data 源数据矩阵(二维数组)
 * @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("数据插值耗时:", (t2 - t1));
    
    return resData;
}

/**
 * 双线性插值
 * @param sw 目标矩阵的宽度
 * @param sh 目标矩阵的高度
 * @param x_ 目标矩阵中的x坐标
 * @param y_ 目标矩阵中的y坐标
 * @param data 源数据矩阵(二维数组)
 */
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 源数据矩阵(二维数组)
 */
DataUtil.cubicInterpolation = function (sw, sh, x_, y_, data) {
    let w = data[0].length;
    let h = data.length;
    // 计算缩放后坐标对应源数据上的坐标
    let x = x_ * w / sw;
    let y = y_ * h / sh;
    
    
    // 计算x和y方向的最近的4*4的坐标和权重
    let wcx = DataUtil.getCubicWeight(x);
    let wcy = DataUtil.getCubicWeight(y);
    
    // 权重
    let wx = wcx.weight;
    let wy = wcy.weight;
    
    // 坐标
    let xs = wcx.coordinate;
    let ys = wcy.coordinate;
    
    let val = 0;
    // 遍历周围4*4的点,根据权重相加
    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];
            // 该点的权重
            let w_x = wx[i];
            let w_y = wy[j];
            // 根据加权加起来
            val += (dv * w_x * w_y);
        }
    }
    
    return val;
}

/**
 * 双三次插值法中,基于BiCubic基函数,计算源坐标v,最近的4*4的坐标和坐标对应的权重
 * @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;
    
    // 计算权重
    let ws = new Array(4);
    for (let i = 0; i < 4; i++) {
        let val = Math.abs(xList[i]);
        let w = 0;
        // 基于BiCubic基函数的双三次插值
        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项目地址
项目中有上述源码,和使用案例

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352