Unity的AR Foundation經過上層抽象,對ARKit和ARCore這些底層接口進行了封裝,從而實現了AR項目的跨平臺開發能力。html
而蘋果的CoreML是一個能夠用來將機器學習模型與iOS平臺上的app進行集成的框架。git
本文以及本文結尾處的demo工程,將介紹和演示如何使Unity的AR Foundation與蘋果的CoreML一同工做,以實現使用咱們的手來和虛擬物體進行交互的功能。github
Unity AR Foundation手部檢測json
本文參考了Gil Nakache的文章,而且所使用的機器學習模型也來自他的文章。在他的那篇文章中,他描述瞭如何使用Swift在iOS原平生臺上實現相似的功能。swift
Unity Version: 2018.3.13f1xcode
Xcode Version: 10.2.1app
The ARFoundation Plugin: 1.5.0-preview.5框架
iPhone 7: 12.3.1機器學習
爲了方便,我使用了本地pacakge導入的形式。這種實現方式十分簡單,只須要修改工程目錄下Package文件夾內的manifest.json文件,在manifest.json文件中添加本地package便可。async
"com.unity.xr.arfoundation": "file:../ARPackages/com.unity.xr.arfoundation",
"com.unity.xr.arkit": "file:../ARPackages/com.unity.xr.arkit
導入AR Foundation Package以後,咱們就能夠在場景中建立一些相關的組件了,好比AR Session、AR Session Origin等等。
以後在咱們的腳本中,監聽frameReceived
事件來獲取每一幀的數據。
if (m_CameraManager != null)
{
m_CameraManager.frameReceived += OnCameraFrameReceived;
}
爲了使C#語言能夠和Swift語言進行交互,咱們須要先建立一個Objective-C文件做爲橋接。這種方式就是,C#經過[DllImport("__Internal")]
來調用一個Objective-C的方法。以後,Objective-C再經過@objc
來調用Swift。引入UnityInterface.h
以後,Swift能夠調用UnitySendMessage
方法來向C#傳送數據。
這裏有一個示例工程,演示瞭如何爲Unity建立一個使用Swift的原生插件,而且在Unity中打印出「Hello, I’m Swift」。
本文所使用的Unity-ARFoundation-HandDetection工程,它的plugins文件夾的目錄結構以下:
可是,須要注意的是,Unity直接導出的Xcode工程是沒有指定Swift版本的。
所以,咱們須要手動指定一個版本,或者建立一個Unity的腳原本自動設置Swift的版本。
將HandModel添加到咱們的Xcode工程中,以後它會自動生成一個Objective-C model類。可是我但願獲得一個Swift的類,所以咱們能夠在Build Settings/CoreML Model Compiler - Code Generation Language這裏將選項從Auto修改成Swift。
以後,咱們會得到一個叫作HandModel的自動生成的Swift類。
固然,若是你不想老是手動添加,一樣也能夠選擇在Unity中建立一個build post processing腳原本自動添加機器學習模型。
完成了以上步驟以後,基本的交互框架就已經成型了。接下來,咱們就須要使用CoreML來實現手部的檢測和追蹤的具體功能了。
@objc func startDetection(buffer: CVPixelBuffer) -> Bool { //TODO self.retainedBuffer = buffer let imageRequestHandler = VNImageRequestHandler(cvPixelBuffer: self.retainedBuffer!, orientation: .right) visionQueue.async { do { defer { self.retainedBuffer = nil } try imageRequestHandler.perform([self.predictionRequest]) } catch { fatalError("Perform Failed:\"\(error)\"") } } return true }
在Swift中,咱們須要一個CVPixelBuffer
來建立VNImageRequestHandler
以執行手部檢測。一般咱們須要從ARFrame中來獲取它。
CVPixelBufferRef buffer = frame.capturedImage;
所以,下一個問題就是如何從Unity的AR Foundation的C#腳本中獲取來自ARKit的ARFrame指針,而且將其傳遞給使用Objective-C和Swift語言的Hand Detection插件。
在AR Foundation中,咱們能夠從XRCameraFrame
中獲取nativePtr
,它指向一個ARKit的結構,以下所示:
typedef struct UnityXRNativeFrame_1 { int version; void* framePtr; } UnityXRNativeFrame_1;
而且這個framePtr
指向了最新的ARFrame
。
具體來講,咱們能夠調用定義在XRCameraSubsystem的TryGetLatestFrame
方法來獲取一個XRCameraFrame實例。
cameraManager.subsystem.TryGetLatestFrame(cameraParams, out frame)
以後將nativePtr從C#傳遞給Objective-C。
m_HandDetector.StartDetect(frame.nativePtr);
在Objective-C這邊,咱們會得到一個UnityXRNativeFrame_1
指針而且咱們能從其中獲取ARFrame
指針。
UnityXRNativeFrame_1* unityXRFrame = (UnityXRNativeFrame_1*) ptr;
ARFrame* frame = (__bridge ARFrame*)unityXRFrame->framePtr;
CVPixelBufferRef buffer = frame.capturedImage
一旦獲取了ARFrame,接下來就來到了iOS開發的領域。建立一個VNImageRequestHandler對象而且開始執行手部檢測。一旦檢測完成,detectionCompleteHandler回調會被調用而且會經過UnitySendMessage
將檢測的結果傳遞給Unity。
private func detectionCompleteHandler(request: VNRequest, error: Error?) { DispatchQueue.main.async { if(error != nil) { UnitySendMessage(self.callbackTarget, "OnHandDetecedFromNative", "") fatalError("error\(error)") } guard let observation = self.predictionRequest.results?.first as? VNPixelBufferObservation else { UnitySendMessage(self.callbackTarget, "OnHandDetecedFromNative", "") fatalError("Unexpected result type from VNCoreMLRequest") } let outBuffer = observation.pixelBuffer guard let point = outBuffer.searchTopPoint() else{ UnitySendMessage(self.callbackTarget, "OnHandDetecedFromNative", "") return } UnitySendMessage(self.callbackTarget, "OnHandDetecedFromNative", "\(point.x),\(point.y)") } }
以後咱們會獲取在viewport空間的position數據。viewport空間是相對於相機標準化的。 viewport的左下角是(0,0); 右上角是(1,1)。
一旦咱們獲取了viewport空間的位置,就能夠經過Unity的ViewportToWorldPoint
方法將它從viewport空間轉換到world空間。傳遞給該方法的向量參數中的x、y來自Hand Detection的結果,z值則是距離相機的距離。
var handPos = new Vector3(); handPos.x = pos.x; handPos.y = 1 - pos.y; handPos.z = 4;//m_Cam.nearClipPlane; var handWorldPos = m_Cam.ViewportToWorldPoint(handPos);
咱們能夠在Unity中使用這個世界座標來建立新的Object,或者是將已有的Object移動到這個世界座標。換句話說,這個Object的位置會根據手的位置而改變。
正如上文說過的,咱們能夠在Unity中寫一個C#腳原本自動設置生成的Xcode工程中的一些屬性。例如,咱們能夠設置Xcode工程中Build Setting中的Swift Version屬性。咱們甚至還能夠將機器學習模型添加到Build Phases中,好比添加到Compile Sources Phase。這裏咱們會使用定義在UnityEditor.iOS.Xcode
命名空間中的PBXProject類。PBXProject類提供了不少有用的方法,例如AddBuildProperty
, SetBuildProperty
, AddSourcesBuildPhase
。
[PostProcessBuild] public static void OnPostProcessBuild(BuildTarget buildTarget, string path) { if(buildTarget != BuildTarget.iOS) { return; } string projPath = path + "/Unity-iPhone.xcodeproj/project.pbxproj"; var proj = new PBXProject(); proj.ReadFromFile(projPath); var targetGUID = proj.TargetGuidByName("Unity-iPhone"); //set xcode proj properties proj.AddBuildProperty(targetGUID, "SWIFT_VERSION", "4.0"); proj.SetBuildProperty(targetGUID, "SWIFT_OBJC_BRIDGING_HEADER", "Libraries/Plugins/iOS/HandDetector/Native/HandDetector.h"); proj.SetBuildProperty(targetGUID, "SWIFT_OBJC_INTERFACE_HEADER_NAME","HandDetector-Swift.h"); proj.SetBuildProperty(targetGUID, "COREML_CODEGEN_LANGUAGE", "Swift"); //add handmodel to xcode proj build phase. var buildPhaseGUID = proj.AddSourcesBuildPhase(targetGUID); var handModelPath = Application.dataPath + "/../CoreML/HandModel.mlmodel"; var fileGUID = proj.AddFile(handModelPath, "/HandModel.mlmodel"); proj.AddFileToBuildSection(targetGUID, buildPhaseGUID, fileGUID); proj.WriteToFile(projPath); }
使用Unity中的AR Foundation和CoreML,咱們可讓Unity Chan站在咱們的手指上。
本文簡單描述了集成CoreML和AR Foundation的過程。我相信你們可使用它們做出更有趣的內容。
這裏是文中所使用的demo工程。
https://github.com/chenjd/Unity-ARFoundation-HandDetection
https://heartbeat.fritz.ai/hand-detection-with-core-ml-and-arkit-f4c8da98e88e
https://medium.com/@kevinhuyskens/implementing-swift-in-unity-53e0b668f895
http://chenjd.xyz/2019/07/22/Unity-ARFoundation-CoreML/