---
# System prepended metadata

title: 產投_AI深度學習及人臉辨識實作班(2020/08/01-2020/08/15)
tags: [人臉識別, 機器學習, azure, C#]

---

---
title: 產投_AI深度學習及人臉辨識實作班(2020/08/01-2020/08/15)
tags: C#,azure,機器學習,人臉識別
description: 本門課的重點在於讓學生能夠利用現有的API對人臉進行相關的分析，並利用C#的程式開發出一套簡易的人臉識別系統並做出相關的應用，建議修課時數為18小時
lang: zh
---

# AI深度學習及人臉辨識實作班
###### tags: C#,azure,機器學習,人臉識別

## 概觀

### 認知服務簡介
傳統我們在做影像辨識，語音識別等相關服務時會具備相關AI知識與機器學習的能力，而這個限制將會使開發者將重點放在如何有效的識別影像與分析，相對於用在應用領域上的時間將會大幅降低。透過微軟的認知服務引擎，開發者可以擁有更多的時間與精力將重點放在如何利用已經做好的AI引擎來進行有趣的應用，利用微軟所提供的認知服務可以大幅降低入門門檻，使用者僅需要呼叫API的方式就能將視訊、音訊、語音、搜尋、理解和決策制定加速功能內嵌於您的應用程式。

### 上課時數
* 本門課的重點在引導學生如何利用現有的AI引擎開發出一套<font color="blue">人臉識別系統</font>。
* 建議的授課時數為<font color="blue">18</font>小時。

### 課程大綱
本門課程大約分為三個階段
1. 認識微軟認知服務
2. 利用現有工具(如:Postman)來了解認知服務引擎所提供的API與相關操作
3. 利用C#開發人臉分析與識別系統

### 認知服務引擎
微軟所提供的認知服務引擎依據類別大約分為以下幾個部分
1. 決策
2. 語言
3. 語音
4. 識別
5. 網路內容搜尋

### 課程重點
本門課的重點會放在<font color="blue">識別</font>中的<font color="blue">臉部</font>部分，在認知服務中識別又因對象分為
1. Computer Vision : 分析影像中的內容
2. 自訂視覺 : 使用者提供相關的相片自行訓練相關的圖片
3. 表單辨識器 : 從文件擷取文字、索引鍵/值組與表格
4. 筆跡辨識器 : 辨識數位筆跡與手寫，並指出常見圖形
5. 影片索引器 : 分析影片的視覺和語音通道，並編製其內容索引
6. 臉部 : 分析相片中人物的資訊，以及判斷人物是否屬於同一人

---

## 課前準備工作

### 註冊微軟帳號
:::info
:bulb: 為了使後續開發服務更能夠的跟微軟相關環境整合，這邊建議使用者使用取得新的郵件地址來進行註冊，以減少後續不確定的因素。
:::
* 首先我們先需要至[微軟網站](https://login.microsoftonline.com/)註冊一組微軟帳號。

    ![](https://i.imgur.com/JVDA8Pb.png)

* 若沒有微軟帳號的可以點選<font color="blue">建立一個吧！</font>，建立帳號可以使用電話號碼或是現有的 E-Mail 來申請微軟帳號，或是直接註冊一組新的微軟電子郵件地址。

    ![](https://i.imgur.com/QYuzdYl.png)

* 點選<font color="blue">取得新的電子郵件地址</font>來註冊帳號

    ![](https://i.imgur.com/XOpiBuU.png)

* 註冊的部分相當簡單，僅需要輸入想要的 EMail 點選下一步，再輸入想要的密碼即可完成。

    ![](https://i.imgur.com/AhRrCv3.png)

---

### 申請 Microsoft Azure 服務
:::danger
[警告] 在申請 Microsoft Azure 服務時請務必注意各個選項，以免有額外的費用產生。
:::
* 有了微軟的帳號之後，接下來我們可以到 [Azure 官方網站](https://azure.microsoft.com/zh-tw/free/cognitive-services/) 來申請免費的服務，每個申請微軟 Azure 服務的帳號可享有１年有限額額度的使用，包含了為期１年免費的熱門服務、6100元台幣額度內的付費額度、以及至少２５種以上永久免費的服務，為了能夠註冊相關服務，我們可以點選<font color="blue">開始免費使用</font>已啟用 Azure 的各類型服務。

    ![](https://i.imgur.com/usbGZyX.png)

* 為了完成註冊，使用者需準備一隻可以接受簡訊的手機與信用卡，在填寫完相關資訊後點選下一步即可完成註冊。

    ![](https://i.imgur.com/RSmqku6.png)

* 註冊成功後我們可以看到以下畫面，此時我們即可開始使用Microsoft Azure 相關服務。

    ![](https://i.imgur.com/etcCX5a.png)

---

### Microsoft Azure 後台管理
* 註冊完成之後，我們可以在 Microsoft Azure [後台界面](https://portal.azure.com/#home)中看到目前啟用的相關服務。

    ![](https://i.imgur.com/Nv8tFtI.jpg)

---

#### 查看訂用帳戶
:::info
:bulb:雖然我們已完成 Microsoft Azure 的註冊，但是我們還需要完成訂用帳戶的選定，才可以開始使用免費的服務喔。
:::
* 我們可以在訂用帳戶中查看目前帳號的相關資訊，若訂用帳戶中沒有任何訂用帳戶名稱的話需要啟用免費試用版的權限。

    ![](https://i.imgur.com/nHFIec1.png)

---

#### 新增免費試用帳戶
:::info
:bulb:建議先請用免費試用的權限，若具有學生身份可額外申請 Azure for Students 權限。
:::
* 我們可以先點選<font color="blue">新增</font>來啟用相關免費試用版的權限。

    ![](https://i.imgur.com/89A1ujZ.png)


:::danger
[警告] 為了避免信用卡收到額外的服務費用，請務必確認支援方案是<font color="blue">無技術支援</font>，否則就算是免費方案仍然有機率被收取相關費用。
:::
* 為了啟用免費試用，我們可以點選免費試用中的 <font color="blue">Click here to upgrade</font>，在訂用帳戶易記名稱中可以輸入一組自己想要的名字，並選擇<font color="red">無技術支援</font>，完成之後點選<font color="blue">升級</font>。

    ![](https://i.imgur.com/SrGwWvX.png)

* 當完成升級後我們可以在訂用帳戶中看到剛剛申請的方案。

    ![](https://i.imgur.com/gnAGsrl.png)

---

#### 新增學生方案
:::info
:bulb:使用學生方案須具備大專院校學生身份，並有學校提供的EMail才能通過認證喔。
:::

* 若您具有學生身份可以使用學生方案來進行註冊，與免費方案不同的是使用學生方案註冊不需要使用到信用卡來驗證，這對沒有信用卡的學生來說相當方便，但學生方案在使用上具有一定的限制，相關的限制可參考[微軟官方說明](https://azure.microsoft.com/zh-tw/offers/ms-azr-0170p/)，為了啟用學生方案我們可以在訂用帳戶中來新增 Azure for Students。

    ![](https://i.imgur.com/DSlHL3D.png)

* 我們可以先在 Azure for Students 點選選取供應項目，此時會跳到 [Azure for Students 的頁面](https://azure.microsoft.com/zh-tw/free/students/)，我們可以點選<font color="blue">立即啟用</font>來啟用相關服務。

    ![](https://i.imgur.com/wSlo2zV.png)

* 為了驗證學生身份，這邊需要提供一組學校的 Email 來進行驗證。

    ![](https://i.imgur.com/oyoOFR3.png)

* 完成上述的動作之後，微軟會寄發一封指定的郵件到您的學校信箱，根據信箱的連結來驗證學生身份。點選信箱中的連結後並勾選同意相關協議，之後點選註冊即可完成學生身份驗證。

    ![](https://i.imgur.com/EyeozKl.png)

* 此時我們可以在訂用帳戶中看到目前除了免費帳號以外，還有學生免費的服務。

    ![](https://i.imgur.com/t68kKuX.png)

:::success
恭喜，到目前為止我們已經完成基本的服務註冊摟。
:::

---

## 開始使用人臉識別資源
### 建立人臉識別資源
:::info
:bulb: 雖然我們已經完成 Azure 服務的註冊，但是在使用每項資源前都需要完成相關的申請喔。
:::

:::info
:bulb: 在訂用帳戶的部分會依據帳號的不同有不同的選擇(如：免費帳戶、學生帳戶 等)
:::

* 為了使用 Azure 所提供的人臉識別服務，我們需要先建立人臉識別資源，我們可以在[後台界面](https://portal.azure.com/#home)中點選<font color="blue">建立資源</font>，我們可以在AI+ 機器學習服務中看到<font color="blue">臉部</font>服務，我們可以在訂用帳戶中選擇免費試用版權限。

    ![](https://i.imgur.com/ES3aWOd.png)

:::info
:bulb: 一個資源群組下可以擁有多個服務（如：臉部、電腦圖像、語音等）
:::

* 為了使資源能夠有效的分類，首先我們需要先為服務建立相關的資源群組，我們可以在臉部中訂用帳戶下方的資源群組點選<font color="blue">新建</font>，我們可以輸入一組易記的名稱來建立。

    ![](https://i.imgur.com/ohRvmuG.png)

:::info
:bulb: 每個區域能提供的服務不盡相同，請在選擇區域前務必確認該區域有支援該服務。
:::

* 在執行個體資料中可以依據使用環境選擇適當的區域，這邊使用<font color="blue">日本東區</font>來示範，名稱的部分可以輸入好記的名字用來代表<font color="blue">臉部</font>服務。

    ![](https://i.imgur.com/mrfIKnI.png)


:::danger
[警告] 為了避免帳單收到額外的費用，請盡量使用免費(F0)服務
:::

* 依據選擇定價策略的不同，每個策略提供的服務數量與限制也會有所不同，根據臉部服務其相關[定價策略](https://azure.microsoft.com/zh-tw/pricing/details/cognitive-services/face-api/)分為兩個部分，完成上述的動作之後，點選<font color="blue">檢閱＋建立</font>即可完成建立服務。

    1. 免費-F0(每分鐘只能查詢20次，每月上限3萬次)
    2. 付費-S0(每秒呼叫10次)

* 當我們建立完成之後可以在 Azure 後台中的資源群組中查看目前所建立的資源。

    ![](https://i.imgur.com/PaQQ2uy.png)

:::success
恭喜，我們已經完成臉部服務的申請了。
:::

---

### 管理人臉識別資源
* 我們可以在資源群組中查看目前群組中啟用的服務，為了要查看<font color="blue">人臉</font>服務，我們可以在剛剛建立的識別資源中點選臉部資源（D20200801）。

    ![](https://i.imgur.com/xDk3tom.png)

* 為了查看資源使用率，我們可以在概觀的部分查詢出目前帳號的狀態、位置與配額資訊。

    ![](https://i.imgur.com/BHlT4UY.png)

* 由於後續在呼叫臉部 API 時需要使用相關<font color="blue">端點</font>資訊與<font color="blue">金鑰</font>資訊，這兩樣資訊我們可以在金鑰與端點頁面中查看到。

:::danger
[警告] 金鑰是在呼叫API中非常重要的資訊，外洩時可能會造成帳單有額外費用的風險，請務必妥善保護各自的金鑰。
:::

---

## 快速入門
### 準備工作
為了讓使用者能夠快速體驗 Azure 中機器學習的魅力，我們在接下來的章節中會介紹如何快速利用現有工具來帶各位體驗相關 Azure Rest API 的服務，在開始使用前請使用者準備下列幾項東西。
1. 一張到多張的相片
2. 電腦需安裝 [Postman](https://www.postman.com/downloads/) 工具
3. 完成相關服務註冊並取得金鑰與端點

### Postman 安裝與使用
Postman 是一套程式設計師在開發與測試API必備的工具，利用 Postman 能夠讓程式設計師快速的測試 API 是否能按照我們所預期的方式來使用，若還沒有安裝 Postman 的同學可以在[Postman 官網](https://www.postman.com/downloads/) 下載並安裝使用。

---

## 開始使用 Face API
### 識別相片中的人物資訊
:::info
:bulb: FaceAPI 是微軟認知服務引擎中用來識別相片中人物的方法。
:::

:::info
:bulb: 在大多數時候，Detect 是一個相當基礎的方法，我們首先可以利用這個方法識別出相片中人物的位置，與相關的資訊，包含：年齡、性別、是否有配戴眼鏡、口罩等相關資訊。
:::

#### 呼叫基礎 Detect API
* 為了讓使用者了解 Detect API 的用法，這邊我們使用 Postman 軟體來讓使用者快速體驗 Detect API 的魅力，首先我們先開啟 Postman 軟體，並在網址列的部分輸入下列資訊。

    ```
    https://{endpoint}/face/v1.0/detect
    ```

    :::warning
    :bulb: {endpoint} 的部分需輸入我們在臉部服務中申請的<font color="red">端點</font>位址
    :::

* 在HTTP Method 的部份我們需要設定為POST的方法

    ![](https://i.imgur.com/vQ00h7V.png)


* 點選 Headers 欄位，並在 Headers 新增兩個屬性 Content-Type=application/octet-stream 與 Ocp-Apim-Subscription-Key = 你在臉部服務中申請的<font color="red">金鑰</font>

    ![](https://i.imgur.com/5XwBsLU.png)

* 在 Body 中選擇 binary 格式，並在下方點選要分析的相片，完成之後點選 Send 。

    ![](https://i.imgur.com/NkbqBtc.png)

* 在完成上述的動作後可在下方的 Response 看到下列資訊。

    ```json=
    [
        {
            "faceId": "58fd8c0a-27ec-4c2e-bfef-20b184505ed0",
            "faceRectangle": {
                "top": 637,
                "left": 747,
                "width": 100,
                "height": 100
            }
        }
    ]
    ```

---

#### 新增 Detect 查詢參數
Detect 的查詢參數相當的多樣，以下我們將介紹幾個常用的查詢參數。
1. returnFaceId：查詢後是否要顯示 FaceId
2. returnFaceLandmarks：是否要顯示瞳孔、鼻子、嘴巴、眉毛...等臉部資訊位置。
3. returnFaceAttributes：顯示要查詢的進階資訊
    * age 顯示使用者的年齡
    * gender 顯示使用者的性別
    * smile 顯示使用者的微笑程度 (0,1)
    * facialHair 顯示使用者各種鬍子的程度 (moustache , beard , sideburns) 其程度範圍為 (0,1)
        ![](https://i.imgur.com/k8Bnh8r.jpg)
    * headPose 頭部角度 (pitch roll yaw)
        ![](https://i.imgur.com/WockYiH.png)
    * glasses 是否配戴眼鏡 
        * 'NoGlasses' ： 無佩戴眼鏡
        * 'ReadingGlasses' ： 粗框眼鏡
        * 'Sunglasses' ： 太陽眼鏡
        * 'SwimmingGoggles' ： 泳鏡
    * emotion 臉部情緒
        * neutral 呆滯程度
        * anger 生氣指數
        * contempt 鄙視指數
        * disgust 厭惡指數
        * fear 恐懼指數
        * happiness 幸福指數
        * sadness 悲傷指數
        * surprise 吃驚指數 

* 我們可以在 Query Params 中輸入想要查詢的參數，如returnFaceId、returnFaceLandmarks、returnFaceAttributes，按下 Send 後即可在 Response 中看到剛剛所查詢到的相關資訊。

    ![](https://i.imgur.com/L1tuyI1.png)

:::success
恭喜，到目前為止我們已經掌握了基礎 Detect 的查詢方法摟，若要學習完整的方法可參考[微軟API](https://westus.dev.cognitive.microsoft.com/docs/services/563879b61984550e40cbbe8d/operations/563879b61984550f30395236)喔。
:::

---

### 建立相片識別群組
在上個章節中我們已經了解到如何上傳相片並分析相片中的人物屬性，在這個章節裡我們會繼續介紹如何分析相片中的人物是誰。
:::info
:bulb:建立相片識別群組的方法有非常多種，這邊以 FaceList 為例，同學可以自行練習其他方法喔。
:::
#### 建立 FaceList 清單
* 建立相片群組的第一個步驟首先就是建立 FaceList 清單，這邊我們一樣使用 Postman 來做示範，首先我們在網址列的部分輸入以下資訊。

    ```
    https://{endpoint}/face/v1.0/facelists/{faceListId}
    ```

* {endpoint} 跟前面章節所描述的一樣，需使用註冊<font color="blue">臉部</font>服務的端點資訊，而 {faceListId} 需要使用者自行產生一組Guid，若使用者不熟悉規則的話可在[這邊](https://www.guidgenerator.com)產生。

    ![](https://i.imgur.com/Knkizce.png)

* 在 Headers 的部份我們需要將 <font color="blue">Content-Type</font> 設定為 <font color="blue">application/json</font>

    ![](https://i.imgur.com/NXAqBWZ.png)

    :::spoiler 備註
    Content-Type 的格式會依據使用者在Http欄位中Body的檔案格式而有所不同，以微軟API為例，若上傳的是檔案需要設定為 <font color="blue">application/octet-stream</font> 若JSON格式需設定為 <font color="blue">application/json</font>
    :::

* 在 Body 中我們需要使用 raw 的方式來上傳我們所設定的JSON，並在檔案格式中選擇為JSON

    ```json=
    {
        "name": "資管系甲班人物清單",
        "userData": "ＯＯ大學資管系甲班人物列表",
        "recognitionModel": "recognition_02"
    }
    ```
    
    :::danger
    recognitionModel 是一個非常重要的屬性，後續再分析相片中的人物是否相同時需使用同一個 recognitionModel 若選用的 recognitionModel 不同會造成識別失敗。
    :::

* 完成上述的設定後可以點選 Send 查看建立後的結果，若看到 Status 的狀態代碼為 200 就代表新增成功了。

    ![](https://i.imgur.com/izLpdTK.png)

* 若新增失敗會在 Body 中看到失敗的原因，並且 Status 不會為 200。

    ![](https://i.imgur.com/LduNRfx.png)

:::success
恭喜，到目前為止我們已經完成建立 FaceList 的任務摟。
:::

---

### 查看目前所有的相片識別群組
為了瞭解目前該資源區域之下有哪些 FaceList 清單，我們可以利用 GET 的方式來查詢該資源下擁有哪些清單喔。
```
https://{endpoint}/face/v1.0/facelists
```

* 此外我們也可以在屬性中新增returnRecognitionModel=true的欄位，來查看各個清單所使用的RecognitionModel。

    ![](https://i.imgur.com/rhH3YDO.png)

---

### 將相片新增至 FaceList 中
為了讓微軟的識別引擎認識我們，我們需要在 FaceList 中加入識別臉孔，這邊我們一樣使用Postman來示範如何新增相片並將相片加入到清單中，首先我們需要在網址列中輸入下列資訊。
```
https://{endpoint}/face/v1.0/facelists/{faceListId}/persistedFaces
```

* 在參數的部分我們有以下幾個參數要輸入
    * faceListId：需要將相片加入到的faceListID
    * userData：使用者對相片自訂的描述(可選)
    * detectionModel：識別模型（注意需要與faceListID中設定的一致）

    ![](https://i.imgur.com/nt6F25G.png)

* 在 Headers 中我們一樣需要設定 Content-Type 與 Ocp-Apim-Subscription-Key 兩個參數

    ![](https://i.imgur.com/ZyeUIhx.png)

* 此時在 Body 欄位中即可選擇我們想要上傳的相片，若成功會顯示 persistedFaceId 代表已將相片上傳至清單中，此時我們需要將這組persistedFaceId記錄起來，以利後續解析相片中的人物。
    :::danger
    利用這種方法新增的相片人物中僅支援一人，若超過人數伺服器將會回應錯誤訊息。
    :::
    ![](https://i.imgur.com/DMpmQ38.png)


:::success
恭喜，到目前為止我們已經完成將相片加入到清單中的工作了。
:::

---

### 查看群組中的相片清單
在完成相片上傳後，我們可以利用下列網址，並利用 GET 的方法來查詢該清單中所建立的人臉清單。
```
https://{endpoint}/face/v1.0/facelists/{faceListId}
```
* 在參數設定上我們可以使用 returnRecognitionModel=true 來確認每張相片所使用的 RecognitionModel
![](https://i.imgur.com/oB9qBH2.png)

### 刪除清單中指定的persistedFaceId
再將相片上傳至清單後若發現上傳錯誤，我們可以利用下列網址並使用 DELETE 的方法來刪除清單中所建立的persistedFaceId
```
https://{endpoint}/face/v1.0/facelists/{faceListId}/persistedFaces/{persistedFaceId}
```

* 在這個方法中我們除了需要設定{faceListId}以外，還需要指定需要刪除的臉孔{persistedFaceId}

    :::danger
    [警告] persistedFaceId 一旦刪除就沒有任何挽回的餘地，在刪除前請務必確認{persistedFaceId}的正確性。
    :::
    ![](https://i.imgur.com/hy3qkH0.png)



### 刪除臉部清單
若我們需要將整個FaceList清除，可以利用下列網址並使用 DELETE 的方法來刪除清單
```
https://{endpoint}/face/v1.0/facelists/{faceListId}
```
* 系統刪除後會回應 200 OK 的資訊

    :::danger
    [警告] 清單一旦刪除則會同步刪除清單內所有的臉孔{persistedFaceId}，刪除前請務必確認是否執行該命令。
    :::

    ![](https://i.imgur.com/hSWIvhC.png)



:::success
恭喜，到目前為止我們已經完成所有 FaceList 操作了。
:::

---

### 利用 Find Similar 來分析指定相片中的人物
:::info
:bulb: 在使用 Find Similar 之前我們需要先利用 Detect 方法取得 FaceId
:::
:::danger
Detect 中的 recognitionModel 需要與FaceList中設定相同，這邊的設定為recognitionModel=recognition_02
:::
* 首先我們先使用 Detect 呼叫並取得FaceID
![](https://i.imgur.com/lSOYFwr.png)

* 接下來我們可以利用下列網址並使用POST方法呼叫Find Similar

    ```
    https://{endpoint}/face/v1.0/findsimilars
    ```

* 在 Body 的部份我們使用 JSON 的方式來上傳資訊

    ```json=
    {
        "faceId": "78091225-1f97-4979-a702-b40db1a76186",
        "FaceListId": "b426f7c9-d341-4671-a4bf-498df648eded"
    }
    ```

* 查詢成功後可以看到伺服器回傳對應的結果

    ```jsonld=
    [
        {
            "persistedFaceId": "a37b3fd3-e30d-4ff1-a89f-56c5f48f3b89",
            "confidence": 0.90336
        }
    ]
    ```

* 由上面的列表得知我們查詢出對應的persistedFaceId為a37b3fd3-e30d-4ff1-a89f-56c5f48f3b89，信心水準為90.336%，若要查詢persistedFaceId所對應到的人可以呼叫<font color="blue">查看群組中的相片清單</font>

    ```json=
    {
        "persistedFaces": [
            {
                "persistedFaceId": "a37b3fd3-e30d-4ff1-a89f-56c5f48f3b89",
                "userData": "小班"
            },
            {
                "persistedFaceId": "bb32ec66-ef99-4b66-8aff-6d1c0f64741f",
                "userData": "弟弟"
            }
        ],
        "faceListId": "b426f7c9-d341-4671-a4bf-498df648eded",
        "name": "資管系甲班人物清單",
        "userData": "ＯＯ大學資管系甲班人物列表"
    }
    ```

* 由<font color="blue">查看群組中的相片清單</font>我們可以知道，剛剛分析出的相片為<font color="blue">弟弟</font>

:::success
恭喜，到目前為止我們已經完成判別相片中的人物摟。
:::

---

### 判斷兩張人物的相似程度
在大多時候，有時候我們並不是需要判別相片中的是誰，我們需要知道的是相片中人物與另外一個人物的相似程度為何，此時我們可以利用 <font color="blue">Verify</font> 的方法來驗證兩張相片的相似程度。
:::info
:bulb:在使用 <font color="blue">Verify</font> 函數前我們一樣需先呼叫 <font color="blue">Detect</font> 函數後取得FaceID才能進行後續判斷
:::

* 首先我們先利用<font color="blue">Detect</font>函數取得下列相片的FaceID
    
    ![](https://i.imgur.com/NaSyf6l.jpg)

* 取得相片資訊由左往右依序如下 
    * "faceId": "846bc331-5667-40ef-89bd-039f3fb35be7"
    * "faceId": "12c34923-5698-4e1e-a376-d0b22f77739b"
    * "faceId": "44b9b17b-2562-4936-b49b-f91044eb2148"

* 接下來我們可以利用 POST 的方法呼叫下列函數，用來判斷兩張相片的相似程度

    ```
    https://{endpoint}/face/v1.0/verify
    ```

* 相關 Body 的內容如下

    ```json=
    {
        "faceId1": "846bc331-5667-40ef-89bd-039f3fb35be7",
        "faceId2": "12c34923-5698-4e1e-a376-d0b22f77739b"
    }
    ```

* 第一張相片與第二張相片比對後的結果，根據比對後的結果我們發現兩張相片的相似程度為87.284%，系統顯示為同一人

    ```json=
    {
        "isIdentical": true,
        "confidence": 0.87284
    }
    ```

* 第一張相片與第三張相片比對後的結果，根據比對後的結果我們發現兩張相片的相似程度為8.675%，系統顯示兩張相片不相似

    ```json=
    {
        "isIdentical": false,
        "confidence": 0.08675
    }
    ```

:::info
利用 Verify 方法我們可以比對兩張相片的相似程度，各位同學不訪拿自己的相片與其他人比較一下，是否能找出與自己相似的臉孔喔。
:::

---

## 實作課程1:人臉分析與識別
在了解基本的RESTAPI操作以後，本章節將來介紹如何利用API來實作一個簡易的人臉分析系統，並利用相關資訊判別載入中的人臉是誰。

### 建立 Visual Studio 專案
#### 步驟一:選擇建立新的專案
* 首先我們需要建立一個空的專案，我們的執行環境為Visual Studio 2019，並選擇<font color="blue">建立新的專案</font>

    ![](https://i.imgur.com/PyCM6pN.png)

#### 步驟二:建立 Windows Forms 專案
* 為了等一下使我們開發系統能有較好的體驗環境，這邊我們將使用 Windows Forms 的專案來建立

    :::warning
    為了有良好的使用者體驗，這邊強烈建議使用者使用 Windows Forms 來建立專案
    :::
    
    ![](https://i.imgur.com/Lbr0NPf.png)

#### 步驟三:設定相關專案參數
* 這邊我們的專案名稱設定為<font color="blue">FaceDemo，NetFramework 版本使用 4.5.1</font>，設定完成後點選<font color="blue">建立</font>產生專案
    
    :::danger
    為避免程式執行上發生不可預期的錯誤，專案名稱與路徑建議不要出現中文字或特殊字元
    :::
    
    ![](https://i.imgur.com/hMW1c3f.png)

---

### 建立使用者介面
首先我們先來設計一個簡易的介面，目的在於分析相片中人物的年齡與性別
#### 步驟一:變更 Windows From 的標題名稱

* 首先我們先點選 Form1 的視窗，此時可在右側的屬性視窗中找尋一個Text屬性，並將屬性值設定為<font color="blue">人臉分析系統</font>

    ![](https://i.imgur.com/dU1rDNT.png)

#### 步驟二:新增相關元件
* 在這個部分我們需要新增三個元件

    1.PictureBox
    2.ListBox
    3.Button
    
    ![](https://i.imgur.com/c4pXk5B.png)

---

### 建立檔案開啟視窗
* 為了讓使用者點選分析相片資訊時能夠產生檔案視窗，這邊我們需要在Button撰寫下列程式碼

    ```csharp=221
    private void button1_Click(object sender, EventArgs e)
    {
        //產生檔案開啟視窗
        OpenFileDialog dialog = new OpenFileDialog();

        //是否允許多個檔案
        dialog.Multiselect = false;
        dialog.Title = "請選擇要分析的相片";
        dialog.Filter = "圖片檔(*.jpg;*.png;*.gif;*.bmp)|*.jpg;*.png;*.gif;*.bmp";

        //判斷使用者是否選擇正確的檔案
        if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
        {
            string file = dialog.FileName;
        }
    }
    ```

---

### 建立PictureBox相關處理

#### 讓PictureBox啟用自動縮放功能
* 在PictureBox的屬性視窗中將<font color="blue">SizeMode</font>設定為<font color="blue">StretchImage</font>

    ![](https://i.imgur.com/faQzooA.png)

---
#### 利用檔案開啟視窗更新PictureBox圖片
* 方法一:利用Image處理相片

    ```csharp=231
    //判斷使用者是否選擇正確的檔案
    if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
    {
        string file = dialog.FileName;

        //更新PictureBox圖片
        pictureBox1.Image = Image.FromFile(file);
    }
    ```

* 方法二:利用FileStream處理相片

    ```csharp=231
    //判斷使用者是否選擇正確的檔案
    if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
    {
        string file = dialog.FileName;

        //更新PictureBox圖片
        FileStream fs = File.OpenRead(file);
        pictureBox1.Image = Image.FromStream(fs);
        fs.Close();
    }
    ```
---

### 建立 Face - Detect 程式碼

* 新增使用端點與金鑰的全域變數

    ```csharp=21
    //建立金鑰與使用端點
    private const string uriBase = "https://XXXX.cognitiveservices.azure.com/";
    private const string subscriptionKey = "XXXXXXXX";
    ```

* 建立影像轉換函數
我們利用 GetImageAsByteArray 函數來接收使用者所丟入的檔案位置，並利用FileStream函數將讀取到的圖片轉換為Byte Array的型態

    ```csharp=34
    //建立讀取影像的函數
    private byte[] GetImageAsByteArray(string filePath)
    {
        using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
        {
            BinaryReader binaryReader = new BinaryReader(fileStream);
            return binaryReader.ReadBytes((int)fileStream.Length);
        }
    }
    ```

* 建立影像分析函數

    透過 GetFaceDetect 函數可將使用者所輸入的圖片進行分析，並呼叫Azure的方式來進行相關的影像分析

    ```csharp=44
    //建立影像分析函數
    private async void GetFaceDetect(string file)
    {
        //建立HttpClient並與Azure連線
        HttpClient client = new HttpClient();
        client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", subscriptionKey);

        //組合查詢參數(分析相片中人物的年齡與性別)
        StringBuilder sb = new StringBuilder();
        sb.Append(String.Format("{0}={1}", "returnFaceAttributes", "age,gender"));

        //組合查詢URL
        String url = String.Format("{0}{1}?{2}", uriBase, "face/v1.0/detect", sb.ToString());

        //開啟檔案
        byte[] byteData = GetImageAsByteArray(file);


        //將檔案上傳放置到Http Body中並呼叫Detect API
        using (ByteArrayContent content = new ByteArrayContent(byteData))
        {
            //發送請求以查詢資料
            content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
            HttpResponseMessage response = await client.PostAsync(url, content);

            //取得回傳資料
            string contentString = await response.Content.ReadAsStringAsync();
        }
    }
    ```
    
    :::danger
    部分較舊的作業系統可能會因為TLS認證的問題發生錯誤，若出現傳送要求時發生錯誤需使用下列程式碼進行修正
    :::

* 當我們呼叫API時，有時候會出現連線 HTTPS 網站發生驗證失敗導致基礎連接已關閉，會出現這個問題主要是因為目前主流的服務已經不支援TLS1.0的問題，因此我們需要透過下列程式碼進行修正，明確的告知我們的程式碼需使用TLS 1.2 來進行連接

    ```csharp=47
    //建立HttpClient並與Azure連線
    HttpClient client = new HttpClient();
    client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", subscriptionKey);

    //修正連線意外中斷問題
    ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
    ```

* 解析 Azure-Detect 回傳的 JSON 結果

    當我們呼叫 Detect API 成功後可以取得類似下列格式的 JSON 字串

    ```json=
    [
       {
          "faceId":"9fec28f2-2d22-44ee-806a-3742d67c1538",
          "faceRectangle":{
             "top":291,
             "left":328,
             "width":96,
             "height":96
          },
          "faceAttributes":{
             "gender":"male",
             "age":38.0
          }
       },
       {
          "faceId":"b16ab15e-c66c-4218-adf4-62dc8a430af2",
          "faceRectangle":{
             "top":282,
             "left":889,
             "width":92,
             "height":92
          },
          "faceAttributes":{
             "gender":"male",
             "age":30.0
          }
       },
       {
          "faceId":"c66283ea-686b-43fe-9283-f6444fc81ad9",
          "faceRectangle":{
             "top":298,
             "left":584,
             "width":87,
             "height":87
          },
          "faceAttributes":{
             "gender":"male",
             "age":44.0
          }
       },
       {
          "faceId":"e66371af-01a7-4967-a947-0ec36c281346",
          "faceRectangle":{
             "top":345,
             "left":751,
             "width":76,
             "height":76
          },
          "faceAttributes":{
             "gender":"female",
             "age":24.0
          }
       }
    ]
    ```

* 安裝 Newtonsoft.Json 套件
    為了處理伺服器回傳的 JSON 格式我們需要先安裝 Newtonsoft.Json 套件，首先我們先在點選 工具/Neget套件管理員/套件管理主控台

    ![](https://i.imgur.com/ThVRJvf.png)

* 接下來我們可以在套件管理主控台中輸入以下指令

    ```
    PM> Install-Package Newtonsoft.Json
    ```

* 按下Enter後套件庫會進行相關程式碼的安裝，若安裝過程沒有問題會顯示下列資訊
    ```
    PM> Install-Package Newtonsoft.Json

    正在嘗試針對專案 'FaceDemo' 及目標 '.NETFramework,Version=v4.5.1' 收集封裝 'Newtonsoft.Json.12.0.3' 的相依性資訊
    收集相依性資訊花費了 21 ms
    嘗試解析具有 DependencyBehavior 'Lowest' 之套件 'Newtonsoft.Json.12.0.3' 的相依性
    解析相依性資訊花費了 0 ms
    正在解析安裝封裝 'Newtonsoft.Json.12.0.3' 的動作
    已解析安裝封裝 'Newtonsoft.Json.12.0.3' 的動作
    正在從 'nuget.org' 擷取封裝 'Newtonsoft.Json 12.0.3'。
      GET https://api.nuget.org/v3-flatcontainer/newtonsoft.json/12.0.3/newtonsoft.json.12.0.3.nupkg
      OK https://api.nuget.org/v3-flatcontainer/newtonsoft.json/12.0.3/newtonsoft.json.12.0.3.nupkg 911 毫秒
    正在安裝 Newtonsoft.Json 12.0.3。
    正在將封裝 'Newtonsoft.Json.12.0.3' 加入資料夾 'C:\Project\FaceDemo\packages'
    已將封裝 'Newtonsoft.Json.12.0.3' 加入資料夾 'C:\Project\FaceDemo\packages'
    已將封裝 'Newtonsoft.Json.12.0.3' 加入 'packages.config'
    已成功將 'Newtonsoft.Json 12.0.3' 安裝到 FaceDemo
    執行 NuGet 動作花費了 6.87 sec
    經過時間: 00:00:08.4195738
    ```

* 利用 Newtonsoft.Json 解析JSON

    ```csharp=
    //取得回傳資料
    string contentString = await response.Content.ReadAsStringAsync();

    //建立JSONArray物件
    JArray personArray = JArray.Parse(contentString);

    //取得相片中的人數
    int personCount = personArray.Count;

    //讀取每個人物資訊
    foreach (JObject jo in personArray)
    {
        //讀取FaceID
        String userFaceID = jo.GetValue("faceId").ToString();

        //讀取faceAttributes
        JObject faceAttributes = jo.GetValue("faceAttributes") as JObject;

        //讀取使用者年齡
        int userAge = Int32.Parse(faceAttributes.GetValue("age").ToString());

        //讀取使用者性別
        String userGender = faceAttributes.GetValue("gender").ToString();

        //將使用者資訊顯示到listBox中
        listBox1.Items.Add(String.Format("使用者ID:{0}",userFaceID));
        listBox1.Items.Add(String.Format("使用者年齡:{0}", userAge));
        listBox1.Items.Add(String.Format("使用者性別:{0}", userGender));
    }
    ```

---

### 建立 FaceList 資料庫
為了讓我們的相片具有分析人物的功能，首先我們需要先在 FaceList 資料庫中新增識別人物

* 利用 Postman 查詢目前擁有的 FaceList
首先我們先利用 Postman 查詢目前帳號底下擁有的FaceList清單

    :::warning
    請務必確認所建立的FaceList清單中的 recognitionModel 格式，這邊將使用 recognition_02 來進行示範
    :::

    ![](https://i.imgur.com/dmAGeUa.png)

* 根據查詢的回傳結果得知目前系統中具有一個 FaceList 其中 FaceList ID 為 <font color="blue">b3960bd5-b71b-4784-b5cc-52217ebb33dd</font>，recognitionModel為 <font color="red">recognition_02</font>

    ```json=
    [
        {
            "recognitionModel": "recognition_02",
            "faceListId": "b3960bd5-b71b-4784-b5cc-52217ebb33dd",
            "name": "人物資料庫"
        }
    ]
    ```

* 在介面上新增相關元件
    為了將已知的人物新增進學習系統中，我們首先在原有介面上新增兩個UI
    1.TextBox
    2.Button

    ![](https://i.imgur.com/vduO4nC.png)

---

### 撰寫新增資料庫人物程式碼
* 步驟一:建立 Dictionary

    ```csharp=26
    //建立Dictionary
    Dictionary<String, String> personDB = new Dictionary<String, String>();
    ```

* 步驟二:新增 AddFace 函數

    :::warning
    請務必確認所建立的FaceList清單中的 detectionModel 格式，這邊將使用 detection_02 來進行示範
    :::

    ```csharp=111
    //新增 AddFaceData 函數
    private async void AddFaceData(string userName , string file)
    {
        //建立HttpClient並與Azure連線
        HttpClient client = new HttpClient();
        client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", subscriptionKey);

        //修正連線意外中斷問題
        ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;

        //組合查詢參數
        StringBuilder sb = new StringBuilder();
        sb.Append(String.Format("{0}={1}", "detectionModel", "detection_02"));

        //組合查詢URL
        String url = String.Format("{0}{1}?{2}", uriBase, "face/v1.0/facelists/b3960bd5-b71b-4784-b5cc-52217ebb33dd/persistedfaces", sb.ToString());

        //開啟檔案
        byte[] byteData = GetImageAsByteArray(file);

        //將檔案上傳放置到Http Body中並呼叫Detect API
        using (ByteArrayContent content = new ByteArrayContent(byteData))
        {
            //發送請求以查詢資料
            content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
            HttpResponseMessage response = await client.PostAsync(url, content);

            //取得回傳資料
            string contentString = await response.Content.ReadAsStringAsync();

            //取得伺服器回傳資訊
            JObject jo =  JObject.Parse(contentString);

            //判斷persistedFaceId是否存在
            JToken persistedFaceId = "";
            if (jo.TryGetValue("persistedFaceId", out persistedFaceId))
            {
                //將資料寫入Dictionary中
                personDB.Add(persistedFaceId.ToString(), userName);

                //在ListBox上顯示資訊
                MessageBox.Show(String.Format("人物姓名:{0} \r\n人物ID:{1}",userName,persistedFaceId), "新增成功");
            }
            else
            {
                MessageBox.Show("相片不符規定", "新增失敗");
            }
        }
    }
    ```

* 步驟三:建立 新增資料庫人物函數
    ```csharp=224
    private void button2_Click(object sender, EventArgs e)
    {
        //產生檔案開啟視窗
        OpenFileDialog dialog = new OpenFileDialog();

        //是否允許多個檔案
        dialog.Multiselect = false;
        dialog.Title = "請選擇要分析的相片";
        dialog.Filter = "圖片檔(*.jpg;*.png;*.gif;*.bmp)|*.jpg;*.png;*.gif;*.bmp";

        //判斷使用者是否選擇正確的檔案
        if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
        {
            string file = dialog.FileName;

            //更新PictureBox圖片
            pictureBox1.Image = Image.FromFile(file);

            //分析PictureBox相片
            AddFaceData(textBox1.Text , file);
        }
    }
    ```

### 建立 FindSimilars 比對函數
在上個步驟我們已經人臉識別資料庫，接下來我們將示範如何上傳一張相片，並識別出相片中的人物

* 步驟一:建立 FindSimilars 函數
    
    ```csharp=160
    private async void FindSimilars(string faceID,string faceListId)
    {
        //建立HttpClient並與Azure連線
        HttpClient client = new HttpClient();
        client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", subscriptionKey);

        //修正連線意外中斷問題
        ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;

        //組合要查詢的JSON格式
        JObject jo = new JObject();
        jo.Add("faceId", faceID);
        jo.Add("faceListId", faceListId);

        //組合查詢參數
        StringBuilder sb = new StringBuilder();

        //組合查詢URL
        String url = String.Format("{0}{1}?{2}", uriBase, "face/v1.0/findsimilars", sb.ToString());

        //將檔案上傳放置到Http Body中並呼叫Detect API
        using (HttpContent content = new StringContent(jo.ToString()))
        {
            //發送請求以查詢資料
            content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
            HttpResponseMessage response = await client.PostAsync(url, content);

            //取得回傳資料
            string contentString = await response.Content.ReadAsStringAsync();

            //建立JSONArray物件
            JArray personArray = JArray.Parse(contentString);

            //取得相片中的人數
            int personCount = personArray.Count;

            //取得最佳的判斷結果
            if (personCount >= 1)
            {
                JObject similarsData = personArray[0] as JObject;

                //取得最佳的persistedFaceId
                String persistedFaceId = similarsData.GetValue("persistedFaceId").ToString();
                double confidence = Double.Parse(similarsData.GetValue("confidence").ToString())*100;

                //利用persistedFaceId取得人物名稱
                String userName = "";
                personDB.TryGetValue(persistedFaceId, out userName);

                //將比對出的相似相片顯示至listBox中
                listBox1.Items.Add("比對成功");
                listBox1.Items.Add(String.Format("人物名稱:{0}",userName));
                listBox1.Items.Add(String.Format("人物ID:{0}", persistedFaceId));
                listBox1.Items.Add(String.Format("信心水準:{0}%", confidence));
            }

        }
    }
    ```
* 步驟二:在 GetFaceDetect 函數中新增 recognitionModel 屬性

    :::info
    :bulb: Detect 中新增的相片必需與 Find Similar 相同
    :::

    ```csharp=53
    //組合查詢參數(分析相片中人物的年齡與性別)
    StringBuilder sb = new StringBuilder();
    sb.Append(String.Format("{0}={1}", "returnFaceAttributes", "age,gender"));
    sb.Append(String.Format("&{0}={1}", "recognitionModel", "recognition_02"));
    ```

* 步驟三:在 GetFaceDetect 函數中呼叫 FindSimilars 函數

    ```csharp=96
    //將使用者資訊顯示到listBox中
    listBox1.Items.Add(String.Format("使用者ID:{0}",userFaceID));
    listBox1.Items.Add(String.Format("使用者年齡:{0}", userAge));
    listBox1.Items.Add(String.Format("使用者性別:{0}", userGender));

    //建立分析人物姓名資訊
    FindSimilars(userFaceID, "b3960bd5-b71b-4784-b5cc-52217ebb33dd");
    ```
---
### Demo
* 首先我們先建立一張識別人臉的資訊，並將姓名取名為Jerry

    ![](https://i.imgur.com/zCm8D0Z.png)

* 點選新增資料庫人物將相片與姓名進行配對，若配對成功會顯示人物姓名與人物ID

    ![](https://i.imgur.com/qElO6iK.png)

* 點選分析相片資訊取得相關相片資料

    ![](https://i.imgur.com/Nhkli8t.png)

* 若比對成功會顯示配對到的人物名稱與信心水準

---
### 完整程式碼
[完整程式碼下載](https://drive.google.com/drive/u/2/folders/18ps2aaFGbxrURlcmURxno6rDYga-G1Wo)
```csharp=1
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace FaceDemo
{
    public partial class Form1 : Form
    {
        //建立金鑰與使用端點
        private const string uriBase = "https://XXXXXXXX.cognitiveservices.azure.com/";
        private const string subscriptionKey = "XXXXXXXXXXXXXX";

        //建立Dictionary
        Dictionary<String, String> personDB = new Dictionary<String, String>();

        public Form1()
        {
            InitializeComponent();
        }

        //建立影像轉換函數
        private byte[] GetImageAsByteArray(string filePath)
        {
            using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
            {
                BinaryReader binaryReader = new BinaryReader(fileStream);
                return binaryReader.ReadBytes((int)fileStream.Length);
            }
        }

        //建立影像分析函數
        private async void GetFaceDetect(string file)
        {
            //建立HttpClient並與Azure連線
            HttpClient client = new HttpClient();
            client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", subscriptionKey);
            
            //修正連線意外中斷問題
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;

            //組合查詢參數(分析相片中人物的年齡與性別)
            StringBuilder sb = new StringBuilder();
            sb.Append(String.Format("{0}={1}", "returnFaceAttributes", "age,gender"));
            sb.Append(String.Format("&{0}={1}", "recognitionModel", "recognition_02"));

            //組合查詢URL
            String url = String.Format("{0}{1}?{2}", uriBase, "face/v1.0/detect", sb.ToString());

            //開啟檔案
            byte[] byteData = GetImageAsByteArray(file);


            //將檔案上傳放置到Http Body中並呼叫Detect API
            using (ByteArrayContent content = new ByteArrayContent(byteData))
            {
                //發送請求以查詢資料
                content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
                HttpResponseMessage response = await client.PostAsync(url, content);

                //取得回傳資料
                string contentString = await response.Content.ReadAsStringAsync();

                //建立JSONArray物件
                JArray personArray = JArray.Parse(contentString);

                //取得相片中的人數
                int personCount = personArray.Count;

                //讀取每個人物資訊
                foreach (JObject jo in personArray)
                {
                    //讀取FaceID
                    String userFaceID = jo.GetValue("faceId").ToString();

                    //讀取faceAttributes
                    JObject faceAttributes = jo.GetValue("faceAttributes") as JObject;

                    //讀取使用者年齡
                    int userAge = Int32.Parse(faceAttributes.GetValue("age").ToString());

                    //讀取使用者性別
                    String userGender = faceAttributes.GetValue("gender").ToString();

                    //將使用者資訊顯示到listBox中
                    listBox1.Items.Add(String.Format("使用者ID:{0}",userFaceID));
                    listBox1.Items.Add(String.Format("使用者年齡:{0}", userAge));
                    listBox1.Items.Add(String.Format("使用者性別:{0}", userGender));

                    //建立分析人物姓名資訊
                    FindSimilars(userFaceID, "b3960bd5-b71b-4784-b5cc-52217ebb33dd");
                }



            }
        }

        //新增 AddFaceData 函數
        private async void AddFaceData(string userName , string file)
        {
            //建立HttpClient並與Azure連線
            HttpClient client = new HttpClient();
            client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", subscriptionKey);

            //修正連線意外中斷問題
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;

            //組合查詢參數
            StringBuilder sb = new StringBuilder();
            sb.Append(String.Format("{0}={1}", "detectionModel", "detection_02"));

            //組合查詢URL
            String url = String.Format("{0}{1}?{2}", uriBase, "face/v1.0/facelists/b3960bd5-b71b-4784-b5cc-52217ebb33dd/persistedfaces", sb.ToString());

            //開啟檔案
            byte[] byteData = GetImageAsByteArray(file);

            //將檔案上傳放置到Http Body中並呼叫Detect API
            using (ByteArrayContent content = new ByteArrayContent(byteData))
            {
                //發送請求以查詢資料
                content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
                HttpResponseMessage response = await client.PostAsync(url, content);

                //取得回傳資料
                string contentString = await response.Content.ReadAsStringAsync();

                //取得伺服器回傳資訊
                JObject jo =  JObject.Parse(contentString);

                //判斷persistedFaceId是否存在
                JToken persistedFaceId = "";
                if (jo.TryGetValue("persistedFaceId", out persistedFaceId))
                {
                    //將資料寫入Dictionary中
                    personDB.Add(persistedFaceId.ToString(), userName);

                    //在ListBox上顯示資訊
                    MessageBox.Show(String.Format("人物姓名:{0} \r\n人物ID:{1}",userName,persistedFaceId), "新增成功");
                }
                else
                {
                    MessageBox.Show("相片不符規定", "新增失敗");
                }
            }
        }

        private async void FindSimilars(string faceID,string faceListId)
        {
            //建立HttpClient並與Azure連線
            HttpClient client = new HttpClient();
            client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", subscriptionKey);

            //修正連線意外中斷問題
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;

            //組合要查詢的JSON格式
            JObject jo = new JObject();
            jo.Add("faceId", faceID);
            jo.Add("faceListId", faceListId);

            //組合查詢參數
            StringBuilder sb = new StringBuilder();

            //組合查詢URL
            String url = String.Format("{0}{1}?{2}", uriBase, "face/v1.0/findsimilars", sb.ToString());

            //將檔案上傳放置到Http Body中並呼叫Detect API
            using (HttpContent content = new StringContent(jo.ToString()))
            {
                //發送請求以查詢資料
                content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
                HttpResponseMessage response = await client.PostAsync(url, content);

                //取得回傳資料
                string contentString = await response.Content.ReadAsStringAsync();

                //建立JSONArray物件
                JArray personArray = JArray.Parse(contentString);

                //取得相片中的人數
                int personCount = personArray.Count;

                //取得最佳的判斷結果
                if (personCount >= 1)
                {
                    JObject similarsData = personArray[0] as JObject;

                    //取得最佳的persistedFaceId
                    String persistedFaceId = similarsData.GetValue("persistedFaceId").ToString();
                    double confidence = Double.Parse(similarsData.GetValue("confidence").ToString())*100;

                    //利用persistedFaceId取得人物名稱
                    String userName = "";
                    personDB.TryGetValue(persistedFaceId, out userName);

                    //將比對出的相似相片顯示至listBox中
                    listBox1.Items.Add("比對成功");
                    listBox1.Items.Add(String.Format("人物名稱:{0}",userName));
                    listBox1.Items.Add(String.Format("人物ID:{0}", persistedFaceId));
                    listBox1.Items.Add(String.Format("信心水準:{0}%", confidence));
                }

            }
        }



        private void button1_Click(object sender, EventArgs e)
        {
            //產生檔案開啟視窗
            OpenFileDialog dialog = new OpenFileDialog();
            
            //是否允許多個檔案
            dialog.Multiselect = false;
            dialog.Title = "請選擇要分析的相片";
            dialog.Filter = "圖片檔(*.jpg;*.png;*.gif;*.bmp)|*.jpg;*.png;*.gif;*.bmp";

            //判斷使用者是否選擇正確的檔案
            if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                string file = dialog.FileName;

                //更新PictureBox圖片
                pictureBox1.Image = Image.FromFile(file);

                //分析PictureBox相片
                GetFaceDetect(file);
            }
        }

        private void button2_Click(object sender, EventArgs e)
        {
            //產生檔案開啟視窗
            OpenFileDialog dialog = new OpenFileDialog();

            //是否允許多個檔案
            dialog.Multiselect = false;
            dialog.Title = "請選擇要分析的相片";
            dialog.Filter = "圖片檔(*.jpg;*.png;*.gif;*.bmp)|*.jpg;*.png;*.gif;*.bmp";

            //判斷使用者是否選擇正確的檔案
            if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                string file = dialog.FileName;

                //更新PictureBox圖片
                pictureBox1.Image = Image.FromFile(file);

                //分析PictureBox相片
                AddFaceData(textBox1.Text , file);
            }
        }
    }
}
```

## 參考資料來源
1. [課堂分享檔案](https://drive.google.com/drive/u/2/folders/1ddMw2BceJNKnDo2bJxHfpPe8Cc4hM0po)
2. [微軟Azure管理後台](https://azure.microsoft.com/zh-tw/)
3. [Face API Document](https://docs.microsoft.com/zh-tw/azure/cognitive-services/face/)
4. [Face RESTAPI](https://westus.dev.cognitive.microsoft.com/docs/services/563879b61984550e40cbbe8d/operations/563879b61984550f30395236)
5. [computer-vision Document](https://docs.microsoft.com/zh-tw/azure/cognitive-services/computer-vision/)
6. [computer-vision RESTAPI](https://westus.dev.cognitive.microsoft.com/docs/services/563879b61984550e40cbbe8d/operations/563879b61984550f30395236)

