图像分割(Image Segmentation)

转载自博客:http://www.cnblogs.com/xrwang/archive/2010/02/28/ImageSegmentation.html

 

图像分割

作者:王先荣

前言

     图像分割指的是将数字图像细分为多个图像子区域的过程,在OpenCv中实现了三种跟图像分割相关的算法,它们分别是:分水岭分割算法、金字塔分割算法以及均值漂移分割算法。它们的使用过程都很简单,下面的文章权且用于记录,并使该系列保持完整吧。

分水岭分割算法
    分水岭分割算法需要您或者先前算法提供标记,该标记用于指定哪些大致区域是目标,哪些大致区域是背景等等;分水岭分割算法的分割效果严重依赖于提供的标记。OpenCv中的函数cvWatershed实现了该算法,函数定义如下:

    
    
void cvWatershed( const CvArr * image, CvArr * markers)

其中:image为8为三通道的彩色图像;
          markers是单通道整型图像,它用不同的正整数来标记不同的区域,下面的代码演示了如果响应鼠标事件,并生成标记图像。

//生成标记图像       

private void pbSource_MouseMove(object sender, MouseEventArgs e)
        {
            //如果按下了左键
            if (e.Button == MouseButtons.Left)
            {
                if (previousMouseLocation.X >= 0 && previousMouseLocation.Y >= 0)
                {
                    Point p1 = new Point((int)(previousMouseLocation.X * xScale), (int)(previousMouseLocation.Y * yScale));
                    Point p2 = new Point((int)(e.Location.X * xScale), (int)(e.Location.Y * yScale));
                    LineSegment2D ls = new LineSegment2D(p1, p2);
                    int thickness = (int)(LineWidth * xScale);
                    imageSourceClone.Draw(ls, new Bgr(255d, 255d, 255d), thickness);
                    pbSource.Image = imageSourceClone.Bitmap;
                    imageMarkers.Draw(ls, new Gray(drawCount), thickness);
                }
                previousMouseLocation = e.Location;
            }
        }

        //当松开鼠标左键时,将绘图的前一位置设置为(-1,-1)
        private void pbSource_MouseUp(object sender, MouseEventArgs e)
        {
            previousMouseLocation = new Point(-1, -1);
            drawCount++;
        }

 您可以用类似下面的方式来使用分水岭算法:

 /// 分水岭算法图像分割
  private string Watershed()
        {
            //分水岭算法分割
            Image<Gray, Int32> imageMarkers2 = imageMarkers.Copy();
            Stopwatch sw = new Stopwatch();
            sw.Start();
            CvInvoke.cvWatershed(imageSource.Ptr, imageMarkers2.Ptr);
            sw.Stop();
            //将分割的结果转换到256级灰度图像
            pbResult.Image = imageMarkers2.Bitmap;
            imageMarkers2.Dispose();
            return string.Format("分水岭图像分割,用时:{0:F05}毫秒。\r\n", sw.Elapsed.TotalMilliseconds);
        }

 

金字塔分割算法
    金字塔分割算法由cvPrySegmentation所实现,该函数的使用很简单;需要注意的是图像的尺寸以及金字塔的层数,图像的宽度和高度必须能被2整除,能够被2整除的次数决定了金字塔的最大层数。下面的代码演示了如何校验金字塔层数:

校验金字塔分割的金字塔层数

    
    
/// <summary>         /// 当改变金字塔分割的参数“金字塔层数”时,对参数进行校验         /// </summary>         /// <param name="sender"></param>         /// <param name="e"></param>         private void txtPSLevel_TextChanged(object sender, EventArgs e)         {             int level = int.Parse(txtPSLevel.Text);             if (level < 1 || imageSource.Width % (int)(Math.Pow(2, level - 1)) != 0 || imageSource.Height % (int)(Math.Pow(2, level - 1)) != 0)                 MessageBox.Show(this, "注意:您输入的金字塔层数不符合要求,计算结果可能会无效。", "金字塔层数错误");         }

使用金字塔分割的示例代码如下:

 

/// <summary>
        /// 金字塔分割算法
        /// </summary>
        /// <returns></returns>
        private string PrySegmentation()
        {
            //准备参数
            Image<Bgr, Byte> imageDest = new Image<Bgr, byte>(imageSource.Size);
            MemStorage storage = new MemStorage();
            IntPtr ptrComp = IntPtr.Zero;
            int level = int.Parse(txtPSLevel.Text);
            double threshold1 = double.Parse(txtPSThreshold1.Text);
            double threshold2 = double.Parse(txtPSThreshold2.Text);
            //金字塔分割
            Stopwatch sw = new Stopwatch();
            sw.Start();
            CvInvoke.cvPyrSegmentation(imageSource.Ptr, imageDest.Ptr, storage.Ptr, out ptrComp, level, threshold1, threshold2);
            sw.Stop();
            //显示结果
            pbResult.Image = imageDest.Bitmap;
            //释放资源
            imageDest.Dispose();
            storage.Dispose();
            return string.Format("金字塔分割,用时:{0:F05}毫秒。\r\n", sw.Elapsed.TotalMilliseconds);
        }

 

均值漂移分割算法
    均值漂移分割算法由cvPryMeanShiftFiltering所实现,均值漂移分割的金字塔层数只能介于[1,7]之间,您可以用类似下面的代码来使用它:

    /// <summary>
        /// 均值漂移分割算法
        /// </summary>
        /// <returns></returns>
        private string PryMeanShiftFiltering()
        {
            //准备参数
            Image<Bgr, Byte> imageDest = new Image<Bgr, byte>(imageSource.Size);
            double spatialRadius = double.Parse(txtPMSFSpatialRadius.Text);
            double colorRadius = double.Parse(txtPMSFColorRadius.Text);
            int maxLevel = int.Parse(txtPMSFNaxLevel.Text);
            int maxIter = int.Parse(txtPMSFMaxIter.Text);
            double epsilon = double.Parse(txtPMSFEpsilon.Text);
            MCvTermCriteria termcrit = new MCvTermCriteria(maxIter, epsilon);
            //均值漂移分割
            Stopwatch sw = new Stopwatch();
            sw.Start();
            OpenCvInvoke.cvPyrMeanShiftFiltering(imageSource.Ptr, imageDest.Ptr, spatialRadius, colorRadius, maxLevel, termcrit);
            sw.Stop();
            //显示结果
            pbResult.Image = imageDest.Bitmap;
            //释放资源
            imageDest.Dispose();
            return string.Format("均值漂移分割,用时:{0:F05}毫秒。\r\n", sw.Elapsed.TotalMilliseconds);
        }

 

        }函数cvPryMeanShiftFiltering在EmguCv中没有实现,我们可以用下面的方式来使用:

调用均值漂移分割
     
     
//均值漂移分割         [DllImport("cv200.dll")]         public static extern void cvPyrMeanShiftFiltering(IntPtr src, IntPtr dst, double spatialRadius, double colorRadius, int max_level, MCvTermCriteria termcrit);


(1)分水岭分割算法的分割效果效果最好,均值漂移分割算法次之,而金字塔分割算法的效果最差;
    (2)均值漂移分割算法效率最高,分水岭分割算法接近于均值漂移算法,金字塔分割算法需要很长的时间。
    值得注意的是分水岭算法对标记很敏感,需要仔细而认真的绘制。  

 

分割效果及性能对比
    上述三种分割算法的效果如何呢?下面我们以它们的默认参数,对一幅2272x1704大小的图像进行分割。得到的结果如下所示:

  

图1 分水岭分割算法(左图白色的线条用于标记区域)

图2 金字塔分割算法

图3 均值漂移分割算法
从上面我们可以看出:
(1)分水岭分割算法的分割效果效果最好,均值漂移分割算法次之,而金字塔分割算法的效果最差;
  (2)均值漂移分割算法效率最高,分水岭分割算法接近于均值漂移算法,金字塔分割算法需要很长的时间。
    值得注意的是分水岭算法对标记很敏感,需要仔细而认真的绘制。本文的完整代码如下:

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值