OpenCvSharp4で特定の色だけを抜き出す
以下の記事をOpenCvSharpを使ってやった内容になります。
RGBで指定
RGBで色の範囲を指定して抜き出すには以下のようにします。
using OpenCvSharp; using System; namespace OpenCVColorExtraction { class Program { //抽出したい色の範囲をRGBで指定 const int B_MAX = 40; const int B_MIN = 0; const int G_MAX = 50; const int G_MIN = 0; const int R_MAX = 170; const int R_MIN = 100; static void Main(string[] args) { Console.WriteLine("Hello World!"); string inputFilePath = "Images/gauge-1.jpg"; //画像を読み込み var src = Cv2.ImRead(inputFilePath); if (src is null) Console.WriteLine("fail to read file."); //マスクを作成 Scalar s_min = new Scalar(B_MIN, G_MIN, R_MIN); Scalar s_max = new Scalar(B_MAX, G_MAX, R_MAX); Mat maskImage = new Mat(); Cv2.InRange(src, s_min, s_max, maskImage); //マスクを使ってフィルタリング Mat masked = new Mat(); src.CopyTo(masked, maskImage); //表示 using (new Window("src", src)) using (new Window("maskImage", maskImage)) using (new Window("masked", masked)) Cv2.WaitKey();//何かキーが押されるまで待つ } } }
概ねOKですね。
元画像
マスク
抽出
HSVで指定
(追記:HSVの値を変えながらデバッグを繰り返すのは面倒なので簡易確認ツールを作成しました。
using OpenCvSharp; using System; namespace OpenCVColorExtraction { class Program { //抽出したい色の範囲をHSVで指定 const int H_MAX = 120; const int H_MIN = 111; const int S_MAX = 255; const int S_MIN = 50; const int V_MAX = 255; const int V_MIN = 50; static void Main(string[] args) { Console.WriteLine("Hello World!"); string inputFilePath = "Images/gauge-1.jpg"; //画像を読み込み var src = Cv2.ImRead(inputFilePath); if (src is null) Console.WriteLine("fail to read file."); //HSVに変換 Mat hsv = new Mat(); Cv2.CvtColor(src, hsv, ColorConversionCodes.RGB2HSV); //マスクを作成 Scalar s_min = new Scalar(H_MIN, S_MIN, V_MIN); Scalar s_max = new Scalar(H_MAX, S_MAX, V_MAX); Mat maskImage = new Mat(); Cv2.InRange(hsv, s_min, s_max, maskImage); //マスクを使ってフィルタリング Mat masked = new Mat(); src.CopyTo(masked, maskImage); //表示 using (new Window("src", src)) using (new Window("hsv", hsv)) using (new Window("maskImage", maskImage)) using (new Window("masked", masked)) Cv2.WaitKey();//何かキーが押されるまで待つ }
でも、色相の指定がよくわからないな。 なぜこの色がこの範囲になるのか。
ソースコード
C#でOpenCVを使うOpenCVSharp
OpenCVのC#ラッパーとしてOpenCvSharpというNugetパッケージがあります。
これを使うとC#でOpenCVによる画像処理や色々ができます。
例えば、画像を読み込んで表示させたり、画像の色空間を変更したりというのは以下のようなコードを書きます。
using OpenCvSharp; using System; namespace OpenCVColorExtraction { class Program { static void Main(string[] args) { Console.WriteLine("Hello World!"); string inputFilePath = "Images/gauge-1.jpg"; var src = Cv2.ImRead(inputFilePath); if (src is null) Console.WriteLine("fail to read file."); Mat hsv = new Mat(); Cv2.CvtColor(src, hsv, ColorConversionCodes.RGB2HSV); using (new Window("src", src)) using (new Window("hsv", hsv)) Cv2.WaitKey(); } } }
これを実行すると以下のように元画像とHSV色空間に変換された画像が表示されます。
インストール
インストールは簡単で、OpenCvSharpのReadMeにも書かれているとおり、Nugetパッケージからインストールするだけです。
ここではWindowsの場合のみを紹介しますが、他にもUWPの場合やUbuntuの場合などが書いてありますので、自分の環境に合わせてインストールするパッケージを選択します。
Windowsの場合は2とおりあります。
- OpenCVSharp4 と OpenCVSharp4.runtime.winの組み合わせ
- OpenCVSharp4.Windowsのみ
特段理由がなければ後者のOpenCvSharp.Windowsをインストールすればよいでしょう。
Dell Latitude 7300でFnキーロック
Latitude 7300ではファンクションキーを使う場合にはFnキーをあわせて押す必要があります。 例えば、Visual Studioでデバッグを実行したいとき(F5)などですね。
Fnキーを一緒に押さない場合は、画面の明るさや音量を変更する機能が割り当てられています。
Surfaceとは真逆なので、結構戸惑ってしまいますが、当然それを解消する設定が用意されており、以下のように公式で紹介されています。
Fn+Esc
でFnキーをロックすることができ、Surfaceなどと同じ操作にすることができます。
音量などを変更したい場合は逆にFnキーと併用することになります。
正規表現でマッチした部分を同じ文字数の記号で置換する
実装の一例としてメモ
例えば,連続する半角スペースに対して2文字目以降をnbspで置き換える場合は以下のようにする.
public static string ConsecutiveBlanksToNBSP(string sentense) { Regex re = new Regex(@"(?<= )( )+"); MatchCollection matches = re.Matches(sentense); if (matches.Count == 0) { Console.WriteLine("nothing"); return sentense; } var strBuilder = new StringBuilder(sentense); foreach (Match m in matches) { strBuilder.Remove(m.Index, m.Length);//対象部分を削除 strBuilder.Insert(m.Index, new string('\u00a0', m.Length));//挿入 } return strBuilder.ToString(); }
Gitでrefusing to merge unrelated historiesエラーの対処
remoteとlocalでcommit履歴が一致していない場合にpullやmergeを行うと以下のエラーが発生する.
refusing to merge unrelated histories
上のリンク先で使用されている画像がわかりやすいので引用するが,こんな感じ.
どういう状況かというと,remoteとlocalで別々に作成されたリポジトリがあって,local側でremote側のリポジトリをgit remote add
コマンドで追加した,という状況.
local側でfb62efc
のコミットが行われ,それとは別にremote側でda7cc7a
のコミットが行われている.
このため,local側とremote側で保持しているコミット履歴が異なるためにエラーが発生している,という状況.
このエラーが発生している状態でgit log
を実行すると,以下のとおり.
> git log --oneline --all da7cc7a (origin/master) Initial commit fb62efc (HEAD -> master) Created sln and added gitignore file.
これに対して以下のコマンドを実行する.
git pull origin master --allow-unrelated-histories
> git pull origin master --allow-unrelated-histories From https://github.com/shuheydev/xUnitPractice * branch master -> FETCH_HEAD Merge made by the 'recursive' strategy. README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md
コンフリクトなどがなければこれまで関連のない2つのリポジトリがマージされて以下のようになる.
> git log --all --oneline --graph * df81915 (HEAD -> master) Merge branch 'master' of https://github.com/shuheydev/xUnitPractice into master |\ | * da7cc7a (origin/master) Initial commit * fb62efc Created sln and added gitignore file.
privateフィールドの値を取得したりprivateメソッドを実行したり
フィールドの場合はBindingFlags.GetField
, メソッドの場合はBindingFlags.InvokeMethod
を指定している.
GetField()
メソッドやGetMethod()
メソッドを使う場合は上記のフラグはそもそも必要ない.
ClassHoge hoge = new ClassHoge(); Type hogeType = hoge.GetType(); //private field FieldInfo field = hogeType.GetField("_fieldA", BindingFlags.NonPublic | BindingFlags.Instance); string val = (string)field.GetValue(hoge); Console.WriteLine(val); //or var val2 = (string)hogeType.InvokeMember("_fieldA", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField, null, hoge, null); Console.WriteLine(val2); //private method var method = hogeType.GetMethod("MethodA", BindingFlags.NonPublic | BindingFlags.Instance); var result = method.Invoke(hoge, new object[] { 10, 20 }); Console.WriteLine(result); //or var result2 = (string)hogeType.InvokeMember("MethodA", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod, null, hoge, new object[] { 30, 40 }); Console.WriteLine(result2); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public class ClassHoge { private string _fieldA = "This is private Field"; private string MethodA(int a, int b) { return $"This is private method: a = {a}, b = {b}"; } }
実行結果
This is private Field This is private Field This is private method: a = 10, b = 20 This is private method: a = 30, b = 40
C#のDisposeパターン
クラスを作成するときに,クラス内で使用するリソースを適切に開放するために,IDisposable
インターフェイスを実装します.
IDisposableを実装するときに,以下のように「Disposeパターンを使って明示的にインターフェイスを実装します」という項目があるのでそれを使ってみると.
このような雛形が追加されます.(日本語部分は私がメモとして追加)
private bool disposedValue; protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { // TODO: dispose managed state (managed objects).管理リソースの開放 } // TODO: free unmanaged resources (unmanaged objects) and override finalizer // TODO: set large fields to null // 非管理リソースの開放 // 一時ファイルなどを削除したり,大きなフィールドにnullをセットするなど disposedValue = true; } } // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources: 非管理リソースの開放が必要な場合のみ,このデストラクタを有効にする // ~TemporaryFile() // { // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method // Dispose(disposing: false); // } void IDisposable.Dispose() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(disposing: true); GC.SuppressFinalize(this); }
と,Visual Studioで用意してくれる雛形に合わせて,それぞれ管理リソースと非管理リソースの開放処理を記述すると良いです.
IDisposableについての詳しい解説は以下の記事がとてもわかり易いです. 管理リソースの開放,非管理リソースの開放はそれぞれどこに書くのか,どうしてそこに書くのか,についてとても丁寧に書かれています.
大事な点としては,
- 非管理リソースは確実に開放するためにデストラクタからも開放されるようにする
- 管理リソースの開放は.NET Frameworkに任せるので,デストラクタには開放処理を書かない
これらを満たすために上のようなDisposeパターンが使われる.
Disposeパターンを使った非管理リソースの開放については以下の記事にちょっと書きました.