AI時代的網站與手機App建置與開發Part10 - 建立支援影像分類功能的手機App

 

·       摘要

手機因為內建相機功能, 又是眾多使用者無時不刻隨身携帶的裝置, 如果能夠安裝一個支援辨識圖片內容的App, 就能夠隨手將相機拍到的相片傳送給具圖片分類功能的機器學習模型進行辨識與分類, 再將辨識的結果傳回到手機, 讓手機擁有人得知辨識的結果. 在這篇文章, 我們將建立基於Microsoft MAUI跨平台架構的手機App專案, 搭配[AI時代的網站與手機App建置與開發Part8 - 使用ML.NET執行影像分類]這篇文章建立的圖片分類模型與Web API專案, 製作具圖片辨識功能的手機App.

·       編輯使用圖片分類模型的ASP.NET Core Web API專案

使用Visual Studio 2022搭配ML.NET建立的使用圖片分類模型的ASP.NET Core Web API專案提供給用戶端呼叫的端點, 其接受的參數是字串型態的檔案名稱, Program.cs這個檔案中的程式碼所示:

// Define prediction route & handler

app.MapPost("/predict",

async (PredictionEnginePool<MLModel.ModelInput, MLModel.ModelOutput>

predictionEnginePool, string imagePath) =>

    var data = new MLModel.ModelInput()

    {

      ImageSource = System.IO.File.ReadAllBytes(imagePath)

    };

    return await Task.FromResult(predictionEnginePool.Predict(input));

});

如果用戶端只將欲辨識的圖片的檔案名稱從手機傳送到Web API專案, 而不是將欲辨識的圖片檔案包裝成IFormFile資料型態, 或是將圖片檔案的內容讀取到byte陣列, 再傳送到Web API專案, Web API專案將會面臨如何取得使用者欲分類的圖片的內容的難題, 所以我們要將上述提供給使用者使用的端點修改成以下的樣子, 支援使用者將欲分類的圖片準備成ModelInput類別的型態的參數, 再傳送到Web API專案供其分類使用::

// Define prediction route & handler

app.MapPost("/predict",

async (PredictionEnginePool<MLModel.ModelInput, MLModel.ModelOutput>

predictionEnginePool, [FromBody]MLModel.ModelInput input) =>

    return await Task.FromResult(predictionEnginePool.Predict(input));

});

·       建立基於MAUI跨平台架構的手機APP專案

請啟動Visual Studio 2022, 執行[檔案 | 新增 | 專案]功能, 選擇建立[.NET MAUI應用程式]型態的專案, 如圖1所示:

1: 使用Visual Studio 2022建立[.NET MAUI應用程式]型態的專案的畫面

·       信賴ASP.NET Core HTTPS development certificate

ASP.NET Core Web API專案會使用開發憑証(Development Certificate)Web API能夠支援用戶端使用HTTPS叫用Web API服務的功能, 但是開發憑証並未設定為被信任的憑証, 所以我們要先開啟[命令提示字元]程式, 執行下列的命令, 設定信任ASP.NET Core Web API專案使用的開發憑証:

dotnet dev-certs https --trust

·       建立HttpsClientHandlerService 類別

接下來請為專案加入以下名稱為HttpsClientHandlerService的類別, 負責設定執行在Android模擬器的MAUI手機程式和執行在iOS模擬器的MAUI手機程式信任本機電腦的自我簽署憑証:

public class HttpsClientHandlerService

{

    public HttpMessageHandler GetPlatformMessageHandler()

    {

#if ANDROID

        var handler = new Xamarin.Android.Net.AndroidMessageHandler();

        handler.ServerCertificateCustomValidationCallback =                                 (message, cert, chain, errors) =>

        {

            if (cert != null && cert.Issuer.Equals("CN=localhost"))

                return true;

            return errors == System.Net.Security.SslPolicyErrors.None;

        };

        return handler;

#elif IOS

        var handler = new NSUrlSessionHandler

        {

            TrustOverrideForUrl = IsHttpsLocalhost

        };

        return handler;

#else

     throw new PlatformNotSupportedException("Only Android and iOS supported.");

#endif

    }

#if IOS

    public bool IsHttpsLocalhost(NSUrlSessionHandler sender, string url, Security.SecTrust trust)

    {

        if (url.StartsWith("https://localhost"))

            return true;

        return false;

    }

#endif

}

·       附予MAUI手機App專案讀取Android裝置外部儲存體的權限

因為手機App的使用者需要選擇手機裝置中的欲分類的圖片, 所以我們必須附予手機App讀取Android裝置外部記憶體的權限.

請開啟MAUI專案, [Platforms | Android]資料夾下的AndroidManifest.xml檔案, 勾選[READ_EXTERNAL_STORAGE], 如圖2所示:

2: 附予MAUI手機App專案讀取Android裝置外部儲存體的權限

·       定義描述呼叫部署機器學習模型的Web API服務傳入的參數的類別

請參考[AI時代的網站與手機App建置與開發Part8 - 使用ML.NET執行影像分類]這篇文件提到由ML.NET程式庫產生的MLModel.consumption.cs檔案中的ModelInput類別, 為專案加入以下的ModelInput類別:

public class ModelInput

{

    public string Label { get; set; }

    public byte[] ImageSource { get; set; }

}

·       定義描述呼叫部署機器學習模型的Web API服務傳回的執行結果的類別

請參考[AI時代的網站與手機App建置與開發Part8 - 使用ML.NET執行影像分類]這篇文件提到由ML.NET程式庫產生的MLModel.consumption.cs檔案中的ModelOutput類別, 為專案加入以下的ModelOutput類別:

public class ModelOutput

{

    public uint label { get; set; }

    public byte[] imageSource { get; set; }

    public string predictedLabel { get; set; }

    public float[] score { get; set; }

}

·       製作手機App貸款申請表單使用者介面

編輯MAUI手機App專案根目錄下的MainPage.xaml檔案, <ScrollView>的內容編輯成以下的樣子, 負責選擇欲分類的圖片的使用者介面:

<ScrollView>

    <VerticalStackLayout Padding="30,0"Spacing="25">

        <Label Text="Picture:" />

        <Image x:Name="imgSelected" HeightRequest="200" WidthRequest="200" Aspect="AspectFit" />

        <HorizontalStackLayout HorizontalOptions="FillAndExpand">

            <Button x:Name="btnSelect" Text="選取檔案" Clicked="OnSelectClicked"

                HorizontalOptions="Start" />

            <Button x:Name="btnAnalyze" Text="分類圖片" Clicked="OnAnalyzeClicked"

                HorizontalOptions="End" />              

        </HorizontalStackLayout>

    </VerticalStackLayout>

</ScrollView>

·       處理手機App使用者按下[選取檔案]按鍵的事件

請為MainPage.xaml.cs類別加入以下的OnSelectClicked函式, 負責使用FilePicker協助使用者選取欲分類的圖片, 並顯示到MainPage畫面供使用者預覽:

string imageSource = "";

private async void OnSelectClicked(object sender, EventArgs e)

{

    var images = await FilePicker.Default.PickAsync(new PickOptions

    {

       PickerTitle = "選取欲分析的檔案",

       FileTypes = FilePickerFileType.Images         //協助使用者選取圖形檔案

    });

    imageSource=images.FullPath.ToString();//將使用者選取的檔名存到imageSource變數

    imgSelected.Source = imageSource;       //顯示到Image控制項供使用者預覽:

}

·       處理手機App使用者按下[分類圖片]按鍵的事件

請為MainPage.xaml.cs類別加入以下的OnAnalyzeClicked函式, 負責發送HTTP呼叫至Web API提供的機器學習服務(請注意在Android模擬器執行的手機程式必須使用10.0.2.2 IP位址呼叫佈署在本機電腦的Web API服務), 傳入欲分類的圖片的相關資料, 並顯示分類結果:

private async void OnAnalyzeClicked(object sender, EventArgs e)

{

  //請注意下列的63201為佈署在本機電腦的Web API服務使用的連接埠

   string BaseAddress = DeviceInfo.Platform == DevicePlatform.Android

         ? "https://10.0.2.2:63201" : "https://localhost:63201";

//叫用佈署在本機電腦的Web API服務時請利用HttpsClientHandlerService建立HttpClient類別的物件

#if DEBUG

    HttpsClientHandlerService handler = new HttpsClientHandlerService();

    using (HttpClient Client = new HttpClient(handler.GetPlatformMessageHandler()))

#else

    using (HttpClient Client = new HttpClient())

#endif

    {

        string requestUri = $"{BaseAddress}/predict";    //欲呼叫的服端的端點

        byte[] ImageBytes = null;

        var stream = File.OpenRead(imageSource);         //讀取欲分類的圖片

        using BinaryReader br=new BinaryReader(stream);//建立BinaryReader類別的物件

        ImageBytes = br.ReadBytes((int)stream.Length);   //將圖片內容讀入到byte陣列

        ModelInput input = new ModelInput            //建立ModelInput類別的物件

        {

            Label = "",

            ImageSource = ImageBytes                 //填入byte陣列

        };

        string strContent=JsonSerializer.Serialize(input);//將資料序列化成JSON文件

        var content = new StringContent(strContent, Encoding.UTF8,

"application/json");//準備成StringContent

        HttpResponseMessage response =               //發送呼叫到Web API服務

await Client.PostAsync(requestUri, content);

        string Json = await response.Content.ReadAsStringAsync();//讀取呼叫結果

        ModelOutput Result =                //將結果還原成ModelOutput型態

JsonSerializer.Deserialize<ModelOutput>(Json);

        await DisplayAlert("分類結果",      //示結果                                    $"內容:{Result.predictedLabel},信心指數:{Result.score.Max():n2}", "關閉");

     }

}

·       執行手機App並送出欲分類的圖片

請於Visual Studio開發工具中使用F5功能鍵以DEBUG模式執行手機App專案, 因為我們的程式會於DEBUG模式利用HttpsClientHandlerService類別建立呼叫位於本機電腦的Web API服務的HttpClient類別物件, 請注意手機App欲呼叫的Web API服務必須預先啟動, 叫用Web API服務的功能才會成功. 3所示即為手機App呼叫佈署機器學習模型的Web API服務的畫面:

3: 使用手機App提交欲分類的圖片的畫面



留言

這個網誌中的熱門文章

AI時代的網站與手機App建置與開發Part27 - ML.NET與物件偵測

AI時代的網站與手機App建置與開發Part28 - 使用YOLO模型進行物件偵測

AI時代的網站與手機App建置與開發Part24 - ML.NET與圖片異常偵測