---
# System prepended metadata

title: 智動擬真標記式AR引擎 使用教學 Tutorial
tags: [畢業專題]

---

# 智動擬真標記式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
開發團隊將盡力為您解答，謝謝！