您的位置:首页 > 其它

图像分割(Image Segmentation)

2015-07-23 15:01 267 查看

前言

图像分割指的是将数字图像细分为多个图像子区域的过程,在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之间。",
"金字塔层数错误");

}

}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: