14.OpenCVで画像処理アプリを作ろう(ModelからViewへの展開方法2:Model編)

こんにちは、ゆたんぽです。

引き続き改善報告で紹介したModelからView、ViewModelへの自動展開を紹介していこうと思います。

前回の記事を下に載せます。

今回はModel部分を紹介していきます。


Modelの構想

まず、実装の中身を紹介する前に考え方について説明します。

Model側でやりたいことは各画像処理のクラスにViewで作成したParamDecimalなどの情報を載せることです。

ParamDecimalの情報は、Viewで作成したParamDecimal.xamlにあるNameとValue,その他最大、最小値など情報を持つ必要があります。

なので、簡単に行うなら上記で上げた情報を持つParamDecimalのクラスを作って、それをリストにすることでParamDecimal.xamlに必要な情報の集合体を持つことができます。

しかし、それだけだとParamDecimalしか対応できないので汎用性がなくなってしまいます。

そこで、ParamDecimalだけでなく他の型にも対応できるようにParamBaseというものを作成します。

上の図で示したように、ParamBaseでは今後作成するクラスで共通部分であるName(パラメータの名前)を持ったクラスです。

これをBaseとして、継承したパラメータのクラス(ここでは参考にStringデータを持ったクラス)を作成することでParamBase型でリスト型を作ることができます。

上の図では、ParamManagerがその情報をもつクラスとなります。

このようなことを準備すると様々な形の集合体をParamBase型で持つことができるので、ViewModel側でParamManagerの中身を確認することで様々なViewへ情報を紐づけることができるので汎用性を確保できます。

このような考えでModel側を準備していきます。


Modelの実装

実際に実装を進めていきます。

Modelの構想で表した図の通りModelを実装していきます。

ParamBaseの作成

上記の図で上げた通り、まずはParamBaseから作成していきます。

図を見るとParamDecimalやParamStringに共通しているのはName部分です。

この共通部分をBaseが持つようにClassを作成していきます。

using PropertyChanged;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace OpenCV_Prism.Param
{
    [AddINotifyPropertyChangedInterface]

    public class ParamBase : INotifyPropertyChanged
    {
        #region 【INotifyPropertyChanged】

        public event PropertyChangedEventHandler PropertyChanged;

        protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

        #endregion

        #region 【プロパティ】

        /// <summary>
        /// 名前
        /// </summary>

        public string Name { get; set; }

        #endregion

        public ParamBase()
        {
        }

    }
}

上記のようにシンプルなクラスとなります。

まずは、INotifyPropertyChangedが実装されていることがわかると思います。

この理由は、今回作成するParamBaseを継承するクラスはReactivePropertyでViewmodel側と紐づけを行う必要があるからです。

INotifyPropertyChangedを実装していないとReactiveRroperty変換時にエラーが発生します。

そのため、このINotifyPropertyChangedは継承するクラスで必要なことがわかりますので共通部分を反映させるParamBaseで実装しておくことで
その継承後のクラスの記述が不要となります。

これらの理由でINotifyPropertyChangedを実装しています。

INotifyPropertyChangedを除けば、プロパティにNameを持っているだけのクラスとなります。

ParamDecimalの作成

次にParamBaseを継承したParamDecimalを作成していきます。

View部分でも説明しましたが、ParamDecimalは設定するプロパティが多いので下記のようなクラスとなります。

using PropertyChanged;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace OpenCV_Prism.Param
{

    [AddINotifyPropertyChangedInterface]

    public class ParamDecimal: ParamBase
    {

        #region 【プロパティ】

        /// <summary>
        /// 値
        /// </summary>
        public decimal Value { get; set; }

        /// <summary>
        /// 最大値
        /// </summary>
        public Decimal Maximum { get; set; }

        /// <summary>
        /// 最小値
        /// </summary>
        public Decimal Minimum { get; set; }

        /// <summary>
        /// 増減量
        /// </summary>
        public Decimal Interval { get; set; }

        /// <summary>
        /// 表示規則
        /// </summary>
        public string StringFormat { get; set; }

        #endregion

        #region 【コンストラクタ】

        public ParamDecimal(string name,decimal value, Decimal min = Int32.MinValue, Decimal max = Int32.MaxValue,  Decimal interval = 1, string stringFormat = "")
        {
            Name = name;
            Value = value;
            Maximum = max;
            Minimum = min;
            Interval = interval;
            StringFormat = stringFormat;
        }

        #endregion

    }
}

プロパティでは実際に使用するパラメータを実装しているだけなので、説明は割愛します。

コンストラクタ部分では、初期値が予想されているものだけ値を入れています。

最大値や最小値、インターバルはおおよそ共通することが多いので初期値を入れている感じなのでこの辺は好みで実装してください。

Model側でDecimal型でのパラメータを追加したいときはこのクラスを使用するようにします。

ParamManagerの作成

次にParamDecimalなどのParamBaseを継承したクラスをリストで所有するParamManagerを実装していきます。

using OpenCV_Prism.Param;
using PropertyChanged;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace OpenCV_Prism.Abstract
{
    /// <summary>
    /// ユニット基準クラス
    /// </summary>
    [AddINotifyPropertyChangedInterface]

    public class ParamManager : INotifyPropertyChanged
    {
        #region 【INotifyPropertyChanged】

        public event PropertyChangedEventHandler PropertyChanged;

        protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

        #endregion

        //パラメータリスト
        public ObservableCollection<ParamBase> ParamList { get; set; }

        public ParamManager()
        {
            ParamList = new ObservableCollection<ParamBase>();
        }
        #region 【メソッド】

        public void Add(ParamBase[] testm)
        {
            foreach (var item in testm)
            {
                ParamList.Add(item);
            }
        }

        #endregion

    }
}

例のごとくINotifyPropertyChangedを実装しています。

次にParamBaseの情報を持つParamListをプロパティで準備します。

ParamListにModel側で必要な情報を追加していくことでどんなパラメータがどれだけあるか情報を持たせることができます。

最後にメソッドです。

ここでは、Addメソッドを実装しています。

これは、先ほど定義したParamListにParamBase型でパラメータ情報を追加するものです。

ParamBaseの配列型を指定することで追加できるようになります。

ImageProcessのプロパティ追加

画像処理系のクラスへ継承されているImageProcessへプロパティを追加します。

namespace OpenCV_Prism.Abstract
{
    [AddINotifyPropertyChangedInterface]

    public abstract class ImageProcess :UnitBase,INotifyPropertyChanged
    {
        #region 【メンバー】

        protected BitmapImage  _originImage;

        #endregion

        #region 【プロパティ】

        public BitmapImage Image { get; set; }

        public Mat SaveImage { get; set; }

        /// <summary>
        /// パラメータ管理
        /// </summary>
        public ParamManager PrmManager { get; protected set; }

        #endregion

        #region 【コンストラクタ】

        public ImageProcess()
        {
            _originImage = new BitmapImage();
            Image = new BitmapImage();
            SaveImage = new Mat();

            PrmManager = new ParamManager();
        }

        #endregion

--------------------以下省略---------------------------------

追加プロパティは先ほど作成したParamManagerクラスでPrmManagerという名前で実装しています。

コンストラクタで初期化しましょう。

これにより、ImageProcessを継承して画像処理を行う際に、定義したプロパティ(ParamDecimalやParamStringなど)をコンストラクタでPrmManagerにAddすることで、パラメータ情報を持つ共通の名前のリストを持つことができます。

ImageThreshold.csへのパラメータ実装

過去に実装したImageThreshold.csに今回準備してきたパラメータの追加を実装してきます。

必要あれば過去の記事を参照してください。

以下に修正したコードを載せます。

using OpenCV_Prism.Abstract;
using OpenCV_Prism.MyExtension;
using OpenCV_Prism.Param;
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 ParamDecimal PrmThreshold { get; set; }

        public ParamDecimal PrmMaxVal { get; set; }

        #endregion

        #region 【コンストラクタ】//②

        public ImageThreshold()
        {
            PrmThreshold = new ParamDecimal("Threshold", (decimal)50,0,255);
            PrmMaxVal = new ParamDecimal("MaxVal", (decimal)100,0,255);
            PrmManager.Add(new ParamBase[] { PrmThreshold, PrmMaxVal });
        }

        #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, (double)PrmThreshold.Value, (double)PrmMaxVal.Value, ThresholdTypes.Binary);//③

                //Mat⇒Bitmap⇒Bitmapへ変換
                Image = BitmapConverter.ToBitmap(dst_thre).ToBitmapImage();

                SaveImage = dst_thre.Clone();

            }
        }
    }
}

まずは、プロパティの追加です。

以前はString型で実装していたプロパティをParamDecimal型へ変更していきます。

プロパティはParamDecimal.csのクラスへ変更するだけです。

コンストラクタで中の値を定義していきます。

まずは、PrmThreshold(閾値)の方ですが”Threshold”という名前で初期値50、最小値0、最大値255としています。

インターバルやフォーマットは初期値のままでよいので実装していません。

MaxVal(最大値)も同様に設定していきます。

PrmManager.Add(new ParamBase[] { PrmThreshold, PrmMaxVal })で、ParamBase型のリストへ先ほど定義したプロパティを追加しています。

これにより、ImageProcessを継承したクラスでは、PrmManagerのリストを見に行けば、プロパティの情報を取得できるようになります。

実際に画像処理内で値を使用する際は、(double)PrmThreshold.Valueのように.Valueを使って値を読み出して使いたい型へキャストすることで使用できます。


まとめ&次回予告

今回は、「ModelからView、ViewModelへの自動展開」方法の中でModelの実装を説明してきました。

Model側では親のクラスを作って、共通のリストを作ることで情報を取得しやすい環境を構築しました。

次回は、Viewmodelの実装を紹介して、Modelからの除法をView側へ展開することができます。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です