这篇文章主要介绍如何实现js的双线性插值和双三次插值法,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!
在网页中利用canvas进行绘图时,遇到一个问题,原始的数据分辨率很小,而图片要放大到整个网页,所以需要把数据进行插值放大。学习了双线性插值和三次内插法插值,两种方式实现效果不同,都用js代码实现了一下。
双线性插值
原理
双线性插值即在x和y两个方向上,对数据各进行一次线性插值。
原始数据的矩阵,即一个二维数组,大小为a*b,目标矩阵大小为m*n,m、n比a、b可以大(放大),也可以小(缩小),当然比例也可以不一样, 取决于你插值后的数据需要多大。
基本思想为,遍历目标矩阵的坐标,如x*y这个点,找到这个点在原始矩阵中对应的位置,称为映射点,然后找到这个映射点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时,放大的数据挺好,生成的图片非常平滑,也保留了很多细节。
具体为什么要用这个函数,我也没有深入研究,不过利用该方法放大数据后,生成图片效果很好,没有马赛克现象
js实现
/**
* 数据处理工具类(也可以自己直接定义方法,不用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] = Math.round(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 };}
以上是“如何实现js的双线性插值和双三次插值法”这篇文章的所有内容,感谢各位的阅读!希望分享的内容对大家有帮助,更多相关知识,欢迎关注天达云行业资讯频道!