前言
滤镜主要是用来实现图像的各种特殊效果,比如灰色、颜色反转、黑白、马赛克、锐化等,我们在Photoshop中处理图片时经常能看到,这些看似很复杂的功能前端同学通过Canvas也能很容易实现。本文先通过几个简单的例子,解释如何实现简单的滤镜效果;之后再介绍卷积的基础知识,通过卷积运算来实现比较复杂的滤镜效果。
一、基础
1、图像处理流程中所用到的Canvas API主要有:
- 清空给定矩形内的指定像素:clearRect(x, y, width, height)
- 向画布上面绘制图片:drawImage(img, x, y, width, height)
- 返回画布指定矩形的像素数据:getImageData(x, y, width, height)
- 将图像数据放回画布上:putImageData(imgData, x, y)
2、处理过程
<canvas id="my_canvas"></canvas>
// 滤镜函数
function filter (imageData, ctx) {
// todo... 处理imageData
return imageData;
}
// 加载图片
let img = new Image();
img.src = "img.jpg";
img.onload = function () {
// canvas
let myCanvas = document.querySelector("#my_canvas");
myCanvas.width = 400;
myCanvas.height = 300;
let myContext = myCanvas.getContext("2d");
// 将图片绘制到画布中
myContext.drawImage(img, 0, 0, myCanvas.width, myCanvas.height);
// 获取画布的像素数据
let imageData = myContext.getImageData(0, 0, myCanvas.width, myCanvas.height);
// 处理像素数据
imageData = filter(imageData, myContext);
// 将处理过的像素数据放回画布
myContext.putImageData(imageData, 0, 0);
}
处理过程很简单,可是如何处理像素数据呢?
3、认识像素数据
// 从x=0,y=0开始,取宽=2,高=2的像素数据
let imageData = ctx.getImageData(, , 2, 2)
console.log(imageData);
getImageData获取图片像素数据,方法返回ImageData对象,是拷贝了画布指定矩形的像素数据,如下图
二、实现滤镜
既然我们知道了像素数据的含义,就可以在filter函数中对像素数据imageData进行相应的数学运算即可,现在我们对这三只小狗下手
1、单色滤镜(红色)
顾名思义,就是只保留红色值不变,把绿色和蓝色去除掉(值设为0)
// 滤镜函数 - 红色滤镜
function filter (imageData, ctx) {
let imageData_length = imageData.data.length / 4; // 4个为一个像素
for (let i = ; i < imageData_length; i++) {
// imageData.data[i * 4 + 0] = 0; // 红色值不变
imageData.data[i * 4 + 1] = ; // 绿色值设置为0
imageData.data[i * 4 + 2] = ; // 蓝色值设置为0
}
return imageData;
}
效果如下:
2、灰色滤镜
黑白照片效果,将颜色的RGB设置为相同的值即可使得图片为灰色,我们可以取三个色值的平均值。
// 滤镜函数 - 灰色滤镜
function filter (imageData, ctx) {
let imageData_length = imageData.data.length / 4; // 4个为一个像素
for (let i = ; i < imageData_length; i++) {
let newColor = (imageData.data[i * 4] + imageData.data[i * 4 + 1] + imageData.data[i * 4 + 2]) / 3;
imageData.data[i * 4 + ] = newColor;
imageData.data[i * 4 + 1] = newColor;
imageData.data[i * 4 + 2] = newColor;
}
return imageData;
}
效果如下:
3、反向滤镜
就是RGB三种颜色分别取255的差值
// 滤镜函数 - 反向滤镜
function filter (imageData, ctx) {
let imageData_length = imageData.data.length / 4; // 4个为一个像素
for (let i = ; i < imageData_length; i++) {
imageData.data[i * 4 + ] = 255 - imageData.data[i * 4];
imageData.data[i * 4 + 1] = 255 - imageData.data[i * 4 + 1];
imageData.data[i * 4 + 2] = 255 - imageData.data[i * 4 + 2];
}
return imageData;
}
效果如下:
以上,通过控制每个像素4个数据的值,即可达到简单滤镜的效果。但是复杂的滤镜比如边缘检测,就需要用到卷积运算来实现。
三、卷积
卷积是一个常用的图像处理技术。在图像处理中,卷积操作是使用一个卷积核(kernel)对图像中的每一个像素进行一些列操作,可以改变像素强度,使用卷积技术,你可以获取一些流行的图像效果,比如边缘检测、锐化、模糊、浮雕等。
1、卷积运算过程
按照我们上面讲的图片卷积,如果原始图片尺寸为6 x 6,卷积核尺寸为3 x 3,则卷积后的图片尺寸为(6-3+1) x (6-3+1) = 4 x 4,卷积运算后,输出图片尺寸缩小了,这显然不是我们想要的结果! 为了解决这个问题,可以使用padding方法,即把原始图片尺寸进行扩展,扩展区域补零,扩展尺寸为卷积核的半径(3x3卷积核半径为1,5x5卷积核半径为2)。
2、卷积核特性
- 大小应该是奇数,这样它才有一个中心,例如3x3,5x5或者7x7。
- 卷积核上的每一位乘数被称为权值,它们决定了这个像素的分量有多重。
- 它们的总和加起来如果等于1,计算结果不会改变图像的灰度强度。
- 如果大于1,会增加灰度强度,计算结果使得图像变亮。
- 如果小于1,会减少灰度强度,计算结果使得图像变暗。
- 如果和为0,计算结果图像不会变黑,但也会非常暗。
3、边缘检测
常用于检测物体边缘的卷积核是一个中间是8,周围是-1的3x3数据矩阵。
// 卷积计算函数
function convolutionMatrix(output, input, kernel) {
let w = input.width, h = input.height;
let iD = input.data, oD = output.data;
for (let y = 1; y < h - 1; y += 1) {
for (let x = 1; x < w - 1; x += 1) {
for (let c = ; c < 3; c += 1) {
let i = (y * w + x) * 4 + c;
oD[i] = kernel[] * iD[i - w * 4 - 4] +
kernel[1] * iD[i - w * 4] +
kernel[2] * iD[i - w * 4 + 4] +
kernel[3] * iD[i - 4] +
kernel[4] * iD[i] +
kernel[5] * iD[i + 4] +
kernel[6] * iD[i + w * 4 - 4] +
kernel[7] * iD[i + w * 4] +
kernel[8] * iD[i + w * 4 + 4];
}
oD[(y * w + x) * 4 + 3] = 255;
}
}
return output;
}
// 滤镜函数
function filter (imageData, ctx) {
let kernel = [-1, -1, -1,
-1, 8, -1,
-1, -1, -1]; // 边缘检测卷积核
return convolutionMatrix(ctx.createImageData(imageData), imageData, kernel);
}
我们只要使用不同的卷积核就能得到不同的图像处理效果,比如使用下面这个卷积核,就能得到锐化效果
let kernel = [-1, -1, -1,
-1, 9, -1,
-1, -1, -1]; // 锐化卷积核
四、总结
图像处理是一个很有意思的事情,大家还可以试试通过navigator.mediaDevices获取摄像头video,然后通过requestAnimationFrame实时把当前video的图片数据通过滤镜处理后,再画到canvas中,这样我们就得到了滤镜处理过的视频(参考Demo)! 另外如果你看懂了本文的卷积部分,也许你就踏进了【神经网络与深度学习】的大门,因为卷积运算是神经网络与深度学习中基本的组成部分,边缘检测只是一个入门样例,我们还可以用来做人脸识别等应用,想想都有一点小激动~
链接:https://juejin.im/post/5e2110b86fb9a02fcf18df8c
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。