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%,線上應用可行性較強。
(組件識別應用全鏈路演示)
組件識別能力的應用須要用戶配置組件、訓練模型用於識別、構建畫布資源用於渲染,組件配置和畫布構建比較簡單,但對於用戶自有的組件庫,須要針對這個組件庫生成對應的組件樣本圖片用於訓練模型,目前用於淘系專用的組件識別模型訓練的樣本須要人工收集或編寫程序自動生成,若是讓用戶本身去收集或編寫樣本生成程序,成本較大。
也有一些用戶但願能接入組件識別的能力,但識別能力依賴於模型泛化能力,模型泛化能力依賴訓練模型使用的樣本,咱們沒法統一提供一個能識別全部組件的通用模型,因此須要給用戶提供自定義模型和自動生成樣本的能力,最大程度下降接入成本。
(樣本管理、模型訓練、模型服務應用一站式管理原型圖)
目前樣本製造機已具有經過上傳設計稿自動幫用戶生成訓練樣本的能力,而且算法模型服務也具有在線訓練的能力,但尚未在線串通整個個流程,下一步須要將流程全鏈路在線化,支持模型根據線上用戶數據反饋自我迭代。