HUSKING - kotteri

技術系Note

【C#】判別分析法を用いて画像2値化の閾値を算出してみる

前回の続き

husk.hatenablog.com


今回は対象の画像から閾値を動的に算出するメソッドを作成してみた

        /// <summary>
        /// 判別分析法により閾値を求める
        /// </summary>
        /// <param name="img"></param>
        /// <returns></returns>
        public int GetThreshold(Bitmap img)
        {
            BitmapData imgData = null;
            try
            {
                //=====================================================================
                // 変換する画像の1ピクセルあたりのバイト数を取得
                //=====================================================================
                PixelFormat pixelFormat = img.PixelFormat;
                int pixelSize = Image.GetPixelFormatSize(pixelFormat) / 8;

                //=====================================================================
                // 変換する画像データをアンマネージ配列にコピー
                //=====================================================================
                imgData = img.LockBits(
                    new Rectangle(0, 0, img.Width, img.Height),
                    ImageLockMode.ReadWrite,
                    pixelFormat);
                byte[] buf = new byte[imgData.Stride * imgData.Height];
                Marshal.Copy(imgData.Scan0, buf, 0, buf.Length);

                //=====================================================================
                // ヒストグラム算出
                //=====================================================================
                int[] hist = new int[256];
                int sum = 0;
                int cnt = 0;

                for (int y = 0; y < imgData.Height; y++)
                {
                    for (int x = 0; x < imgData.Width; x++)
                    {
                        // ピクセルで考えた場合の開始位置を計算する
                        int pos = y * imgData.Stride + x * pixelSize;

                        // ピクセルの輝度を算出
                        int gray = (int)(0.299 * buf[pos + 2] + 0.587 * buf[pos + 1] + 0.114 * buf[pos]);

                        hist[gray]++;
                        sum += gray;
                        cnt++;
                    }
                }

                // 全体の輝度の平均値
                double ave = sum / cnt;

                //=====================================================================
                // 閾値算出
                //=====================================================================
                int sh = 0;
                double sMax = 0;

                for (int i = 0; i < 256; i++)
                {
                    // クラス1とクラス2のピクセル数とピクセル値の合計値を算出
                    int n1 = 0;
                    int n2 = 0;
                    int sum1 = 0;
                    int sum2 = 0;

                    for (int j = 0; j < 256; j++)
                    {
                        if (j <= i)
                        {
                            n1 += hist[j];
                            sum1 += hist[j] * j;
                        }
                        else
                        {
                            n2 += hist[j];
                            sum2 += hist[j] * j;
                        }
                    }

                    // クラス1とクラス2のピクセル値の平均を計算
                    double ave1 = sum1 == 0 ? 0 : sum1 / n1;
                    double ave2 = sum2 == 0 ? 0 : sum2 / n2;

                    // クラス間分散の分子を計算
                    double s = n1 * n2 * Math.Pow((ave1 - ave2), 2);

                    // クラス間分散の分子が最大のとき、クラス間分散の分子と閾値を記録
                    if (s > sMax)
                    {
                        sh = i;
                        sMax = s;
                    }
                }
                return sh;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                return 0;
            }
            finally
            {
                if (img != null && imgData != null)
                {
                    img.UnlockBits(imgData);
                }
            }
        }

このメソッドの戻り値で閾値が取得できる
<例>194


前回のプログラムと今回のを組み合わせれば、画像に応じて最適な閾値を取得して、うまく2値化できる・・・はず。