AI 明星辨識 SPA 全端專案

作品說明

這是一個用 AI 辨識相片中明星的姓名的網路應用程式。

我們在瀏覽網路的時候,看到一張相片的角落有個很面熟的明星,似乎在某個英國影集中看過,但想不起來。這時候只需要在相片上面複製相片的網址,貼在 CeleRec 網路應用程式裡面,送出網址,你就會知道這個名星是誰了。

我們逛街在路上巧遇一位國際明星,想不起名字,拿起手機,連上 CeleRec ,點上傳,按下快門,可以輕鬆得到明星的名字。

動機

決定 project 題目前,我就想用 clarifai 這家 AI 公司的雲端服務來做,主要是他們有免費運算額度,良好的 API 文件,而且 AI 題材總是很吸引人。

另外,我喜歡看電影和美劇,有時候和朋友在 line 群組裡面聊天傳一些網路上的相片:「欸你知道這是誰嗎?」,這時候會發現除了一些配角不容易認出來,還有一些有些是八卦記者捕捉的野生明星,通常會故意是比較狼狽或發福讓人認不出來的狀況。

在看 clarifai 這家雲端運算的服務時,剛好看到他們有一個已經訓練好的明星辨識的 model 可以串接,我就想:「那就來做明星辨識的 project 吧?」

AI 明星辨識 SPA 的主要功能

目前已經開發上線的功能如下:

桌面、平板、手機三種版型
桌面、平板、手機三種版型

  1. 完全 mobile first 的 RWD,桌面、平板、手機三種版型。
  2. 註冊、登入功能。
  3. 如果輸入有 .jpg 等相片格式的網址,會將明星的臉框起來,並顯示明星的姓名資料。
  4. 如果上傳相片檔案,也會顯示一樣的效果。
  5. 如果上傳非圖片網址,會將這個網址的桌面版網頁截圖辨識,並將結果回傳。
  6. 儲存每個使用者辨識次數,並每次使用後進行更新的功能。
  7. 會將目前進度,例如目前上傳中或截圖的狀態顯示給使用者,增進使用者體驗。
  8. 提供繁體中文、英文、西班牙語三種語言的使用者介面,會自動偵測使用者瀏覽器的語系,使用者也可以自己手動切換。
  9. 後端不儲存明碼,使用者密碼都hash加salt才儲存在資料庫。

馬上試試

明星辨識主要架構

我前端主要用 react + redux ,後端主要用 node js + express + postgreSQL ,前後端的溝通是 REST API 。系統有四個比較主要的功能給使用者使用:註冊、送出相片網址、上傳相片檔案、送出非相片網址。

celerec system design

使用者註冊

前端收到使用者的註冊資料之後,會送往後端,後端將使用者密碼 hash 之後存入資料庫。當使用者登入的時候,前端將密碼送到後端,後端將密碼的 hash 比對後,將使用者的基本資料傳回前端。回傳前端的資料包括,如果先前已經使用過的人,他的使用次數。

使用者送出相片網址及更新使用次數

使用者在前端送出相片網址,其中前端判斷網址裡面有 .jpg 等相片格式的時候,會直接送給後端,讓後端送去 AI 運算服務,後端收到結果會直接傳回前端,前端再 parse 結果,並呈現出來。

送出相片網址的之後,也會向送出一個使用次數加1的要求,後端會向資料庫的使用次數欄位發出加1的要求,然後把更新後的使用者資料,再回傳給前端。

連接 AI 運算放後端是因為如此 api key 才不會曝露於前端,安全性較高。

使用者上傳檔案

clarifai AI 運算服務不收非相片的網址,但是很多使用者其實不太知道怎麼取得含有 .jpg 的網址,為了降低使用者使用門檻,我就加了上傳檔案的功能,讓使用者可以截圖後上傳。

當使用者截圖後上傳檔案的時候,檔案會送到後端去,後端就靜態伺服這個檔案,並且把檔名回傳給前端,前端再根據這個檔名加上後端靜態伺服器的網址,就成了標準相片的網址,再如上一段的過程傳給後端,最後獲得辨識的結果。

使用者送出非相片的網址

即使做了上傳檔案的功能,使用者直覺上還是非常可能會直接把不是圖片網址的網址送出,如此雖然會得到我們給他「請用 .jpg 圖片網址」的提示,使用者體驗可能仍然不夠好。

於是我就在後端再加上一個截圖的套件,前端判斷如果收到的不是圖片的網址,就會傳到截圖用的 end point,後端就去截圖,接著把檔案靜態伺服,並且把檔名回傳給前端。

前端收到後端傳來的檔名之後,就把後端的網址加上去,這就是一個標準的圖片網址,接著就用一開始傳相片網址的方法,送出給後端,然後會收到最後結果,再加以呈現。

主要架構設計理由

要達成以上的功能可能有很多的作法,我之所以在上傳檔案和送出非相片的網址的時候,不是由後端直接產生靜態網址丟給 AI 雲端運算服務,而是丟檔名給前端再丟回後端,不是什麼特殊厲害的理由,單純是因為這個 app 一開始只能辨識相片網址,後來才一次一次的將後面這些功能是加上去,而這種前端拿到後端檔名之後再傳相片網址給後端的安排,可以直接用先前已經完成的送出相片網址的架構,可以少寫一些後端的程式碼,開發起來比較快。

這個 app 的版本變化
這個 app 的版本變化

前端其它細節

前端原始碼

雖然不是一個UI非常複雜的 web app,但是希望能有良好的互動性,未來也有可能增加新的功能,所以一開始就用 React 和 Hooks 當作前端的主要開發框架,程式碼比較好維護、好擴充。

Ajax 的部份是用 ES6 原生 fetch API 就很好用,也不需要灌 jQuery了。多國語言的部份,因為內容不多,也沒有 ssr 的需求,所以就用 react-intl 套件。

為了讓自己和使用者更清楚系統狀態,我才又引入了 redux 管理state,在 async 的部份,使用者可以很清楚在 pending, success, fail 的時候看到不同的訊息。

css 的框架選擇 Tachyons,Tachyons 和 Tailwind 一樣是 Utility-First CSS 的 frame work,UI 並不複雜,所以選比較輕量化的 Tachyons 而非 Tailwind,非常適合 mobile first RWD。多國語言的 drop down menu 就自己刻了,沒有用其他 bootstrap 或 reactstrap 之類的套件。

html 的部份符合 html 5 semantic 語法。logo 的部份則是利用最近自學的figma 亂搞出來的,題外話,使用 figma 的時候非常驚訝,原來前端用上 WebAssembly 竟然可以讓 web app 順暢到這種機乎和 native 沒兩樣的程度。

另外有使用 react-tilt 套件,來讓 hover 的時候 Logo 會動;有用 react-particles-js 套件,讓背景有動畫。

登入表格的地方,所有欄位如果輸入有錯,會有錯誤提示,再點欄位就會清空錯誤提示。畫面支援多人的姓名辨識,編號清楚,而且符合各種大小的螢幕。

支援多人的姓名辨識
支援多人的姓名辨識

React App 我放在 netlify 上,它不會像 heroku 的免費Dyno會睡著,而且世界各地很多 CDN ,速度很快。另外,我在前端 app 一載入的時候會先對後端丟一個 request ,這樣可以把在 heroku 睡覺的後端先喚醒,可以加快使用者登入的速度。

測試

有利用 Mocking Service Woker 來 mock 後端,這套工具甚至可以在開發環境下也可以 mock 後端,相當方便。

用 MSW 搭配 React Testing Library 和 Jest 寫 unit tests 和 integration tests,把大部份的使用者操作邏輯都寫測試保護起來,之後用新學的工具增加功能或重構的時候,就可以很省時間,不怕把邏輯改壞掉。

測試
測試

後端其它細節

後端原始碼

使用後端 nodeJS + expressJS 套件建立 REST API 是我後端的首選,除了可以前後端都統一用 JS 一種語言搞定之外,nodeJS 採用google v8 執行引擎,執行效率很高,而且具有 non blocking I/O,而且這個 app 人臉運算是採用外部的需端服務,後端不需要大量的運算,所以用 nodeJS + expressJS 這個套件組合再適合不過了。

由於會用到資料庫,不過沒有儲存各種不特定檔案的需求,所以選擇 PostgreSQL。database 裡面建了兩個 table,一個是使用者 email 和一個 hash 過的密碼欄位。另一個 table 儲存使用者姓名、email、使用次數、註冊時間等。

所有使用者密碼都 hash 過才儲存到資料庫中,hash 是用 bcrypt 套件,bcrypt 預設加鹽 (Salt),即使這個網站被駭入,偷到的 hash 值也用處不大,所以安全性較高。

我用很受歡迎的 Knex 當作連接 database 的 query builder,knex 不但可以預防 sql injection,安全性較高,而且支援很多種不同的資料庫,而且文件很詳細好用。在註冊使用者的地方,有用 transaction,不會造成使用者資料的 inconsistancy。

檔案上傳的部份,因為 Express.js 無法 parse form-data,所以用了 expressJS 官方出的 multer 套件。當接收到檔案的時候,就儲存到一個 static server 的資料夾。

截取網站的部份是用 capture-website 這個套件,他底層其實是用 chrome 的 puppeteer,我單純用它來截圖和存檔在 static server 的資料夾。

這邊有一個細節提一下,前端上傳檔案的時候,我就讓圖片顯示在 browser 裡,這邊 static server 的相片網址是要送去給 AI 雲端運算的,當接受到運算結果的時候,就會把這個圖檔從 static server 刪除。當前端送來的是非相片網站的時候,有一點點不同,檔名傳回去的時候前端會顯示這個後端 static server 的相片,然後依照一般相片網址的辨識流程進行,但是辨識完之後,後端也是會再把這個相片刪除。

後端和資料庫則是都 deploy 到 heroku,而且 API 的 token 都是用 heroku 的 environment variable 傳入,才不會從 github 的原始碼洩漏出去。

心得

做 project 是一個永無止進的進步,有時候看起來相同的 feature,隨著我技術的不斷的進步,就會不斷的把新學到的業界技術用上去,project 就會愈來愈成熟,先不論這個 app 的實用性如何,希望隨著一個版本一個版本的更替,能盡速達到一個成熟商業等級的作品。