布局算法篇 - 设计稿生成代码 Imgcook 3.0 系列

avatar
阿里巴巴 前端委员会智能化小组 @阿里巴巴

文/ 阿里淘系 F(x) Team - 琻中 、民超

背景介绍

布局还原是 D2C 整个链路偏核心的部分。在布局部分,imgcook 使用一套布局算法将设计稿图层转换为合理的布局结构,从而生成更加“开发友好”的、拥有层级关系的树状结构(例如 页端的DOM结构 或是 Native 开发的 XML 描述体)。

在布局部分之前,插件会将整个视觉稿结构化,得到一份记录了每个元素的绝对位置、大小、样式的扁平化 JSON。通过这份扁平的 JSON 我们能精准还原整张视觉稿。但是,在日常开发中,组件与组件之间的关系并非扁平的绝对定位,而是更加复杂的关系:例如包含与被包含、同一模块的反复使用、同一模块的不同逻辑状态等等(如下图)。因此,布局算法需要进一步升级,拥有支持这些复杂布局的能力。

(“双十一猜你喜欢” 中的循环体)

(“双十一笔笔返“ 中的多态)

问题分析

从以下「UI 信息架构图」来看,一个页面自上而下被细分为 6 层。从设计稿中,我们只能得到_元件_级别的信息。为了使 imgcook 生成的页面结构更加符合前端开发者的编码逻辑,布局算法需要具备将一些元件组合起来,形成组件、区块或模块的能力。

(UI 信息架构图)

因此,我们使用了以下几个方案来结构化 schema 的生成:

  • 页面分割(把页面分割成不同的子模块)
  • 成组(决定子模块内组件的包含与被包含关系)
  • 循环(决定子模块内相同组件平铺堆叠的逻辑关系)
  • 多态(决定子模块内同一组件的不同状态的逻辑关系)

以此为框架,imgcook 致力于推动布局向智能化的方向发展,使最终生成的代码更加符合开发者预期,对开发者更加友好。

技术方案

在下面几章节中,我将着重介绍「页面分割」、「循环」与「多态」的能力。在 imgcook 的发展历程中,我们会渐渐使用更加智能化(能力分级更高的阶段)代替以往纯手写纯干预的过程,从而让整个还原链路具备更大的拓展性与泛化能力。但是,鉴于智能化模型在极端的情况不能 100% 精准,我们还是会保留低分级阶段的能力以确保主链路的「鲁棒性」。

(布局还原阶段的智能化能力分层)

页面分割

首先是页面分割,当使用 imgcook 对整个页面进行还原时,我们会对页面进行切割,把一整个页面切分成几个模块来维护。

L1 阶段人工辅助生成:设计稿模块协议

在设计稿中,用户可以通过设计稿协议手动将页面分割成指定模块,以此来加强对页面分割的干预。在对应模块的中加入 #module:Name# 的协议,即可将对应的部分转换成模块(如下图)

(标注部分)

最终生成(见左边组件树,模块被识别)

L2 阶段规则自动生成:结构化数据分割算法

为了减少人工干预,提高自动识别分割的能力,我们通过规则匹配相邻的元素,以此来判断这些元素是否属于同一个子模块。算法会查询所有相邻行,并将大型块状结构则合并。具体的效果如下图所示:


L3 阶段智能辅助生成:CV 边缘检测

不过,基于规则的页面分割缺乏泛化能力(只有文字与图片的的边缘才能被识别为同一模块)。在实际的应用过程中无法取得非常好的效果。因此我们推出了第二版,通过计算机视觉来智能地分割模块。我们使用 CV 对设计稿进行像素级别的对比,来增强识别的泛化度。


然而,从上图可以看出:目前基于 CV 的边缘检测虽然比规则具有更强的识别与泛化能力。但是:

  • 像素级别的分割可能会过度分割,将原本不应该被拆开的部分拆成两个模块(例如:标题栏和内容部分);
  • 另外,基于像素级的检测对服务端性能的要求非常严苛。据实验数据,在 8 核多线程环境下,一张页面设计稿需要 2 秒才能完成检测。这对于整个还原链路来说,耗时还是非常高的。因此,接下来我们计划通过将图片降维、池化等方式减少像素数量,从而降低运算的成本,以提高识别的效率。

循环识别

循环布局是界面设计特别常用的一种布局模式。比如(卡片)列表、导航tab、轮播组件都用到了循环的结构。在写码的过程中,合理使用循环可以使代码结构更加合理,又能极大地提高代码效率。例如以下案例,我们只需实现一个子组件,并将子组件垂直循环就能得到一个完整的列表组件(如下图):


生成一个循环布局主要经历了三个阶段:
  • 识别阶段
  • 标注阶段
  • 生成阶段

在识别阶段,我们通过算法、模型等方法将循环体从整个 schema 中提取出来;在标注阶段,我们会筛查循环体中的循环元素,并给他们标上序号和循环唯一表示;最后,逻辑库会将所有筛查出循环体绑定上循环变量并批量生成。

在本章节中,我们着重介绍循环的第一个阶段 —— 识别阶段。

识别方案


循环的识别部分位于整个还原链路的最后部分(如下图所示)。在布局算法进行到最后阶段时,元素与元素之间的嵌套层级关系已经趋于稳定,这时候的 schema 便可用于循环的识别计算。

L1 阶段人工辅助生成:设计稿循环协议

作为基础标注能力,imgcook 提供节点打标的方法,可以强制将某些元素识别成循环。只需要在设计稿软件中将循环元素前加上连续的 #loop# 标签,即可在还原时被识别为循环子元素(如下图)。在如下标注下生成代码,即可得到一个 “5循环节点”。



L2 阶段规则自动生成:循环检测算法

在识别循环之前,首先需要了解为什么会有循环布局?前端的循环布局很大程度上由其对应的服务端抽象数据结构有关。在电商行业(尤其是手淘)中,大多数商品都以列表或者feeds流的形式展示,对应到抽象数据结构中就是 ArrayList。因此,相似的数据结构在同一个组件中对应的前端样式也是以循环呈现。


了解了以上背景,不难推出,循环布局一般都具有以下特性:
  • 循环的元素一般都在同一父节点下。
  • 循环的元素一般具有相似的形状、样式属性。

拿营销域下非常常见的卡片式商品举例(如下图),每一张商品卡片都具有类似的布局:正方形头图,大字号标题,描述性文字,清晰可辨的行动点。


因此,我们推出了第一版的循环识别算法:首先遍历所有的父级元素,对所有父元素的直属子节点进行初步的筛选。初步筛选之后,我们得到了一组内部可能存在循环结构的父元素的集合。接下来,我们会扫描每一个可能存在循环结构的父元素的子元素,并计算它们之间的差异。如果该父元素下的子元素几乎没有布局与样式上的差异,那它极有可能就是循环布局。最后,我们将所有循环元素打上标记,并交由业务逻辑库做统一的出码处理。

L3 阶段智能辅助生成:循环识别模型算法

L1和L2阶段均是基于规则的一种人为定义的节点遍历算法,基于规则的方法必然不能处理不符合规则的情况。为此D2C把目光移到当前如火如荼的人工智能领域,利用深度学习中的特征提取能力,希望从海量的数据中找出布局特征,实现端到端的布局识别能力,降低甚至没有人为定义的规则。

经调研,生成对抗网络作为一种新颖的数据生成模型在AI界脱颖而出,在智能布局识别算法中本文引入了当前CV界火热的生成对抗网络,期待生成对抗网络能够找出布局规律特征,将同一布局里所有元素进行风格转换。下面详细介绍本文的工作原理与实践经验。

条件生成对抗网络

大名鼎鼎的谷歌科学家Goodfellow在2014年提出了重量级的深度学习模型:生成对抗网络(Generative Adversarial Networks,GAN),GAN作为一种优秀的图像生成算法成为当前无监督学习中最具前景方法之一。GAN中有两个组件:生成器G(Generative Model)和判别器D(Discriminative Model),生成器负责生成符合真实数据分布的合成数据,判别器负责判别数据的真假,二者互相博弈,互相提升各自的能力。由于深度卷积神经网络可以拟合任意函数,故常被应用于生成器和判别器设计中。GAN的原理框架如下:


上图中,随机噪声z作为生成器G的输入,生成器输出合成数据G(z);判别器判别输入数据是否为真,输入数据为真实数据x时输出为D(x),输入数据为生成器生成的合成数据时输出为D(G(z))。GAN的损失函数数学建模如下:

由上式可知GAN一方面要增强判别器的判别能力,另一方面要增强生成器生成逼真数据能力,在GAN的训练过程中两者不断进行对抗进而提升各自的能力,最终两者处于一种动态均衡:生成器能够生成逼真的合成数据;判别器无法将生成器生成的合成数据和真实数据区分开来,即对输入数据判别为真的概率基本为50%。

条件生成对抗网络(Conditional Generative Adversarial Networks,CGAN)技术是GAN技术的一个进化版本,能够根据输入条件生成符合真实数据分布的合成数据,其在原有GAN基础上加入了监督信息。具体为:传统GAN从随机向量z(噪声)中学习到图像y:G:z->y;与传统GAN不同的是CGAN直接从条件图像中学习到一种映射,即s:G(y,z)->s,式中y为条件图,s为生成器生成的合成图。

模型训练与实践

数据集制作: 首先进行数据集制作,对同一组的布局进行风格转换,对应的标签是一种大白块风格。生成后的一张训练数据展示如下:

模型训练: 接着利用算法pix2pix对模型进行训练,训练过程中的生成器和判别器loss变化如下图,可知两个网络loss均已收敛,训练完成。


效果展示: 随机从测试集中选取图片进行测试,测试结果证明了模型有效。




(识别效果)

由上图可看出模型将图片里的每组用一块白色区域代替,布局分组成功。

表达方案

当循环在布局算法识别之后,在 schema 的 smart.repeat 字段下,就会被标注循环体信息。信息描述了具体哪些元素是在一个循环体中的,每个元素分别对应循环体的第几位,等等。

最后在逻辑库中,imgcook 会用 Array.map 的形式将一个数组映射到组件中。在如下的例子中,一个商品列表被循环生成:


多态识别

除循环布局以外,多态(multi-status)也是前端编码中非常重要的一环。一个元素在不同的状态下可能会有不同的展示状态与行动状态。例如,在如下物品卡片的案例中,针对是否有货,右下角的“购买”按钮有 3 种不同的状态:“暂时无货”,“立刻预约” 和 “立刻下单”。它们有相似的外观,位置和布局,但是,它们也有部分差异:

  • 展示差异(颜色、长短、背景图)
  • 逻辑点差异(点击后执行的逻辑代码不同)

这样的前端模式被称之为多态,而 imgcook 也在逐渐强化自己识别多态的能力


在 2020 年,imgcook 支持了多态识别的 I1 和 I2 部分能力,能够

识别方案

L1 阶段人工辅助生成:设计稿多态协议

除了算法识别之外,我们还提供了人工标注的方法,以便在算法未能精准识别时,手动干预生成多态。在 imgcook 的菜单中选择「生成元素多状态」(或者使用快捷键 Ctrl + Shift + M)即可将多个元素绑定为某一个元素的多种状态,从而在布局算法中被识别为多态元素。


L2 阶段规则自动生成:多态检测规则算法

多态识别算法采用了和循环识别类似的逻辑。首先,它位全还原链路的最后一层,它能够将同一个元素的不同状态提取出来,并进行统一的样式修正与状态的还原和绑定。


  • 首先找到同一个容器的不同状态,并分析它的子元素
  • 找到布局位置相近的子元素,分别检测他们是否有多种状态
  • 若子元素存在样式差异则,且通过相似度计算得出他们相似但不相同,则标记为多态
  • 最终,将拥有多态的子元素抽离,并合并不同状态的样式布局

算法的可视化部分可以参考以下动图:


这样,我们就能从视觉稿中提取出一个元素具有的多种状态,并将它们统一合并在一起。对于前端而言,只需要传入对应状态的参数即可控制展示状态,从而实现研发提效。

L3 阶段智能辅助生成:多态识别模型算法

目前,我们正在推进多态从 I2 到 I3 发展,使用模型的方法来识别设计稿中可能存在的多态。


imgcook 设计了通过 [YOLO](https://en.wikipedia.org/wiki/Object_detection) 提取设计稿中的相似元素,并且分析它们之间的语义相关性来判断这些组件是否构成多态。它的主要流程如下:
  • 将整个设计稿作为图像信息输入;
  • 通过 YOLO 目标检测输出可能存在多态的元素簇;
  • 对各元素簇内的元素进行语义相似度计算。

表达方案

当多态在布局算法识别之后,在 schema 的 smart.layerProtocol.multiStatus 字段下,就会被标注多态信息。信息描述了具体哪些元素是在一个多态簇中的,每个元素分别对应多态的的第几个状态。

最后在逻辑库中,imgcook 会用 condition 字段将每个状态的展示条件映射到抽象的逻辑数据中。绑定了 "condition" 字段之后,就可以通过切换不同的数据来预览不同状态下模块的样式/逻辑(如下图)。


布局可维护性度量

在不断优化布局算法的同时,我们需要一套体系来衡量算法对整个还原链路的优化程度:我们的算法与模型优化对生成的代码到底有多大帮助?这些优化的是否合理?是否真正做到了研发提效?于是我们推出了 2 种布局可维护性度量方案,来评估布局还原的准确度:

  • UI 还原度量:度量经过布局算法后视觉效果上是否 100% 还原。
  • 布局可维护性度量:根据用户最后保存的 schema 度量生成的布局结构的合理性。

UI 还原准确度

UI 还原度量将设计稿原图片与布局还原后的 schema 经过 DSL 出码后渲染的视图做 CV 对比,以视觉相似度与 DOM 结构复杂程度作为判断标准计算的还原度量。


目前 62,807 次的 UI 布局还原平均分值是 92.1%, 其中 Sketch 设计稿还原平均准确度是 92.45%, PSD 设计稿还原平均准确度是 88.21%。准确度低的主要原因在于缺乏自动判断非 box 类元素的宽高的能力,例如无法判断艺术字的合理宽高而生成错误的 box size,需要手动调整。

但是,UI 还原度量只能衡量渲染出来的 UI 和视觉稿的一致程度,不能确保代码结构的合理性,因此我们还需要另一种可以度量布局结构的可维护性的方式:

布局可维护性

在布局可维护性度量中,我们将布局还原生成的 schema 与用户在编辑器内修改后最后保存的 schema 进行差异对比,通过计算用户改动量来推测还原效果以及可用率。

schema 变更会有 4 种可能:节点变动、位置变动、样式变动、属性变动。其中,后两种为一般能力,对布局影响不大;而前两种,为布局算法的核心能力。所以,在计算可用度时,前者的权重高于后者。之后,只要将变动部分的比例整合,即可计算出整体可用率。

我们定义了计算布局还原可维护度的公式:



由此可以得出每棵子树的变动率,以及总体变动率。这种可用度度量方式可以更显式地反映用户在使用 Imgcook 时遇到的问题,例如节点名称、样式、DOM 结构的改变。
(可布局变动查看面板)

除了字段绑定、节点属性、样式的变化,在最后保存时还删除了大量循环节点,但这些循环节点并不是由人工删除,而是在布局还原阶段识别了循环之后,在后面的业务逻辑生成阶段自动删除。

2020 年双 11 大促会场中,带循环体的模块占新增模块 67.31%,在布局还原阶段循环被识别的模块占比 43%,业务逻辑生成阶段根据布局识别结果自动删除多余的循环体并生成循环结构代码。



F(X)Team 开通 微博 啦!
除文章外还有更多的团队内容等你解锁🔓