转:图像分割

 

 

 

作者:王先荣
前言
    图像分割指的是将数字图像细分为多个图像子区域的过程,在OpenCv中实现了三种跟图像分割相关的算法,它们分别是:分水岭分割算法、金字塔分割算法以及均值漂移分割算法。它们的使用过程都很简单,下面的文章权且用于记录,并使该系列保持完整吧。
分水岭分割算法
    分水岭分割算法需要您或者先前算法提供标记,该标记用于指定哪些大致区域是目标,哪些大致区域是背景等等;分水岭分割算法的分割效果严重依赖于提供的标记。OpenCv中的函数cvWatershed实现了该算法,函数定义如下:

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

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

[转载]图像分割(Image <wbr>Segmentation) 生成标记图像
// 当鼠标按下并在源图像上移动时,在源图像上绘制分割线条
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
++ ;
}

 

 

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

[转载]图像分割(Image <wbr>Segmentation) 使用分水岭分割算法
///   <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}毫秒。rn " , sw.Elapsed.TotalMilliseconds);
}

 

 

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

[转载]图像分割(Image <wbr>Segmentation) 校验金字塔分割的金字塔层数
///   <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 ,   " 注意:您输入的金字塔层数不符合要求,计算结果可能会无效。 " ,   " 金字塔层数错误 " );
}

 

 

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

[转载]图像分割(Image <wbr>Segmentation) 使用金字塔分割算法
///   <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}毫秒。rn " , sw.Elapsed.TotalMilliseconds);
}

 

 

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

[转载]图像分割(Image <wbr>Segmentation) 使用均值漂移分割算法
///   <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}毫秒。rn " , sw.Elapsed.TotalMilliseconds);
}

 

 

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

[转载]图像分割(Image <wbr>Segmentation) 调用均值漂移分割
// 均值漂移分割
[DllImport( " cv200.dll " )]
public   static   extern   void   cvPyrMeanShiftFiltering(IntPtr src, IntPtr dst,   double   spatialRadius, double   colorRadius,   int   max_level, MCvTermCriteria termcrit);

 

 

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

[转载]图像分割(Image <wbr>Segmentation)

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

[转载]图像分割(Image <wbr>Segmentation)

图2 金字塔分割算法

[转载]图像分割(Image <wbr>Segmentation)

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

 

    本文的完整代码如下:

[转载]图像分割(Image <wbr>Segmentation) 本文完整代码
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}毫秒。rn " , 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}毫秒。rn " , 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}毫秒。rn " , 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之间。 " ,   " 金字塔层数错误 " );
}
}
}

转载于:https://www.cnblogs.com/hedengfeng/p/3440071.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值