ML-NET 示例:對象檢測 - ASP-NET Core Web 和 WPF 桌面示例

AQ2VWc

問題

對象檢測是計算機視覺中的經典問題之一:識別給定圖像中包含哪些對象以及它們在圖像中的位置。對於這些情況,您可以使用預先訓練的模型,也可以訓練自己的模型對自定義域特定的圖像進行分類。默認情況下,此示例使用預先訓練的模型,但是您也可以添加從 Custom Vision 導出的模型。

示例的工作原理

此示例由兩個獨立的應用程序組成:

Web 應用程序顯示右側列出的圖像,可以選擇每個圖像進行處理。一旦圖像被處理,它將被繪製在屏幕的中間,每個檢測到的對象周圍都有標記的邊界框,如下所示。

或者,您可以嘗試上傳自己的圖片,如下所示。

ONNX

開放式神經網絡交換即 ONNX 是一種表示深度學習模型的開放格式。使用 ONNX,開發人員可以在最先進的工具之間移動模型,並選擇最適合他們的組合。ONNX 是由包括微軟在內的衆多合作伙伴共同開發和支持的。

預訓練模型

有多個預先訓練的模型用於識別圖像中的多個對象。WPF appWeb app 都默認使用從 ONNX Model Zoo 下載的預先訓練好的模型 Tiny YOLOv2; 一組經過預先訓練的、最先進的 ONNX 格式模型。Tiny YOLOv2 是一種用於目標檢測的實時神經網絡,用於檢測 20 個不同的類,並在 Pascal VOC 數據集上進行訓練。它由 9 個卷積層和 6 個最大池層組成,是更復雜的完整的 YOLOv2 網絡的較小版本。

Custom Vision 模型

此示例默認使用上述預先訓練的 Tiny YOLOv2 模型。不過,它也支持從微軟 Custom Vision 導出的 ONNX 模型。

要使用自己的模型,請使用以下步驟

  1. 使用 Custom Vision 創建和訓練物體探測器。要導出模型,請確保選擇一個緊湊域,例如常規(緊湊)。要導出現有的對象檢測器,請通過選擇右上角的齒輪圖標將域轉換爲緊湊型。在_ **設置** _中,選擇一個緊湊的模型,保存並訓練您的項目。

  2. 轉到_**性能**選項卡導出模型。選擇一個用緊湊域訓練的迭代,將出現一個 “導出” 按鈕。選擇_導出、ONNX、ONNX1.2,然後選擇導出。文件準備好後,選擇 “下載” 按鈕。

  3. 導出的是一個包含多個文件的 zip 文件,包括一些示例代碼、標籤列表和 ONNX 模型。將. zip 文件放到 OnnxObjectDetection 項目中的 OnnxModels 文件夾中。

  4. 在解決方案資源管理器中,右鍵單擊 OnnxModels 文件夾,然後選擇_添加現有項_。選擇剛添加的. zip 文件。

  5. 在解決方案資源管理器中,從 OnnxModels 文件夾中選擇. zip 文件。更改文件的以下屬性:

現在,當你生成和運行應用程序時,它將使用你的模型而不是 Tiny YOLOv2 模型。

模型輸入和輸出

爲了解析 ONNL 模型的預測輸出,我們需要了解輸入和輸出張量的格式(或形狀)。爲此,我們將首先使用 Netron,一個用於神經網絡和機器學習模型的 GUI 可視化工具,用於檢查模型。

下面是一個例子,我們將看到使用 Netron 打開這個示例的 Tiny YOLOv2 模型:

從上面的輸出中,我們可以看到 Tiny YOLOv2 模型具有以下輸入 / 輸出格式:

輸入: 'image' 3x416x416

首先要注意的是,輸入張量的名稱'image'。稍後在定義評估管道的** input** 參數時,我們將需要這個名稱。

我們還可以看到,輸入張量的形狀3x416x416。這說明傳入模型的位圖圖像應該是 416 高 x 416 寬。“3”表示圖像應爲 BGR 格式;前 3 個 “通道” 分別是藍色、綠色和紅色。

輸出: 'data' 125x13x13

與輸入張量一樣,我們可以看到輸出名稱'data'。同樣,在定義評估管道的** output** 參數時,我們會注意到這一點。

我們還可以看到, 輸出張量的形狀125x13x13

125x13x13 的 “13x13” 部分意味着圖像被分成一個 13x13 的 “單元格” 網格(13 列 x 13 行)。因爲我們知道輸入圖像是 416x416,所以我們可以推斷出每個 “單元” 都是 32 高 x 32 寬(416/13=32)

   ├──────────────── 416 ─────────────────┤
   ┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐ ┬     416/13=32
   ├──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┤ │          ┌──┐
   ├──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┤ │          └──┘
   ├──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┤ │         32x32
   ├──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┤ │
   ├──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┤ │
13 ├──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┤ │
   ├──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┤ 416
   ├──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┤ │
   ├──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┤ │
   ├──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┤ │
   ├──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┤ │
   ├──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┤ │
   └──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘ ┴
                      13

那 125 呢?“125” 告訴我們,對於每個網格單元,模型返回 125 個 “通道”(或數據)作爲該單個單元的預測輸出。

要了解爲什麼有 125 個通道,我們首先需要了解該模型不能預測對象的任意邊界框。相反,每個單元格負責預測 5 個預定的邊界框。這 5 個框是根據以下每個anchor 框的偏移量計算得出的:

┌───────────────────┐
│       ┌───┐       │
│ ┌─────┼───┼─────┐ │
│ │  ┌──┼───┼──┐  │ │
│ │  │  │┌─┐│  │  │ │
│ │  │  │└─┘│  │  │ │
│ │  └──┼───┼──┘  │ │
│ └─────┼───┼─────┘ │
│       └───┘       │
└───────────────────┘

因此,對於每個單獨的單元格,該模型返回 5 個預測(每個錨定一個,由上面的框形表示),每個預測包括以下 25 個參數:

5 個盒子 x 25 個參數 = 125 個'通道'

注意,如果對模型進行訓練以檢測不同數量的類,則該值將不同。例如,僅能檢測 3 個不同類的模型的輸出格式爲 40x13x13:

解決方案

此解決方案中的項目使用. NET Core 3.0。爲了運行此示例,您必須安裝. NET Core SDK 3.0。爲此,請執行以下任一操作:

  1. 通過轉到. NET Core 3.0 下載頁面手動安裝 SDK,並在 “SDK” 列中下載最新的“.NET Core 安裝程序”。

  2. 或者,如果您使用的是 Visual Studio 2019,請轉至: 工具 > 選項 > 環境 > 預覽功能 ,然後選中以下複選框: 使用. NET Core SDK 的預覽

解決方案包含三個項目

代碼演練

該示例與 getting-started object detection sample 不同,在這裏我們加載 / 處理在內存中的圖像 ,而入門示例從文件**中加載圖像。

創建一個類,該類定義將數據加載到 IDataView 中時要使用的數據模式。ML.NET 支持圖像的Bitmap類型,因此我們將指定以ImageTypeAttribute修飾的Bitmap屬性,並傳入通過 [檢查模型]](#model-input-and-output) 得到的高度和寬度尺寸,如下所示。

public struct ImageSettings
{
    public const int imageHeight = 416;
    public const int imageWidth = 416;
}

public class ImageInputData
{
    [ImageType(ImageSettings.imageHeight, ImageSettings.imageWidth)]
    public Bitmap Image { get; set; }
}

ML.NET: 配置模型

第一步是創建一個空的DataView,以獲取配置模型時要使用的數據架構。

var dataView = _mlContext.Data.LoadFromEnumerable(new List<ImageInputData>());

第二步是定義評估器管道。通常在處理深度神經網絡時,必須使圖像適應網絡所期望的格式。因此,下面的代碼將調整圖像的大小並對其進行變換(所有 R、G、B 通道的像素值都已標準化)。

var pipeline = mlContext.Transforms.ResizeImages(resizing: ImageResizingEstimator.ResizingKind.Fill, outputColumnName: onnxModel.ModelInput, imageWidth: ImageSettings.imageWidth, imageHeight: ImageSettings.imageHeight, inputColumnName: nameof(ImageInputData.Image))
                .Append(mlContext.Transforms.ExtractPixels(outputColumnName: onnxModel.ModelInput))
                .Append(mlContext.Transforms.ApplyOnnxModel(modelFile: onnxModel.ModelPath, outputColumnName: onnxModel.ModelOutput, inputColumnName: onnxModel.ModelInput));

接下來,我們將使用通過檢查模型 得到的輸入和輸出張量名稱來定義 Tiny YOLOv2 Onnx 模型的 inputoutput 參數。

public struct TinyYoloModelSettings
{
    public const string ModelInput = "image";
    public const string ModelOutput = "grid";
}

最後,通過擬合 “DataView” 來創建模型。

var model = pipeline.Fit(dataView);

加載模型並創建 PredictionEngine

配置模型後,我們需要保存模型,加載保存的模型,創建一個PredictionEngine,然後將圖像傳遞給引擎以使用模型檢測對象。這是一個 Web 應用程序和 WPF 應用程序略有不同的地方。

Web 應用程序使用一個PredictionEnginePool來高效地管理服務,併爲服務提供一個PredictionEngine來進行預測。在內部,它進行了優化,以便在創建這些對象時,以最小的開銷在 Http 請求之間緩存和共享對象依賴關係。

public ObjectDetectionService(PredictionEnginePool<ImageInputData, TinyYoloPrediction> predictionEngine)
{
    this.predictionEngine = predictionEngine;
}

WPF 桌面應用程序創建一個PredictionEngine,並在本地緩存以用於每個幀預測。需要澄清的關鍵點是,實例化PredictionEngine的調用代碼負責處理緩存(與PredictionEnginePool相比)。

public PredictionEngine<ImageInputData, TinyYoloPrediction> GetMlNetPredictionEngine()
{
    return mlModel.Model.CreatePredictionEngine<ImageInputData, TinyYoloPrediction>(mlModel);
}

檢測圖像中的對象

獲取預測時,我們在PredictedLabels屬性中得到一個大小爲 21125float數組。這是前面討論過的模型的 125x13x13 輸出。然後,我們使用OnnxOutputParser類解釋並返回每個圖像的多個邊界框。同樣,這些框被過濾,因此我們只檢索到 5 個具有高置信度的框。

var labels = tinyYoloPredictionEngine?.Predict(imageInputData).PredictedLabels;
var boundingBoxes = outputParser.ParseOutputs(labels);
var filteredBoxes = outputParser.FilterBoundingBoxes(boundingBoxes, 5, 0.5f);

在圖像中檢測到的對象周圍繪製邊界框

最後一步是在對象周圍繪製邊界框。

Web 應用程序使用 Paint API 將框直接繪製到圖像上,並返回圖像以在瀏覽器中顯示。

var img = _objectDetectionService.DrawBoundingBox(imageFilePath);

using (MemoryStream m = new MemoryStream())
{
   img.Save(m, img.RawFormat);
   byte[] imageBytes = m.ToArray();

   // Convert byte[] to Base64 String
   base64String = Convert.ToBase64String(imageBytes);
   var result = new Result { imageString = base64String };
   return result;
}

另外,WPF 應用程序在與流式視頻播放重疊的Canvas 元素上繪製邊界框。

DrawOverlays(filteredBoxes, WebCamImage.ActualHeight, WebCamImage.ActualWidth);

WebCamCanvas.Children.Clear();

foreach (var box in filteredBoxes)
{
    var objBox = new Rectangle {/* ... */ };

    var objDescription = new TextBlock {/* ... */ };

    var objDescriptionBackground = new Rectangle {/* ... */ };

    WebCamCanvas.Children.Add(objDescriptionBackground);
    WebCamCanvas.Children.Add(objDescription);
    WebCamCanvas.Children.Add(objBox);
}

關於準確性的說明

Tiny YOLOv2 的精確度明顯低於完整的 YOLOv2 模型,但是對於這個示例應用程序來說,Tiny 版本已經足夠了。

疑難解答(Web 應用程序)

通過應用程序服務在 Azure 上部署此應用程序時,您可能會遇到一些常見問題。

  1. 應用程序返回 5xx 代碼
  1. 部署應用程序後,您可能會得到 5xx 代碼的一個原因是平臺。web 應用程序僅在 64 位體系結構上運行。在 Azure 中,更改設置 > 配置 > 常規設置菜單中相應應用程序服務中的平臺設置。

  2. 部署應用程序後使用 5xx 代碼的另一個原因是 web 應用程序的目標框架是. NET Core 3.0,它目前正在預覽中。您可以將應用程序和引用的項目還原爲. NET Core 2.x 或嚮應用程序服務添加擴展。

  3. 相對路徑

    在本地和 Azure 上工作時,路徑的工作方式略有不同。如果成功地部署了應用程序,但單擊其中一個預加載的映像或上載自己的映像不起作用,請嘗試更改相對路徑。爲此,在 Controllers/ObjectDetectionController.cs 文件中,更改構造函數中的_imagesTmpFolder的值。

    _imagesTmpFolder = CommonHelpers.GetAbsolutePath(@"ImagesTemp");

    Get操作中的imageFileRelativePath執行相同的操作。

    string imageFileRelativePath = @"assets" + url;

    或者,您可以根據環境(dev / prod)設置條件,是使用路徑的本地版本還是 Azure 首選的版本。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/JqvYIWhRTjuCzuh3pZXTHQ