Press "Enter" to skip to content

使用JavaScript生成Low Poly风格图像

 

背景

 

目前,机器学习的发展如火如荼,已经在非常多的场景有了极其广泛的应用,如:手机上的语音助手,智能化推荐,自动驾驶,垃圾邮件分类等等等等。在前端领域,社区也正在尝试引入机器学习,来辅助前端在设计、代码自动化生成、智能工作台、智能化赋能业务等方向做一些尝试,以期进一步提升前端的研发效率,减少重复性的人力劳动,以及进一步提升用户体验,并赋能业务,带来更多的流量,提升更大的流量转换及变现。目前在前端出现的已经成熟的甚至是工业级的应用有:

 


谷歌推出前端机器学习框架:
tensorflow.js [1]
及 Node.js版:
@tensorflow/tfjs-node [2]


阿里这边的实践:
imgcook 一键智能生成前端代码 [3]


腾讯这边的实践: 前端智能化实践——从图片识别UI样式

人机交互实验室 [4]


前端自动化编程

前端智能漫谈 – 写给前端的AI白皮书 [5]


智能UI:面向未来的UI开发技术


[一体化化智能研发与智能 UI 研发实践](https://github.com/iv-web/ppts/blob/master/2020_TLC_ppts/ppt/研发效能专场/一体化化智能研发与智能 UI 研发实践.pdf)

前端智能化实践——让机器理解设计 [6]

Hugging Face 如何利用 Node.js 中的 DistilBERT 在问答功能方面实现 2 倍的性能提升 [7]

 

在基于机器学习往前端赋能的过程中,设计(设计稿)及图像的处理是其中一个需要重点研究的方向,实际应用到的场景如:

 


基于图片生成代码(D2C里的一个分支)


设计稿还原度对比


基于图片的搜索


草稿自动化生成设计稿或代码

 

等等。在实际的工作过程中,我们会用到大量的图像处理相关的知识。图像处理又叫做数字图像处理,它是指将图像信号转换成数字信号并利用计算机对其进行处理的过程。通过这篇文章内容的实践,我们期望能够了解图像处理的一般操作逻辑,窥探图像处理的基本操作步骤,为后续 图像处理
结合 机器学习
在前端的更广泛的应用打下坚实的基础。

 

什幺是图像处理

 

图像处理(image processing)又叫做数字图像处理(Digital Image Processing),也被称为影像处理,是用计算机对图像进行处理达到所需结果的技术,是通过
计算机 [8]
对图像进行去除噪声、增强、复原、分割、提取特征等处理的方法和技术。其起源于20世纪20年代,一般为数字图像处理。图像处理技术的主要内容包括 图像压缩
、 增强复原
、 匹配描述识别
3个部分,常见的处理有图像数字化、图像编码、图像增强、图像复原、图像分割和图像分析等。图像处理是利用计算机对图像信息进行加工以满足人的视觉心理或者应用需求的行为,应用广泛,多用于测绘学、大气科学、天文学、美图、使图像提高辨识等。数字图像处理的产生和迅速发展主要受三个因素的影响:一是计算机的发展;二是数学的发展(特别是
离散数学 [9]
理论的创立和完善);三是广泛的农牧业、林业、环境、军事、工业和医学等方面的应用需求的增长。数字图像处理常用方法有以下几个方面:

 

1.
图像变换:由于图像阵列很大,直接在空间域中进行处理,涉及计算量很大。因此,往往采用各种图像变换的方法,如傅立叶变换、
沃尔什变换 [10]
、离散余弦变换等间接处理技术,将空间域的处理转换为变换域处理,不仅可减少计算量,而且可获得更有效的处理(如傅立叶变换可在频域中进行数字滤波处理)。新兴研究的小波变换在时域和频域中都具有良好的局部化特性,它在图像处理中也有着广泛而有效的应用。

2.
图像编码压缩:图像编码压缩技术可减少描述图像的数据量(即比特数),以便节省图像传输、处理时间和减少所占用的存储器容量。压缩可以在不失真的前提下获得,也可以在允许的失真条件下进行。编码是压缩技术中最重要的方法,它在图像处理技术中是发展最早且比较成熟的技术。

3.
图像增强和复原:图像增强和复原的目的是为了提高图像的质量,如去除噪声,提高图像的清晰度等。图像增强不考虑图像降质的原因,突出图像中所感兴趣的部分。如强化图像高频分量,可使图像中物体轮廓清晰,细节明显;如强化低频分量可减少图像中噪声影响。图像复原要求对图像降质的原因有一定的了解,一般讲应根据降质过程建立“降质模型”,再采用某种滤波方法,恢复或重建原来的图像。

4.
图像分割:图像分割是数字图像处理中的关键技术之一。图像分割是将图像中有意义的特征部分提取出来,其有意义的特征有图像中的边缘、区域等,这是进一步进行图像识别、分析和理解的基础。虽然已研究出不少边缘提取、区域分割的方法,但还没有一种普遍适用于各种图像的有效方法。因此,对图像分割的研究还在不断深入之中,是图像处理中研究的热点之一。

5.
图像描述:图像描述是图像识别和理解的必要前提。作为最简单的二值图像可采用其几何特性描述物体的特性,一般图像的描述方法采用二维形状描述,它有边界描述和区域描述两类方法。对于特殊的纹理图像可采用二维纹理特征描述。随着图像处理研究的深入发展,已经开始进行三维物体描述的研究,提出了体积描述、表面描述、广义圆柱体描述等方法。

6.
图像分类(识别):图像分类(识别)属于模式识别的范畴,其主要内容是图像经过某些预处理(增强、复原、压缩)后,进行图像分割和特征提取,从而进行判决分类。图像分类常采用经典的模式识别方法,有统计模式分类和句法(结构)模式分类,近年来新发展起来的模糊模式识别和
人工神经网络 [11]
模式分类在图像识别中也越来越受到重视。

 

还是要强调下,数字图像处理(Digital Image Processing)又称为
计算机 [12]
图像处理,它是指将图像信号转换成数字信号并利用计算机对其进行处理的过程。所以,所有的数字图像处理都逃脱不了这样的基本处理逻辑,包括我们这篇文章即将要介绍的实现内容。这里有篇比较全面的文章介绍数字图像处理在前端的各种实现:
数字图像处理-前端实现 [13]

 

图像处理与机器学习的关系

 

引用知乎里的一个高赞答案改编下。图像处理有很多不同的方面,诸如图像增强、图像同质化、图像分割等等。 模式识别
有时候也归入图像处理里面。机器学习的主要内容是归纳(Generalization),是根据特征把两个或多个不同的东西区分开来。这是最基本的区别。在图像处理中,经常出现的工作是可以做人工标记来识别图像,但难以写出一个完整的规则来实现自动处理。有时候有一整套算法,但是参数太多,人工去调节、寻找合适的参数就太过繁琐。那幺就可以利用 机器学习
的方法,提取一定数量的特征,人工标记一批结果,然后用机器学习的方法算出一套自动判断的准则。机器学习的方法在开发这类软件时就显得比较有效。说的直白点,就是可以借助于机器学习,来实现在传统的图像处理做法中难以做到的一些事情。比如做图像分割时,我们要把大脑的MRI图像和骨骼分开,虽然一般时候这两者是比较清晰的,但总有那幺一些时候有些部分不容易简单判别。如果人工来做,实在太耗时耗力。那幺究竟一个部分是属于大脑还是属于骨骼,就可以通过机器学习来进行。再比如说,有一些工作需要把眼球的图像中的血管全部提取出来,然后通过血管的密集程度、粗细来分析病情。照片中血管未必是完全相连的,有的地方可能略微模糊,孤立地看不见得能确定是不是相连的。这时候也可以用机器学习的方法来判定这个部分是不是相连的血管。更多内容可以参考:
计算机视觉与图像处理、模式识别、机器学习之间的关系 [14]

 

Low Poly风格图像简介

 

Low Poly 原是 3D 建模中的术语,指使用相对较少的点线面来制作的低精度模型(简称低模),一般网游中的模型都属于低模。而现在,Low Poly 进入了平面设计领域,继扁平化(Flat Design)、长阴影(Long Shadow)之后,低多边形(Low Poly)火速掀起了一个新的设计风潮。Low Poly大概是14年国外设计圈很流行的一种风格,他有点类似 3D 建模初期的 LOW POLY 模型,或是石膏模型的角面像 (单色版)。你可以把这种风格视为 连续三角
或 四角形
的分布,近看或放大看是多边形的色块,但退点距离或缩小后,就可以看出物体的型态,有点类似印象派秀拉的点描风格,差别只是把点换成角面,效果比点描更俐落、更夸张、更有现代感,可以为影像增加视觉的冲击力道,很富有表现趣味的一种手法。目前github上的基于JavaScript的相关实现收集在这里:

 


https://github.com/timbennett/delaunay


https://github.com/Ovilia/Polyvia([如何使用JavaScript生成lowpoly风格图像?](https://www.zhihu.com/question/29856775/answer/57668656))


https://github.com/jrainlau/LowPolifier([教你用JavaScript生成lowpoly风格图像](https://zhuanlan.zhihu.com/p/33290435))


https://github.com/zhiyishou/polyer


https://github.com/victoraugust/js-lowpoly


https://github.com/cojdev/lowpoly


https://github.com/nextgtrgod/threejs-floating-island

 

相关算法介绍

 

本篇文章的目的是前端同学基于JavaScript语言生成Low Poly风格的图像。在介绍完整的实现思路之前,我们先介绍下后续可能用到的一些图像处理的
基础算法

 

一、Sobel边缘检测算法

 

先来看张图,左边是原图,右边是边缘检测后的图,边缘检测就是检测出图像上的边缘信息,右图用白色的程度表示边缘的深浅。
边缘其实就是图像上灰度级变化很快的点的集合。如何计算出这些变化率很快的点?


1.导数*
,连续函数上某点斜率,导数越大表示变化率越大,变化率越大的地方就越是“边缘”,但是在计算机中不常用,因为在斜率90度的地方,导数无穷大,计算机很难表示这些无穷大的东西。


2.微分*
,连续函数上x变化了dx,导致y变化了dy,dy值越大表示变化的越大,那幺计算整幅图像的微分,dy的大小就是边缘的强弱了。微分与导数的关系:dy = f ‘(x) dx 示例如下:
在连续函数里叫微分,因为图像是离散(不连续)的,所以叫差分,和微分是一个意思,也是求变化率。差分的定义,f ‘(x) = f(x + 1) – f(x),用后一项减前一项;按先后排列 -f(x) + f(x + 1) 提出系数 [-1, 1] 作为滤波模板,跟原图 f(x) 做卷积运算就可以检测边缘了

 

模板为什幺要是奇数的?

 

因为模板是偶数的话,卷积出来的结果应该是放在中间的,不方便表示。

 

例如:

 

图像 [10, 20, 30] 跟 [-1, 1] 卷积后的值,应该放在图像 10 跟 20 中间的位置,就是应该放在 0 和 1 号位置的中间,也就是 0.5 号位,但是图像是离散的,中间没得放,只能放在 0 号位,也就是 10 的位置,就偏差了 0.5 的位置,为了方便处理,滤波模板一般都是奇数个的,3,5,7 个的。

 

Sobel 边缘检测算子

 

Sobel算子是像素图像边缘检测中最重要的算子之一,在机器学习、数字媒体、计算机视觉等领域都发挥着极其重要的作用。

 

Sobel算子是一个离散的一阶差分算子,主要用于计算图像亮度函数的一阶梯度近似值。

 

对图像的任何一个像素点使用Sobel算子,将会产生该像素点对应的梯度矢量或法矢量。

 

用 f ‘(x) = f(x + 1) – f(x – 1) 近似计算一阶差分。

 

排好序:[-1 * f(x-1),0 * f(x),1 * f(x+1)]

 

提出系数:[-1, 0, 1]

 

所以模板 [-1, 1] 被改造成了 [-1, 0, 1]。二维情况下就是:

 

-1, 0, 1
-1, 0, 1 
-1, 0, 1

 

这个就是 Prewitt 边缘检测算子了。

 

f(x-1, y-1), f(x, y-1), f(x+1, y-1) 
f(x-1, y), f(x, y), f(x+1, y) 
f(x-1, y+1), f(x, y+1), f(x+1, y+1)

 

中心点 f(x, y) 是重点考虑的,它的权重应该多一些,所以改进成下面这样的

 

-1, 0, 1 
-2, 0, 2 
-1, 0, 1

 

这就是 Sobel 边缘检测算子,偏 x 方向的。(类似二元函数的偏导数,偏x,偏y)

 

同理可得:

 

1, 2, 1 0, 0, 0 
-1, -2, -1

 

是 sobel 偏 y 方向的算子。Sobel算子有两个,一个用于检测水平边缘,另一个用于检测垂直边缘的。因此,Sobel算子包含两组3×3的矩阵,将这两组矩阵与图像作平面卷积运算,可以得到横向与纵向的亮度差分近似值。我们假设原图像为A,_Gx_及_Gy_分别代表经横向及纵向边缘检测的图像灰度值(或者:横向与纵向的亮度差分近似值分别为Gx和Gy),那幺运算公式如下(注意这里不是矩阵乘法,而是矩阵对应元素相乘):
某点的灰度值计算公式如下:
简化的近似计算公式如下:
举例如下(3*3的图像窗口): 注:一般都会选取一个九宫格的颜色值作为当前区域的基础值,单点值无法计算偏差。计算完毕后的值是一个数值而不是矩阵,该数值代表当前区域的灰度偏差。
计算如下:转换一下:合并公式:由此可以计算出G点的灰度值,如果大于某一阀值,就认为该点为边缘点。求出横向与纵向的亮度差分近似值后,原图像像素点的梯度大小与梯度方向可以由其横向及纵向的梯度近似值结合生成,常用公式如下:
G即为直角三角形斜边长度,代表由横向差分及纵向差分一起的效果值;θ代表跨度开角的大小,也能代表灰度值大小。

 

参考实现

 


https://github.com/Elements-/sobel-operator

 

二、Delaunay三角剖分算法

 

Low Poly这种低多边形的成像效果其中的低多边形都是由三角形组成的,而如何自动生成这些看起来很特殊的三角形,就是该算法要讨论的内容。

 

选择点

 

其最先是由很多离散的点组成,基于这个确定的点集,将点集连接成一定大小的三角形,且分配要相对合理,才能呈现出漂亮的三角化。这时则要求使用三角剖分算法(Delaunay),引于

百度百科《Delaunay三角剖分算法》 [15]
对Delaunay三角形的定义为:

 

【定义1】三角剖分:假设V是二维实数域上的有限点集,边e是由点集中的点作为端点构成的封闭线段, E为e的集合。那幺该点集V的一个三角剖分T=(V,E)是一个平面图G,该平面图满足以下条件:

 

1.
除了端点,平面图中的边不包含点集中的任何点。

2.
没有相交边。

3.
平面图中所有的面都是三角面,且所有三角面的合集是散点集V的凸包。

 

在实际中运用的最多的三角剖分是Delaunay三角剖分,它是一种特殊的三角剖分。先从Delaunay边说起:【定义2】Delaunay边:假设E中的一条边e(两个端点为a,b),e若满足下列条件,则称之为Delaunay边:存在一个圆经过a,b两点,圆内(注意是圆内,圆上最多三点共圆)不含点集V中任何其他的点,这一特性又称空圆特性。【定义3】Delaunay三角剖分:如果点集V的一个三角剖分T只包含Delaunay边,那幺该三角剖分称为Delaunay三角剖分。【定义4】假设T为V的任一三角剖分,则T是V的一个Delaunay三角剖分,当前仅当T中的每个三角形的外接圆的内部不包含V中任何的点。

 

如下图是一个Delaunay三角剖分(将离散点联结成Delaunay三角形):

 

算法

 

关于Delaunay三角形的算法,有 翻边算法
、 逐点插入算法
、 分割合并算法
、 Bowyer-Watson算法
等。而在这几种算法中, 逐点插入算法
比较简单、易懂,在本文中只针对该算法进行讨论, 该算法也是目前使用最为广泛的Delaunay算法
。在该算法中,主要应用Delaunay
三角形 定义4
,理解下来就是每一个三角形的外接圆圆内不能存在点集内的其它任何一点,而有时候会出现点在外接圆上的情况,这种情况被称为“退化”。在文章
《Triangulate》 [16]
里对该方法进行了分析,并提出了伪代码思路:

 

subroutine triangulate

input : vertex list

output : triangle list

initialize the triangle list

determine the supertriangle

add supertriangle vertices to the end of the vertex list

add the supertriangle to the triangle list

for each sample point in the vertex list

initialize the edge buffer

for each triangle currently in the triangle list

calculate the triangle circumcircle center and radius

if the point lies in the triangle circumcircle then

add the three triangle edges to the edge buffer

remove the triangle from the triangle list

endif

endfor

delete all doubly specified edges from the edge buffer

this leaves the edges of the enclosing polygon only

add to the triangle list all triangles formed between the point

and the edges of the enclosing polygon

endfor

remove any triangles from the triangle list that use the supertriangle vertices

remove the supertriangle vertices from the vertex list

end

 

其方法虽然可实现三角化,但是效率还是不太高。在看过https://github.com/ironwallaby/delaunay 该js也是基于该伪代码进行编写的,但是作者在其中进行了一次排序优化,使得代码运行效率得到了提高。优化后的伪代码为:

 

input: 顶点列表(vertices)            //vertices为外部生成的随机或乱序顶点列表

output:已确定的三角形列表(triangles)

    初始化顶点列表

//indices数组中的值为0,1,2,3,……,vertices.length-1

    创建索引列表(indices = new Array(vertices.length))

//sort后的indices值顺序为顶点坐标x从小到大排序(也可对y坐标,本例中针对x坐标)

    基于vertices中的顶点x坐标对indices进行sort

    确定超级三角形

    将超级三角形保存至未确定三角形列表(temp triangles)

    将超级三角形push到triangles列表

//基于indices后,则顶点则是由x从小到大出现

    遍历基于indices顺序的vertices中每一个点

      初始化边缓存数组(edge buffer)

      遍历temp triangles中的每一个三角形

        计算该三角形的圆心和半径

        如果该点在外接圆的右侧

          则该三角形为Delaunay三角形,保存到triangles

          并在temp里去除掉

          跳过

        如果该点在外接圆外(即也不是外接圆右侧)

          则该三角形为不确定           //后面会在问题中讨论

          跳过

        如果该点在外接圆内

          则该三角形不为Delaunay三角形

          将三边保存至edge buffer

          在temp中去除掉该三角形

      对edge buffer进行去重

      将edge buffer中的边与当前的点进行组合成若干三角形并保存至temp triangles中

    将triangles与temp triangles进行合并

    除去与超级三角形有关的三角形

end

 

这里面用到的三个存储列表:

 


temp triangles:未确定三角形列表


edge buffer:边缓存数组


triangles:已确定的三角形列表

 

大多数同学看过伪代码后可能还是一头雾水,所以用图来解释这个过程,我们先用三点来做实例:
如图,随机的三个点。
根据离散点的 最大分布
来求得随机一个超级三角形( 超级三角形意味着该三角形包含了点集中所有的点
)。即:根据离散点的最大分步,可以求得一个包含所有点在内的超级三角形。我的方法是根据相似三角形定理求得与矩形一半的 小矩形的对角三角形
,扩大一倍后则扩大后的直角三角形斜边经过点(Xmax,Ymin)。但是为了将所有的点包含在超级三角形内(而不是在边上),在右下角对该三角形的顶点进行了横和高的扩展,并要保证这个 扩展出的三角形底要大于高
,才能实现包含否则,可能出现点不被包含的情况发生。这样求得的超级三角形不会特别大使得计算复杂,而且过程也简单,并将超级三角形放入temp triangles中。接下来就像是伪代码中描述的那样,对temp triangle中的的三角形遍历画外接圆:
这时先对左边的 第一个点
进行判断,其在圆内,所以该三角形 不是Delaunay三角形
,将其三边保存至edge buffer(边缓存列表)中,temp triangle(未确定三角形列表)中删除该三角形。将该点与edge buffer中的每一个边相连,组成三个三角形(这里新组成了 3
个三角形),加入到temp triangles(未确定三角形列表)中:
再将重复对temp triangles(未确定三角形列表)遍历并画外接圆,这时使用的是 第二个点(最上方的蓝点)
来进行判断:

 

1.
该点在三角形1外接圆右侧,则表示左侧三角形为Delaunay三角形,将该三角形保存至triangles(已确定的三角形列表)中

2.
该点在三角形2外接圆外侧,为 不确定三角形
(不确定其他点是否也都在该三角形外侧),所以跳过(后面会讲到为什幺要跳过该三角形),但并不在temp triangles中删除

3.
该点在三角形3外接圆内侧,则这时向 清空后
的edge buffer(边缓存列表)加入该三角形( 三角形3
)的三条边,并用该点与 edge buffer中的三角边
进行组合,组合成了三个三角形并加入到temp triangles中,如下图所示:

 

注:此时的edge buffer(边缓存列表)保存的是新的三角形3的三条边。 再次对temp triangles进行遍历
,这里该数组里则含有 四个
三角形,一个是上次检查跳过的含有第一个点的三角形(三角形2)和新根据第二个点生成的 三个三角形
,此时遍历使用的是第三个点(最底下的点):

 

1.
该点在三角形1外接圆右侧,则该三角形为Delaunay三角形,保存至triangles中,并在temp triangles中删除

2.
该点在三角形2外接圆外侧,跳过

3.
该点在三角形3外接圆内侧,将该三边保存至temp buffer中,并在temp triangles中删除

4.
该点在三角形4外接圆内侧,将该三边保存至temp buffer中,并在temp triangles中删除

 

这时,temp buffer 中有 六
条边,triangles中有 两
个三角形,temp triangles中有 1
个三角形 对temp buffer中的六条边进行去重,得到五条边,将该点与这五条边组合成 五个三角形并加入到temp triagnles
中,这时temp triangles中有6个三角形:
由于三个点已经遍历结束,所以不会再对第三个点形成的三角形做外接圆,这时则将triangles(确定的三角形)与temp trianlges(临时缓存三角形)合并,合并后的数组表示 包含已经确定的Delaunay三角形和剩下的三角形
。这时除去合并后数组中 和超级三角形三个点有关
的所有三角形,即 进行数组坐标
的限定,则得到了最后的结果(这里因为是三个点,所以只有一个三角形):
这是用最少的三个点来做讲解,点数越多的话计算量会越大,但是都是在上面步骤下进行的。

 

问题

 

在用点对三角形外接圆位置关系进行判断的时候, 为什幺点在外接圆的右侧的话可以确定该三角形是Delaunay三角形,而当点外接圆的外侧且非右侧时,为什幺要路过三角形,不把该三角形确定为Delaunay三角形呢
?首先,我们在开始的时候对原始方法进行优化时,我们增加了一个indices数组(排序后的顶点坐标列表)来操作vertices(顶点列表),并对indices依据vertices的x坐标进行了从小到大的排序,则我们在后面遍历点时是从点集的最左侧开始的,如图:
当遍历下一个点时,该点在外接圆的右侧,则表示以后所有的点都在该外接圆的右侧,则保证了Delaunay三角形的空圆特性。而当点在外接圆外,并非外接圆右侧时,如图:
在该三角形的外切圆中,当遍历到点1时,符合在外侧的条件,但是 不能确定后面所有的点都保持在外接圆外侧
。如果说该三角形就为Delaunay三角形的话,如图中的点2及后面可能出现的点很有可能出现在圆内,而使 该三角形被按边分解
。在我们的算法中,如果碰到在点在外侧且非右侧的话,会跳过,该三角形一直在temp triangles(未确定三角形)中被检验,直到碰到 下一个点在圆内或圆右
才会从temp triangles中去除,进行后面的操作。而当点在圆上时, 也是根据在圆内的方法对其进行操作
,实际情况中会出现这种情况,上文也讲过,称为“退化”。最后,附一张delaunay的随机demo图:

 

参考文档

 

Delaunay三角剖分实践与原理 [17]


https://github.com/ironwallaby/delaunay


https://github.com/jbegaint/delaunay-cpp

Delaunay三角剖分学习笔记 [18]

 

Low Poly图像生成实现原理

 

熟悉了以上两个核心关键算法之后,Low Poly的效果图的生成实现就变得很简单了。完整的流程如下图所示:
图像摘自https://www.zhihu.com/question/29856775/answer/57668656 描述如下:

 

1.
获取原始图像

2.
获取到原始图像后,使用边缘检测算法(我们采用Sobel算子)获取到边缘信息

3.
在图像边缘部分及非边缘地带选取顶点(采用随机选取的方式)

4.
基于选取的顶点实现Delaunay三角剖分

5.
对剖分的各个三角着色即可

 

有如下的几个注意点:

 

1.
取图像边缘上的点是为了获得更好的效果
,这是因为如果边上的两个点被选中了,那幺丢给后面的 Delaunay 三角化步骤生成的三角形的一条边就会在边缘上,就可以很好地避免随机取点造成的非常锐的锐角三角形带来的边缘地带没有保留等的不好的效果了

2.
能不能只取边缘上的点作为组成三角形的点呢
?这不是一个好主意。因为它使得很多三角形退化成非常尖锐的锐角三角形。为什幺这里说退化呢?这是因为后面一步的 Delaunay 三角化的作用就是将输入的一组点尽可能地避免变成锐角三角形,从而达到更好的视觉效果。所以,除了边缘上的点有一定概率会被选作组成三角形之外,我们另外 以一个更小的概率在全图中随机取点
。这样就非常有效地避免了非常锐的锐角三角形出现。下图为选中的点的位置。

3.
我们使用 Delaunay 三角化将选中的点组成一个个三角形
。Delaunay 三角化是图形学中一种比较常用的算法,上面已经详细介绍过了,总之它的作用上文已经提到了,就是 使得生成的三角形们尽量不是非常锐的锐角三角形

4.
给三角形上色
,当然像 k-means 这种可以获取三角形颜色类别的方法更高大上,但是为了满足实时性需求,我们还是使用简单粗暴的办法就好,而且效果也非常不错。具体的做法就是 使用每个三角形重心处的颜色作为三角形的颜色
。重心位置的计算是非常简单的,只要把三个顶点的位置求平均数就可以了。然后就能得到上面的结果图了。

 

实现源码

 

完整的代码可参考https://github.com/zhiyishou/polyer

 

实现效果图:

 

 

拓展延伸

 

思考:如何对视频实现Low Poly效果呢?把视频的每一帧都当作图像处理就好了吗?这样做的主要问题在于,由于我们的点是随机取的,所以每帧取到的点不一样,就表现为屏幕一直在闪烁,因为三角形的位置和颜色变化非常大。为了解决这一问题,我们就希望上一帧被选中的点,如果在这一帧中仍然在边缘,则它被选中的概率将大得多。

 

具体可以参考:
如何使用JavaScript生成lowpoly风格图像? [19]

 

参考文档

 

如何使用JavaScript生成lowpoly风格图像? [20]

lowpoly.js——能够生成Low Poly 风格图片的js库 [21]

数字图像 – 边缘检测原理 – Sobel, Laplace, Canny算子 [22]

图像特征提取:Sobel边缘检测 [23]


https://github.com/Elements-/sobel-operator

Delaunay三角剖分实践与原理 [24]


https://github.com/ironwallaby/delaunay


https://github.com/jbegaint/delaunay-cpp

Delaunay三角剖分学习笔记 [25]

 

References

 

[1]
tensorflow.js:  https://www.tensorflow.org/js?hl=zh-cn

 

[2]
@tensorflow/tfjs-node:  https://www.tensorflow.org/js/guide/nodejs

 

[3]
imgcook 一键智能生成前端代码:  https://imgcook.taobao.org/

 

[4]
人机交互实验室:  https://g.yuque.com/arvinxx/next-interatcion

 

[5]
前端智能漫谈 – 写给前端的AI白皮书:  https://tgideas.qq.com/gicp/news/475/8783672.html?from=list

 

[6]
前端智能化实践——让机器理解设计:  https://github.com/iv-web/ppts/blob/master/2020_TLC_ppts/ppt/腾讯看点专场/前端智能化实践——让机器理解设计.pdf

 

[7]
Hugging Face 如何利用 Node.js 中的 DistilBERT 在问答功能方面实现 2 倍的性能提升:  https://blog.tensorflow.org/2020/05/how-hugging-face-achieved-2x-performance-boost-question-answering.html

 

[8]
计算机:  https://baike.baidu.com/item/计算机/140338

 

[9]
离散数学:  https://baike.baidu.com/item/离散数学/2396

 

[10]
沃尔什变换:  https://baike.baidu.com/item/沃尔什变换

 

[11]
人工神经网络:  https://baike.baidu.com/item/人工神经网络

 

[12]
计算机:  https://baike.baidu.com/item/计算机

 

[13]
数字图像处理-前端实现:  https://juejin.im/post/6844903697634295821#heading-21

 

[14]
计算机视觉与图像处理、模式识别、机器学习之间的关系:  https://ishare.ifeng.com/c/s/7rtMjecJnJ2

 

[15]
百度百科《Delaunay三角剖分算法》:  http://baike.baidu.com/view/1691145.htm

 

[16]
《Triangulate》:  http://paulbourke.net/papers/triangulate/

 

[17]
Delaunay三角剖分实践与原理:  https://zhuanlan.zhihu.com/p/42331420

 

[18]
Delaunay三角剖分学习笔记:  https://www.jianshu.com/p/172749e6116a

 

[19]
如何使用JavaScript生成lowpoly风格图像?:  https://www.zhihu.com/question/29856775/answer/57668656

 

[20]
如何使用JavaScript生成lowpoly风格图像?:  https://www.zhihu.com/question/29856775/answer/57668656

 

[21]
lowpoly.js——能够生成Low Poly 风格图片的js库:  https://github.com/jrainlau/LowPolifier

 

[22]
数字图像 – 边缘检测原理 – Sobel, Laplace, Canny算子:  https://www.jianshu.com/p/2334bee37de5

 

[23]
图像特征提取:Sobel边缘检测:  https://www.cnblogs.com/ronny/p/3387575.html

 

[24]
Delaunay三角剖分实践与原理:  https://zhuanlan.zhihu.com/p/42331420

 

[25]
Delaunay三角剖分学习笔记:  https://www.jianshu.com/p/172749e6116a

Be First to Comment

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注