转自:https://segmentfault.com/a/1190000012473392
前言
图像处理中有三种常用的插值算法:
- 最邻近插值
- 双线性插值
- 双立方(三次卷积)插值
本文介绍其中的双线性插值
如果想先看效果和源码,可以拉到最底部
何时进行双线性插值
相比于最邻近插值的粗糙以及双立方插值的计算量大,双线性插值的效果比较折中
- 计算量不是那么巨大
- 效果也还可以
- 一般可以作为应用中的默认处理算法
需要注意的是,使用双线性插值后有明显的模糊效果(低通滤波)
数学理论
双线性插值只涉及到邻近的4
个像素点,如图
简单分析如下:
- 目标插值图中的某像素点
(distI, distJ)
在原图中的映射为(i + v, j + u)
(i + v, j + u)
处值的计算就是邻近4
个像素点的分别在x
轴和y
轴的权值和
插值公式
- 设
f(i, j)
为(i, j)
坐标点的值(灰度值) u
为列方向的偏差v
为行方向的偏差- 那么插值公式如下(最终
F(i + v, j + u)
处的实际值)
F(i + v, j + u) = partV + partV1;
partV = v * ((1 - u) * f(i + 1, j) + u * f(i + 1, j + 1));
partV1 = (1 - v) * ((1 - u) * f(i, j) + u * f(i, j + 1));
或展开为:
F(i + v, j + u) = f(0, 0)(1 - v)(1 - u) + f(0, 1)(1 - v)(u) + f(1, 0)(v)(1 - u) + f(1, 1)(v)(u)
上式中,分别是四个坐标点对x
和y
方向进行插值,简单的说
u
越接近0
,(i, j)
与(i + 1, j)
的权值越大v
越接近0
,(i, j)
与(i, j + 1)
的权值越大
代码实现
以下是JavaScript
代码的完整实现
function getRGBAValue(data, srcWidth, srcHeight, row, col) {
let newRow = row;
let newCol = col;
if (newRow >= srcHeight) {
newRow = srcHeight - 1;
} else if (newRow < 0) {
newRow = 0;
}
if (newCol >= srcWidth) {
newCol = srcWidth - 1;
} else if (newCol < 0) {
newCol = 0;
}
let newIndex = (newRow * srcWidth) + newCol;
newIndex *= 4;
return [
data[newIndex + 0],
data[newIndex + 1],
data[newIndex + 2],
data[newIndex + 3],
];
}
function scale(data, width, height, newData, newWidth, newHeight) {
// 计算压缩后的缩放比
const scaleW = newWidth / width;
const scaleH = newHeight / height;
const dstData = newData;
const filter = (dstCol, dstRow) => {
// 源图像中的坐标(可能是一个浮点)
const srcCol = Math.min(width - 1, dstCol / scaleW);
const srcRow = Math.min(height - 1, dstRow / scaleH);
const intCol = Math.floor(srcCol);
const intRow = Math.floor(srcRow);
// 计算u和v
const u = srcCol - intCol;
const v = srcRow - intRow;
// 1-u与1-v
const u1 = 1 - u;
const v1 = 1 - v;
// 真实的index,因为数组是一维的
let dstI = (dstRow * newWidth) + dstCol;
// rgba,所以要乘以4
dstI *= 4;
const rgba00 = getRGBAValue(
data,
width,
height,
intRow + 0,
intCol + 0,
);
const rgba01 = getRGBAValue(
data,
width,
height,
intRow + 0,
intCol + 1,
);
const rgba10 = getRGBAValue(
data,
width,
height,
intRow + 1,
intCol + 0,
);
const rgba11 = getRGBAValue(
data,
width,
height,
intRow + 1,
intCol + 1,
);
for (let j = 0; j <= 3; j += 1) {
const partV = v * ((u1 * rgba10[j]) + (u * rgba11[j]));
const partV1 = v1 * ((u1 * rgba00[j]) + (u * rgba01[j]));
dstData[dstI + j] = partV + partV1;
}
};
for (let col = 0; col < newWidth; col += 1) {
for (let row = 0; row < newHeight; row += 1) {
filter(col, row);
}
}
}
export default function bilinearInterpolation(imgData, newImgData) {
scale(imgData.data,
imgData.width,
imgData.height,
newImgData.data,
newImgData.width,
newImgData.height);
return newImgData;
}
运行效果
可参考同系列中的双立方插值
中的效果图
开源项目
这个项目里用JS
实现了几种插值算法,包括(最邻近值,双线性,三次卷积-包括两种不同实现等)
https://github.com/dailc/image-process
附录
博客
初次发布2016.11.02
于我个人博客上面
http://www.dailichun.com/2017/11/01/imageprocess_bilinearinterpolation.html