スポンサーリンク

CustomVisionによる一番イングラムっぽいMS選手権

お仕事
スポンサーリンク

こんちわ。seiです。えー、タイトルから言ってネタですが。

最近、子供たちと一緒にパトレイバーにはまってます。

パトレイバー生誕30周年を超え、まだまだ人気が衰えることのないコンテンツです。

TVアニメ版「特車二課壊滅す」とかもう抱腹絶倒です。自分が子供の時に見たときは面白いって感覚よりも「イングラムかっこいい」って気持ちが先行してましたが、今になって見直すとイングラムが登場しなくたっておもしろいし、パトレイバーが登場した当時のまだ社会にITなんて概念が広く浸透していない時代にHOSとかコンピューターウィルスといった設定や、バビロンプロジェクトのように未来を予言でもしているかというような先進的な内容も多く、今になってパトレイバーというコンテンツの素晴らしさを再認識させられました。アーリーデイズも新OVA版も最高ですよ。

そんなパトレイバーの主役機、AV-98イングラムのプラモデルでも作ろうというのが今回のブログの内容になります。

スポンサーリンク

イングラムのプラモデルを探してみた

ってことでイングラムのプラモデル探してみました。自分が子供のころに作ったのは1/60のモデル。その後、なんとMGとして1/35のイングラムが販売されていました。ちょいと高い・・・。気が付いたら再販されてるっぽいし。

現在はMGも入手困難でしてグッドスマイルカンパニーのモデロイドがパトレイバーつくるには入手しやすいでしょうか・・・。

ただ、イングラム買ってそのまま作っても面白みがないので、今回は「HGのモビルスーツの中で一番イングラムに似ていると思われる機体をイングラムっぽく塗装して我慢するw」って趣旨で行ってみたいと思います。

CustomVisonを使って一番イングラムに似ているMSを探し出す

では、一番イングラムっぽいMSって何かなぁってなった時に、普通であれば直感に頼るんでしょうか?いろいろな人の意見を聞いて決めるのでしょうか?

決め方はいろいろあるのですが、今回はMicrosoftが提供しているAIや機械学習の技術を用いたサービスであるCustomVisionを使用して決定したいと思います。

CustomVisionとは

CustomVisionとは、Microsoftが提供しているCognitive Servicesのサービスの中の1つで、タグ(=正解)を付けた画像をアップロードし、学習させることで独自の画像分類器を作成することができるサービスです。

Microsoft Public Affiliate Program (JP)(マイクロソフトアフィリエイトプログラム)

すでに登場から3、4年たっていますが今も進化を続けています。要Azureサブスクリプションかな・・・。無料で取得できると思いますが。

今回のシチュエーションでいうと、

①HGのMSの画像にそのMS名をタグとしてつけてCustomVisionプロジェクト上にアップロードする。

②登録したプロジェクトを学習させ、それぞれの画像の違いを理解させることで画像分類器を作成する。

③AV-98イングラムの画像を画像分類器に渡し、どのHGのMS名が一番高い確率で返ってくるかでイングラムに一番似ているMSとする。

というような流れになります。

CustomVisionを使用した画像分類器の作成方法

画像分類モデルの作成なんですが、CustomVisionのサイトにアクセスして画像をアップロードしてタグをつけてトレーニングすればあっという間に出来上がってしまいます。あとはイングラムの画像を食わせて、どのMS名が返ってくるかを見るだけです。

ただ、今回はVisualBasicを使用して、1からプログラミングしてみました。といっても、CognitiveServicesに関してはRESTfulAPIでほとんどのサービスが提供されているため、エンドポイントとリクエストパラメーターなど少し変えればほぼ大体のことがこなせてしまうためあまり苦労はないと思います。一番苦労するのはリファレンスが英語であることかな・・・。

ってことで、CustomVisionを使用した画像分類器を作るまでは、

【CustomVisionTrainingAPI】

①プロジェクトを登録する。

②対象プロジェクトに画像をアップロードする。

③画像に対してタグを設定する。

④プロジェクトをトレーニングする。

【CustomVisionPredictionAPI】

予測用画像を引き渡し、結果を得る。

上記の流れになります。

CustomVisionの実装内容

実装コードは以下のようになります。VisualStudio2019です。VS2015以降なら問題ないかな。CustomVisionAPIへのリクエスト部分のみ抜粋。削除などは省略してあります。Jsonの解析にはNewtonsoft.json.dllを使用しています。コントロールのコードなんかも入っちゃってるのでコピペしただけでは動かないと思います。悪しからず。

Azure側で取得したサブスクリプションキーはテストプログラムなのでapp.config内にべた書きしてあります。

登録したプロジェクト、タグなどの情報はXMLでシリアル化してローカルに保存してWebAPIとのやり取りを節約しております。まぁそんなにがつがつやらなければ無料枠内で可能かと思いますが。

ちなみに、CustomVisionの無料枠は

  • プロジェクトは2つまで
  • 1時間/月のトレーニング
  • 登録画像は5000枚まで
  • 10000回/月までの予測実行

となっております。登録画像数によってはトレーニング時間が一番引っ掛かりやすいかな。

CustomVisionAPIは現在v3が最新かと思いますが、記述した当時のv1.1のままになっています。

トレーニング状況の取得はもうちょいちゃんとやった方がよかったかな・・・。

Private Async Sub Request(ByVal action As RequestType)

        Dim client = New HttpClient()
        Dim queryString = HttpUtility.ParseQueryString(String.Empty)

        ' Request headers
        Dim uri = String.Empty

        Dim response As HttpResponseMessage

        ' RESTful APIの呼び出し
        Select Case action
            Case RequestType.CreateProject
                ' Projectの登録
                uri = "https://japaneast.api.cognitive.microsoft.com/customvision/v1.1/Training/projects?name=" & Me.ProjectNameTextBox.Text

                ' uriにクエリーストリングを付加
                uri &= queryString.ToString

                ' Request headers
                client.DefaultRequestHeaders.Add("Training-key", My.Settings.CustomVisionTrainSubscriptuionKey)

                Dim bodyString As String = String.Empty
                Dim byteData() As Byte = Encoding.UTF8.GetBytes(bodyString)

                Using content = New ByteArrayContent(byteData)
                    content.Headers.ContentType = New MediaTypeHeaderValue("application/json")
                    response = Await client.PostAsync(uri, content)
                End Using

                If response.StatusCode = System.Net.HttpStatusCode.OK Then
                    Dim responceBody = Await response.Content.ReadAsStringAsync()

                    ' 要求が成功した場合
                    Me.ProjectNameTextBox.ReadOnly = True
                    Me.CreateProjectButton.Enabled = False
                    Me.DeleteProjectButton.Enabled = True
                    Me.ProjectIDLabel.Text = JsonConvert.DeserializeObject(responceBody)("Id")

                    MessageBox.Show("プロジェクトの登録に成功しました。", My.Application.Info.AssemblyName, MessageBoxButtons.OK, MessageBoxIcon.Information)
                Else
                    MessageBox.Show("プロジェクトのの登録に失敗しました。", My.Application.Info.AssemblyName, MessageBoxButtons.OK, MessageBoxIcon.Error)
                End If

            Case RequestType.CreateTag
                Me.CreateTagsButton.Enabled = False
                Me.DeleteTagsButton.Enabled = False
                Dim targetTextBox As TextBox = Nothing

                For i As Integer = 1 To 5
                    Select Case i
                        Case 1
                            targetTextBox = Me.Tag1TextBox

                        Case 2
                            targetTextBox = Me.Tag2TextBox

                        Case 3
                            targetTextBox = Me.Tag3TextBox

                        Case 4
                            targetTextBox = Me.Tag4TextBox

                        Case 5
                            targetTextBox = Me.Tag5TextBox
                    End Select

                    If targetTextBox.Text = String.Empty Then
                        Continue For
                    End If

                    ' タグ名の存在チェック
                    Dim target As CustomVisionAccessor.TagInfo = (From value In accessor.TagList
                                                                  Where value.Name = targetTextBox.Text.Trim
                                                                  Select value).FirstOrDefault

                    If Not target.Equals(CType(Nothing, CustomVisionAccessor.TagInfo)) Then
                        ' すでに登録済みのタグ名は登録スキップ
                        ' Do nothing
                    Else
                        ' タグの登録を行い、タグIDを取得する
                        uri = "https://japaneast.api.cognitive.microsoft.com/customvision/v1.1/Training/projects/" & Me.ProjectIDLabel.Text & "/tags?name=" & targetTextBox.Text
                        ' uriにクエリーストリングを付加
                        uri &= queryString.ToString

                        ' Request headers
                        client.DefaultRequestHeaders.Add("Training-key", My.Settings.CustomVisionTrainSubscriptuionKey)

                        Dim bodyString As String = String.Empty
                        Dim byteData() As Byte = Encoding.UTF8.GetBytes(bodyString)

                        Using content = New ByteArrayContent(byteData)
                            content.Headers.ContentType = New MediaTypeHeaderValue("application/json")
                            response = Await client.PostAsync(uri, content)

                            If response.StatusCode = Net.HttpStatusCode.OK Then
                                Dim responceBody = Await response.Content.ReadAsStringAsync()

                                Dim id As String = JsonConvert.DeserializeObject(responceBody)("Id")

                                Me.TagListView.Items.Add(New ListViewItem(targetTextBox.Text))
                                Me.TagListView.Items(Me.TagListView.Items.Count - 1).SubItems.Add(id)
                                Me.TagListView.Items(Me.TagListView.Items.Count - 1).SubItems.Add(0)

                                ' タグListに追加
                                accessor.TagList.Add(New CustomVisionAccessor.TagInfo With {.Name = targetTextBox.Text, .Id = id, .ImageCount = 0})

                                targetTextBox.Text = String.Empty
                            End If
                        End Using
                    End If
                Next

                Me.CreateTagsButton.Enabled = True
                Me.DeleteTagsButton.Enabled = True

            Case RequestType.AddImages
                Me.AddImagesButton.Enabled = False
                Me.TrainingButton.Enabled = False
                Me.AddImageStatusLabel.ForeColor = Color.Red
                Me.AddImageStatusLabel.Text = "画像登録中..."

                Dim index As Integer = 0
                For Each item As ListViewItem In Me.TagListView.CheckedItems
                    ' 画像に同時に追加するタグはクエリー文字列に配列として追加する必要あり!!
                    queryString.Add("tagIds[]", item.SubItems(1).Text)
                Next

                ' Projectにタグと画像の一括登録
                uri = "https://japaneast.api.cognitive.microsoft.com/customvision/v1.1/Training/projects/" & Me.ProjectIDLabel.Text & "/images/image?"

                ' uriにクエリーストリングを付加
                uri &= queryString.ToString

                Dim files() As String = Nothing

                If Me.FolderSelectRadioButton.Checked Then
                    ' 指定ディレクトリ内の画像ファイルを一括登録する場合
                    files = Directory.GetFiles(Me.ImageDirectoryTextBox.Text)
                Else
                    ' 単一画像ファイルを登録する場合
                    files = {Me.ImageDirectoryTextBox.Text}
                End If

                ' Request headers
                client.DefaultRequestHeaders.Add("Training-key", My.Settings.CustomVisionTrainSubscriptuionKey)

                Dim imageCounter As Integer = 0

                For Each imageFilePath As String In files
                    Dim byteData() As Byte

                    Try
                        ' 画像ファイルをバイト配列にする
                        Using fs As FileStream = New FileStream(imageFilePath, FileMode.Open, FileAccess.Read)
                            byteData = New Byte(fs.Length) {}
                            fs.Read(byteData, 0, byteData.Length)
                        End Using

                        Using content = New ByteArrayContent(byteData)
                            ' 画像ファイルを直接指定の場合
                            content.Headers.ContentType = New MediaTypeHeaderValue("application/octet-stream")
                            response = Await client.PostAsync(uri, content)
                        End Using

                        If response.StatusCode = Net.HttpStatusCode.OK Then
                            imageCounter += 1
                        End If

                    Catch ex As Exception
                        ' 失敗した場合は、処理を継続する
                        Using sw As StreamWriter = New StreamWriter(Path.Combine(Application.StartupPath, "Error.log)"), True, Encoding.GetEncoding("Shift_JIS"))
                            sw.WriteLine(DateTime.Now.ToString("yyyy/MM/dd hh:mm:ss") & " " & "画像登録失敗:ファイル名 = " & imageFilePath & ",エラー詳細 = " & ex.ToString)
                        End Using
                    End Try

                    If Me.FolderSelectRadioButton.Checked Then
                        ' 指定ディレクトリ内の画像ファイルを一括登録する場合
                        ' 2トランザクション/秒の制限があるため、500ミリ秒待機する
                        Threading.Thread.Sleep(500)
                    Else
                        ' 単一画像ファイルを登録する場合
                        ' Do nothing
                    End If
                Next

                ' 対象となるタグのイメージ件数をアップデートする
                For Each item As ListViewItem In Me.TagListView.CheckedItems
                    item.SubItems(2).Text = CType(item.SubItems(2).Text, Integer) + imageCounter
                    ' AccessorのタグListもアップデート
                    For i As Integer = 0 To accessor.TagList.Count - 1
                        If accessor.TagList(i).Name = item.Text Then
                            Dim value As CustomVisionAccessor.TagInfo = accessor.TagList(i)
                            value.ImageCount += imageCounter
                            accessor.TagList(i) = value
                            Exit For
                        End If
                    Next
                Next

                ' 画像登録を行った場合はトレーニング未実施状態にする
                Me.TrainingStatusLabel.ForeColor = Color.Black
                Me.TrainingStatusLabel.Text = "トレーニング未実施"
                accessor.HasTrained = False

                Me.AddImagesButton.Enabled = True
                Me.TrainingButton.Enabled = True
                Me.AddImageStatusLabel.ForeColor = Color.Blue
                Me.AddImageStatusLabel.Text = "画像登録完了"

            Case RequestType.Train
                ' トレーニングの実施
                uri = "https://japaneast.api.cognitive.microsoft.com/customvision/v1.1/Training/projects/" & Me.ProjectIDLabel.Text & "/train"

                ' uriにクエリーストリングを付加
                uri &= queryString.ToString

                ' Request headers
                client.DefaultRequestHeaders.Add("Training-key", My.Settings.CustomVisionTrainSubscriptuionKey)

                Dim bodyString As String = String.Empty
                Dim byteData() As Byte = Encoding.UTF8.GetBytes(bodyString)

                Using content = New ByteArrayContent(byteData)
                    content.Headers.ContentType = New MediaTypeHeaderValue("application/json")
                    response = Await client.PostAsync(uri, content)
                End Using

                Dim responceBody = Await response.Content.ReadAsStringAsync()
                Dim errorMessage As String = JsonConvert.DeserializeObject(responceBody)("Message")

                If response.StatusCode = System.Net.HttpStatusCode.OK Then
                    Me.TrainingStatusLabel.ForeColor = Color.Blue
                    Me.TrainingStatusLabel.Text = "トレーニング完了"
                    accessor.HasTrained = True
                    accessor.LastIterationId = JsonConvert.DeserializeObject(responceBody)("Id")
                Else
                    If errorMessage.Contains("Nothing changed since last training") Then
                        Me.TrainingStatusLabel.ForeColor = Color.Blue
                        Me.TrainingStatusLabel.Text = "トレーニング完了"
                        accessor.HasTrained = True
                        MessageBox.Show("前回のトレーニング後から変更されていません。", My.Application.Info.AssemblyName, MessageBoxButtons.OK, MessageBoxIcon.Information)
                    Else
                        Me.TrainingStatusLabel.ForeColor = Color.Red
                        Me.TrainingStatusLabel.Text = "※トレーニング失敗"
                        accessor.HasTrained = False
                        MessageBox.Show("トレーニングに失敗しました。" & vbCrLf & "エラー詳細 = " & errorMessage, My.Application.Info.AssemblyName, MessageBoxButtons.OK, MessageBoxIcon.Error)
                    End If
                End If

            Case RequestType.Predict
                ' 画像の予測を行う
                uri = "https://japaneast.api.cognitive.microsoft.com/customvision/v1.1/Prediction/" & Me.ProjectIDLabel.Text & "/image/nostore?"

                ' uriにイテレーションIDを追加する
                ' 複数イテレーションがある状態だと「デフォルトのイテレーションが存在しないよ」って怒られてPredictionできないため
                queryString("iterationId") = accessor.LastIterationId

                ' uriにクエリーストリングを付加
                uri &= queryString.ToString

                ' Request headers
                ' CustomVisionPredict用のサブスクリプションキーを使う
                client.DefaultRequestHeaders.Add("Prediction-Key", My.Settings.CustomVisionPredictSubscriptuionKey)

                Dim byteData() As Byte

                ' 画像ファイルをバイト配列にする
                Using fs As FileStream = New FileStream(Me.FilePathTextBox.Text, FileMode.Open, FileAccess.Read)
                    byteData = New Byte(fs.Length) {}
                    fs.Read(byteData, 0, byteData.Length)
                End Using

                Using content = New ByteArrayContent(byteData)
                    ' 画像ファイルを直接指定の場合
                    content.Headers.ContentType = New MediaTypeHeaderValue("application/octet-stream")
                    response = Await client.PostAsync(uri, content)
                End Using

                If response.StatusCode = Net.HttpStatusCode.OK Then
                    Me.PredictionStatusLabel.Text = "予測完了"
                    Me.PredictionStatusLabel.ForeColor = Color.Blue

                    Dim responceBody = Await response.Content.ReadAsStringAsync()

                    Dim predictions = JsonConvert.DeserializeObject(responceBody)("Predictions")

                    Dim dic As Dictionary(Of String, String) = New Dictionary(Of String, String)

                    For Each result In predictions
                        If Not dic.ContainsKey(result("Tag")) Then
                            dic.Add(result("Tag"), Math.Round(CType(result("Probability"), Decimal) * 100) & "%")
                        End If
                    Next

                    Dim list As List(Of KeyValuePair(Of String, String)) = New List(Of KeyValuePair(Of String, String))
                    list.AddRange(dic)
                    Me.ResultDataGridView.DataSource = list

                    With Me.ResultDataGridView
                        .AutoGenerateColumns = True
                        .AutoResizeRowHeadersWidth(DataGridViewRowHeadersWidthSizeMode.AutoSizeToAllHeaders)
                        .AutoResizeColumnHeadersHeight()
                        ' 編集不可に設定
                        .ReadOnly = True
                        ' 新規行の追加を不可に設定
                        .AllowUserToAddRows = False
                        .AllowUserToDeleteRows = False
                        ' 行ヘッダーを非表示に設定
                        .RowHeadersVisible = False
                        ' 行の高さ変更不可に設定
                        .AllowUserToResizeRows = False
                        ' 垂直スクロールバーのみ表示を設定
                        .ScrollBars = ScrollBars.Vertical
                        .RowsDefaultCellStyle.BackColor = Color.PaleGreen
                        .AlternatingRowsDefaultCellStyle.BackColor = Color.White
                        .Columns(0).Width = CType(.ClientRectangle.Width / 2, Integer)
                        .Columns(1).Width = CType(.ClientRectangle.Width / 2, Integer)
                        .Font = New Font("MS 明朝", 14)
                    End With
                Else
                    Me.PredictionStatusLabel.Text = "予測失敗" & vbCrLf &
                                                    "トレーニング中の可能性もありますので、しばらくしてから再実行してください"
                    Me.PredictionStatusLabel.ForeColor = Color.Red
                End If
        End Select

End Sub

アプリケーションを使用して画像分類モデルを構築

さっそく出来上がったアプリケーションを使用して、HG画像の登録・タグ付けを行い、イングラム検索モデルを作成してみました。

現在発売されているすべてのHGではトレーニング用画像の用意が大変なため、独断と偏見でジム系のMSのみエントリーさせることにしました。すんません。ちなみに、購入が安易にできるようにバンダイホビーサイト未記載のHG、および、オンラインショップ系のHGは省いております。それではエントリーしたMSはこちらです。

GM/GMガンキャノンジェガン(エコーズ仕様)
ジェガンブラストマスタージム(サンダーボルト)ジム・インターセプトカスタム
ジム・ガードカスタムジムⅢジムⅢ・ビームマスター
ジムカスタムジムスナイパーⅡジムスナイパーK9
スタークジェガンネモ(デザートカラー)ネモ(ユニコーンVer)
パワードジム・カーディガンブルーディスティニーリゼル
リゼル(ゼネラルレビル)陸戦型ジム

CustomVisionの制約にある「各タグごとに5枚の画像が必要」とのことなので、各MS最低5枚の画像を用意しています。

それでは「IngramSearch」というプロジェクトを作り、画像登録・タグ付けを行いました。初めはイングラムは白と黒だけだからと、各トレーニング画像すべてをグレースケール変換したものをアップしてからトレーニングを行った結果です。CustomVisionサイトで確認するもあんまり精度が出ていません。白黒2値のパターンで判断されてしまっているのでしょうか・・・。

ですので、こんどはちゃんとフルカラー画像をアップして、先ほどのグレースケールと合わせてトレーニングを行ってみました。そこそこ精度が上がり、これなら使い物になるかと思います。

構築したモデルで画像の予測を行う

さぁ、それでは作成した「イングラム検索」CustomVisionモデルを使用してNo.1イングラムに似ているMSを決定したいと思います。

どぅるるるるるるるるるるる・・・・じゃん!!

最もイングラムに似ているMSは、「ジムカスタム」でした!!おめでとぅーーすっ!!

ちなみに、予測に使ったイングラムの画像はプラモデルではなく、ロボット魂の画像です。

左前方からの画像。トレーニング画像には必ず入っているポジションです。頭一つジムカスタムかな。

背面からの画像もジムカスタムが若干高い。

他の対象物ある画像ですがジムカスタム。ジムスナイパーⅡが検討しているのもなんとなくうなずけます。

ポージングを決めている画像はトレーニング画像にあまりなかったのですが、こいつは文句なしでジムカスタムですね。すばらしい。

その他の画像も例外ありますがほぼほぼジムカスタムと認識されています。

さいごに

CustomVisionですが、とても簡単にユーザー独自の画像認識技術を利用することができます。 今回行ったのは「画像は何か?」といった分類の内容ですが、 最近の機能アップデートでは、「画像内に対象はいくつ存在しどこにあるか?」といった物体検出の機能も追加され、ますます利用価値が増したように感じます。

それでは次回、今回の「一番イングラムっぽいMS選手権」の結果を踏まえて、ジムカスタムのイングラム化塗装を行っていきたいと思いますw

塗装した後もう一度CustomVisionに予測させてイングラム率はどれだけ上がるかも試してみたいですね。

いつになるかわかりませんがお楽しみにw

続きはこちらで。

Microsoft Public Affiliate Program (JP)(マイクロソフトアフィリエイトプログラム)

コメント

タイトルとURLをコピーしました