图像分割(Image Segmentation)

259 篇文章 4 订阅

http://www.cnblogs.com/xrwang/archive/2010/02/28/ImageSegmentation.html?login=1

作者:王先荣
前言
    图像分割指的是将数字图像细分为多个图像子区域的过程,在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 ++ ; }
复制代码

 

 

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

使用分水岭分割算法
复制代码
   
   
/// <summary> /// 分水岭算法图像分割 /// </summary> /// <returns> 返回用时 </returns> 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);

 

 

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

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

图2 金字塔分割算法

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

 

    本文的完整代码如下:

本文完整代码
复制代码
   
   
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Diagnostics; using System.Runtime.InteropServices; using Emgu.CV; using Emgu.CV.CvEnum; using Emgu.CV.Structure; using Emgu.CV.UI; namespace ImageProcessLearn { public partial class FormImageSegment : Form { // 成员变量 private string sourceImageFileName = " wky_tms_2272x1704.jpg " ; // 源图像文件名 private Image < Bgr, Byte > imageSource = null ; // 源图像 private Image < Bgr, Byte > imageSourceClone = null ; // 源图像的克隆 private Image < Gray, Int32 > imageMarkers = null ; // 标记图像 private double xScale = 1d; // 原始图像与PictureBox在x轴方向上的缩放 private double yScale = 1d; // 原始图像与PictureBox在y轴方向上的缩放 private Point previousMouseLocation = new Point( - 1 , - 1 ); // 上次绘制线条时,鼠标所处的位置 private const int LineWidth = 5 ; // 绘制线条的宽度 private int drawCount = 1 ; // 用户绘制的线条数目,用于指定线条的颜色 public FormImageSegment() { InitializeComponent(); } // 窗体加载时 private void FormImageSegment_Load( object sender, EventArgs e) { // 设置提示 toolTip.SetToolTip(rbWatershed, " 可以在源图像上用鼠标绘制大致分割区域线条,该线条用于分水岭算法 " ); toolTip.SetToolTip(txtPSLevel, " 金字塔层数跟图像尺寸有关,该值只能是图像尺寸被2整除的次数,否则将得出错误结果 " ); toolTip.SetToolTip(txtPSThreshold1, " 建立连接的错误阀值 " ); toolTip.SetToolTip(txtPSThreshold2, " 分割簇的错误阀值 " ); toolTip.SetToolTip(txtPMSFSpatialRadius, " 空间窗的半径 " ); toolTip.SetToolTip(txtPMSFColorRadius, " 色彩窗的半径 " ); toolTip.SetToolTip(btnClearMarkers, " 清除绘制在源图像上,用于分水岭算法的大致分割区域线条 " ); // 加载图像 LoadImage(); } // 当窗体关闭时,释放资源 private void FormImageSegment_FormClosing( object sender, FormClosingEventArgs e) { if (imageSource != null ) imageSource.Dispose(); if (imageSourceClone != null ) imageSourceClone.Dispose(); if (imageMarkers != null ) imageMarkers.Dispose(); } // 加载源图像 private void btnLoadImage_Click( object sender, EventArgs e) { OpenFileDialog ofd = new OpenFileDialog(); ofd.CheckFileExists = true ; ofd.DefaultExt = " jpg " ; ofd.Filter = " 图片文件|*.jpg;*.png;*.bmp|所有文件|*.* " ; if (ofd.ShowDialog( this ) == DialogResult.OK) { if (ofd.FileName != "" ) { sourceImageFileName = ofd.FileName; LoadImage(); } } ofd.Dispose(); } // 清除分割线条 private void btnClearMarkers_Click( object sender, EventArgs e) { if (imageSourceClone != null ) imageSourceClone.Dispose(); imageSourceClone = imageSource.Copy(); pbSource.Image = imageSourceClone.Bitmap; imageMarkers.SetZero(); drawCount = 1 ; } // 当鼠标按下并在源图像上移动时,在源图像上绘制分割线条 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 void LoadImage() { if (imageSource != null ) imageSource.Dispose(); imageSource = new Image < Bgr, byte > (sourceImageFileName); if (imageSourceClone != null ) imageSourceClone.Dispose(); imageSourceClone = imageSource.Copy(); pbSource.Image = imageSourceClone.Bitmap; if (imageMarkers != null ) imageMarkers.Dispose(); imageMarkers = new Image < Gray, Int32 > (imageSource.Size); imageMarkers.SetZero(); xScale = 1d * imageSource.Width / pbSource.Width; yScale = 1d * imageSource.Height / pbSource.Height; drawCount = 1 ; } // 分割图像 private void btnImageSegment_Click( object sender, EventArgs e) { if (rbWatershed.Checked) txtResult.Text += Watershed(); else if (rbPrySegmentation.Checked) txtResult.Text += PrySegmentation(); else if (rbPryMeanShiftFiltering.Checked) txtResult.Text += PryMeanShiftFiltering(); } /// <summary> /// 分水岭算法图像分割 /// </summary> /// <returns> 返回用时 </returns> 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); } /// <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); } /// <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); } /// <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> /// <param name="sender"></param> /// <param name="e"></param> private void txtPMSFNaxLevel_TextChanged( object sender, EventArgs e) { int maxLevel = int .Parse(txtPMSFNaxLevel.Text); if (maxLevel < 0 || maxLevel > 8 ) MessageBox.Show( this , " 注意:均值漂移分割的金字塔层数只能在0至8之间。 " , " 金字塔层数错误 " ); } } }
复制代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值