Day 25 - 影像处理篇 - 用Canvas实作在IE上也可运行的模糊滤镜I - 成为Canvas Ninja ~ 理解2D渲染的精髓

在这一篇我们要来讲一些比较进阶的内容。

那就是图像模糊演算法

大部分有Debug过IE的人应该都知道,IE是不支援css的filter属性的。

但是很多时候设计师交上来的稿子里面又常常会含有很多的模糊特效。

不得不吐槽一下设计师几乎都只爱那种会吃爆效能的Feature,而且还都屡屡讲不听= =

通常这种时候大部分的工程师都会自己取舍要不要实现这样的Feature,

本文的主旨就是提供各位一个备选方案~ 也就是用canvas来实作模糊特效。

这篇文一共会分成两篇来讲解,第一篇我们会进行理论面的讲解,而第二篇才会进入实作

从像素观点来看所谓的模糊

img

不知道大家还对小时候的考试成绩结算的方法有没有印象~

例如小时候老师在月考前会说:『这次月考的成绩统计会采加权制度,数学的加权权重会是3, 国/英文是2,社会/自然是1』

老师这样讲的意思就是说~最後平均成绩的计算方式会是:

『(数学分数*3+国文分数*2+英文分数*2+社会分数*1+自然分数*1) / (3+2+2+1+1)』

像这样的分数计算,就是一种加权运算

而我们在这边要提到的模糊说穿了其实也就是一种加权运算

为什麽这样说呢? 我们先来看看最简单的图形模糊演算法: 方框模糊(Box Blur)的做法~

img

这张照片右半部就是图像透过方框模糊运算之後产生的结果

熟悉Photoshop操作的人一定知道,模糊其实有分成很多种类,除了我们现在提到的方框模糊(Box Blur)以外,还有动态模糊(Motion Blur), 高斯模糊(Gaussian Blur)...,etc.

这几种模糊的差别其实就在於背後实现它们的加权运算权重不太一样。

方框模糊(Box Blur)来讲, 它的加权运算是取每颗像素(这边先把它叫做像素i)的n宫格(九宫格是3*3, 那n宫格就是√n *√n)范围内的邻近像素(包括自己),每颗像素的权重都定为1,最後把这些像素加起来取平均(也就是把他们的r/g/b/a值统统个别的加起来,然後不做任何权重加成来取平均), 最後把这个平均赋予给像素i,像这样的动作去把每个像素都处理一遍,就会变成我们上面看到的图样。

如果光看文字还是不懂意思,可以看下面这张图

img

在上面这张图我们可以看到n宫格范围内的r/g/b值都分别被取了平均,然後赋予到n宫格中间的像素上面。

这个就是方框模糊(Box Blur)的做法。

模糊与卷积核

我们刚刚提到的『n宫格,而且每一格的权重都只有1』,像这样的一个Pattern,我们先姑且把它称为像素加权模型,我们可以用一个阵列来表示之:

[
  1,1,1
  1,1,1
  1,1,1
]

像这样的像素加权模型其实有一个正式的名称,那就是Convolution kernel(卷积核)

模糊之所以会有不同的类型,主要原因就是卷积核的不同。

例如以高斯模糊为例,高斯模糊的卷积核,以大小3*3/5*5/7*7的情况来看,大致上个别是这样:

img

我们这边把这样子的卷积核做成3D模型来看,就看起来会像钟型分布

img

而我们在做高斯模糊的时候,其实就是把一张图像每个像素卷积核范围内 的 像素,按照这些像素在卷积核上的权重,来取加权平均,最後把这个加权平均赋予到卷积核中间那颗像素上面。

这个就是图像模糊运算的经典理论

经典与实作的差别

我们刚刚提到了图像模糊运算的经典理论,BUT 实际上我们在前端如果要实现如同理论中描述的运算的话,我们来看看会发生什麽事~

先假设今天我们的卷积核大小是5*5, 然後图像是一张500*500的png;


for (图像中的每一颗像素 i ){
  let 总和 = 0
  let 权重总计 = 0
  for (i 的 二十五宫格范围内所有像素中的某一个颗 j ){
  总和 += j* 二十五宫格上面 j位置 的权重
  权重总计 += 二十五宫格上面 j位置 的权重
  }
  let 平均= 总和/权重总计
  
}

这样去计算起来,我们做这个运算至少需要跑500*500*5*5 = 6250000 次回圈才做得完一张图片的图像模糊运算。

吓到了吧~,可以试着想像一下我们亲爱的使用者打开你那放了100张图片的网页的状况XD。

上述的这个状况意味着如果我们照着书上的理论去实作大尺度的模糊滤镜,肯定是会GG的。

那麽应该怎麽办呢?

我们需要做的事情简单来说就是简化理论,例如最常见的解法就是把n宫格简化成n十字,也就是先做一次横向范围√n格内的的加权平均,然後再做一次纵向范围√n格内的的加权平均,这麽一来运算的状况就会变成

for (图像中的每一颗像素 i ){
  let 总和 = 0
  let 权重总计 = 0
  for (i 的 横向范围五格内所有像素中的某一颗 j ){
    总和 += j* n宫格上面 j位置 的权重
    权重总计 += n宫格上面 j位置 的权重
  }
  let 平均= 总和/权重总计
  // 然後把平均值先赋予给该颗像素
}

for (图像中的每一颗像素 i ){
  let 总和 = 0
  let 权重总计 = 0
  for (i 的 纵向范围√n格内所有像素中的某一颗 j ){
    总和 += j* n宫格上面 j位置 的权重
    权重总计 += n宫格上面 j位置 的权重
  }
  let 平均= 总和/权重总计
}

用刚刚的情况来看(卷积核大小是5*5, 然後图像是一张500*500的png), 回圈次数就变成了2 *500*500*(3+3) = 3000000

直接减少了3250000次~(超过一半)

By This Way 我们就成功削减了一大票需运行回圈次数~ 而这样我们就可以在前端实现近似的效果,但是实际与理论细节不同方框模糊

我们今天的介绍大概就先到这边,相信各位已经对模糊的理论简化理论的办法有基本的认识~

明天我们将会继续来到实作的部分~ 敬请各位期待 :D


P.S 本文後半段在2021/10/11号晚上有调整过,主要是修正效能改良计算的部分,有因应後来实作的方法调整过,如果造成各位不便,还请见谅 >< !


<<:  [Day 25] SQL DISTINCT

>>:  用 Python 畅玩 Line bot - 06:Image Message

Day21_CSS语法4

font-family属性设定HTML元素的文字字型 以下例子为将文字字型设定为标楷体 font-s...

[Day15] 建立订单交易API_8

本节将进行完整的虚拟订单请求发送 def get_order(shop_no, need_pay, ...

C# 入门之正则表达式匹配并替换

好久没有更新了,最近比较忙,不过今天遇到一个很有意思的问题,就过来记录一下。 通过正则表达式匹配文本...

Day 6 Odoo的Form View

Odoo模组开发实战 目录 VIEW-Form View 第一章 VIEW-Form View 1....