こんにちは、ゆたんぽです。
前回に引き続き画像処理アプリの作成を行っていきます。
今回は閾値処理について説明していこうと思いますのでよろしくお願いします。
目次
モデルの作成
まず、画像処理を行うモデルを作成していこうと思います。
今回は閾値処理を行うために「CV2.Threshold」を使用していきます。
CV2.Thresholdの説明
CV2.Threshold はシングルチャンネルの行列に対して、固定閾値を使用し、閾値処理を行います。
グレイ画像から2値化画像を生成する場合やノイズ除去に用いられます。
public static double Threshold(
InputArray src,
OutputArray dst,
double thresh,
double maxval,
ThresholdTypes type
);
以下の表に説明を載せます。
引数 | 説明 |
InputArray src | 入力画像(行列)です。形式は、シングルチャンネルの8ビット、あるいは32ビット浮動小数点です。Matを入れます。 |
OutputArray dst | 入力画像(行列)です。 形式はsrcと同じです。 |
thresh | 閾値を設定します。 |
maxval | 最大値を指定します。typeがBinaryかBinaryInvの時に使用されます。 |
type | 閾値処理の種類です。ThresholdTypes.(type名)で指定します。 以下、代表的なタイプの説明 Binary: dst(x,y) = src(x,y) > thresh ? maxval : 0 BinaryInv: dst(x,y) = src(x,y) > thresh ? 0 : maxval Trunc: dst(x,y) = src(x,y) > thresh ? thresh : src(x,y) ToZero: dst(x,y) = src(x,y) > thresh ? src(x,y) : 0 ToZeroInv: dst(x,y) = src(x,y) > thresh ? 0 :src(x,y) |
CV2.Thresholdの実装
前回までに画像処理の抽象クラス「ImageProcess.cs」を作成していますので、継承を行って実装していきたいと思います。
まず、実装したプログラムを載せます。
using OpenCV_Prism.Abstract;
using OpenCV_Prism.MyExtension;
using OpenCvSharp;
using OpenCvSharp.Extensions;
using OpenCvSharp.WpfExtensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OpenCV_Prism.Models
{
class ImageThreshold : ImageProcess
{
#region 【プロパティ】
public string ParamThreshold1 { get; set; }//①
public string ParamThreshold2 { get; set; }
#endregion
#region 【コンストラクタ】
public ImageThreshold()
{
ParamThreshold1 = 50.ToString();//②
ParamThreshold2 = 100.ToString();
}
#endregion
public override void myRun()//③
{
//BitmaoImage⇒Matへ変換
using (var src = _originImage.ToMat())
using (var dst = new Mat())
using (var dst_thre = new Mat())
{
//グレー画像へ変換
Cv2.CvtColor(src, dst, ColorConversionCodes.RGB2GRAY);
Cv2.Threshold(dst, dst_thre, Convert.ToDouble(ParamThreshold1), Convert.ToDouble(ParamThreshold2), ThresholdTypes.Binary);
//Mat⇒Bitmap⇒Bitmapへ変換
Image = BitmapConverter.ToBitmap(dst_thre).ToBitmapImage();
}
}
}
}
上記のプログラムを説明します。
①プロパティを宣言しています。ここでは、CV2.Thresholdで利用するパラメータをアプリから指定できるように2つのプロパティを準備しています。
②ここでは数値の初期化をしています。
③myRunの中で閾値処理の処理内容を記載しています。
④最初のusingでは、画像処理で利用するMat型の画像を用意しており、処理が終わったらDisposeするようにしています。3つ画像を用意したのは、入力画像、グレー処理、閾値処理の順で処理を行うのでそれぞれに画像を用意したということです。
⑤ここではグレー画像を作成しています。前回の内容なので割愛します。
⑥閾値処理を行っています。詳細は先ほど説明しています。⑤で作成したグレー画像を入力画像として処理しています。パラメータは今回プロパティで用意した2つを指定しています。
⑦最後に閾値処理を行った画像をBitmapImageに変換しています。変換処理は前回同様、作成した拡張メソッドを利用しています。
ボタンの追加&画面切り替え実装
今回、作成した「ImageThreshold.cs」で画像処理を行うために切り替えボタンを作成し、各々の画像処理で画面を切り替えます。
まずは、View(xaml)から作成します。
MainWindowの実装
<Window x:Class="OpenCV_Prism.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks"
Title="{Binding Title}"
Width="auto"
Height="auto"
MinWidth="900"
MinHeight="600">
<Grid>
<StackPanel>
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Left"
Margin="10">
<Button Content="Gray"
Command="{Binding ShowImgageProcess}"
Margin="10,0,10,0"
FontSize="25"
Height="50"
Width="100">
</Button>
<Button Content="Thre"
Command="{Binding ShowImageThreshold}"
Margin="10,0,10,0"
FontSize="25"
Height="50"
Width="100">
</Button>
</StackPanel>
<ContentControl prism:RegionManager.RegionName="DisplayArea" />
</StackPanel>
</Grid>
</Window>
上記では、StackPanelを使用して、ボタンを配置しています。
前回までに作成したグレイ処理用の「Gray」、今回作成した閾値処理用の「Thre」のボタンを用意し、各々Commandに「ShowImgageProcess」、「ShowImageThreshold」をバインディングするようにしています。
これらのボタンを使用して、画像処理の表示画面を切り替えます。
MainWindowViewModelの実装
先ほど作成したボタンをViewModel側で実装していきます。
using OpenCV_Prism.Models;
using OpenCV_Prism.Views;
using Prism.Commands;
using Prism.Mvvm;
using Prism.Regions;
using System;
namespace OpenCV_Prism.ViewModels
{
public class MainWindowViewModel : BindableBase
{
private readonly IRegionManager _regionManager;
private string _title = "OpenCVApp";
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
#region 【コマンド】
public DelegateCommand ShowImgageProcess { get; }//①
public DelegateCommand ShowImageThreshold { get; }
#endregion
public MainWindowViewModel(IRegionManager regionManager)
{
_regionManager = regionManager;
_regionManager.RegisterViewWithRegion("DisplayArea", typeof(DisplayArea));
ShowImgageProcess = new DelegateCommand(ShowImgageProcessExe);//②
ShowImageThreshold = new DelegateCommand(ShowImgageThresholdExe);
}
#region 【メソッド】
private void ShowImgageProcessExe()//③
{
var p = new NavigationParameters();
p.Add("ScreenInfo", nameof(ImageProcessExe));
_regionManager.RequestNavigate("DisplayArea", nameof(DisplayArea), p);
}
private void ShowImgageThresholdExe()
{
var p = new NavigationParameters();
p.Add("ScreenInfo", nameof(ImageThreshold));
_regionManager.RequestNavigate("DisplayArea", nameof(DisplayArea), p);
}
#endregion
}
}
①先ほど作成した、ボタンのバインディングを行うために、定義した名前と同じコマンドを作成しています。
2つのボタンで同様に作成していきます。
② ①で定義したコマンドにコンストラクタでメソッドを定義しています。
③ メソッドの中身を記述しています。NavigationParametersを使用して、”ScreenInfo”というキーに、それぞれ表示するためのModel側の名前を追加しています。これで表示後の画面にパラメータを受け渡すことができ、
受け取った側で何を表示するコマンドなのかがわかるようになります。
画面遷移時のパラメータの受け渡しは過去の記事で説明していますので以下を参考にしてください。
DisplayAreaViewModelの実装
次にDisplayAreaViewModelを実装していきます。先ほどMainWindowViewModel側でボタンを押した際に「DisplayArea」にパラメータ渡しで画面遷移するようにしていますので、パラメータを受け取り、表示する画面を指定していきます。
using OpenCV_Prism.Abstract;
using OpenCV_Prism.Models;
using OpenCV_Prism.Views;
using Prism.Commands;
using Prism.Mvvm;
using Prism.Regions;
using System;
using System.Collections.Generic;
using System.Linq;
namespace OpenCV_Prism.ViewModels
{
public class DisplayAreaViewModel : BindableBase, INavigationAware//①
{
#region 【メンバー】
private readonly IRegionManager _regionManager;
#endregion
public DisplayAreaViewModel(IRegionManager regionManager)
{
_regionManager = regionManager;
_regionManager.RegisterViewWithRegion("ImageViewArea", typeof(ImageViewArea));
}
public bool IsNavigationTarget(NavigationContext navigationContext)//②
{
return true;
}
public void OnNavigatedFrom(NavigationContext navigationContext)
{
}
public void OnNavigatedTo(NavigationContext navigationContext)//③
{
var p = navigationContext.Parameters;//④
_regionManager.RequestNavigate("ImageViewArea", nameof(ImageViewArea), p);//⑤
}
}
}
上記を説明していきます。
①先ほど述べた通り MainWindowViewModel側でボタンを押した際に「DisplayArea」にパラメータ渡しで画面遷移 しているのでパラメータを受け取る設定が必要です。その設定が、INavigationAwareなので継承して、インターフェースを実装(ctrl + .など利用)しましょう。
インターフェースを実装すると「IsNavigationTarget」、「OnNavigatedFrom」、「OnNavigatedTo」が実装されます。過去の記事で説明しているので参考にしてください。
② IsNavigationTarget は、画面遷移時に表示しているデータを記憶するかどうかの設定です。他の画面に遷移した後、戻ってきた際に前回の画面を記憶したいので、return trueにしています。
③OnNavigatedToは、画面遷移をした際に実行されるので、ここでパラメータを受け取り処理を記述します。
④パラメータを受け取り、pへ格納しています。
⑤”ImageViewArea”の画面遷移を行っています。ImageViewAreaは、「画像を表示、画像取得、画像処理実行」が共通していますので同じように「ImageViewArea」を表示するようにしています。ただ処理内容は変えたいので、受け取ったパラメータをそのままパラメータ渡しで渡して、 ImageViewArea の画面遷移時に設定するようにしています。
ImageViewAreaViewModelの実装
using OpenCV_Prism.Abstract;
using OpenCV_Prism.Models;
using Prism.Commands;
using Prism.Mvvm;
using Prism.Regions;
using Reactive.Bindings;
using Reactive.Bindings.Extensions;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Reactive.Disposables;
using System.Windows.Media.Imaging;
namespace OpenCV_Prism.ViewModels
{
public class ImageViewAreaViewModel : BindableBase, INavigationAware//①
{
// リアクティブプロパティ破棄
protected CompositeDisposable _disposables = new CompositeDisposable();
ImageProcessExe imageProcessExe;//②
ImageThreshold ImageThreshold;
public ReactiveProperty<BitmapImage> Image { get; set; }
public ImageViewAreaViewModel()
{
imageProcessExe = new ImageProcessExe();//③
ImageThreshold = new ImageThreshold();
ImageSelect = new DelegateCommand(ImageSelectExe);
ImageProcess = new DelegateCommand(ImageProcessExe);
Image = imageProcessExe.ToReactivePropertyAsSynchronized(x => x.Image)
.AddTo(_disposables);
}
public DelegateCommand ImageSelect { get; set; }
public DelegateCommand ImageProcess{ get; set; }
#region 【InavigateAware】
public void OnNavigatedTo(NavigationContext navigationContext)
{
}
public bool IsNavigationTarget(NavigationContext navigationContext)//④
{
return true;
}
public void OnNavigatedFrom(NavigationContext navigationContext)//⑤
{
var screenInfo = navigationContext.Parameters.GetValue<string>("ScreenInfo");//⑥
switch (screenInfo)
{
case nameof(ImageProcessExe)://⑦
ImageSelect = new DelegateCommand(()=>imageProcessExe.ImageSelect());
ImageProcess = new DelegateCommand(() => imageProcessExe.myRun());
Image = imageProcessExe.ToReactivePropertyAsSynchronized(x => x.Image)
.AddTo(_disposables);
break;
case nameof(ImageThreshold)://⑧
ImageSelect = new DelegateCommand(() => ImageThreshold.ImageSelect());
ImageProcess = new DelegateCommand(() => ImageThreshold.myRun());
Image = ImageThreshold.ToReactivePropertyAsSynchronized(x => x.Image)
.AddTo(_disposables);
break;
default:
break;
}
}
#endregion
#region 【メソッド】
private void ImageSelectExe()
{
imageProcessExe.ImageSelect();
}
private void ImageProcessExe()
{
imageProcessExe.myRun();
}
#endregion
}
}
ImageViewAreaViewModelは前回までに実装した内容を変更しているので、変更部分のみを以下で説明します。
①画面遷移時にパラメータを受け取るためのインターフェースです。
②今回使用する2つのメソッド(ImageProcessExe、ImageProcessExe)を変数で宣言しています。
③ ②で宣言したメソッドをコンストラクタでインスタンス化しています。
④画面遷移の画面状態を保持するかどうかの設定です。保持したいのでreturn trueにしています。
⑤画面遷移時に起動する内容を記述します。パラメータ受け取りをここで行います。
⑥navigationContext.Parameters.GetValue(“ScreenInfo”)で受け渡してきたパラメータを「 “ScreenInfo” 」のキーを使用して受け取っています。
⑦Switch文で「screenInfo」の内容で分岐処理を行っています。
screenInfo がnameof(ImageProcessExe)だった場合に、2つのコマンドと画面に表示するBitmapImageを ImageProcessExe と紐づけています。
⑧ screenInfo がnameof(ImageProcessExe) だった場合に、 2つのコマンドと画面に表示するBitmapImageを ImageThreshold と紐づけています。
起動確認
ここまで来たらデバッグを開始してみましょう。
上記のようにGrayボタンでは「グレー画像」の画像処理が行われ、Threボタンでは「閾値処理」が行われていれば成功です。ボタンを切り替えていくとそれぞれで画像処理後の画像が保持されているのでわかりやすいと思います。
まとめ&次回予告
今回は閾値処理を説明していきました。
画面遷移などでインスタンス化などの記述が多く、わかりにくい部部があると思います。
実際は「シングルトン」という設計パターンを使って、メソッドのインスタンス化は1つの場所で行って無駄なインスタンス化などは行わないように実装しています。
そのため、次回はシングルトンの内容を説明していこうと思います。
「シングルトン」は、パラメータを別のViewで指定する際も使用するのでそこも含めて説明していこうと思います。
次回もよろしくお願いします。
6.OpenCVで画像処理アプリを作ろう(シングルトン)