imgcook 可以自动生成代码主要作两件事: 从视觉稿中识别信息,而后将这些信息表达成代码。javascript
本质是经过设计工具插件从设计稿中提取 JSON 描述信息,经过规则系统、计算机视觉和机器学习等智能还原技术对 JSON 进行处理和转换,最终获得一个符合代码结构和代码语义的 JSON,再用一个 DSL 转换器,转换为前端代码。DSL 转换器就是一个 JS 函数,输入是一个JSON,输出就是咱们须要的代码。前端
例如 React DSL 的输出就是符合 React 开发规范的 React 代码,其中核心部分在于 JSON to JSON 这部分,这个 JSON 的格式能够查看 imgcook schema 这篇文档。java
设计稿中只有图像、文本这些元信息,位置信息是绝对坐标,直接生成的代码是由 div、img、span 或 View、Image、Text 这些元件粒度的标签组成,但实际开发中,咱们会将 UI 界面不一样粒度的物料组件化,例如 搜索框、按钮这种基础组件,或者计时器、券、视频、轮播等这种带有业务属性的组件,又或者更大颗粒度的 UI 区块。node
若是但愿能生成组件粒度的代码, 须要能识别视觉稿中的组件,而且转化成对应的组件化代码。例如如下视觉稿中电饭煲位置处是一个视频,但从视觉稿中只能提取到图片信息,并生成如右侧的代码。算法
实际生成的代码须要用 Rax 组件 rax-video 来表达,以下:json
import { createElement, useState, useEffect, memo } from 'rax';
import View from 'rax-view';
import Picture from 'rax-picture';
import Text from 'rax-text';
import Video from 'rax-video';
<View className="side"> ... <Video className="group" autoPlay={true} src="//cloud.video.taobao.com/play/u/2979107860/p/1/e/6/t/1/272458092675.mp4" /> ... </View>
复制代码
那咱们要作两件事:markdown
按照对智能化能力分级定义,I1 级别可经过设计稿协议人工辅助生成组件代码,I2 级别可经过规则算法分析元素样式识别组件生成组件代码,I3 级别使用目标检测模型识别组件,但目标检测方案没法避免复杂设计稿背景带来的专模型准确度低的问题。在探索出图片分类方案后,在特定业务域下即便设计稿很复杂训练出来的模型准确度也较高,目前在 I4 级别优化算法工程链路下降业务接入成本。架构
(组件识别能力模型)机器学习
直接在设计稿中标记组件名称,在使用 imgcook 插件导出 JSON 描述数据时经过解析标记拿到图层中的人工设定的组件信息。async
(人工设置组件协议生成组件化代码)
这种方式须要人工标记视觉稿填写组件名称和属性,一个页面上的组件可能会有不少,这种人工约定方式让开发者多了不少额外工做,咱们指望能自动化、智能化的识别视觉稿中的须要组件化的 UI。
经过规则算法可以自动帮咱们检测出一些有通用样式特征的组件,例若有 4 个圆角的宽度大于高度的节点能够认为是按钮这种规则判断,可是规则判断的泛化能力不好,没法应对复杂多样的视觉表现。
找到视觉稿中须要组件化的元素,它是什么组件,它在 DOM 树中的位置或者在设计稿中的位置,这是深度学习技术很适合解决的问题,能够接受大量丰富的样本数据,学习和概括出经验,预测类似组件样本的类别,这种类似特征就再也不局限于使用规则算法的宽高样式等,泛化能力较强。
在 如何使用深度学习识别 UI 界面组件?这篇文章中我把这个问题定义为一个目标检测的问题,使用深度学习对 UI 界面进行目标检测,找到可在代码中组件化的页面 UI 的类别和边界框,可是这篇文章主要是以 UI 界面组件识别为例介绍使用深度学习解决问题的方式,并无考虑到实际应用。这里以解决 D2C 组件化出码这个实际问题为核心,分享一下如何才能在实际项目中应用组件识别的能力。
咱们很难收集到全部用户的样本提供一个准确度较高的通用组件识别模型,另外不一样团队使用的组件类别和样式差别较大,可能会有相同类别的样本但 UI 差别却很大,或者不一样类别的样本 UI 却很类似,这样会致使识别效果会不好。所以须要能支持用户以本身的组件为训练集,训练一个专有的组件识别模型。这里以淘系营销经常使用的几个组件为例,介绍组件识别的应用方案。
在 如何使用深度学习识别 UI 界面组件?这篇文章中有详细介绍目标检测的知识,将视觉稿的图像做为输入,训练一个目标检测模型,用于识别图片中的组件。
(目标检测模型训练与预测路径)
如上图所示,训练目标检测模型须要输入大量样本,样本是视觉稿的整张图片,而且须要给图片标记你想要模型识别的组件,训练出能够识别组件的目标检测模型,当有的新的须要识别的设计稿时,将设计稿图像输入给模型识别,最终获得模型识别的结果。
使用目标检测的方案会存在一些问题:
在 imgcook 智能生成代码的场景中,组件识别的结果是须要精确到具体的 DOM 节点的,用这种目标检测的方案既须要识别出准确的位置又须要识别出正确的类别,线下实验的模型准确度自己就不高,线上应用的准确度就会比较低,基本上没法肯定最终识别的结果应该到哪一个 DOM 节点上。
因为咱们能够从设计稿中获取图像的 JSON 描述信息,图像中每一个文本节点和图像节点都已经具有位置信息,而且通过 imgcook 智能还原后能生成较为合理的布局树。因此咱们能够基于这个布局树,以容器节点为粒度将可能的组件节点裁剪出来。
(图片分类模型训练与预测路径)
例如咱们能够把这里的 div/view 节点都裁剪出来,就能够获得一个小的图片的集合,而后将这些图片送给一个图片分类模型预测,这样咱们把一个目标检测问题转换成了一个图片分类问题。
模型会给每张图片在每个分类中分配一个几率值,某个分类的几率值越大表示模型预测该图片是这个分类的几率越大。咱们能够设置一个置信度为 0.7,当几率值大于置信度 0.7 时则认为是最终分类的结果,例如上图中,最终只有两张图片是可信的识别结果。若是对分类的准确度要求很高,就能够将置信度设置高一点。
相比目标检测,使用图片分类方案,样本能够用程序自动生成,无需人工打标;只须要识别类别,类别准确则位置信息绝对准确。因此咱们改用基于布局识别结果的图片分类方案,识别准确度大大提高。
布局算法生成布局以后的 JSON Schema 进入组件分类识别层,组件识别结果会更新到 JSON Schema 中传入下一层级。 (组件分类识别在技术分层中的位置)
咱们能够这样直观的看下组件识别的结果,识别结果会挂在这个节点的 smart 属性中。
(组件分类结果)
从(图片分类模型训练与预测路径)中能够看到,根据布局结构裁剪出来的图片,通过组件分类识别后,因为模型准确度问题可能会识别出多个 videobtn 类别的节点。
那如今咱们须要根据组件识别的结果,找到须要替换成组件 Video 的节点,会遇到这些问题:
基于这些问题,想要将组件识别的结果最终应用到工程链路,而且能支持用户个性化的组件需求,咱们须要提供一套开放的智能物料体系,支持组件可配置、可识别、可干预、可渲染、可出码。
组件识别应用的整个流程以下,用户配置了本身的组件库以后,还须要配置识别组件的模型服务,在视觉稿还原的组件识别阶段调用模型服务识别组件,在进入业务逻辑生成阶段时,调用配置好的逻辑库来将组件的识别结果(smart 字段)表达成组件(componentName),并检测视觉稿中可获取的组件属性信息用于补充组件属性。最后在画布中渲染,须要预先配置支持组件渲染的画布资源。
(组件识别应用流程)
这里详细介绍下在业务逻辑生成阶段业务逻辑库如何承接组件识别结果的应用表达,以及画布如何支持组件渲染以在视觉上表达识别结果。
业务逻辑库的核心功能之一是用户能够自定义识别函数和表达函数,在业务逻辑生成阶段对每个节点调用识别函数和表达函数。识别函数用于判断当前节点是否为想要的节点,若是是则执行对应的表达逻辑。
例如组件识别的结果会放在 D2C Schema 协议的 smart 节点上,咱们能够自定义识别函数判断当前节点是否被识别为组件。这里的难点在于可能会有多个被识别为组件的节点,须要能精确的肯定最终要表达为组件的节点,由于有的节点是误识别,有的节点虽然识别正确但并非直接更改此节点的 componentName,而是须要寻找合适的节点。
对于这里的视频时间显示组件 videobtn,有多个识别结果,须要根据这个结果找到对应的须要替换为前端视频时间组件 VideoBtn 的节点,并将此节点的 componentName 替换为组件名称 VideoBtn,其中组件名称根据组件的类别 videobtn 以及录入组件时给组件输入的标签来关联,即录入组件时须要同时录入组件的类别用于组件识别。
因此在自定义识别函数中,咱们须要添加一些过滤规则,例如若是有多个有嵌套包含关系的节点被识别为 videobtn,只取最里面一层的节点做为识别结果。
/* * allSchema 原始数据 schema * ctx 上下文 * 执行时机:每一个节点执行一次,返回为true时识别成功,可执行表达逻辑 */
async function recognize(allSchema, ctx) {
// ctx.curSchema - 当前选中节点Schema
// ctx.activeKey - 当前选中Key
// 判断是否被识别为 videobtn 的节点
const isVideoBtnComp = (node) => {
return _.get(node, 'smart.layerProtocol.component.type', '') === 'videobtn';
}
// 是否有子节点被识别为 videobtn
const isChildVideoBtnComp = (node)=>{
if(node.children){
for(var i=0; i<node.children.length; i++){
const _isChildVideoBtn = isVideoBtnComp(node.children[i]);
if (_isChildVideoBtn) {
return true;
}
return isChildVideoBtnComp(node.children[i]);
}
}
return false;
}
// 若是当前节点是咱们须要的 videobtn 节点(节点自己被识别为 videobtn 类别而且子节点没有被识别为 videobtn 类别),
// 则返回 true,使该节点进入表达函数的逻辑
const isMatchVideoBtn = isVideoBtnComp(ctx.curSchema) && !isChildVideoBtnComp(ctx.curSchema);
return isMatchVideoBtn;
}
复制代码
而后自定义表达函数,若是某一个节点执行识别函数后输出为 true, 则执行对应的表达函数。下面在自定义表达函数中更改 componentName 为 VideoBtn,并提取时间信息做为 VideoBtn 组件的属性值。
/* * json 原始数据 schema * ctx 上下文 */
async function logic(json, ctx) {
getTime = (node) => {
for(var i=0; i<node.children.length; i++) {
if(_.get(node.children[i], 'componentName', '') === 'Text') {
return _.get(node.children[i], 'props.text', '');
}
}
return "00:00";
}
// 设置节点名称为组件 @ali/pcom-imgcook-video-58096 的名称 VideoBtn
_.set(ctx.curSchema, 'componentName', 'VideoBtn');
// 获取时间做为组件属性值
const time = getTime(ctx.curSchema);
// 设置获取的时间做为组件 VideoBtn 的 data 属性的值
_.set(ctx.curSchema, 'props.data', {time: time});
// 删除 VideoBtn 节点下的子节点
ctx.curSchema.children = [];
return json
}
复制代码
通过业务逻辑层将组件识别的结果表达以后,就能够获得组件化的 Schema,最终生成组件化代码。
这就是一个经过组件分类模型识别视觉稿中 videobtn 的位置,最终用前端组件 @ali/pcom-imgcook-video-58096 应用出码的例子。
若是但愿识别到视觉稿中 videobtn 类别以后,能够将视觉稿中的商品图片替换成视频,例如用 rax-video 组件出码,咱们能够增长一个自定义表达函数,找到与 videobtn 节点同级的图片节点,并将此节点替换为 rax-video 组件。
/* * json 原始数据 schema * ctx 上下文 */
async function logic(json, ctx) {
const getBrotherImageNode = (node) => {
const pKey = node.__ctx.parentKey;
const parentNode = ctx.schemaMap[pKey];
for(var i=0; i<parentNode.children.length; i++){
if (parentNode.children[i].componentName == 'Picture') {
return parentNode.children[i];
}
}
}
const videoNode = getBrotherImageNode(ctx.curSchema);
_.set(ctx.curSchema, 'componentName', 'Video');
_.set(ctx.curSchema, 'props.poster', _.get(ctx.curSchema, 'props.source.uri');
_.unset(ctx.curSchema, 'props.source');
return json
}
复制代码
使用业务逻辑库来应用组件识别结果的好处是:组件识别能够与业务逻辑解耦,用户的组件是不肯定的,每一个组件的名称和属性都不同,识别以后应用的逻辑也不同,业务逻辑库能够支持用户自定义组件应用的需求,不然组件识别的结果没法落地使用。
若是编辑器画布不支持渲染组件,组件节点会被渲染为空节点,没法在画布中展现,也就看不到视觉稿还原后所见即所得的效果,画布支持组件渲染非必须可是有必要。
目前组件支持以 NPM 包的形式打包到画布资源中,借助 iceluna 开放的渲染引擎 SDK 为 imgcook 用户提供可自定义编辑器画布的能力。用户能够选择须要的组件打包构建,构建以后获取的画布资源经过配置生效。
(编辑器画布构建架构)
目前针对淘系常见的轮播组件、视频组件等训练了一个特定的组件识别模型,线上全链路支持组件可配置、可识别、可渲染、可干预、可出码,并在双 11 会场、聚划算等业务中应用。这种针对特定域的组件样本训练的模型识别准确率较高,可达 82%,线上应用可行性较强。
(组件识别应用全链路演示)
组件识别能力的应用须要用户配置组件、训练模型用于识别、构建画布资源用于渲染,组件配置和画布构建比较简单,但对于用户自有的组件库,须要针对这个组件库生成对应的组件样本图片用于训练模型,目前用于淘系专用的组件识别模型训练的样本须要人工收集或编写程序自动生成,若是让用户本身去收集或编写样本生成程序,成本较大。
也有一些用户但愿能接入组件识别的能力,但识别能力依赖于模型泛化能力,模型泛化能力依赖训练模型使用的样本,咱们没法统一提供一个能识别全部组件的通用模型,因此须要给用户提供自定义模型和自动生成样本的能力,最大程度下降接入成本。
(样本管理、模型训练、模型服务应用一站式管理原型图)
目前样本制造机已具有经过上传设计稿自动帮用户生成训练样本的能力,而且算法模型服务也具有在线训练的能力,但尚未在线串通整个个流程,下一步须要将流程全链路在线化,支持模型根据线上用户数据反馈自我迭代。