日本少年用姿勢估計變成3D人物,動作實時同步,iOS上也能實現

日本少年用姿勢估計變成3D人物,動作實時同步,iOS上也能實現

大數據文摘出品

不知道從什麼時候開始,3D動畫就熱起來了,但是很多經典動畫3D化後就變味了,人物的肢體動作看上去僵硬了不少。並且,傳統3D靠一幀一幀製作,費時費力。

日本少年用姿勢估計變成3D人物,動作實時同步,iOS上也能實現

現在,你就擁有一個拯救3D動畫的機會!

一位日本中二少年自學了機器學習後,就給自己做了個酷炫的模型,可以把自己的動作實時變成流暢的3D人物動作,而且整個過程非常簡單易操作。

話不多說先看效果圖:

日本少年用姿勢估計變成3D人物,動作實時同步,iOS上也能實現

這個推特名為幸彥青柳(Yukihiko Aoyagi)的日本小哥將3D姿態估計與3D開發平臺和一些渲染引擎(比如Unity)相結合,於此更夠跟準確地跟蹤3D空間中的人體運動。上面的動圖就是針對動作的實時估計和生成。

不過可惜的是,這個項目目前還只支持單人動作,不能實現雙人對打。

日本少年用姿勢估計變成3D人物,動作實時同步,iOS上也能實現

項目已經在GitHub上開源:

https://github.com/yukihiko/ThreeDPoseUnitySample?source=post_page-----e74d7d347c2----------------------

趁著它還沒刷爆朋友圈,趕緊上手試一試!

用3D姿勢估計的Onnx模型移動Unity

青柳君嘗試過多種實現方式,包括WindowsML,ML.Net,Onnx Runtime等,但最終選擇了OpenCVSharp,也就是OpenCV模型導入功能,在Unity中加載和執行Onnx,因為OpenCVSharp在Unity和.Net環境中可以用相同的方式處理,圖像也不會被轉換為Mat格式。

儘管看上去處理起來很容易,但目前還缺少相關數據,青柳君特意總結了他的這次嘗試,將文章公佈在了Qiita上。

相關鏈接:

https://qiita.com/yukihiko_a/items/386e3a86a5e523757707

有關Onnx的代碼部分如下:

// Properties for onnx and estimation private Net Onnx; private Mat[] outputs = new Mat[4]; private const int inputImageSize = 224; private const int JointNum = 24; private const int HeatMapCol = 14; private const int HeatMapCol_Squared = 14 * 14; private const int HeatMapCol_Cube = 14 * 14 * 14; char[] heatMap2Dbuf = new char[JointNum * HeatMapCol_Squared * 4]; float[] heatMap2D = new float[JointNum * HeatMapCol_Squared]; char[] offset2Dbuf = new char[JointNum * HeatMapCol_Squared * 2 * 4]; float[] offset2D = new float[JointNum * HeatMapCol_Squared * 2]; char[] heatMap3Dbuf = new char[JointNum * HeatMapCol_Cube * 4]; float[] heatMap3D = new float[JointNum * HeatMapCol_Cube]; char[] offset3Dbuf = new char[JointNum * HeatMapCol_Cube * 3 * 4]; float[] offset3D = new float[JointNum * HeatMapCol_Cube * 3]; public void InitONNX() { Onnx = Net.ReadNetFromONNX(Application.dataPath + @"\\MobileNet3D2.onnx"); for (var i = 0; i < 4; i++) outputs[i] = new Mat(); } /// <summary> /// Predict /// /<summary> /// <param> public void Predict(Mat img) { var blob = CvDnn.BlobFromImage(img, 1.0 / 255.0, new OpenCvSharp.Size(inputImageSize, inputImageSize), 0.0, false, false); Onnx.SetInput(blob); Onnx.Forward(outputs, new string[] { "369", "373", "361", "365" }); // copy 2D outputs Marshal.Copy(outputs[2].Data, heatMap2Dbuf, 0, heatMap2Dbuf.Length); Buffer.BlockCopy(heatMap2Dbuf, 0, heatMap2D, 0, heatMap2Dbuf.Length); Marshal.Copy(outputs[3].Data, offset2Dbuf, 0, offset2Dbuf.Length); Buffer.BlockCopy(offset2Dbuf, 0, offset2D, 0, offset2Dbuf.Length); for (var j = 0; j < JointNum; j++) { var maxXIndex = 0; var maxYIndex = 0; jointPoints[j].score2D = 0.0f; for (var y = 0; y < HeatMapCol; y++) { for (var x = 0; x < HeatMapCol; x++) { var l = new List(); var v = heatMap2D[(HeatMapCol_Squared) * j + HeatMapCol * y + x]; if (v > jointPoints[j].score2D) { jointPoints[j].score2D = v; maxXIndex = x; maxYIndex = y; } } } jointPoints[j].Pos2D.x = (offset2D[HeatMapCol_Squared * j + HeatMapCol * maxYIndex + maxXIndex] + maxXIndex / (float)HeatMapCol) * (float)inputImageSize; jointPoints[j].Pos2D.y = (offset2D[HeatMapCol_Squared * (j + JointNum) + HeatMapCol * maxYIndex + maxXIndex] + maxYIndex / (float)HeatMapCol) * (float)inputImageSize; } // copy 3D outputs Marshal.Copy(outputs[0].Data, heatMap3Dbuf, 0, heatMap3Dbuf.Length); Buffer.BlockCopy(heatMap3Dbuf, 0, heatMap3D, 0, heatMap3Dbuf.Length); Marshal.Copy(outputs[1].Data, offset3Dbuf, 0, offset3Dbuf.Length); Buffer.BlockCopy(offset3Dbuf, 0, offset3D, 0, offset3Dbuf.Length); for (var j = 0; j < JointNum; j++) { var maxXIndex = 0; var maxYIndex = 0; var maxZIndex = 0; jointPoints[j].score3D = 0.0f; for (var z = 0; z < HeatMapCol; z++) { for (var y = 0; y < HeatMapCol; y++) { for (var x = 0; x < HeatMapCol; x++)
{
float v = heatMap3D[HeatMapCol_Cube * j + HeatMapCol_Squared * z + HeatMapCol * y + x];
if (v > jointPoints[j].score3D)
{
jointPoints[j].score3D = v;
maxXIndex = x;
maxYIndex = y;
maxZIndex = z;
}
}
}
}

jointPoints[j].Now3D.x = (offset3D[HeatMapCol_Cube * j + HeatMapCol_Squared * maxZIndex + HeatMapCol * maxYIndex + maxXIndex] + (float)maxXIndex / (float)HeatMapCol) * (float)inputImageSize;
jointPoints[j].Now3D.y = (float)inputImageSize - (offset3D[HeatMapCol_Cube * (j + JointNum) + HeatMapCol_Squared * maxZIndex + HeatMapCol * maxYIndex + maxXIndex] + (float)maxYIndex / (float)HeatMapCol) * (float)inputImageSize;
jointPoints[j].Now3D.z = (offset3D[HeatMapCol_Cube * (j + JointNum * 2) + HeatMapCol_Squared * maxZIndex + HeatMapCol * maxYIndex + maxXIndex] + (float)(maxZIndex - 7) / (float)HeatMapCol) * (float)inputImageSize;
}
}

模型輸入224x224的圖像,輸出的關節數為24個,熱圖(Heatmap)為14x14。

2D熱圖格式是24x14x14,3D的是24x14x14x14。將其作為與熱圖的座標偏移值,輸出的2D(x,y)變為2x24x14x14,3D(x,y,z)變為3x24x14x14x14。

日本少年用姿勢估計變成3D人物,動作實時同步,iOS上也能實現

 public void InitONNX() { Onnx = Net.ReadNetFromONNX(Application.dataPath + @"\\MobileNet3D2.onnx"); for (var i = 0; i < 4; i++) outputs[i] = new Mat();
}

首先,使用InitONNX()讀取Onnx文件。

由於OpenCV的輸出是通過Mat對象返回的,需要準備四個數組。

 public void Predict(Mat img)
{
var blob = CvDnn.BlobFromImage(img, 1.0 / 255.0, new OpenCvSharp.Size(inputImageSize, inputImageSize), 0.0, false, false);
Onnx.SetInput(blob);
Onnx.Forward(outputs, new string[] { "369", "373", "361", "365" });

// copy 2D outputs
Marshal.Copy(outputs[2].Data, heatMap2Dbuf, 0, heatMap2Dbuf.Length);
Buffer.BlockCopy(heatMap2Dbuf, 0, heatMap2D, 0, heatMap2Dbuf.Length);
Marshal.Copy(outputs[3].Data, offset2Dbuf, 0, offset2Dbuf.Length);
Buffer.BlockCopy(offset2Dbuf, 0, offset2D, 0, offset2Dbuf.Length);

Predict方法參數的Mat對象是正常的CV_8UC3 Mat圖像數據,需要將其轉換為Blob Mat才能傳遞給Onnx,這個過程利用BlobFromImage就能完成。

在Output中,“369”和“373”是3D,“361”和“365”是2D。但如果是Mat對象,處理起來就稍微複雜一些,因為還需要將其轉換為float數組。

然後,通過改變關節數和熱圖大小,找到最大熱圖。

由於3D是一個相當大的循環,最好再做一些改進,但是由於它現在移動得足夠快,保持原樣也是可以的。

在iOS上也能實現3D姿勢估計

去年的日本黃金週,青柳君第一次接觸機器學習,也一直在3D姿勢估計這塊有所鑽研。

今年3月份,他在iOS上實現了3D姿勢估計。據本人推特發言稱,他用了一天時間學習,然後做出了這個模型。

日本少年用姿勢估計變成3D人物,動作實時同步,iOS上也能實現

根據青柳君本人介紹,iOS項目的學習環境是Windows10/PyTorch0.4,執行環境是iPhone XS Max,至於選擇iPhone XS Max的原因,青柳君說,iPhone XS Max的A12處理器功能非常強大。

還是先看看效果如何:

日本少年用姿勢估計變成3D人物,動作實時同步,iOS上也能實現

青柳君準備了2D和3D的數據集,2D數據集是利茲運動姿勢數據集,利茲運動姿勢擴展訓練數據集、MPII人類姿勢數據集、Microsoft COCO;而3D數據集是原始數據集。

在此之前他還做了很多準備,包括從AssetStore購買的數據等,當然還有Unity。

然後就可以利用Unity創建3D角色動畫了,創建角色圖像和座標,包括肩膀、肘部、手腕、拇指、中指、腳、膝蓋、腳踝、腳趾、耳朵、眼睛、鼻子,以輸出身體的中心位置,即肚臍。

該數據集由於許可原因結果變得十分複雜,導致發佈失敗。

日本少年用姿勢估計變成3D人物,動作實時同步,iOS上也能實現

由於這是CG,因此可以隨意更改角色的紋理和姿勢。最初,他希望更改每個時期數據集的內容,以提高泛化性能,但沒有效果,為此大約有100,000個副本用於學習。

即使是用3D版本的圖像,也可以照原樣學習,最後可以獲得相似的圖像,但是無法獲得預期的性能。

將通過PyTorch學習得到的模型導出到Onnx,用coremltools轉換為CoreML模型,此時就算是估計到了相同的圖像,結果也會有所不同,所以準確度未知。

將模型導入Mac,使用XCode的iPhone版本,通過實時捕獲後方攝像機圖像執行3D估計。

XS Max能以大約40fps的速度運行,但是,一段時間,手機會變熱,速記也會下降至約30fps。如果僅用於學習2D模型,其運行速度會接近100fps。

由於這是個3D項目,顯示時無法從攝像機看到的部分,判斷熱圖的閾值已降低到幾乎為零。例如,如果手臂正常可見,熱圖的最大部分為0.5或更高(最大值為1.0);如果看不到手臂,將得到0.2或0.1的值,閾值降低。

但就結果而言,無論身在何處,系統都可以判斷為有人。

Adobe發佈最新動作追蹤軟件

上週,Adobe也發佈了一款用於視覺效果和動態圖形軟件After Effects,該軟件的AI功能能夠自動跟蹤人體運動並將其應用於動畫。

簡單地說,就是能夠把現實人物的動作直接轉換成為動畫。

日本少年用姿勢估計變成3D人物,動作實時同步,iOS上也能實現

與青柳君的機器學習項目的效果相差無幾!

Adobe研究科學家Jimei Yang在演示中說,這一功能利用了Adobe的人工智能平臺Sensei,該平臺用超過10000張圖像進行了訓練,從而能夠識別人體的關鍵點。

據瞭解,人體跟蹤器在源視頻中能夠檢測到人體的運動,胳膊、軀幹和腿部的18個關節點將生成相關跟蹤點,然後將跟蹤點轉移到動畫角色上,利用該功能,快速創建2D人物動畫根本不在話下!

怎麼樣,有沒有覺得打開了新世界的大門?

當然,對於姿勢估計的實現還遠遠不止現在的程度,未來希望不僅是青柳君和Adobe,有更多人都參與到這個領域的研究和學習中來,促進相關領域的發展。


分享到:


相關文章: