最近火爆朋友圈的軍裝照H5大家一定還記憶猶新,其原理是先提取出照片中的面部,然後與模板進行合成,官方的合成處理據說由天天P圖提供技術支援,後端合成後返回給前端展示,形式很新穎效果也非常好,整個流程涉及的人臉識別和影象合成兩項核心技術在前端都有對應的解決方案,因此理論上前端也可以完成人臉識別-提取-合成整個流程,實現純前端的軍裝照H5效果。
前端人臉識別
首先需要的是人臉識別,這個一聽就覺得高大上的東西原理並不深奧,無非是用人的面部特徵規則對影象進行匹配和識別,這項工作前端雖然可以實現,但前端實現基本就只能依據內建規則庫進行匹配,這個庫的質量就決定了識別質量,而通常更成熟的方案是引入機器學習,讓程式不斷自我修正和提高,進一步提高識別率,機器學習的前端庫倒是也有,但把這兩者結合起來的還沒發現,因此對前端人臉識別的準確率不要報太高期望。
現有的前端人臉識別庫不算多,這裡我們選擇的是效果相對好點的trackingjs,這個類庫功能非常強大,庫如其名,它可以完成各種追蹤類的影象處理任務,人臉識別只是其眾多功能之一,而且通過選配外掛,還可以精確識別眼睛、鼻子等五官的位置,貌似稍微折騰一下也可以實現美圖秀秀的效果。
這裡我們只用trackingjs
實現面部識別,初始化一個面部識別任務的程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//例項化 var tracker = new tracking.ObjectTracker(['face']); //識別回撥 tracker.on('track', function(event) { if (!event.data.length) { return console.log('畫面中沒有人臉'); } event.data.forEach(function(rect, i) { console.log(rect);//單個面部資料 }) }) //配置引數 ... |
這樣一個面部識別任務就初始化完成了,呼叫方式如下:
1 2 |
tracking.track('#img', tracker); //其中'#img'引數是目標影象的選擇器 |
在識別回撥中event.data
就是陣列格式的面部資料,如果長度為0則表示影象中沒有人臉或者識別失敗,如果識別成功,單個面部資料的格式如下:
1 2 3 4 5 6 |
{ x: number, //面部位於原圖x軸方向位置 y: nuber, //面部位於原圖y軸方向位置 width:number, //面部區域寬度 height:nubmer //面部區域高度 } |
有了這個面部資料就可以很容易的將該區域從原圖中提取出來,前端當然就用canvas
啦,示例如下:
1 2 3 4 5 6 7 |
var img = document.getElementById("img"); var faceCtx = document.getElementById("mycanvas").getContext('2d'); var theFace = ...; //假設我們識別到了theFace //使用drawImage()方法將面部繪製出來 faceCtx.drawImage(img, theFace.x, theFace.y, theFace.width, theFace.height, 0, 0, theFace.width, theFace.height); |
到這裡我們已經實現了面部識別 + 提取,而且程式碼量也沒多少,其實這裡面有個小坑要在實踐中才會發現,那就是trackingjs
的配置,文件中能找到4個跟識別有關的配置,分別是:
1 2 3 4 |
setClassifiers(classifiers) setEdgesDensity(edgesDensity) setScaleFactor(scaleFactor) setStepSize(stepSize) |
看不懂吧,我也看不懂,而且文件中對他們沒有任何有用的說明,在測試中我只使用了後兩個配置,翻譯過來分別是”比例因子”和”步長”,經過枯燥的人肉測試發現,這兩個引數的有效取值範圍分別在1 - 2
和1.1 - 2
,其中setStepSize
不能為1
,否則會瀏覽器會卡死,所以從1.1開始取值,取值超過2也可以,但識別成功的概率就很低了。通過調整這兩個引數絕大多數影象都可以成功識別,唯獨對面部大特寫很難識別,這可能需要配合另外兩個引數吧,我實在沒耐心繼續人肉測試下去了,感興趣的自己回去玩吧。
前端影象處理
經過上一步的識別+提取我們已經得到了面部影象,要實現合成軍裝照效果我們還需要對面部影象進行處理,使色調與模板一致,將來才能毫無違和感的融合在一起,具體到軍裝照這個例子我們需要將面部重新著色,並達到”做舊”的老照片效果,如果用PS想必大家都會,但在前端怎麼實現呢?
這裡我們需要藉助騰訊前端團隊出品的AlloyImage,這是一個堪稱前端PS的前端影象處理類庫,比如要實現上述效果,我們只需要這樣:
1 2 3 4 5 6 7 8 9 |
var faceImg = document.getElementById("theFace"); faceImg.loadOnce(function() { AlloyImage(this).act("灰度處理").add( AlloyImage(this.width, this.height, "#808080") .act("高斯模糊", 4) .act("色相/飽和度調節", 22, 45, 0, true), "疊加" ).replace(this); } |
然後你就得到了一個做舊的人臉,還是非常簡單的,AlloyImage的使用基本可以說是傻瓜化,感興趣的就自己花個五分鐘去看下官方文件吧,這裡不再贅述。
然後就要說一下我們這個影象處理和人家天天P圖的差距了,雖然我們得到了理想的色調,但要想把隨便一張人臉與特定模板做合成,有兩件事必不可少。首先是面部角度矯正,如果模板是正的而你的照片是歪的,直接暴力拼接肯定很違和,所以需要先識別出面部角度,並糾正到指定角度;然後是面部中心定位,因為人臉識別的結果提取出來後不一定是以面部中心為中心的,所以在合成之前要識別出面部中心線,並以此為依據與模板進行定位。然而這些我們都沒有,所以我們只能對輸入的影象的要求更高,如果輸入了嘴歪眼斜的圖片,結果就只能尷尬了。
最後的圖片合成部分就更簡陋了,先將處理好的面部畫到畫布指定位置,然後將摳好圖的臉部透明png模板鋪在上面,完成。實際過程中需要處理一些小問題,比如要根據模板的面部尺寸將面部影象縮放到合適的尺寸;摳模板時要將邊緣模糊處理,而且儘量保留模板本來的面部輪廓,只將五官摳掉。即便這樣,合成結果還是很容易穿幫,不過純前端處理也沒有更好的辦法了。
效果展示
好了,說的再多不如看個例子,示例提供三種圖片輸入源,分別是本地圖片、遠端圖片、內建示例。其中內建的圖片大部分是提前在PS中糾正過角度的,而且內建圖片會自動匹配到我事先調校好的引數,不出意外可以直接識別出人臉;如果選擇本地圖片作為圖片源,最好選擇頭部姿態垂直的正面照,同時參考內建圖片的 引數設定調節引數,一次識別不成功很正常,需要多調幾次;也可以使用遠端圖片識別,但因為canvas受到跨域策略影響,遠端圖片只能識別不能提取和合成。
示例:純前端軍裝照合成
後記
最初是抱著好奇的心態開始搗鼓這個專案的,雖然最終的合成效果遠遠達不到生產要求,但整個示例擼下來後對人臉識別和圖片處理技術都有了基本的認識,對canvas操作中一些細節問題的解決也略微補足了一下這方面的知識空白,算略有收穫吧。