天天看點

利用邊緣改進全局門檻值處理-c#實作-基于EmguCv

關于邊緣改進全局門檻值處理基本實作是:

 1.先計算其邊界,利用拉普拉斯或者梯度變換都以。

 2.計算變化後邊界圖像的絕對值

3.指定一個門檻值(一般以百分比的形式指定,比如我指定90%,如果存在有灰階k,灰階小于K的像素總數占全部像素的90%,那麼K就是我們要求的灰階)

4.對2中計算完的圖像進行門檻值變換,轉化為2值圖像

5.用4中計算得到的二值圖像乘原始圖像。

6.計算5中計算的到的圖像中灰階大于0的直方圖。

7.對6中得到的直方圖進行全局分隔。

8.提出全局分隔出來的灰階,用該灰階對原始圖檔進行門檻值分割,即可得到結果。

c#算法實作

主函數:

/// <summary>
        /// 邊界修飾自适應門檻值變換
        /// </summary>
        /// <param name="image">輸入圖像</param>
        /// <param name="percent">百分比</param>
        /// <returns></returns>
        public static Mat EdgeModifyOtsu(Mat image,double percent=0.99)
        {
            Mat _m = new Mat();//拉普拉斯變換後的圖像
            Mat _m1 = new Mat();
            Mat _Rmat = new Mat();//要傳回的圖像
            //拉普拉斯變換
            CvInvoke.Laplacian(image, _m, Emgu.CV.CvEnum.DepthType.Cv16S,3);
            //取絕對值
            _m1 = AbsCv16s(_m);
            //門檻值變換
            double max = PercentGary(_m1, percent);
            CvInvoke.Threshold(_m1, _m1, max, 1, Emgu.CV.CvEnum.ThresholdType.Binary);

            //轉換為8位元組
            _m1 = Cv16sTo8u(_m1);

            //與原圖像相乘
            CvInvoke.Multiply(_m1, image, _m1, 1, Emgu.CV.CvEnum.DepthType.Cv8U);
            //尋找相乘後的圖像灰階大于0的直方圖的自适應門檻值
            int _K = OtsuThreshold(_m1, 1);
            CvInvoke.Threshold(image, _Rmat, _K, 255, Emgu.CV.CvEnum.ThresholdType.Binary);
            return _Rmat;
        }
           

取絕對值函數:

/// <summary>
        /// 将16位像素的灰階值絕對值化
        /// </summary>
        /// <param name="image">圖檔</param>
        /// <returns>轉換好的圖檔</returns>
        public static Mat AbsCv16s(Mat image)
        {
            Mat _m_ = new Mat(image.Size, Emgu.CV.CvEnum.DepthType.Cv16S, 1);
            unsafe
            {
                Int16* dataImage = (Int16*)image.DataPointer.ToPointer();
                Int16* data_m_ = (Int16*)_m_.DataPointer.ToPointer();
                for (int row = 0; row < image.Height; row++)
                {
                    //data = data + row * image.Cols;
                    for (int col = 0; col < image.Width; col++)
                    {
                    
                            Int16 _ii = *dataImage;
                            * data_m_ = Math.Abs(_ii); 
                        
                        dataImage++;
                        data_m_++;
                    }
                }
            }
            return _m_;
        }
           

用百分比計算分割函數:

/// <summary>
        /// 計算圖檔百分比像素的灰階值
        /// </summary>
        /// <param name="image">輸入圖檔</param>
        /// <param name="percent">百分比像素</param>
        /// <returns>傳回灰階值</returns>
        public static double PercentGary(Mat image, double percent)
        {
            long[] _his = Histogram(image, 0,16);
            long _count = 0;//表示在k中存在的像素
            for (int _index = 0; _index < Math.Pow(2,16); _index++)//若所有像素都在k内,就将其方差置為0
            {
                _count += _his[_index];
                if ((double)_count / image.Total.ToInt64() > percent)
                {
                    return _index;
                }

            }
            return Math.Pow(2, 16);

        }
           

将16位有符号二值圖像轉換為8位無符二值圖像函數:

/// <summary>
        /// 将16有符号位二值圖轉化為8位
        /// </summary>
        /// <param name="image">圖檔</param>
        /// <returns>傳回轉換好的圖檔</returns>
     public static Mat Cv16sTo8u(Mat image)
        {
            Mat _m_ = new Mat(image.Size, Emgu.CV.CvEnum.DepthType.Cv8U, 1);
            unsafe
            {
                Int16* dataImage = (Int16*)image.DataPointer.ToPointer();
                byte* data_m_ = (byte*)_m_.DataPointer.ToPointer();
                for (int row = 0; row < image.Height; row++)
                {
                    //data = data + row * image.Cols;
                    for (int col = 0; col < image.Width; col++)
                    {
                        *data_m_ = Convert.ToByte( Math.Abs(*dataImage));
                        dataImage++;
                        data_m_++;
                    }
                }
            }
            return _m_;
        }
           
尋找相乘後的圖像灰階大于0的直方圖的自适應門檻值函數:
           
/// <summary>
        /// 尋找圖檔大于d值像素集的最适應門檻值
        /// </summary>
        /// <param name="image">輸入的圖檔</param>
        /// <param name="d"></param>
        /// <returns>傳回最合适門檻值</returns>
        public static int OtsuThreshold(Mat image, int d)
        {
            long[] his = Histogram(image,8,d);

            float _PK;
            float _MK;//第k級累加灰階均值;
            float _MG = 0;//整個圖檔的灰階均值

            long _MN = 0;//圖檔的像素數目
            float[] _Ks = new float[256];//存儲類值最大方差
            float _Max;//類間最大方差
            List<int> _MaxKs = new List<int>();//存儲使類間方差最大的多個K值;

            for (int _index = 0; _index < his.Length; _index++)//計算直方圖中像素的總數
            {
                _MN += his[_index];
            }

            for (int i = 0; i < 256; i++)//計算圖檔平均灰階值
            {
                _MG += (float)(i * (double)his[i] / _MN);
            }


            for (int k = 0; k < 256; k++)//計算 圖檔在不同K的類值最大方差
            {
                long _count = 0;//表示在k中存在的像素
                for (int _index = 0; _index <= k; _index++)//若所有像素都在k内,就将其方差置為0
                {
                    _count += his[_index];
                }
                if (_count == _MN)
                {
                    _Ks[k] = 0;
                    continue;
                }
                else if (_count == 0)
                {
                    _Ks[k] = 0;
                    continue;
                }

                _PK = (float)((double)_count / _MN);

                _MK = 0;

                for (int i = d; i <= k; i++)
                {
                    float p = (float)((double)his[i] / _MN);
                    //_PK += p;
                    _MK += i * p;
                }

                _Ks[k] = (float)Math.Pow(_MG * _PK - _MK, 2) / (_PK * (1 - _PK));

            }

            _Ks[0] = 0;
            _Max = _Ks.Max();
            for (int i = 0; i < 256; i++)
            {
                if (_Ks[i] == _Max)
                    _MaxKs.Add(i);
            }

            int _K = (int)_MaxKs.Average();
            return _K;
        }
           

計算直方圖函數:

/// <summary>
        /// 圖檔灰階直方圖計算
        /// </summary>
        /// <param name="image">圖檔</param>
        /// <param name="depth">深度</param>
        /// <param name="d">要跳過的灰階</param>
        /// <returns></returns>
        public static long[] Histogram(Mat image, int depth= 8,int d = 0)
        {
            if (image.NumberOfChannels != 1)
            {
                throw new Exception("通道必須為1");
            }

            //提取直方圖------------------------------------
            long[] _his = new long[(int) Math.Pow(2,depth)];
            for (int i = d; i < (int)Math.Pow(2, depth); i++)
            {
                _his[i] = 0;
            }

            unsafe
            {
                byte* data = (byte*)image.DataPointer.ToPointer();

                for (int row = 0; row < image.Height; row++)
                {
                    //data = data + row * image.Cols;
                    for (int col = 0; col < image.Width; col++)
                    {
                        if (*data >= d)
                        {
                            _his[*data]++;
                        }
                        data++;
                    }
                }
            }
            return _his;
        }
           

這是基于emgucv基本的實作函數.

下面是其處理效果:

原始圖檔:

利用邊緣改進全局門檻值處理-c#實作-基于EmguCv

先用均值濾波器濾波,再用普通的otsu 得到的是:                                                                邊界修飾後的:

利用邊緣改進全局門檻值處理-c#實作-基于EmguCv
利用邊緣改進全局門檻值處理-c#實作-基于EmguCv

原始圖檔:

利用邊緣改進全局門檻值處理-c#實作-基于EmguCv

普通Otsu處理結果:                                                                                                                                     邊界修飾後:

利用邊緣改進全局門檻值處理-c#實作-基于EmguCv
利用邊緣改進全局門檻值處理-c#實作-基于EmguCv

繼續閱讀