# 智動擬真標記式AR引擎 使用教學 Tutorial ###### tags: `畢業專題` 程式庫:https://github.com/54bp6cl6/SmartAutoAR 使用教學 Tutorial:https://hackmd.io/MzwI6hvPRUKN1vdY6TrseA 技術文件 Document:https://hackmd.io/8NsHfsW2QfqYN-4aqOfTlg 撰文:鄭人豪 - 2020 / 11 / 1 ### 1. 環境需求 在開始了解範例專案前,請先確保您的環境符合以下要求: 1. 使用 Windows10 作業系統 2. 裝有支援 OpenGL4 的顯示卡,並更新驅動程式至最新版本 3. 已經安裝 Visual Studio 2019 或以上版本 4. 擁有 4GB 以上的可用記憶體空間 ### 2. 設定專案 如果您想要盡快建置一個範例專案,並跑跑看效果如何, 請直接下載此 Repository 中的所有檔案, 其中的 LearnSmartAutoAR 專案提供您一個立刻可執行的程式範例。 或者您可以開啟新專案,並跟隨教學文件一步一步理解如何使用此引擎。 首先,請開啟一個 Windows Forms App (.NET Core) 方案, 將此 Repository 中的 SmartAutoAR 專案加入方案中, 並加入您建立之專案的參考中。 接著請打開 NuGet 套件管理員, 搜尋並安裝「OpenCvSharp4.runtime.win」於您創建的專案中 ![](https://i.imgur.com/aGahtvC.png) 最後,由於深度學習的權重檔案太大, 無法上傳至 Github,請至此處下載:https://drive.google.com/file/d/1TNzVQ0jtUqOeuzaVYccAvKf2SahVURnn/view?usp=sharing 並將其置放到 *SmartAutoAR\Debug\bin\Debug\netcoreapp3.1\Resources\Caffe\\* 路徑中。 ### 3. 創造 GameWindow 要使用本引擎渲染 AR 畫面需要透過 OpenGL 的 Context 等等複雜的原件, 所幸 OpenTK 裡的 GameWindow 已經幫我們打點好一切了, 因此我們會創建一個 GameWindow 作為起始視窗。 1. 請將系統產生的 Form1.cs 刪除,並新增一個 ArForm.cs 類別 將 using 區塊修改如下: ```csharp= using System; using OpenTK; using OpenTK.Graphics; using SmartAutoAR; using SmartAutoAR.InputSource; using SmartAutoAR.VirtualObject; using SmartAutoAR.VirtualObject.Lights; using Bitmap = System.Drawing.Bitmap; ``` 2. 將以下程式碼貼入 namespace 裡的區塊: ```csharp= public partial class ArForm : GameWindow { public ArForm(int width, int height, string title) : base(width, height, GraphicsMode.Default, title, GameWindowFlags.Default, DisplayDevice.Default, 4, 5, GraphicsContextFlags.ForwardCompatible) { } protected override void OnLoad(EventArgs e) { base.OnLoad(e); } protected override void OnRenderFrame(FrameEventArgs e) { base.OnRenderFrame(e); } protected override void OnResize(EventArgs e) { base.OnResize(e); } protected override void OnUnload(EventArgs e) { base.OnUnload(e); } } ``` 3. 將 Program.cs 裡的程式碼修改為: ```csharp= /// <summary> /// The main entry point for the application. /// </summary> [STAThread] static void Main() { Application.SetHighDpiMode(HighDpiMode.SystemAware); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); using (ArForm game = new ArForm(800, 600, "LearnSmartAutoAR")) { //Run takes a double, which is how many frames per second it should strive to reach. //You can leave that out and it'll just update as fast as the hardware will allow it. game.Run(60.0); } } ``` 接著您可以試著執行程式,應該能夠開啟一個全黑的視窗。 ![](https://i.imgur.com/nJJvjJb.png =x300) ### 4. 選定標記物 Marker 在標記式 AR 中,最重要的便是選定 AR 標記,稱為 Marker, 選擇一個好的 Marker 是 AR 效果好與不好的關鍵之一, 通常建議使用菱角明顯、色彩差異較大的圖案, 關於如何挑選好的 Marker,網路上有需多討論,在此就不贅述。 請先選定您的 Marker,並將其列印在白色紙張上。 接著拍攝一段含有 Marker 的影片,將用作接下來的影片輸入。 :::info 範例提供的 Marker 圖案位於: *SmartAutoAR\resources\marker.png* 影片則位於: *SmartAutoAR\resources\video_test.mp4* ::: ### 5. 準備虛擬物體 除了 Marker 外,虛擬物體也是 AR 不可或缺的一部分, 請先準備最少一份 3D 模型檔案,建議使用 obj 規格, :::info 範例提供的3D模型位於: *SmartAutoAR\\resources\\models\\* 來自:https://free3d.com ::: ### 6. 設定影片輸入 開始寫程式囉! 首先我們將剛才準備的影片輸入到程式中, 本引擎已經預先為您準備好了影片輸入方式, 只需要建立物件,就能輕鬆將影片匯入! 首先我們宣告 VideoSource 變數, 並在視窗初始化函數 OnLoad 中初始化。 ```csharp= const string ResourcesPath = "..\\..\\..\\..\\resources\\"; VideoSource videoSource; protected override void OnLoad(EventArgs e) { // 請輸入您的影片路徑 videoSource = new VideoSource(ResourcesPath + "video_test.mp4"); base.OnLoad(e); } ``` :::info 除了影片輸入 VideoSource 外, 我們也準備了圖片輸入 ImageSource 以及影像串流輸入 StreamSource 供您使用, 您也可以根據需求實作 IInputSource 介面來定義自己的影像輸入類別。 詳見:https://hackmd.io/8NsHfsW2QfqYN-4aqOfTlg#InputSource%E5%91%BD%E5%90%8D%E7%A9%BA%E9%96%93 ::: ### 7. 導入模型 在本引擎中,虛擬世界的架構如下: 1. 當偵測到畫面中有某一個 Marker 時,渲染與其對應的場景 Scene 2. 一個場景 Scene 中可以包含多個「燈光」與「模型」 因此我們先宣告並創建一個場景 Scene, 接著創建模型物件,並放到 Scene 中。 由於與3D模型有關的變數將影響到顯示卡, 因此應在視窗關閉函式將其回收: ```csharp= Scene scene; Model stoneMan; protected override void OnLoad(EventArgs e) { // 前略... // 創建場景 scene = new Scene(); stoneMan = Model.LoadModel(ResourcesPath + @"models\Stone\Stone.obj"); // 請輸入您的模型路徑 scene.Models.Add(stoneMan); base.OnLoad(e); } protected override void OnUnload(EventArgs e) { scene.Dispose(); base.OnUnload(e); } ``` ### 8. 認識 ArWorkflow 類別 由於管理與執行 AR 流程是件麻煩的事情, 因此您絕對需要 ArWorkflow 的幫忙, ArWorkflow 類別是管理與控制 AR 流程的關鍵, 大部分時候,您都會透過呼叫它來快速渲染、管理 AR, 廢話少說,趕快來看看他的威力吧! 在初始化 ArWorkflow 物件時,需要傳入影像輸入物件, 接下來他就會主動幫您取得每一幀該呈現的畫面。 然後我們在 ArWorkflow 的 MarkerPairs 屬性中, 設定 Marker 與場景的對應關係, 記得一定要執行 TrainMarkers() 方法, MarkerPairs 中的設定才會生效喔! :::info Marker 與 Scene 屬於多對一關係, 一個 Marker 只能對應一個 Scene, 一個 Scene 則可以重複用在多個 Marker 上。 ::: ```csharp= ArWorkflow workflow; protected override void OnLoad(EventArgs e) { // 前略... // 建立 workflow 物件 workflow = new ArWorkflow(videoSource); // 設定 marker 對應的 scene Bitmap marker = new Bitmap(ResourcesPath + "marker.png"); // 請輸入您 Marker 圖檔的路徑 workflow.MarkerPairs[marker] = scene; workflow.TrainMarkers(); // 修改後一定要執行此函數!! base.OnLoad(e); } ``` ### 9. 渲染畫面 影像輸入、Marker、模型與對應關係我們都準備完畢, 這樣就可以開始 AR 的繪製了, 我們在視窗刷新函式 OnRenderFrame 中, 呼叫 ArWorkflow 為我們執行渲染。 ```csharp= protected override void OnRenderFrame(FrameEventArgs e) { workflow.Show(); SwapBuffers(); // 這是 GameWindow 中繪製新畫面必須的語法 base.OnRenderFrame(e); } ``` 接著來執行看看吧! 如果您的程式運行正確,應該會看到影片可以順利撥放, 並且有一坨黑乎乎的東西出現在 Marker 上方, 但是影片的長寬比明顯失真, 且影片撥放完畢後程式跳出了錯誤訊息。 請別感到慌張,我們距離終點只差一點點了! :::warning 若您使用自己準備的3D模型, 畫面中有可能沒有出現任何黑色物體, 有可能是您的3D模型體積太大導致不在螢幕中, 我們會在接下來的教學中教您修正這個問題。 ::: ### 10. 讓影片重複撥放 在剛剛的測試中,我們發現影片撥放完畢後, 系統會擲回錯誤訊息 **System.ArgumentException: 'Reach the end of video'** 這是在告訴我們影片已經撥放到結尾了,系統不知道該如何處裡, 因此我們將讓它在撥放完畢後不斷重新撥放。 我們檢查 VideoSource 中的 EndOfVideo 屬性, 當影片到達尾端時重新撥放, 並且呼叫 ClearState 清除 ArWorkflow 中的暫存屬性, 避免上一次撥放影片產生的資料影響重播的影片。 ```csharp= protected override void OnRenderFrame(FrameEventArgs e) { // 當影片撥放完畢後重播 if (videoSource.EndOfVideo) { videoSource.Replay(); workflow.ClearState(); } // 後略... } ``` 再次執行程式,這次應能夠不斷重複撥放, 直到關閉程式為止。 ### 11. 解決比例失真問題 長寬比失真是因為視窗長寬比、大小與輸出影片不符, 因此我們從 ArWorkflow 中取得輸出影像的長寬比, 以此改變視窗大小,來解決比例失真問題。 在改變視窗大小後,必須更新同時更新輸出影像的大小, 我們已經將其包裝在 ArWorkflow 的 SetOutputSize 中。 ```csharp= protected override void OnRenderFrame(FrameEventArgs e) { // 確保視窗比例與背景一致 Width = (int)(Height * workflow.WindowAspectRatio); // 後略... } protected override void OnResize(EventArgs e) { // 設定影像輸出大小 workflow.SetOutputSize(Width, Height); base.OnResize(e); } ``` 再次執行程式,您應該能看見比例正常的畫面。 :::warning 這裡採用了較為簡單的方法維持視窗長寬比正確, 但會造成調整視窗寬度無效, 您可以嘗試使用更好的方法來保證視窗長寬比的正確性。 ::: ### 12. 調整模型與加入燈光 畫面上黑糊糊的東西怎麼看都不像是我們早前匯入的精美模組, 原因是模組實在太大了,不符合我們要的效果, 且由於沒有為他打燈,因此模型變成全黑的。 我們回到視窗初始化函式 OnLoad, 透過調整 Model 物件來縮小模型, 並在場景中加入一個強度 0.8 的環境光, 我們可以得到比較正常的 AR 效果。 :::info 環境光指的是虛擬世界中,瀰漫在空間中的一種光, 可以提升虛擬物體的整體亮度,不會造成任何陰影。 本引擎另外還提供點光源、平行光等元件, 更多細節請參考技術文件,或3D渲染相關書籍、網站。 ::: ```csharp= protected override void OnLoad(EventArgs e) { // 前略... // 調整模型大小 stoneMan.Resize(0.5f); // 加入燈光 scene.Lights.Add(new AmbientLight(Color4.White, 0.8f)); // 後略... } ``` 執行程式,您應該能看見最基礎的 AR 效果了, 但是那突兀的虛擬模型顯然愧對「智動擬真標記式AR引擎」這個名稱, 因此下一段,我們將搬出本引擎的大殺器「擬真化模組」。 ### 13. 開啟光源追蹤系統 首先為您介紹第一個擬真化模組「光源追蹤」, 透過觀察 Marker 上的光影分布, 我們可以大約了解並猜測環境中的亮度與光源方向, 並在虛擬場景中模擬類似的光照, 讓虛擬模型有更好的光影表現。 由於此模組會自動加入光源, 因此我們先將之前加入的環境光註解掉: ```csharp= protected override void OnLoad(EventArgs e) { // 前略... // 加入燈光 // scene.Lights.Add(new AmbientLight(Color4.White, 0.8f)); // 後略... } ``` 並在 OnLoad 函式中開啟光源追蹤模組: ```csharp= protected override void OnLoad(EventArgs e) { // 前略... // 開啟光源追蹤模組 workflow.EnableLightTracking = true; base.OnLoad(e); } ``` 運行程式,您應該能看見虛擬物體在光影上的變化, 試著用東西遮擋光線看看,您甚至能看見虛擬物體也因為陰影而變黑了呢! ### 14. 開啟色彩融合模組 接著為您介紹第二個擬真化模組「色彩融合」, 使用訓練好的類神經網路, 透過觀察輸入影像中的色彩分布, 調整虛擬物體的顏色,使其更融入環境中。 在 OnLoad 函式中開啟色彩融合模組: ```csharp= protected override void OnLoad(EventArgs e) { // 前略... // 開啟色彩融合模組 workflow.EnableColorHarmonizing = true; base.OnLoad(e); } ``` 運行程式,您應該能看見虛擬物體顏色與環境主色調更貼合, 同時保留了光影變化與質地表現。 :::warning 由於色彩融合技術尚未成熟完備,因此運算速度較慢, 且因輸入進類神經網路導致畫值降低, 建議僅用於圖片輸入。 ::: :::danger 色彩融合模組將大量占用系統資源,請謹慎使用, 並於使用完畢後透過 workflow.EnableColorHarmonizing = false; 關閉,以釋放系統資源。 ::: ### 15. 更多資訊 若需要深入研究本系統提供的功能,或理解其架構與運作流程, 請參考技術文件:https://hackmd.io/8NsHfsW2QfqYN-4aqOfTlg 創建 Issue 或來信:54bp6cl6@gmail.com 開發團隊將盡力為您解答,謝謝!