- 追加された行はこの色です。
- 削除された行はこの色です。
#author("2021-05-16T12:11:47+09:00","default:kyo-in","kyo-in")
#author("2023-07-10T11:07:52+09:00","default:kyo-in","kyo-in")
**機械学習による図形推定 [#e2875e85]
機械学習ライブラリ[[ML.net:https://docs.microsoft.com/ja-jp/dotnet/machine-learning/]]を使用し,描かれたストロークの図形を推定して,整形された図形に置き換える機能を実装する.
今回は,楕円と四角どちらかを推定する,二項分類を行う.
#ref(./shapepredict.gif,50%);
**ML.netのインストール [#sf3d923f]
ML.netは.NET standard 2.0に依存している.UWPで.NET standard 2.0を使用可能な状態にするには最小ターゲットバージョンを1809にする必要がある.
ソリューションエクスプローラーから,Kisozemiプロジェクトのプロパティを開き,[アプリケーション]-[ターゲットバージョン]と[最小バージョン]を共に,1809に変更し,保存,いったんVisual Studioを再起動する.
#ref(./target.png,50%);
再起動後,[プロジェクト]-[NuGetパッケージの管理]を開き,[参照]からML.netを検索し,プロジェクトにインストールする.
#ref(./nuget.png,50%);
**学習モデルの読み込み [#r5ec9fc9]
本来,今回のような分類の機械学習を行う場合,学習データの準備と学習モデルの作成が必要となる.
しかし,時間も限られているため,今回はすでに作成されている学習モデルを読み込み,図形の分類推定に使用することとする.
以下のmodel.zipは,楕円と四角の1ストロークで描いた図形を約60個学習させたモデルとなる.
#ref(./model.zip);
model.zipは,プロジェクトのAssetsフォルダにドラッグ&ドロップなどで追加し,「プロパティ」から「ビルドアクション」を「コンテンツ」にしておく.
#ref(./modelzip.png);
適当なButtonを用意し,Clickイベントでmodel.zipを読み込む.
ML.netを使用するため,usingに以下を追加:
using Microsoft.ML;
using Microsoft.ML.Data;
クラスのメンバに以下を追加:
MLContext mlContext = null;
ITransformer strokePredictModel = null;
クリックイベント内で実際にmodel.zipを読み込む,''非同期処理(await, async)を使用しているため,イベントメソッドの先頭にasyncを追加すること,System.IOのクラスを使用しているのでusing System.IOを追加する必要があるが,System.IO.PathとWindows.UI.Xaml.Shapes.Pathがあいまいになるので,Windows.UI.Xaml.Shapes.Pathクラスを使用する場合,明示的にすべて書く必要がある'':
Windows.Storage.StorageFolder installedFolder = Windows.ApplicationModel.Package.Current.InstalledLocation;
Windows.Storage.StorageFolder assetsFolder = await installedFolder.GetFolderAsync("Assets");
Windows.Storage.StorageFile modelFile = await assetsFolder.GetFileAsync("model.zip");
mlContext = new MLContext();
//Define DataViewSchema for data preparation pipeline and trained model
DataViewSchema modelSchema;
// Load trained model
using (System.IO.Stream stream = await modelFile.OpenStreamForReadAsync()) //using System.IO;が必要
{
strokePredictModel = mlContext.Model.Load(stream, out modelSchema);
stream.Flush();
}
''注意''
UWPで作成されるアプリはiOSやAndroidのアプリ同様に,端末内のストレージに自由にアクセスすることはできない.
そのため,
Windows.Storage.StorageFolder installedFolder = Windows.ApplicationModel.Package.Current.InstalledLocation;
Windows.Storage.StorageFolder assetsFolder = await installedFolder.GetFolderAsync("Assets");
によって,プロジェクトのAssetsフォルダの場所を取得し,そこからmodel.zipを読み込んでいる.
Windows.Storage.StorageFolder storageFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
また上記では,アプリが自由に使えるフォルダを取得できる.
**図形の推定 [#ab8dd1e6]
以下の.csファイルをプロジェクトに追加する.中には機械学習用の入力データ(StrokeData),出力データ(StrokePredict)クラスが定義されている.
#ref(./StrokeData.cs);
学習モデルを読み込んだ状態であれば,ストロークを描き終えた後,そのストロークを特徴ベクトルに変換し,それを学習モデルに入力,図形の推定を行い,推定結果を基に該当する図形をストロークの大きさ,位置に生成する.
以下のdrawingStrokeは描画中のWindows.UI.Xaml.Shapes.Pathである.
StrokeData input;
//input に ストロークを特徴ベクトル化したデータを生成(後述)
StrokeData[] inputdata = new[] { input };
IDataView batchinput = mlContext.Data.LoadFromEnumerable(inputdata);
IDataView predictions = strokePredictModel.Transform(batchinput);
IEnumerable<StrokePredict> strokePredict = mlContext.Data.CreateEnumerable<StrokePredict>(predictions, reuseRowObject: false);
foreach(StrokePredict sp in strokePredict)
{
AddMessage($"Prediction: {(Convert.ToBoolean(sp.Prediction) ? "Ellipse" : "Rect")} | Probability: {sp.Probability} ");
var pathgeom = (PathGeometry)drawingStroke.Data;
var rect = pathgeom.Bounds;
if (sp.Prediction == true) //trueであれば楕円として推定されたのでEllipseをrectのサイズ,位置で生成する falseであればRectangleを同様に生成
{
var ellipse = new Windows.UI.Xaml.Shapes.Ellipse {
Stroke = new SolidColorBrush(Color.FromArgb(255, 0, 0, 0)),
StrokeThickness = 1
};
ellipse.Width = rect.Width;
ellipse.Height = rect.Height;
MainCanvas.Children.Add(ellipse);
Canvas.SetLeft(ellipse, rect.X);
Canvas.SetTop(ellipse, rect.Y);
}
else
{
var rectangle = new Windows.UI.Xaml.Shapes.Rectangle
{
Stroke = new SolidColorBrush(Color.FromArgb(255, 0, 0, 0)),
StrokeThickness = 1
};
rectangle.Width = rect.Width;
rectangle.Height = rect.Height;
MainCanvas.Children.Add(rectangle);
Canvas.SetLeft(rectangle, rect.X);
Canvas.SetTop(rectangle, rect.Y);
}
}
Shapeクラスの位置指定はCanvas.Set〜メソッドで行う.
Canvas.SetLeft(ellipse, rect.X);
Canvas.SetTop(ellipse, rect.Y);
**ストロークから特徴ベクトルの生成 [#ea8301d7]
ストロークの点群そのままでは,機械学習に使用できないため,特徴ベクトルへ変換する.
今回は,ストロークを構成する点群の差分ベクトル(Pi+1 - Pi)を算出し,その角度を整数値0〜359とし,各角度の出現回数をカウントすることで,360次元のベクトルを特徴ベクトルとする(少なくとも楕円と四角ではその分布は大きく異なるはず,という仮定)
#ref(./vector.png);
ヒント:
StrokeData input;
input = new StrokeData();
input.DirCount = new float[360];
//点群全体に以下の処理
//差分ベクトルの角度をMath.Atan2で算出し0〜359整数値に変換->deg
input.DirCount[deg] += 1.0f;
//一通り終わったら全体から最大値を求める->max
//すべてをmaxで割り正規化
input.DirCount[i] /= max;
**その他ヒント [#b071af12]
Windows.UI.Xaml.Shapes.Pathから点群,PolyLineSegmentを取り出すメソッド(エラー処理なし)
private PolyLineSegment GetPolyLineSegment(Windows.UI.Xaml.Shapes.Path src)
{
var pathgeom = (PathGeometry)src.Data;
var pathfig = pathgeom.Figures[0];
return (PolyLineSegment)pathfig.Segments[0];
}
**課題提出方法 [#r54a0cd6]
招待されているBoxアップロードフォルダ「情報メディア基礎ゼミ(森谷)」へ,完成させた課題のソリューションフォルダをZIP圧縮し,ファイルを以下の名前でアップロードしてください.
招待されているBoxアップロードフォルダ「情報メディア基礎ゼミ(森谷)」の「第5回」へ,完成させた課題のソリューションフォルダをZIP圧縮し,ファイルを以下の名前でアップロードしてください.
ファイル名:XXFIXXX_5th.zip(例:18FI999_5th.zip)
''提出締め切り:7月29日(水) 18:00''
''提出締め切り:7月14日(金) 23:59''
BOXアップロードフォルダの招待メールが来ていない方は森谷までメール連絡ください.
**課題提出方法 [#r54a0cd6]
招待されているBoxアップロードフォルダ「情報メディア基礎ゼミ(森谷)」の「第6回」へ,完成させた課題のソリューションフォルダをZIP圧縮し,ファイルを以下の名前でアップロードしてください.
ファイル名:XXFIXXX_6th.zip(例:18FI999_6th.zip)
''提出締め切り:7月21日(金) 23:59''
BOXアップロードフォルダの招待メールが来ていない方は森谷までメール連絡ください.
*任意課題 [#k5aba3e3]
ストロークの色や太さを変更できる,図形補間のオンオフ,画像の保存など,独自の機能を実装せよ.
ヒント:Shapeクラスの図形もXAMLからUI上に配置し,イベントを持たせることが可能である.
<Ellipse x:Name="PenColorBlack" Margin="10, 10, 10, 10" Fill="Black" Width="20" Height="20" RelativePanel.Below="PredictModeSwitch" PointerPressed="PenColorBlack_PointerPressed"></Ellipse>
<Ellipse x:Name="PenColorGreen" Margin="10, 10, 10, 10" Fill="LimeGreen" Width="20" Height="20" RelativePanel.Below="PredictModeSwitch" RelativePanel.RightOf="PenColorBlack" PointerPressed="PenColorGreen_PointerPressed"></Ellipse>
#ref(./advance.gif,50%);
**課題提出方法 [#r54a0cd6]
招待されているBoxアップロードフォルダ「情報メディア基礎ゼミ(森谷)」の「第6回」へ,完成させた課題のソリューションフォルダをZIP圧縮し,ファイルを以下の名前でアップロードしてください.
ファイル名:XXFIXXX_6th_advance.zip(例:18FI999_6th_advance.zip)
''提出締め切り:7月21日(金) 23:59''
BOXアップロードフォルダの招待メールが来ていない方は森谷までメール連絡ください.