图像分割(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之间。",
"金字塔层数错误");
}
}
}
前言
图像分割指的是将数字图像细分为多个图像子区域的过程,在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之间。",
"金字塔层数错误");
}
}
}
相关文章推荐
- [ASP.NET MVC 小牛之路]18 - Web API
- 银行业双活数据中心-F5在双活数据中心中的应用
- delphi使用细节
- 知识点二:contentProvider的使用
- python的初级训练
- MSDN订阅用户激活Windows Azure 权益
- iOS Category详解
- iptables限制MACIP绑定上网+Squid 透明代理How to
- mysql 分表
- php cookie不刷新及时生效的实现代码
- 关于PyYAML报错问题解决
- 哈夫曼编码树构建的java实现
- It is an error to use a section registered as allowDefinition='MachineToApplication' beyond applicat
- [java]截取字符串
- Modbus TCP和Modbus Rtu协议的区别
- NUTCH2.3 hadoop2.7.1 hbase1.0.1.1 solr5.2.1部署(二)
- 技巧: 压缩 XML 文件以便有效地传输
- Cisco网络设备配置入门必读的几个命令
- BZOJ 1455 罗马游戏 可并堆裸
- 十五天精通WCF——第十三天 用WCF来玩Rest