手把手教你打造前端智能圖標識別工具

Hi~ 我是前端學徒業楓(@Malpor),今天爲你們帶來一篇硬核前端智能化教程,真·手把手教你用機器學習打造一個純前端運行的圖標智能識別工具。並附上完整代碼,一塊兒來體驗前端智能化的魅力吧~javascript

背景

目前的前端組件庫都使用 Iconfont 來管理圖標,隨着時間推移,圖標愈來愈多,圖標的命名也五花八門,很難約束。開發者還原設計稿時,常常要人肉從幾百個圖標中尋找對應的圖標。有時候連設計師都找不到,致使重複添加圖標。css

最近發如今 AntDesign 官網有以圖搜圖標的功能,用戶對設計稿或任意圖片中的圖標截圖,點擊/拖拽/粘貼上傳,就能夠搜索到匹配度最高的幾個圖標:AntDesign Icon功能開發者文章前端

v2-fee8f94fbfe1885a713cd6fbb854385a_b.gif

這個功能很好的解決了上面提到的問題,但還有些不足:java

  • 截圖最好是正方形的,不然拉伸後識別率會降低(後面會解釋)。
  • 只能識別 AntDesign 的圖標。

爲了解決這些問題,咱們決定本身打造一個前端圖標識別工具。下面將以咱們團隊的開源組件庫 Cloud Design 爲例,手把手教你打造純前端的專屬圖標識別工具。(完整代碼放在文末)node

術語簡介

簡單介紹幾個術語,瞭解的同窗能夠直接跳過。git

機器學習

機器學習研究和構建的是一種特殊算法(而非某一個特定的算法),可以讓計算機本身在數據中學習從而進行預測。github

因此,機器學習不是某種具體的算法,而是不少算法的統稱。算法

機器學習包含:線性迴歸、貝葉斯、聚類、決策樹、深度學習等等。前面 AntDesign 的模型是經過深度學習的表明算法 CNN 訓練獲得的。json

CNN 卷積神經網絡

卷積神經網絡(Convolutional Neural Networks, CNN)是一類包含卷積計算且具備深度結構的前饋神經網絡(Feedforward Neural Networks),最經常使用於分析視覺圖像。canvas

CNN 能有效的將大數據量圖片降維到小數據量,且保留圖像特徵,很是適合處理圖像數據。即便圖像翻轉、旋轉或變換位置也能有效識別,經常使用來解決:圖像分類檢索、目標定位監測、人臉識別等等。

開始行動吧

咱們要對圖標進行識別,屬於機器學習中經典的「圖像分類」問題。CNN(卷積神經網絡) 能夠有效的識別圖標,可是沒法適應拉伸變形的場景。由於模型輸入時要先把圖像變換爲正方形尺寸,截圖尺寸不對會致使圖像拉伸變形,下降識別率,甚至識別錯誤。

截屏2021-03-21 下午10.36.41.png

經常使用的解法有兩種:

一、純機器學習:經過增長不一樣拉伸狀態的樣本,讓模型適應變形的圖像。

二、機器學習 + 圖像處理:用圖像處理算法對數據進行裁剪,保證圖像接近正方形。

第一種方法須要生成大量的訓練數據,訓練速度變慢,並且拉伸變形的狀況很難遍歷。第二種方法只須要進行簡單的圖像處理就能夠有效提升識別率,因此我選擇了它。那最終工做流應該是這樣的:

截屏2021-03-21 下午10.38.04.png

接下來我會從 樣本生成、模型訓練、模型使用 三部分來介紹完整的過程。

樣本生成

圖像分類的訓練樣本都是圖片,咱們的圖標則是 iconfont 渲染在頁面中的。能夠天然想到用 樣本頁面 + Puppeteer 截圖來生成樣本。但截圖速度很慢,我也不想用 Faas 服務,因而想了個本地生成的方法:

首先人工把圖標庫的css部分轉爲js:

截屏2021-03-21 下午10.40.28.png

這樣就能把圖標看成文本繪製在 canvas 上,並用圖像算法裁剪四周的空白區域:

// 用離屏 canvas 繪製圖標
offscreenCtx.font = `20px NextIcon`;
offscreenCtx.fillText(labelMap[labelName]);

// 用 getImageData 獲取圖片數據,計算需裁剪的座標
const { x, y, width: w, height: h } = getCutPosition(canvasSize, canvasSize, offscreenCtx.getImageData(0, 0, canvasSize, canvasSize).data);

// 計算需裁剪的座標
function getCutPosition(width, height, imgData) {
  let lOffset = width; let rOffset = 0; let tOffset = height; let bOffset = 0;
  // 遍歷像素,獲取最小的非空白矩形區域
  for (let i = 0; i < width; i++) {
    for (let j = 0; j < height; j++) {
      const pos = (i + width * j) * 4;
      if (notEmpty(imgData[pos], imgData[pos + 1], imgData[pos + 2], imgData[pos + 3])) {
        // 調整 lOffset、rOffset、tOffset、bOffset
        // 略
      }
    }
  }
  
  // 若是形狀不是正方形,將其擴展爲正方形
  const r = (rOffset - lOffset) / (bOffset - tOffset);
  if (r !== 1) {
    // 略
  }

  return { x: lOffset, y: tOffset, width: rOffset - lOffset, height: bOffset - tOffset };
}

// 閾值 0 - 255
const d = 5;
// 判斷是否非空白像素
function notEmpty(r, g, b, a) {
  return r < 255 - d && g < 255 - d && b < 255 - d;
}

// 用 canvas 裁剪 & 縮放圖像,導出爲 base64
ctx.drawImage(offscreenCanvas, x, y, w, h, 0, 0, 96, 96);
canvas.toDataURL('image/jpeg');
複製代碼

截屏2021-03-21 下午10.42.59.png

生成一張圖片的邏輯就寫完了。改造一下,遍歷不一樣圖標、不一樣字號,能夠獲得全量的樣本:

const fontStep = 1;
const fontSize = [20, 96];

labels.map((labelName) => {
  // 遍歷不一樣的字號繪製圖標
  for (let i = fontSize[0]; i <= fontSize[1]; i += fontStep) {
    // ...before
    offscreenCtx.font = `${i}px NextIcon`;
    // 其它邏輯
  }
});
複製代碼

經過 Blob 將數據做爲一個 json 下載:

const resultData = /* 生成全量數據 */;

const aLink = document.createElement('a');
const blob = new Blob([JSON.stringify(resultData, null, 2)], { type : 'application/json' });
aLink.download = 'icon.json';
aLink.href = URL.createObjectURL(blob);
aLink.click();
複製代碼

這樣就獲得了包含幾萬張(350個圖標,每一個分類約70張圖)樣本圖片的大 json,大概長這樣:

[
  {
    "name": "smile",
    "data": [
      {
        "url": "data:image/jpeg;base64,/9j/4AA...IkB//9k=",
        "size": 20
      },
      {
        "url": "data:image/jpeg;base64,/9j/4AA...JAf//Z",
        "size": 21
      },
      ...
    ]
  },
]
複製代碼

最後寫一個簡單的 node 程序,把每一個分類的樣本按照訓練集70%,驗證集20%,測試集10%的比例拆分打散並存儲爲圖片文件。

--- train
  |-- smile
    |-- smile_3.jpg
    |-- smile_7.jpg
  |-- cry
    |-- cry_2.jpg
    |-- cry_8.jpg
  ...
--- validation
  |-- smile
  |-- cry
  ...
--- test
  |-- smile
  |-- cry
  ...
複製代碼

這樣咱們就獲得了完整的訓練樣本,並且生成速度很快,運行一遍只要1分鐘左右。而後把三個目錄一塊兒打包成一個 zip 文件便可,由於下一步訓練只支持 zip 格式。

模型訓練

機器學習工具備不少種,做爲一個前端,我最終選擇使用 Pipcook 來訓練。

Pipcook 項目是一個開源工具集,它能讓 Web 開發者更好地使用機器學習,從而開啓和加速前端智能化時代!

Pipcook 的安裝和教程看官網(連接)便可,要注意目前只支持 Mac & Linux,Windows 暫時沒法使用(Windows 可使用 Tensorflow.js 訓練)。

寫一份 pipcook 的配置項:

{
  "plugins": {
    "dataCollect": {
      "package": "@pipcook/plugins-image-classification-data-collect",
      "params": {
        "url": "file://絕對路徑,指向上一步打包的文件.zip"
      }
    },
    "dataAccess": {
      "package": "@pipcook/plugins-pascalvoc-data-access"
    },
    "dataProcess": {
      "package": "@pipcook/plugins-tfjs-image-classification-process",
      "params": {
        "resize": [224, 224]
      }
    },
    "modelDefine": {
      "package": "@pipcook/plugins-tfjs-mobilenet-model-define",
      "params": {}
    },
    "modelTrain": {
      "package": "@pipcook/plugins-image-classification-tfjs-model-train",
      "params": {
        "batchSize": 64,
        "epochs": 12
      }
    },
    "modelEvaluate": {
      "package": "@pipcook/plugins-image-classification-tfjs-model-evaluate"
    }
  }
}
複製代碼

使用 Pipcook 配套的 Cli 工具開始訓練:

$ pipcook run 上面寫的配置項.json
複製代碼

看到出現 Epochs 和 Iteration 字樣說明訓練成功開始了。

...
ℹ [job] running modelTrain start
ℹ start loading plugin @pipcook/plugins-image-classification-tfjs-model-train
ℹ @pipcook/plugins-image-classification-tfjs-model-train plugin is loaded
ℹ Epoch 0/12 start
ℹ Iteration 0/303 result --- loss: 5.969481468200684 accuracy: 0
ℹ Iteration 30/303 result --- loss: 5.65574312210083 accuracy: 0.015625
ℹ Iteration 60/303 result --- loss: 5.293442726135254 accuracy: 0.0625
ℹ Iteration 90/303 result --- loss: 4.970404624938965 accuracy: 0.03125
...
複製代碼

兩萬多張樣本以上面的參數在個人 Mac 上訓練大約須要兩個小時,期間電腦的 cpu 資源都會被佔用,因此要找好空閒的時間訓練。若是中途要停下來,用 control + c 是沒用的,須要先用 pipcook job list 查看任務列表,再用 pipcook job stop <jobId> 來中止訓練。

訓練的時長與:樣本的數據量、epochs 和 batchSize 有關。

/* =============== 兩個小時後... =============== */

訓練完成,能看到最終的損失率(越低越好)和準確率(越高越好):

...
ℹ [job] running modelEvaluate start
ℹ start loading plugin @pipcook/plugins-image-classification-tfjs-model-evaluate
ℹ @pipcook/plugins-image-classification-tfjs-model-evaluate plugin is loaded
ℹ Evaluate Result: loss: 0.05339580587460659 accuracy: 0.9850694444444444
...
複製代碼

若是損失率大於 0.2,準確率低於 0.8,那訓練的效果就不太好了,須要調整參數或樣本,而後從新訓練。

同時 pipcook 會在配置項 json 同目錄下建立一個 output 文件夾,裏面包含了咱們須要的模型:

output
  |-- logs    # 訓練日誌文件夾
  |-- model   # 模型文件夾,裏面兩個文件就是最終須要的產物
      |-- weights.bin
      |-- model.json
  |-- metadata.json    # 元信息
  |-- package.json     # 項目信息
  |-- index.js         # 默認入口文件
  |-- boapkg.js        # 輔助文件
複製代碼

模型使用

由於用的 Pipcook 插件底層調用 Tensorflow.js 進行訓練,因此模型能夠直接在前端頁面運行。

咱們先把生成的 model.jsonweights.bin 放在同一目錄下存好。而後找到 metadata.json 中的 output.dataset 字段,是個 Json 字符串,反序列化後找到的 labelArray 屬性的值而且存下來:

// 目前這個順序是隨機生成的,和樣本生成時的順序不同,不要混淆了
const labelArray = ["col-before","h1","solidDown","add-test",...];
複製代碼

準備就緒,只要再寫一些 Tensorflow.js 代碼就能夠進行識別了。

import * as tf from '@tensorflow/tfjs';

const modelUrl = 'model.json 的訪問地址';
// 加載模型
model = await tf.loadLayersModel(modelUrl);

// 對輸入圖像裁剪
const { x, y, width: w, height: h } = getCutPosition(imgW, imgH, offscreenCtx.getImageData(0, 0, imgW, imgH).data, 'white');
ctx.drawImage(offscreenCanvas, x, y, w, h, 0, 0, cutSize, cutSize);

// 圖像轉化爲 tensor
const imgTensor = tf.image
  .resizeBilinear(tf.browser.fromPixels(canvas), [224, 224])
  .reshape([1, 224, 224, 3]);
  
// 模型識別
const pred = model.predict(imgTensor).arraySync()[0];

// 找出類似度最高的 5 項
const result = pred.map((score, i) => ({ score, label: labelArray[i] }))
  .sort((a, b) => b.score - a.score)
  .slice(0, 5);
複製代碼

大功告成

Mar-21-2021 23-20-30.gif

如今能夠開始體驗圖標識別的能力,享受機器學習帶來的便利了。這是一個純前端工具,無需額外後端服務,能夠在靜態網站上部署,很是適合在組件庫網站中查找圖標的場景。團隊有本身的圖標庫也徹底沒問題,只要按照步驟走,就能訓練出專屬的模型。

完整代碼見:github.com/maplor/icon…

總結

從開始寫代碼到模型能用花了一個週末加兩個晚上,而搭建環境和訓練模型的時間佔了很大比例。Pipcook 雖然使用簡單,省去了不少工做,但入門也有很多坑:文檔稀少,插件的參數只有看源碼才明白,運行過程有一些潛規則須要不斷試錯。但願 Pipcook 的文檔能及時更新和維護。

若是有什麼疑問能夠在評論指出,歡迎你們體驗交流~

常見問題

  • 圖標庫若是有 新增/修改 圖標怎麼辦?答:須要從新訓練模型。

參考資料

斯坦福《機器學習》課程

《Tensorflow.js 海量圖標,毫秒級識別!》

Tensorflow.js 官網

Pipcook 官網

一文看懂機器學習

一文看懂卷積神經網絡 CNN

加入咱們

咱們是阿里雲的 TXD(體驗技術)團隊,誠招前端和設計師,22屆的實習生校招也在火熱進行中,感興趣的同窗能夠聯繫我瞭解更多信息:zhaoye.zzy@alibaba-inc.com

mmexport1616514992052.jpg

相關文章
相關標籤/搜索