OpenCVのフィルターを任意の順番で実行するUIを作っていて、データソースで悩んでいます。
実行順番のことを考えるとListなどの要素の順番をもつ構造のコレクションが良さそうですが、要素となるフィルターをオブジェクト化しようとすると、異なるクラスのオブジェクトをListにセットする必要があり、不可能では無さそうですが自分のスキルではキャストの嵐になりそうです。
ということで、単一のクラスで複数のフィルターを呼び出せるような構造を考えてみたいと思います。
まずフィルターのパラメータですが、OpenCVのフィルター類で多いのは数値型です。整数型と浮動小数点型の2種類あります。共通のパラメーターもありますが、基本的にフィルターごとに異なります。とは言え数値は数値、異なる目的で使われるとしても整数型と浮動小数点型などの型を合わせて上げれば同じ器にのせることが出来ます。とは言えなんのパラメーターか識別できなと面倒なので、ジェネリックDictionaryでkeyにあたる部分にパラメータの名称、valueにパラメーターの値をセットすることにします。フィルターに複数のパラメーターがあってもkeyが異なることで複数保持することが出来ます。そのDictionaryを整数と浮動小数で2つもつことにします。これだけでも結構なフィルターをカバーすることが出来そうですし、異なる型が必要な場合でもDictioanryを増やすことで対応できそうです。
次にフィルターの実行ですが、フィルターごとにメソッドを分ける必要があります。だた呼び出す場合同じメソッド名で実行できるとforeachなどの繰り返し処理で都合が良いです。一見矛盾するお話ですが、リフレクションという機能を使うと実現することが出来るようです。
using System.Diagnostics.Contracts;
using System.Reflection;
using System.Reflection.Metadata.Ecma335;
using System.Text;
using System.Text.Json;
public class OpenCVFilter
{
public string Name {get; set;} = "";
public Dictionary<string, int> IntParam {get; set;} = [];
public Dictionary<string, double> DoubleParam {get; set;} = [];
// コンストラクタ
public OpenCVFilter()
{
// privateにすることで直接newさせないようにしたいがJSONデシリアライズで失敗する。
}
// フィルターの実行
public void Execute()
{
// 自身のtype型を取得
Type type = this.GetType();
// this.Nameにセットしたフィルター名のメソッドを取得
MethodInfo? method = type.GetMethod(this.Name);
// メソッドを実行
method?.Invoke(this, null);
}
// ガウシアンフィルターの生成
static public OpenCVFilter GenrateGaussianFilter()
{
// 初期値をセット
OpenCVFilter obj = new()
{
Name = "GaussianFilter",
};
obj.IntParam["Ksize"] = 3;
return obj;
}
// ガウシアンフィルターを実行
public void GaussianFilter()
{
// とりあえずコンソールに文字を出力してみる。
var ksize = IntParam["Ksize"];
Console.WriteLine($"{Name} Ksize={ksize}");
}
// アンシャープマスキングフィルター生成
static public OpenCVFilter GenrateUnsharpFilter()
{
// 初期値をセット
OpenCVFilter obj = new()
{
Name = "UnsharpFilter",
};
obj.DoubleParam["K"] = 1.5;
return obj;
}
// アンシャープマスキングフィルターを実行
public void UnsharpFilter()
{
// とりあえずコンソールに文字を出力してみる。
var k = DoubleParam["K"];
Console.WriteLine($"{Name} K={k}");
}
// ノンローカルミーンフィルター生成
static public OpenCVFilter GenrateNonLocalMeanFilter()
{
// 初期値をセット
OpenCVFilter obj = new()
{
Name = "NonLocalMeanFilter",
};
obj.DoubleParam["H"] = 0;
return obj;
}
// ノンローカルミーンフィルターを実行
public void NonLocalMeanFilter()
{
// とりあえずコンソールに文字を出力してみる。
var h = IntParam["H"];
Console.WriteLine($"{Name} H={h}");
}
}
class Program
{
static void Main()
{
var filter1 = OpenCVFilter.GenrateGaussianFilter();
// パラメータを変更
filter1.IntParam["Ksize"] = 5;
filter1.Execute();
var filter2 = OpenCVFilter.GenrateUnsharpFilter();
filter2.Execute();
var filter3 = OpenCVFilter.GenrateNonLocalMeanFilter();
filter3.IntParam["H"] = 15;
filter3.Execute();
// List<T>の配列にしてみる。
List<OpenCVFilter> filters = [filter1, filter2, filter3];
// JOSNシリアライズ
string jsonStr = JsonSerializer.Serialize(filters);
Console.WriteLine(jsonStr);
// JSONでシリアライズ
List<OpenCVFilter> filters2 = JsonSerializer.Deserialize<List<OpenCVFilter>>(jsonStr) ?? [];
// デシリアライズで戻したオブジェクトを実行
foreach(var filter in filters2)
{
filter.Execute();
}
}
}
サンプルプログラムを書いてみました。フィルターの実行はfilter.Execute()で呼び出していますが、Execute()内でNameプロパティからどのメソッドを呼び出すかを識別し、Invoke()で実行しています。初めて使いましたが何とも気持ちが悪いです。
またこのクラスはnew()で生成せずに、フィルターごとの生成専用のメソッドから生成します。本来であればコンストラクタをprivateにセットするべきなのでしょうが、JSONのシリアライズに支障が出るのでpublicのままにしています。
とりあえず、複数の異なるフィルターをListの乗せてシリアライズすることが出来るようなりました。インスタンスの生成や使い方に決まり事があり(必要なパラメータが無いとか)、それに違反すると破綻してしまう、なんとも雑な作りですが、やりたいことが実装できそうな感じがします。
コメント