日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

卡通角色表情驱动系列一

發布時間:2023/12/13 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 卡通角色表情驱动系列一 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

分析完ThreeDPoseTracker來做卡通角色的身體驅動,接下來在卡通驅動領域還有一個是表情驅動。對這個真的是一竅不通啊,只能慢慢看論文了。

國際慣例,參考博客/論文:

  • 《Landmark-guided deformation transfer of template facial expressions for automatic generation of avatar blendshapes》
  • 《FACSvatar: An Open Source Modular Framework for Real-Time FACS based Facial Animation》
  • 《Real-time Facial Animation for Untrained Users》
  • 《Modeling Facial Expressions in 3D Avatars from 2D Images》
  • 《Practice and Theory of Blendshape Facial Models》
  • 《Real-Time Facial Motion Capture System》
  • 《Semantic 3D Motion Retargeting for Facial Animation》
  • 《Learning Controls for Blend Shape Based Realistic Facial Animation》
  • 《Performance Driven Facial Animation using Blendshape Interpolation》
  • 《Expression Cloning Jun-yong》
  • 蘋果的ARKit blendShapes
  • 如何在Maya流程下創建一整套面部綁定

簡述

此塊內容受知識限制,描述內容有限或者可能有誤,所以大家有何見解可在評論區或者微信公眾號私信我討論。

從參考文獻來看,表情驅動大致分為三種:

  • 網格形變/編輯(mesh deform)
    • 直接基于面部頂點進行網格形變,比如我之前的博客徑向基函數RBF三維網格變形就是其中一種變形方法,但是在表情驅動中,不同論文也會提出各種不同的變形方法,使變形后的人臉表情更加自然,其核心就是變形算法,例如《Landmark-guided deformation transfer of template facial expressions for automatic generation of avatar blendshapes》、《Deformation transfer for triangle meshes》,可以將捕捉到的人臉網格重定向到一個數字人面部,同步他倆的動作。
    • 比如Blender 2.8 Facial motion capture tutorial利用關鍵點和blender自帶的形變功能,此博客中要求人臉3D模型和人臉關鍵點保持一致(作者進行了人工綁定),所以無法用到卡通角色上;不過如果自己能找到人臉和其它卡通角色的面部捕捉點對應關系,同時也能建立一個真人面部關鍵點動作到虛擬角色對應關鍵點運動的映射關系(因為卡通角色和人臉角色的網格差距可能會很大),也可以做卡通角色的驅動。
  • 骨骼驅動。在面部創建骨骼,利用軟件對面部肌肉和骨骼進行權重綁定,然后調整骨骼的時候,面部肌肉會根據綁定值自動計算面部頂點信息,此時不需要網格形變算法,直接基于權重重新計算人臉蒙皮。這個過程很接近人體蒙皮算法,后續應該會開一個博客解析如何將皮膚與骨骼綁定。
    • 比如B站的這個教程使用maya建立面部骨骼,然后做表情綁定的教程。
  • 使用blendshape融合變形;在美術領域,這個方法就是表情控制器的制作基礎,每個表情控制器對應一套BS。關于詳細可看蘋果的ARKit blendShapes列出的BS類型,針對每個表情預先做好對應的面部模型,從無表情到有表情用一個0-1的系數即可控制每種表情的程度;人臉重建的基本方法3DMM就是基于BS的,核心在于如何獲取當前人臉對應的BS系數。通常有兩種方法
    • 一種是用最小二乘法求解,使得所有表情BS組合起來的人臉的關鍵點更加接近真實人臉提取的關鍵點。比如Realtime Facial Animation for Untrained User,StrongTrack
    • 用幾何的方法直接算,比如Blender & OpenCV - Windows Guide - Facial Motion Capture,預先在做好了卡通角色的面部骨骼動畫控制器,然后根據人臉關鍵點計算出五官變化,從而控制虛擬角色的表情。
    • 另一種是直接用深度學習去獲取BS系數,但是非常受限于數據集,比如openface,具體應用為FACSvatar

還有直接用圖像算法驅動表情的我就不說了,比如最近比較火的“螞蟻雅黑”表情驅動就是基于first-order-model的,不想把它歸到3D驅動中。

【注】上述方法不要區分太開,因為本質都是網格形變,只不過骨骼驅動是通過控制面部骨骼,利用預定義的(骨骼對面部影響)權重來自動計算對應表情的網格;BS也是用畫刷結合權重,利用maya或者其它工具調整面部網格做出來的。所以要么是利用算法做網格變形實現驅動,要么預定義網格變形實現驅動。例如還是這個B站教程使用Maya工具節點綁定角色面部表情教程 - Rig a face using Maya’s Utility nodes,或者對應的圖文解析如何在Maya流程下創建一整套面部綁定,就用骨骼驅動人臉,然后用blendshape實現微調,簡單說就是“blendShapes的優勢就是可以提供更精確的表情,而關節可以實現面部區域的拉伸,增加更多的表情靈活性”。因此每種方法都有自己優劣勢,也可以結合使用,只不過常見代碼中的做法基本都是基于BS來實現表情驅動的。

本文和接下來的系列博文,將先針對BS驅動,分析和抽取幾個源碼內容,來加深理解。

源碼理論與實驗分析

拿strongtrack的源碼開刀,作者提供了效果視頻,在油管上自己觀看

【吐槽】本來想把視頻轉發到B站的,但是坑貨B站把視頻當做恐怖內容了,因為作者的視頻只有頭沒有身體,就被當成恐怖鏡頭了,審核不通過。

準備工作

作者提供了一個人頭BlendShape模型,關于Blender的模型可以去作者提供的谷歌網盤下載,也可以從我的百度云下載

鏈接:https://pan.baidu.com/s/15tlWmJ9grXw4eI6TiVC-Iw
提取碼:871c

為了便于分析理論和BS的操作,我預先把所有的BS模型從Blender文件中導出來了,所有的BS可以在我的github上找到,作者做了50組BS,名字分別如下:

'eyeBlinkLeft', 'eyeBlinkRight', 'eyeSquintLeft', 'eyeSquintRight', 'eyeLookDownLeft', 'eyeLookDownRight', 'eyeLookInLeft', 'eyeLookInRight', 'eyeWideLeft', 'eyeWideRight', 'eyeLookOutLeft', 'eyeLookOutRight', 'eyeLookUpLeft', 'eyeLookUpRight', 'browDownLeft', 'browDownRight', 'browInnerUp', 'browOuterUpLeft', 'browOuterUpRight', 'jawOpen', 'mouthClose', 'jawLeft', 'jawRight', 'jawFwd', 'mouthUpperUpLeft', 'mouthUpperUpRight', 'mouthLowerDownLeft', 'mouthLowerDownRight', 'mouthRollUpper', 'mouthRollLower', 'mouthSmileLeft', 'mouthSmileRight', 'mouthDimpleLeft','mouthDimpleRight', 'mouthStretchLeft', 'mouthStretchRight', 'mouthFrownLeft', 'mouthFrownRight', 'mouthPressLeft', 'mouthPressRight', 'mouthPucker', 'mouthFunnel', 'mouthLeft','mouthRight', 'mouthShrugLower','mouthShrugUpper', 'noseSneerLeft', 'noseSneerRight', 'cheekPuff', 'cheekSquintLeft', 'cheekSquintRight'

至于每一組BS代表的表情,可以自行去Blender查看,或者用meshlab打開我導出的OBJ文件觀看。

提前說一句,從代碼里面可以發現,作者只用了其中10組表情基(BS),詳細如下:

Basis, jawOpen, mouthSmile, mouthSmileLeft, mouthSmileRight, mouthFrown, mouthFunnel, mouthPucker, browInnerUp, browDown

源碼流程

先大概介紹一下作者算法流程,在strongtrack.py的VideoThread函數中可以逐步分析

  • 先使用dlib獲取人臉關鍵點,并且做了微調訓練
  • 分別記錄真人的10個關鍵姿態(與使用的BS對應),表情分別為:Neutral,Jaw Open, Closed Smile, Smile Left, Smile Right, Mouth Frown, Lip Funnel, Lip Pucker, Brows Up, Brows Down
  • 進入實時驅動階段時候,核心在decomp_function.py文件中的findCoeffAll函數,其步驟為
    • 提取嘴部系數:因為與嘴巴相關的BS有八個,因此源碼中先針對關鍵姿態和當前幀的嘴部關鍵點做中心對齊,然后使用稀疏編碼求解系數
    • 提取眉毛系數:因為與眉毛相關的BS只有2個,而且一個向上,另一個向下,區分非常明顯,所以可以直接計算,源碼的方法是計算當前幀的眉毛中心相對于自然狀態下眉毛中心的偏移量,分別除以兩個BS相對于自然狀態下眉毛中心的偏移量,就分別得到了眉毛兩個BS的系數,然后對眉毛向下的情形做一下系數值的約束即可。
    • 提取眨眼系數:這個更簡單了,以內外眼角平均坐標值為眼睛中心,上下眼框分別計算坐標中心,除一個指定的固定值即可。
  • 所有系數計算完畢以后,即可驅動模型表情

源碼簡化

上述的整個源碼流程中,我最看好那個稀疏編碼求解系數的過程,漲知識了。其它的都是利用幾何關系計算,沒什么技術含量。結果代碼還寫了好多好多,其中有很大一部分是寫界面,介紹如何訓練人臉關鍵點檢測模型,以及利用OSC建立python和blender的實時通信。

所以按照博客宗旨,我們僅分析系數計算這一塊內容,跳過關鍵點檢測模型的訓練以及通信代碼的書寫。因此,在實驗時候,10個真人表情對應的2D人臉關鍵點,我直接從對應BS中獲取(去掉深度坐標軸),然后隨便組合兩組BS導出來,作為實時驅動時候的人臉關鍵點。

隨后,在源碼的基礎上抽取了提取表情系數部分的代碼,同時針對性修改和簡化了一下:

  • 對齊關鍵點,嘴巴和眉毛是分開提取系數的,所以它們的位置是分開做中心對齊的,流程就是分別提取關鍵表情和當前幀中人臉對應部位關鍵點,然后根據臉寬縮放,最后按照中心坐標對齊

    ''' 將表情基的人臉關鍵點與當前表情關鍵點對齊 可以用于處理局部關鍵點,源碼中分別處理眼、嘴 ''' def shiftKeyPoses(new_width, centroid, keyposes, config): #Scale keypose based on head width to accomodate for translation or different video size.width_keypose = (keyposes[0][67][0]-keyposes[0][51][0]) # 表情基中第一個姿態的人臉寬度width_fac = width_keypose/new_width # 表情基臉寬/真人臉寬keyposes = np.divide(keyposes, [width_fac,width_fac]).astype(int) # 依據比例系數,將所有表情基關鍵點縮放到真人面部大小new_poses = []for i in range(keyposes.shape[0]): # 遍歷所有的表情基#For brows we take average of eyes pointsif config == 'brows': keypose = np.array(keyposes[i][10:22])#Fo mouth we take average of mouth pointsif config == 'mouth':keypose = np.array(keyposes[i][31:51])centroid_keypose = keypose.mean(0) #表情基中眉毛或者嘴部的中心delta = centroid_keypose-centroid # 表情基眉毛或嘴中心與真人眉毛或嘴中心的偏移量new_pose = keyposes[i]-delta.astype(int) # 利用中心偏移量 重新調整表情基的位置new_poses.append(new_pose) # 將新的表情基加入數組中返回return np.array(new_poses)
  • 嘴部BS系數直接使用sklearn中的SparseCoder函數進行求解,用法也很簡單

    ''' 嘴部BS ''' # 對齊keypose和真人嘴部關鍵點 mouth_center = testpose[31:51].mean(0) shift_kps_mouth = shiftKeyPoses(width_points,mouth_center,keyposes_mouth,"mouth") # 重組嘴部坐標,便于計算 target_mouth = testpose[31:51].reshape((1,-1)) dict_2d_mouth = [] for i in range(shift_kps_mouth.shape[0]):dict_2d_mouth.append(shift_kps_mouth[i][31:51]) dict_2d_mouth = np.array(dict_2d_mouth).reshape(shift_kps_mouth.shape[0],-1) # 提取嘴部運動的系數 coder = SparseCoder(dictionary=dict_2d_mouth.astype("float32"),transform_n_nonzero_coefs=None,transform_alpha=10,transform_algorithm='lasso_lars') coeffs = coder.transform(target_mouth.astype("float32"))
  • 計算左右眉毛的上下運動,計算方法上面說過,就是關鍵表情的關鍵點和當前幀關鍵點的眉毛相對于自然狀態下眉毛的偏移比例

    # 計算眉毛 def calBrow(points, keyposes, config, config2):# 眉毛姿態集中,分別有正常,眉毛上,眉毛下if config == 'left':first = 5last = 10if config == 'right':first = 0last = 5if config2 == "up":target = 1 # 眉毛上else:target = 2 # 眉毛下# 計算挑眉的keypose相對于自然表情下眉毛移動deltashifted = keyposes[target][first:last] - keyposes[0][first:last]deltashifted = (sum(sum(abs(deltashifted))))# 計算當前人臉相對于自然表情下眉毛移動deltapoints = (points[first:last]) - (keyposes[target][first:last])deltapoints = (sum(sum(abs(deltapoints))))# 直接相除,得到比例系數if deltapoints < (deltashifted):val = 1 - (deltapoints / deltashifted)else:val = 0.0# 如果是眉毛向下,可以用垂直比例來輔助計算,不然不準if(target==2):ydelt = keypose[2][first:last] - points[first:last]ydelt = sum(ydelt.T[1])if(ydelt<=0):val = 1.0return val

    眉毛向下的時候可能有點難算,或者出問題,所以額外加了個約束:

    # 約束 def constraint(val,lower,upper):factor = 1 / lowerif lower > val:new_val = 0.0if lower <= val < upper:new_val = (val - lower) * factorif val >= upper:new_val = 1.0return new_val

    調用時候如下:

    # 對齊眉毛關鍵點 eye_center = testpose[10:22].mean(0) shift_kps_eye = shiftKeyPoses(width_points,eye_center,keyposes_brows,"brows") # 分別提取左右眉毛上下運動的系數 val_l_up = calBrow(testpose,shift_kps_eye,"left","up") val_r_up = calBrow(testpose,shift_kps_eye,"right","up") val_l_down = constraint(calBrow(testpose,shift_kps_eye,"left","down"),0.4,0.8) val_r_down = constraint(calBrow(testpose,shift_kps_eye,"right","down"),0.4,0.8)
  • 眨眼這個過程就是計算上下眼眶個子的中心坐標以及整個眼眶中心坐標的關系,但是涉及到常量,這個常量應該是依據場景確定出來的,所以代碼無法過于深究:

    #左眼 eye_top_r = testpose[11:13].mean(0) eye_mid_r = testpose[[10,13]].mean(0) eye_bottom_r = testpose[14:16].mean(0) blink_r_coeff = (eye_top_r[1]-eye_mid_r[1]+28)/48 squint_r_coeff = (eye_mid_r[1]-eye_bottom_r[1]+17)/7.5 #右眼 eye_top_l = testpose[17:19].mean(0) eye_mid_l = testpose[[16,19]].mean(0) eye_bottom_l = testpose[20:22].mean(0) blink_l_coeff = (eye_top_l[1]-eye_mid_l[1]+28)/48 squint_l_coeff = (eye_mid_l[1]-eye_bottom_l[1]+17)/7.5

源碼計算BS的核心就是上面了,有點技術含量的就是計算嘴部BS使用的稀疏編碼。

驗證稀疏編碼和BS結果

針對感興趣的部分做一次驗證,必須少不了可視化。提取嘴部BS的理論和代碼就不重復了。

為了驗證結果,上面說過利用BS做了兩個測試用的表情模型,對應關鍵點如下:

關于BS融合變形的原理,通常是基于偏移量來計算的,也就是常看到的一個公式
R=Base+∑iwiOiR = Base + \sum_i w_i O_i R=Base+i?wi?Oi?
其中OiO_iOi?代表的是表情基相對于自然表情基的頂點偏移量
Oi=Bi?BaseO_i = B_i-Base Oi?=Bi??Base
所以利用代碼得到BS融合結果,需要先把每個表情基偏移量算出來:

# 獲取BS偏移量 # 0:Neutral basicVerts = getVerts('./data/Basis.obj') # 1:Jaw Open jawopenVerts = getVerts('./data/jawOpen.obj') # 2:Closed Smile closesmileVerts = getVerts('./data/mouthSmile.obj') # 3:Smile Left smileleftVerts = getVerts('./data/mouthSmileLeft.obj') # 4:Smile Right smilerightVerts = getVerts('./data/mouthSmileRight.obj') # 5:Mouth Frown mouthfrownVerts = getVerts('./data/mouthFrown.obj') # 6:Lip Funnel lipfunnelVerts = getVerts('./data/mouthFunnel.obj') # 7:Lip Pucker lippuckerVerts = getVerts('./data/mouthPucker.obj')offset = [] offset.append(basicVerts-basicVerts) offset.append(jawopenVerts-basicVerts) offset.append(closesmileVerts-basicVerts) offset.append(smileleftVerts-basicVerts) offset.append(smilerightVerts-basicVerts) offset.append(mouthfrownVerts-basicVerts) offset.append(lipfunnelVerts-basicVerts) offset.append(lippuckerVerts-basicVerts)offset = np.array(offset,dtype="float32")

然后再去組合得到結果:

# 根據系數組合BS newVert = basicVerts for i in range(offset.shape[0]):newVert = newVert + coeffs[0,i]*offset[i] writeResult(newVert)

把結果寫入到OBJ并與測試表情模型做對比,結果如下:

可以發現上下表情幾乎一模一樣,所以驗證成功,使用稀疏編碼計算表情系數是可行的。

后記

稀疏編碼看起來貌似是挺強大的,后續也可以嘗試將整個表情關鍵點都用系數編碼計算一下試試,不要手動計算了,雖然靠譜,但是有點low啊。

還有通常BS是被約束到(0,1)(0,1)(0,1)范圍內的,這個庫貌似無法保證最終表情系數在此范圍,后續再繼續探索一下。

完整的python實現放在微信公眾號的簡介中描述的github中,有興趣可以去找找。同時文章也同步到微信公眾號中,有疑問或者興趣歡迎公眾號私信。

總結

以上是生活随笔為你收集整理的卡通角色表情驱动系列一的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。