自己動手製作「平均臉」【2】

2021-03-02 悅思悅讀

本系列的上一篇中,我們講解了用Image morphing方法合成人臉圖片的基本原理。

所有代碼都在:https://github.com/juliali/AverageFace 和 https://github.com/juliali/FaceGenderClassification

用OpenCV + dlib 製作「平均臉」

既然知道了原理,我們現在就要開始動手製作了。

再來回顧一下步驟,當我們要將N張人臉照片合稱為一張平均臉的時候,我們首先要處理每一張照片:

【1】獲取其中的68個臉部特徵點,並以這些點為定點,剖分Delaunay 三角形,就如下圖這樣:

[Code-1] 首先要獲得68個臉部特徵點,這68個點定義了臉型、眉毛、眼睛、鼻子和嘴的輪廓。幸運的是,這麼複雜的操作,我們用OpenCV,幾行代碼就搞定了!

detector = dlib.get_frontal_face_detector()

predictor = dlib.shape_predictor(predictor_path) 

img = io.imread(image_file_path) 

dets = detector(img, 1)

for k, d in enumerate(dets):        

    shape = predictor(img, d)  

    points = np.zeros((68, 2), dtype = int)    

    for i in range(0, 68):        

        points[i] = (int(shape.part(i).x), int(shape.part(i).y))          

[Code-2] 接著剖分Delaunay 三角形,其中points就是68個面部特徵點,rect是臉部所在的矩形:

def calculateDelaunayTriangles(rect, points):    

    subdiv = cv2.Subdiv2D(rect);

    for p in points:

        subdiv.insert((p[0], p[1]));

    triangleList = subdiv.getTriangleList();

    delaunayTri = []

    for t in triangleList:

        pt = []

        pt.append((t[0], t[1]))

        pt.append((t[2], t[3]))

        pt.append((t[4], t[5]))

        pt1 = (t[0], t[1])

        pt2 = (t[2], t[3])

        pt3 = (t[4], t[5])        

        if rectContains(rect, pt1) and rectContains(rect, pt2) and rectContains(rect, pt3):

            ind = []

            for j in xrange(0, 3):

                for k in xrange(0, len(points)):                    

                    if(abs(pt[j][0] - points[k][0]) < 1.0 and abs(pt[j][1] - points[k][1]) < 1.0):

                        ind.append(k)                            

            if len(ind) == 3:                                                

                delaunayTri.append((ind[0], ind[1], ind[2]))

    return delaunayTri

【2】然後計算每張臉上各個Delaunay剖分三角的仿射變換,再通過仿射變換扭曲Delaunay三角形:

[Code-3] 計算仿射變換

def applyAffineTransform(src, srcTri, dstTri, size) :

    warpMat = cv2.getAffineTransform( np.float32(srcTri), np.float32(dstTri) )

    dst = cv2.warpAffine( src, warpMat, (size[0], size[1]), None, flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_REFLECT_101 )

    return dst

[Code-4] 通過仿射變換扭曲Delaunay剖分三角形

def warpTriangle(img1, img2, t1, t2) :

    r1 = cv2.boundingRect(np.float32([t1]))

    r2 = cv2.boundingRect(np.float32([t2]))

    t1Rect = [] 

    t2Rect = []

    t2RectInt = []

    for i in xrange(0, 3):

        t1Rect.append(((t1[i][0] - r1[0]),(t1[i][1] - r1[1])))

        t2Rect.append(((t2[i][0] - r2[0]),(t2[i][1] - r2[1])))

        t2RectInt.append(((t2[i][0] - r2[0]),(t2[i][1] - r2[1])))

    mask = np.zeros((r2[3], r2[2], 3), dtype = np.float32)

    cv2.fillConvexPoly(mask, np.int32(t2RectInt), (1.0, 1.0, 1.0), 16, 0);

    img1Rect = img1[r1[1]:r1[1] + r1[3], r1[0]:r1[0] + r1[2]]

    size = (r2[2], r2[3])

    img2Rect = applyAffineTransform(img1Rect, t1Rect, t2Rect, size)

    img2Rect = img2Rect * mask

    img2[r2[1]:r2[1]+r2[3], r2[0]:r2[0]+r2[2]] = img2[r2[1]:r2[1]+r2[3], r2[0]:r2[0]+r2[2]] * ( (1.0, 1.0, 1.0) - mask )

    img2[r2[1]:r2[1]+r2[3], r2[0]:r2[0]+r2[2]] = img2[r2[1]:r2[1]+r2[3], r2[0]:r2[0]+r2[2]] + img2Rect

以上就是製作平均臉幾個關鍵步驟的代碼。

完整代碼在筆者的GitHub的AverageFace項目:https://github.com/juliali/AverageFace


用大合影構造「平均臉」

原理和代碼都非常簡單,不過在實際運行當中,我們需要注意:

【NOTE-1】我們用來做平均臉的單個人臉圖像的尺寸很可能不一樣,為了方便起見,我們將它們全部轉為600*600大小。而所用原始圖片,最好比這個尺寸大。

【NOTE-2】既然是要做平均臉,最好都是選用正面、端正姿態的人臉,面部表情最好也不要過於誇張。

根據這兩點,我們發現:證件照非常合適用來做平均臉。

不過,一般我們很難找到那麼多證件照,卻比較容易獲得另一類照片——合影。

特別是那種相對正規場合的合影,比如畢業照,公司年會、研討會集體合影之類的。這類照片,大家都朝一個方向看,全部面帶克制、正式的微笑,簡直就是構造平均臉的理想樣本啊!

我們只需要將一張大合影中每個人的頭像「切」下來,生成一張單獨的人臉照片,然後在按照4中的描述來疊加多張人臉不就好了嗎?

可是,如果一張大合影上有幾十幾百,甚至上千人,難道我們手動去切圖嗎?

當然不用,別忘了,我們本來就可以檢測人臉啊!我們只需要檢測到每一張人臉所在的區域,然後再將該區域sub-image獨立存儲成一張照片就好了!所有過程,完全可以自動化完成!

當然所用原圖最好清晰度好一點,不然切出來的照片模糊,得出結果就更模糊了。

正好筆者所在的大部門前不久年會,照了一張高清合影。筆者從中切割出1100+張面孔,構造了如下這張基於大合影的平均臉。


用Caffe製作區分性別的「平均臉」

當筆者把自己部門的平均臉給同事看之後,馬上有同事問:為什麼只平均了男的?

回答:不是只平均了男的,是不分男女一起平均的,不過得出的結果看著像個男的而已。

又問:為什麼不把男女分開平均?

是啊,一般人臉能夠直接提供的信息包括:性別、年齡、種族。從大合影中提取的臉,一般年齡差距不會太大(考慮大多數合影場合),種族也相對單一,性別卻大多是混合的,如果不能區分男女,合成的平均臉意義不大。

如果能自動獲得一張臉的性別信息,然後將男女的照片分開,再構造平均臉顯然合理的多。

於是,又在網上找了一個性別分類模型,用來給人臉照片劃分性別。因為是用現成的模型,所以代碼非常簡單,不過需要預先安裝caffe和cv2:


mean_filename='models\mean.binaryproto'

gender_net_model_file = 'models\deploy_gender.prototxt'

gender_net_pretrained = 'models\gender_net.caffemodel'

gender_net = caffe.Classifier(gender_net_model_file, gender_net_pretrained,

                              mean=mean,

                              channel_swap=(2, 1, 0),

                              raw_scale=255,

                              image_dims=(256, 256))

gender_list = ['Male', 'Female']

img = io.imread(image_file_path)

dets = detector(img, 1)

for k, d in enumerate(dets):

    cropped_face = img[d.top():d.bottom(), d.left():d.right(), :]

    h = d.bottom() - d.top()

    w = d.right() - d.left()

    hF = int(h * 0.1)

    wF = int(w*0.1)

    cropped_face_big = img[d.top() - hF:d.bottom() + hF, d.left() - wF:d.right() + wF, :]

    prediction = gender_net.predict([cropped_face_big])

    gender = gender_list[prediction[0].argmax()].lower()

    print 'predicted gender:', gender

    dirname = dirname + gender + "\\"

    copyfile(image_file_path, dirname + filename)

用這個模型先predict一遍每張人臉的性別,將不同性別的照片分別copy到male或者female目錄下,然後再分別對這兩個目錄下的照片求平均,就可以得到男女不同的平均臉了!

這一步的代碼、運行都很簡單,比較坑的是caffe的安裝!

因為筆者用的是Windows機器,只能下載caffe原始碼自己編譯安裝,全過程遵照https://github.com/BVLC/caffe/tree/windows,相當繁瑣。

而且由於系統設置的問題,編譯後,libraries目錄不是生成在caffe源碼根目錄下,而是位於C:\Users\build.caffe\dependencies\librariesv140x64py271.1.0 —— 這一點未必會發生在你的機器上,但是要注意編譯過程中每一步的結果。

訓練自己的性別識別模型

想法是很好,但是,這個直接download的gender classification模型性能不太好。有很多照片的性別被分錯了!

這種分錯看不出什麼規律,有些明明很女性化的女生頭像被分成了male,很多特徵鮮明的男生頭像卻成了female。

能夠看出來的是,gender_net.caffemodel 是一個而分類模型,而且male是它的positive類,所有不被認為是male的,都被分入了female(包括一些根本就不是人臉的照片)。

筆者用自己從大合影中截取的1100+張頭像做了一次測試,發現此模型的precision相對高一些——83.7%,recall低得多——54%,F1Score只有0.66。

考慮到這是一個西方人訓練的模型,很可能它並不適合亞洲人的臉。筆者決定用自己同事的一千多張照片訓練自己的性別分類模型!

我們用caffe訓練模型,不需要寫代碼,只需要準備好訓練數據(人臉圖片),編寫配置文件,並運行命令即可。

命令和配置文件均在筆者github的FaceGenderClassification項目中:https://github.com/juliali/FaceGenderClassification

為了驗證新模型效果,筆者創建了幾個數據集,最大的一個(下面稱為testds-1)包含110+張照片,取自一張從網上搜索到的某大學畢業照中切分出的人臉;另外還有3個size在10-20不等的小數據集。

原始性別分類模型在testds-1上的Precision = 94%, Recall = 12.8% ——完全不可用啊! 新訓練的性別分類模型在testds-1上的Precision = 95%, Recall = 56% ——明顯高於原始模型。

筆者在一臺內存為7G,CPU為Intel Xeon E5-2660 0 @ 2.20GHz 2.19 GHz的機器上訓練(無GPU);訓練數據為1100+張平均8-9K大小的圖片;每1000次迭代需要大概3個小時。

設置為每1000次迭代輸出一個模型。最後一共訓練了14000輪,輸出了14個模型。通過在幾個不同的test data set上對比,發現整體性能最好的是第10次輸出,也就是10000次迭代的結果。這個迭代的結果也放在github中。

區分性別的平均臉

雖然我們有模型來區分性別,但是如果想要「純粹」的結果,恐怕還是得在模型分類後在人工檢驗並手動糾錯一遍。畢竟,再好的模型,F1Score也不是1。

經過模型分類再手工分揀後,筆者把自己同事的照片分成了兩個set:300+女性和800+男性。然後分別構造了平均臉。

是這個樣子的:

對比一下上面那張不分性別的大平均,女生簡直就被融化了——女生對大平均的貢獻只是讓最終的頭像皮膚好了點,眼睛大了點,整個性別特徵都損失掉了!

歡迎掃面下列二維碼關注「悅思悅讀」公眾微信號

相關焦點

  • 手把手:用OpenCV親手給小扎、Musk等科技大佬們做一張「平均臉」(附Python代碼)
    在進入代碼實操之前,讓我們先來簡單了解一下平均臉的歷史。「平均臉」源於達爾文的堂兄Francis Galton在1878年提出的一種新的攝影技術——通過對準眼睛來合成人臉。他認為,通過生成罪犯的平均臉,人們就可以根據面部特徵來預測一個人是否是罪犯。很顯然,他的假設是錯誤的——你不能通過照片來預測一個人是否是罪犯。然而,「平均臉」這個創意卻流傳了下來。
  • 自己動手製作萬花筒
    當然了,萬能的淘寶上面,什麼都可以買到,要買一個自然不是什麼男士,最多幾十個大洋,不過,如果能自己動手製作一個,不但能培養孩子的動手能力,增進親子感情,而且還有無窮的樂趣,它能陶冶孩子的情操,培養他對科學的興趣和愛好,開闊他的視野,增長見識。
  • 自己動手製作四巧板
    它在培養孩子的動手能力和智力提高以及在鍛鍊人們的反定向思維能力上,具有極其重要的使用價值。今天我們來製作一個四巧板,所用的加工工具是雷射切割機。第一步,了解尺寸。四巧板共四塊,其中一個三角形、一個五邊形、兩個梯形。材料準備時,滿足長方形寬和長之比1:6即可(直角邊:斜邊 = 1:√2≈1:1.414≈1:6)
  • 如何自己動手製作派對帽
    今年,為什麼不自己做?使用一些紙張,膠水,閃粉和其他一些材料,您可以自定義自己的派對帽。自製派對帽既簡單,又有趣,接下來我們就開始動手吧!例如,通過切成直徑為12英寸或更大的圓圈來製作一個很大的高帽子。要製作一個較小的帽子(用於寵物或玩偶),切成一個直徑約6英寸的圓圈。切圓時,請儘量保持與繪製的線條儘可能近,以免出現鋸齒狀邊緣。
  • 自己動手製作超實用的貝肯零錢包
    前陣子,楚晗和大家分享過口金包的製作方法。不過有的小夥伴家裡可能沒有口金,所以無法製作。今天,楚晗再教大家一款貝殼零錢包,不需要口金,製作方法也比較簡單,感興趣的朋友可以動手試一試。首先,我們先把圖紙畫出來。
  • 綠松石麵包圈製作教程,可以自己在家動手做!
    有些玩家喜歡麵包圈,但是買的麵包圈又有點貴,如果你會手工,不妨在家,自己動手製作一枚綠松石的麵包圈,今天咱們就來說說麵包圈的製作過程。1. 挑毛料到毛料倉庫裡,挑出一塊已經經過初步處理的原石,看形狀,適合做麵包圈,並畫好線條,稱重,14.39克。
  • 手工自製玫瑰花雙頭皮筋發繩,自己動手製作的時尚髮飾
    所以作為超級熱愛手工製作的手作娘怎麼可以不對玫瑰花下手呢?趕緊動手給自己做一款玫瑰花發繩吧!玫瑰花做成頭飾,生活喵個人覺得還是玫瑰花苞款式的漂亮,這次我們來製作雙玫瑰花苞的發繩,用漂亮的雪紗絲帶來製作玫瑰花苞,保證好看還好用,當然製作方法也保證簡單不難做,還等什麼呢?如此高顏值美物你值得擁有哦,來開啟自己動手的自給自足模式吧!
  • 自己動手製作鮮花標本吧
    自己動手製作鮮花標本吧花木君它適合含纖維素較多的草花,製作立體乾花時使用。製作方法是:於早晨露水幹後採集花材。 製作方法:1、按氯化亞錫2g,硫酸銅5g,硼氫化鈉2g,亞硫酸5ml,蒸餾水95ml的比例製成固定液。2、採摘新鮮花朵,最好選擇尚未完全開足的。修剪整理後,浸入盛有上述固定液內,浸泡12~24小時。3、按蒸餾水95ml,福馬林5ml,亞硫酸2~3ml製成保存液。
  • 航海西路街道兒童公園社區開展「自己動手,快樂分享」三明治製作...
    大河報·大河客戶端記者 田育臣 實習生 劉昌源 通訊員 李根莉為了培養轄區孩子們的生活技能,提高孩子們的動手操作能力,讓孩子們體驗品嘗自製食物的樂趣,9月12日下午,鄭州市中原區航海西路街道兒童公園社區兒童之家聯合華楓社工開展「自己動手,快樂分享」三明治製作活動。
  • 美食:自己動手製作真芋頭蛋糕,買不到就自製
    外面一小塊蛋糕都要好幾十塊,bb們,這時候我們就可以自己動手起來了呀~忘了是什麼時候吃過某某家的真芋頭cake,於是我的腦袋就一直在回味那個味道,真的真的真的太好吃了呀!吃起來一點都不回覺得膩,味道還挺濃鬱的,很好吃耶!芋頭味好濃真的超好呲呀!!芋頭控一定要試試哦!!!芋頭控在哪裡?!有沒有和我一樣一到秋天就開始超想吃芋頭的寶寶們?!
  • 花生糖製作簡單,自己動手做,健康無添加
    花生糖花生是我自小的最愛,家裡隨時都有媽媽製作的花生零食,到外地上中學、大學、研究生,每年寒暑假從家裡返校,行李箱裡最重的東西總是媽媽專門用花生給我製作的各式各樣的美食,這其中少不了花生糖。儘管現在各種零食琳琅滿目,最愛的依然是這又香又脆的花生糖。
  • 自己動手製作珍珠耳環,教程我出了,剩下的看你的了
    現在生活喵作為一名手作娘發現原來耳環什麼的也完全可以自己製作,那還等啥,趕緊給自己製作幾副耳環吧!先上圖給大家看看成品。怎麼樣?生活喵給自己設計的這款耳環是不是還蠻漂亮的?樣式也是很獨特的吧?本著手工製作省錢的原則,這款耳環的用料是非常廉價的,雖然用料廉價但效果還是不錯的。而且只有耳鉤部分生活喵用的是925銀防過敏的,這樣就解決了耳朵過敏問題啦。
  • 曜換上其他英雄的身材,突然變成平均臉,看到最後一個想健身了!
    王者榮耀當中長得帥的英雄特別多,新英雄慢慢變成了平均臉,除了髮型和身材以外,基本上都長得一樣,我們把曜的頭P到裴擒虎的皮膚上,發現曜比裴擒虎更適合這款KPL限定皮膚,不過曜的FMVP皮膚也應該快來了,希望到時候是最帥的男英雄。
  • DIY動手製作一支屬於你自己的唇膏沒那麼難
    DIY自己動手製作一支蜂蜜潤唇膏其實沒想像的那麼難,而且這支潤唇膏絕對天然無激素,即使不小心吃進去都木有關係。情人節就要到了,愛自己就從為自己做一支潤唇膏開始吧,一起來看看它的製作方法。灌的時候速度要快,唇膏很容易凝固,過幾分種,原料就會慢慢在管內固化、成型,唇膏製作就完成了。注意:第一次灌九分滿,因為熱漲冷縮後膏體還會向中間塌陷,形成一凹洞,等第一次灌的凝結後再補灌滿,就不會出現空洞了。
  • 如何自己動手製作布書?
    糖糖媽回家後感覺可以自己DIY嘛。於是,我懷著幸福溫暖的心情準備給糖糖寶寶做一本布書,想到可以製作一本獨一無二專屬於自己和寶寶的數字布書,真是滿滿的開心呢<( ̄︶ ̄)>,一起看看我的「數字布書」製作教程吧~
  • 自己動手製作沉木
    首先我說的沉木不是那種在沼澤埋藏多年已經完全碳化的那種
  • 自己動手來製作一個
    蝴蝶結對於我們喜歡手工製作的小夥伴們來說可以說是樂趣多多,自己手工製作的蝴蝶結更是好玩又有趣。這次生活喵又發現新款式的蝴蝶結了,還是一個明星同款的蝴蝶結,非常俏皮可愛,真是迫不及待地想要跟你們分享呢!就是這款蝴蝶結了,美少女陳小紜佩戴的這款蝴蝶結真是可愛到爆了。果然是美少女氣質啊,可愛得不要不要的。
  • 【手工製作】自己動手製作創意的沙畫
    製作過程一、構思你可以根據自己手中的素材進行構思,比如上面的海灘類的等,因為構思很關鍵,你要一氣呵成,和你反覆修改出來的畫 的效果絕對是不一樣的。二、上膠 這是正式做沙畫的第二步,也是全部沙畫製作中最關鍵的一步。這是正式做沙畫的第二步,也是全部沙畫製作中最關鍵的一步。
  • 自己動手在家製作簡單漂亮的讀書卡,兒子說太簡單了
    學校每周都要求要寫讀書筆記,發讀書卡填寫,自從上三年級後,要求變化了,需要同學們根據要求,自由發揮,自己製作讀書卡。真是幾家歡喜幾家愁。今天我就分享一下,兒子自己動手,在家做的一份讀書卡!簡單易做還漂亮!下面我們一起來看看吧!
  • 不花錢,自己動手製作一雙 Just Don 天價球鞋
    那麼如何自己在球鞋上揮灑創意,變身獨一無二的定製鞋款呢?現在就請通過視頻讓這位名叫 DAVID 的仁兄來告訴你。Supreme x Nike SB Blazer 變身 Just DonSupreme x Nike SB Blazer 有著與 Just Don x Air Jordan 2 相同的菱形格紋的設計元素。