C#のWinFormsで画像加工アプリ3「2値化」

コンピュータ

2値化を行うフィルター機能を追加します。

ソースコード

ファイル名:Form1.Threshold.cs(新規追加)

using OpenCvSharp;
using OpenCvSharp.Extensions;

namespace GazouKakou02;
public partial class Form1 : Form
{
    // メニュー項目
    readonly ToolStripMenuItem thresholdMenuItem = new()
    {
        Text = "2値化",
    };
    
    /// <summary>
    /// 2値化の初期化
    /// </summary>
    public void Init_Threshold()
    {
        // メニューの登録
        filterMenuItem.DropDownItems.Add(thresholdMenuItem);

        // フィルター(2値化)
        Func<Bitmap, double, Task<Bitmap>> filter = new(async (src, thresh) =>
        {
            return await Task.Run(()=>
            {
                using Mat srcMat = BitmapConverter.ToMat(src);
                using Mat dstMat = new();

                if (srcMat.Channels() > 1)
                {
                    Cv2.CvtColor(srcMat, srcMat, ColorConversionCodes.RGB2GRAY);                  
                }
                Cv2.Threshold(srcMat, dstMat, thresh, 255, ThresholdTypes.Binary);

                return BitmapConverter.ToBitmap(dstMat);
            });
        });

        // メニューアイテムのクリックイベント
        thresholdMenuItem.Click += (s, e) =>
        {
            if (_buffBmp is null) return;

            var dialog = new FilterDialog();

            dialog.Load += (s, e) =>
            {
                dialog.Track1.Maximum = 255;
                dialog.Track1.Minimum = 0;
                dialog.Track1.Value = 127;
            };

            bool filterFlag = false;
            dialog.OkBtn.Enabled = !filterFlag;
            Bitmap? bmp = null;
            dialog.Track1.ValueChanged += async (s, e) =>
            {
                dialog.Track1Label.Text = string.Format("閾値:{0}", dialog.Track1.Value);
                if (filterFlag)
                {
                    // フィルター実行中につきキャンセル
                    return;
                }

                filterFlag = true;
                var backupValue = dialog.Track1.Value;
                var currentValue = backupValue;
                do
                {
                    backupValue = currentValue;


                    double thresh = currentValue;

                    bmp = await filter(_buffBmp, thresh);
                    dialog.Picbox.Image?.Dispose();
                    dialog.Picbox.Image = bmp;
                    currentValue = dialog.Track1.Value;

                } while(backupValue != currentValue);

                filterFlag = false;
                dialog.OkBtn.Enabled = !filterFlag;
            };

            if (dialog.ShowDialog() == DialogResult.OK)
            {
                // OK
                this.Bmp = bmp;
            } else {
                // Cancel
                bmp?.Dispose();
            }
        };
    }
}

実行

画像が表示されている状態でメインメニュー「フィルター」→「2値化」を選ぶ

閾値のトラックバーの移動に合わせてフィルターの結果も変化します。

解説

8bitグレスケールの場合、取りうる値は0~255の256パターンになります。2値化の場合、これを0と1の2パターンに分類することになります。0と1であれば1ピクセルを1bitの配列で表現することも出来ますがbit演算は難しいので、8bitのまま0を0(黒)、1を255(白)としています。

2値化は色々と使い道があり、個人的にはざっくりとした物体の範囲を色で選択することによく使います。GIMPで色域選択やファジー選択をする場合、アンチエイリアスの影響で境界部分が上手く選択できない場合がありますが、2値化した画像だと上手く選択できる場合があります。

また、境界線を検出するフィルタ、直線を検出、物体検出するなどでも2値化画像が使われます。元がカラー画像の場合2値化する前に一度グレスケール化する必要がありますので、グレスケール化と2値化はセットで使う場合が多いです。

2値化する方式も色々ありますが、今回はシンプルに閾値を元に閾値より下を0(黒)それ以外を255(白)にするようになっています。
閾値はトラックバーで調整することが出来るようにしており、トラックバーの変化でプレビュー画像も連動して変化するようにしてあります。

コメント