別@微信團隊了,我用Python給自己戴上了圣誕帽!
這幾天,你的朋友圈一定被“請給我一頂圣誕帽@微信官方”刷屏了……很多不知真相的網(wǎng)友也紛紛求問如何給自己的頭像加上圣誕帽。
圣誕帽火了,上億人瘋狂@微信官方
圣誕節(jié)到了,朋友圈悄悄地掀起一股加帽風,大家紛紛@微信官方,申請給自己的頭像加圣誕帽。
圣誕帽搜索指數(shù)隨之在 24 小時內(nèi)狂飆:
然后,一場花式求圣誕帽的儀式開始刷屏了,還混入了各種奇怪的東西。不料卻發(fā)現(xiàn)@微信官方并不能帶上圣誕帽,才知道“被騙”了。
一大波知道真相的網(wǎng)友隨后開始把@微信官方變成了許愿池:
要錢和禮物的
討房子的
要明星的
要博士帽的
求減肥的
還有送綠帽子的
網(wǎng)友們一邊@微信官方許愿,一邊默默的自己 P 上圣誕帽……戲精這波操作我給滿分!
如何從技術(shù)上實現(xiàn)@微信官方頭像添加圣誕帽?
那么,身為程序員,從技術(shù)的角度思考一下,這事兒到底能不能成呢?
首先看一下網(wǎng)上某猿的分析:
這體現(xiàn)了騰訊強大的人工智能實力,首先微信收到用戶發(fā)送的朋友圈后,便獲取用戶頭像上傳至服務(wù)器云端,然后借助云計算和人臉識別,豬臉識別(來自友商京東的技術(shù))和物體識別,根據(jù)頭像角度、人臉大小,不斷調(diào)整圣誕帽的尺寸和位置,***生成圣誕帽頭像。
網(wǎng)友 @IT大智表示:
這個功能目前幾乎是不可能實現(xiàn)的。首先要明確一點,所有 APP 或者網(wǎng)站都是由代碼程序?qū)崿F(xiàn)的。
假設(shè)微信想給用戶添加圣誕帽,必須要再引用一次圣誕帽的圖片地址,直接添加的話肯定要覆蓋用戶原來的頭像,退一步騰訊可以做到圖片疊加,那么也一定要定義圖片的大小和位置。
所以微信最多做到在固定位置添加一個大小一定的帽子,大家想象一下微信用戶的頭像千差萬別,人頭位置也很不確定,圣誕帽怎么會正好加在頭像頭頂呢?
所以這是個謠言,類似于“明天是馬化騰生日,轉(zhuǎn)發(fā)本條信息到三個群會得到 200Q 幣”等等。
不少好友都發(fā)布了請求添加圣誕帽的朋友圈并@了微信官方,但實際上微信只能@自己的好友,微信并沒有推出自己的官方號,所以你在朋友圈@微信官方,微信的人能收到你的@信息嗎?當然不能。
然而還有更厲害的:
大致來說,當用戶在朋友圈發(fā)布這樣的文字消息:請給我頭像一頂圣誕帽@微信官方。
微信官方會收到這個艾特,并通過該用戶的 openid(微信用戶的唯一標志 ID)獲取到該用戶的微信頭像文件,將該圖片和文本傳至后臺 AI。
所謂“自然語言”,就是我們?nèi)祟惼匠Uf的話,一般情況下,計算機只能理解編程語言,不能理解人類說的話。
但是,騰訊 AI 可以進行“自然語言處理”,將“請給我頭像一頂圣誕帽”這句話進行分解,并解讀其中的意思。
至此,騰訊 AI 理解了該段文本的內(nèi)容,通過智能圖像處理技術(shù),識別到人臉及頭部,選擇好合適的大小及方向,將提前準備好的圣誕帽圖片與頭像疊加,這樣就生成了一張帶圣誕帽的新頭像,并將頭像傳至前端。
微信官方收到圖片后,還是通過讀取 openid 找到該用戶,將該圖片應(yīng)用到該用戶的頭像,并提醒到該用戶,整個過程不到五分鐘。
當然,有些情形會難倒 AI,比如你的頭像圖片中有很多人,AI 就沒法判斷你到底要給哪個人添加圣誕帽,只能給所有人頭上都添加一頂圣誕帽。
所以,大家還是不要調(diào)戲 AI,用一張美美的圖片@微信官方就好!
當然啦,這只是對@微信官方求帽子的調(diào)侃,但是還真的有人站出來說,這事兒能成。
閱文集團***架構(gòu)師徐海峰老師:
他表示使用大數(shù)據(jù)分析+AI+圖片動態(tài)處理能夠?qū)崿F(xiàn),架構(gòu)圖如下:
雖然微信暫時沒有這個功能,但并不代表程序員們實現(xiàn)不了!在人工智能火爆的今天,看程序員如何用 Python 給自己戴上圣誕帽?
用 Python 給頭像加上圣誕帽
大家紛紛@官方微信給自己的頭像加上一頂圣誕帽,當然這種事情用很多 P 圖軟件都可以做到。
但是作為一個學習圖像處理的技術(shù)人,還是覺得我們有必要寫一個程序來做這件事情。
用到的工具
- OpenCV
- dlib(dlib 的人臉檢測比 OpenCV 更好用,而且 dlib 有 OpenCV 沒有的關(guān)鍵點檢測。)
用到的語言為 Python,但是完全可以改成 C++ 版本。
操作的流程
素材準備
首先我們需要準備一個圣誕帽的素材,格式***為 PNG,因為 PNG 我們可以直接用 Alpha 通道作為掩膜使用,用到的圣誕帽如下圖:
我們通過通道分離可以得到圣誕帽圖像的 Alpha 通道,代碼如下:
- r,g,b,a = cv2.split(hat_img)
- rgb_hat = cv2.merge((r,g,b))
- cv2.imwrite("hat_alpha.jpg",a)
為了能夠與 rgb 通道的頭像圖片進行運算,我們把 rgb 三通道合成一張 rgb 的彩色帽子圖,Alpha 通道的圖像如下圖所示:
人臉檢測與人臉關(guān)鍵點檢測
我們用下面這張圖作為我們的測試圖片:
下面我們用 dlib 的正臉檢測器進行人臉檢測,用 dlib 提供的模型提取人臉的五個關(guān)鍵點,代碼如下:
- # dlib人臉關(guān)鍵點檢測器
- predictor_path = "shape_predictor_5_face_landmarks.dat"
- predictor = dlib.shape_predictor(predictor_path)
- # dlib正臉檢測器
- detector = dlib.get_frontal_face_detector()
- # 正臉檢測
- dets = detector(img, 1)
- # 如果檢測到人臉
- if len(dets)>0:
- for d in dets:
- x,y,w,h = d.left(),d.top(), d.right()-d.left(), d.bottom()-d.top()
- # x,y,w,h = faceRect
- cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2,8,0)
- # 關(guān)鍵點檢測,5個關(guān)鍵點
- shape = predictor(img, d)
- for point in shape.parts():
- cv2.circle(img,(point.x,point.y),3,color=(0,255,0))
- cv2.imshow("image",img)
- cv2.waitKey()
這部分效果如下圖:
調(diào)整帽子大小
我們選取兩個眼角的點,求中心作為放置帽子的 x 方向的參考坐標,y 方向的坐標用人臉框上線的y坐標表示。
然后我們根據(jù)人臉檢測得到的人臉的大小調(diào)整帽子的大小,使得帽子大小合適。
- # 選取左右眼眼角的點
- point1 = shape.part(0)
- point2 = shape.part(2)
- # 求兩點中心
- eyes_center = ((point1.x+point2.x)//2,(point1.y+point2.y)//2)
- # cv2.circle(img,eyes_center,3,color=(0,255,0))
- # cv2.imshow("image",img)
- # cv2.waitKey()
- # 根據(jù)人臉大小調(diào)整帽子大小
- factor = 1.5
- resized_hat_h = int(round(rgb_hat.shape[0]*w/rgb_hat.shape[1]*factor))
- resized_hat_w = int(round(rgb_hat.shape[1]*w/rgb_hat.shape[1]*factor))
- if resized_hat_h > y:
- resized_hat_h = y-1
- # 根據(jù)人臉大小調(diào)整帽子大小
- resized_hat = cv2.resize(rgb_hat,(resized_hat_w,resized_hat_h))
提取帽子和需要添加帽子的區(qū)域
按照之前所述,去 Alpha 通道作為 mask,并求反。這兩個 mask 一個用于把帽子圖中的帽子區(qū)域取出來;一個用于把人物圖中需要填帽子的區(qū)域空出來。
- # 用alpha通道作為mask
- mask = cv2.resize(a,(resized_hat_w,resized_hat_h))
- mask_inv = cv2.bitwise_not(mask)
后面你將會看到:
從原圖中取出需要添加帽子的區(qū)域,這里我們用的是位運算操作:
- # 帽子相對與人臉框上線的偏移量
- dh = 0
- dw = 0
- # 原圖ROI
- # bg_roi = img[y+dh-resized_hat_h:y+dh, x+dw:x+dw+resized_hat_w]
- bg_roi = img[y+dh-resized_hat_h:y+dh,(eyes_center[0]-resized_hat_w//3):(eyes_center[0]+resized_hat_w//3*2)]
- # 原圖ROI中提取放帽子的區(qū)域
- bg_roi = bg_roi.astype(float)
- mask_inv = cv2.merge((mask_inv,mask_inv,mask_inv))
- alpha = mask_inv.astype(float)/255
- # 相乘之前保證兩者大小一致(可能會由于四舍五入原因不一致)
- alpha = cv2.resize(alpha,(bg_roi.shape[1],bg_roi.shape[0]))
- # print("alpha size: ",alpha.shape)
- # print("bg_roi size: ",bg_roi.shape)
- bg = cv2.multiply(alpha, bg_roi)
- bg = bg.astype('uint8')
這是背景區(qū)域(bg),如下圖所示,可以看到,剛好是需要填充帽子的區(qū)域缺失了。
然后我們提取帽子區(qū)域。
- # 提取帽子區(qū)域
- hat = cv2.bitwise_and(resized_hat,resized_hat,mask = mask)
提取得到的帽子區(qū)域如下圖,帽子區(qū)域正好與上一個背景區(qū)域互補。
添加圣誕帽
***我們把兩個區(qū)域相加,再放回到原圖中去,就可以得到我們想要的圣誕帽圖了。
這里需要注意的就是,相加之前 resize 一下保證兩者大小一致,因為可能會由于四舍五入原因不一致。
- # 相加之前保證兩者大小一致(可能會由于四舍五入原因不一致)
- hat = cv2.resize(hat,(bg_roi.shape[1],bg_roi.shape[0]))
- # 兩個ROI區(qū)域相加
- add_hat = cv2.add(bg,hat)
- # cv2.imshow("add_hat",add_hat)
- # 把添加好帽子的區(qū)域放回原圖
- img[y+dh-resized_hat_h:y+dh,(eyes_center[0]-resized_hat_w//3):(eyes_center[0]+resized_hat_w//3*2)] = add_hat
***我們得到的效果圖如下所示: