HTML5新增加的最重要的一个元素,我认为非canvas
莫属了。canvas就相当于一个画布,在这上面可以随意进行绘图,还可以显示3D场景和模型。通过canvas
可以获取img
的像素信息,用来做图像处理自然也不在话下了。
canvas元素
<canvas>
看起来和<img>
元素很相像,唯一的不同就是它并没有src
和alt
属性。实际上,<canvas>
标签只有两个属性——width
和height
。可以通过DOM
或CSS
设置。<canvas>
本质上和其他元素也没有什么不同,依然可以使用CSS
设置其他样式。要注意的是,如果不想内容被拉伸变形,应该尽量设置同比例的宽高。
当没有设置宽度和高度的时候,
canvas
会初始化宽度为300像素和高度为150像素。该元素可以使用CSS
来定义大小,但在绘制时图像会伸缩以适应它的框架尺寸:如果CSS
的尺寸与初始画布的比例不一致,它会出现扭曲。
替换内容
canvas
只在现代浏览器中支持,如IE8以下不兼容,常用备用的替换内容作为提示或备选方案。原理是支持canvas
的浏览器会忽略canvas
标签内的内容,并且只正常渲染canvas
,而不支持的浏览器会渲染canvas
的内容,如下面所示,提示文字只有在不支持canvas
的浏览器中才会显示。
渲染上下文
每个canvas
元素都有一个对应的context
对象(上下文对象),Canvas的API定义在这个context
对象上面,所以需要获取这个对象,方法是使用getContext
方法。
getContext
只有一个参数,就是标示上下文的格式,如2D图像使用“2d”
。如果参数是“3d”
,就表示用于生成3D图像,这部分实际上单独叫做WebGL API
。通过检查getContext
方法存在性,也可以检查canvas
的特性支持
与图像处理相关的API
本文只着重介绍与图像处理的API,其他API参考MDN的Canvas教程。canvas
拥有强大的图像操作能力,常用的有以下方法:
drawImage()
使用drawImage
,canvas
可以将图像源插入画布,在canvas
上进行重绘。drawImage
语法
- image
image
是图像源,即要被绘制到canvas
上的对象。可以是HTMLImageElement
、HTMLVideoElement
、HTMLCanvasElement
、ImageBitmap
中的任何一种,意味着页面是页面上的<img>
、<video>
以及<canvas>
元素,也可以使用脚本创建的,如Image()
构造函数创建的HTMLImageElement
对象。 - dx
图像源的左上角在目标canvas
上X轴的位置 - dy
图像源的左上角在目标canvas
上Y轴的位置 - dWidth
图像源在canvas
上的宽度,会对图像进行缩放。如果不说明则绘制的图片源宽度不会缩放 - dHeight
图像源在canvas
上的高度,会对图像进行缩放。如果不说明则绘制的图片源高度不会缩放 - sx
图像源矩阵选择框左上角x坐标 - sy
图像源矩阵选择框左上角y坐标 - sWidth
图像源矩阵选择框宽度,默认是sx到图像右下角的X轴距离 - sHeight
图像源矩阵选择框高度,默认是sy到图像右下角的Y轴距离
是不是有点绕,其实就是在图像源上通过一个矩阵框选择部分放置到canvas
上的一个矩阵框中,两个矩形框都是通过左上角的坐标(x,y)和矩形宽高决定。参考下面的示意图,需要注意的是设置dWidth
和dHeight
会使图像缩放而导致变形。
下面是一个9个参数的demo,黑色框是canvas
的边框。
getImageData()
getImageData()
方法可以获得canvas
的内容,返回一个imageData
对象。
imageData
对象,只包含width
、height
和data
属性。data
它的值是一个一维数组。该数组的值,依次是每个像素的(R)、绿(G)、蓝(B)、不透明度(alpha通道 A)的值,每个值的范围是0–255。因此该数组的长度等于图像的像素宽度 x 图像的像素高度 x 4。这个数组不仅可读,而且可写,因此通过操作这个数组的值,就可以达到操作图像的目的。
同
drawImage()
一样,getImageData()
方法的四个参数分别表示要获取的矩阵框大小的内容,sx
,sy
表示矩阵框的起始位置,sw
,sh
分别表示矩形框的宽高。
putImageData()
putImageData()
是与getImageData()
相反的操作,putImageData()
把一个imageData
对象绘制到canvas
中
dx
,dy
表示imageData
对象在canvas
中绘制的起始点,sx
,sy
,sWidth
,sHeight
确定要绘制在canvas
上的imageData
对象的矩形大小
图像处理实现
网页图片为RGBA模式,即每一像素分别由红(R)、绿(G)、蓝(B)、不透明度(alpha通道 A)构成,每个值有256种(0-255)。根据一定的算法改变每个像素的值,生成的新图像就有了相应的变化。本文使用两种类型的算法,一种是像素处理是独立的,另一种是每个像素点处理结果与周围像素点相关的滤波处理。
本文的算法主要参考以下两篇博文
Image Filters with Canvas
图像卷积与滤波的一些知识点
简单图像处理
我们先看看简单的图像处理,一般使用一个公式遍历处理每个像素点就可以了,如灰度
灰度就是去色。不考虑A值,一般的处理方法是将图片颜色值的RGB三个通道值设为一样,这样原本的256*256*256种颜色就只有256种了,即只剩下亮度值。一般有三种算法:最大值、平均值、加权平均。这里我们用加权法:一般由于人眼对不同颜色的敏感度不一样,所以三种颜色值的权重不一样,一般来说绿色最高,红色其次,蓝色最低,最合理的取值分别为30%,59%,11%。
原图
应用灰度去色后效果
线性滤波与矩阵卷积
前面介绍的处理都是每个像素独立的处理,实际上我们还可以把每个像素周围的像素信息也用起来做线性叠加操作,这就是线性滤波。线性滤波可以说是图像处理最基本的方法,它可以允许我们对图像进行处理,产生很多不同的效果。中间像素和它的领域像素的线性叠加可以用一个矩阵(卷积核)来表示,如图是应用一个二维3*3滤波器对二维图像进行处理的示意图。如图,对源图像中的每一个像素点和该点周围8个像素共9个点,与卷积核对应相乘并累加,最终的值作为中心点的新值,这就是卷积。
矩阵卷积过程中,每次计算新的像素值,使用的都是源图像的像素值,而不是滤波后的值。
一般来说为了保证有中心点,矩阵应该是奇数阶。
需要保证原像素的亮度滤波前后亮度一致时,则矩阵核和应该为0
边界处理
因为矩阵核每次计算只能得到中心的值,如果遇到在边界的像素,怎么办?一般的简单处理方法有4种:
- 填充0
不够的像素,用0来填充 - 用边界像素值拓展
这种方案是认为图像是无限大的,我们使用的只是一部分,边界外的像素值与边界的值是近似的。出于这种思想,我们自然可以用边界值代替 - 周期拓展
周期拓展是认为图像是像平铺一样周期性重复的,左边界与右边界相接,上边界与下边界相邻。 - 不处理
矩阵核应用
为了应用矩阵核,我们编写一个函数处理
现在来看看应用一个锐化矩阵的效果
原图
锐化效果
矩阵核多种多样,有常见的浮雕、锐化、模糊等,当然也可以自己定义。比如这个我自己随便设置如下加亮矩阵
效果如下
综合应用
是不是越来越有意思啦?现在把之前的结合一起应用做个稍微复杂点的。来先把源图像变成灰度图
灰度图我们后面需要继续用,所以复制一份保留。可以拷贝数组,但是还记得之前说的putImageData()
和getImageData()
吗?用它来得到一份新的imgData副本效率更高
有了副本,那么可以随意做其他处理了。对副本反相。
反相又是什么?反相又叫反色,就是我们常见的底片效果 ,实现也相当简单。用255-原来的像素值并代替原来像素的值就可以了。
反相后模糊处理。模糊滤波器就是对周围像素进行加权平均处理,均值模糊很简单,周围像素的权值都相同,所以不是很平滑。高斯模糊就有这个优点,所以被广泛用在图像降噪上。我们就选用高斯模糊。高斯模糊的计算可以参考高斯模糊的算法
嗯,只差最后一步了。把灰度图A和高斯模糊后的图像B进行图像混合,这一步是颜色减淡。千万别弄错顺序哦,上面的步骤也别弄混了(我按照网上某篇博文的算法死活弄不出来,结果是它的步骤顺序是错的),公式为
$$C =MIN( A + \frac{A×B}{255-B},255)$$
终于完成了,我们看看效果。
原图
处理后
嗯,没错这就是素描效果。完整的应用请看imgFilter
在搜集算法的时候看到一个效果很惊艳铅笔画算法,感兴趣的可以看看这篇介绍的博文
图像铅笔画算法