shuhelohelo’s blog

Xamarin.Forms多めです.

OpenCvSharp4で特定の色だけを抜き出す

以下の記事をOpenCvSharpを使ってやった内容になります。

tecsingularity.com

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ですね。

元画像 f:id:shuhelohelo:20201123094350p:plain

マスク f:id:shuhelohelo:20201123094404p:plain

抽出 f:id:shuhelohelo:20201123095126p:plain

HSVで指定

(追記:HSVの値を変えながらデバッグを繰り返すのは面倒なので簡易確認ツールを作成しました。

shuhelohelo.hatenablog.com )

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();//何かキーが押されるまで待つ
        }

でも、色相の指定がよくわからないな。 なぜこの色がこの範囲になるのか。

f:id:shuhelohelo:20201123103926p:plain

f:id:shuhelohelo:20201123103948p:plain

f:id:shuhelohelo:20201123104000p:plain

ソースコード

github.com

C#でOpenCVを使うOpenCVSharp

OpenCVC#ラッパーとしてOpenCvSharpというNugetパッケージがあります。

github.com

これを使うと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();

        }
    }
}

C++Pythonでおなじみだと思います。

これを実行すると以下のように元画像とHSV色空間に変換された画像が表示されます。 f:id:shuhelohelo:20201123091140p:plain

インストール

インストールは簡単で、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キーと併用することになります。

www.dell.com

正規表現でマッチした部分を同じ文字数の記号で置換する

実装の一例としてメモ

例えば,連続する半角スペースに対して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エラーの対処

www.educative.io

remoteとlocalでcommit履歴が一致していない場合にpullやmergeを行うと以下のエラーが発生する.

refusing to merge unrelated histories

上のリンク先で使用されている画像がわかりやすいので引用するが,こんな感じ.

f:id:shuhelohelo:20200826144230p:plain

どういう状況かというと,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パターンを使って明示的にインターフェイスを実装します」という項目があるのでそれを使ってみると.

f:id:shuhelohelo:20201022122609p:plain

このような雛形が追加されます.(日本語部分は私がメモとして追加)

        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についての詳しい解説は以下の記事がとてもわかり易いです. 管理リソースの開放,非管理リソースの開放はそれぞれどこに書くのか,どうしてそこに書くのか,についてとても丁寧に書かれています.

ufcpp.net

大事な点としては,

  • 非管理リソースは確実に開放するためにデストラクタからも開放されるようにする
  • 管理リソースの開放は.NET Frameworkに任せるので,デストラクタには開放処理を書かない

これらを満たすために上のようなDisposeパターンが使われる.

Disposeパターンを使った非管理リソースの開放については以下の記事にちょっと書きました.

shuhelohelo.hatenablog.com