# 智動擬真標記式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」於您創建的專案中

最後,由於深度學習的權重檔案太大,
無法上傳至 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);
}
}
```
接著您可以試著執行程式,應該能夠開啟一個全黑的視窗。

### 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
開發團隊將盡力為您解答,謝謝!