php 图像分割,科学网—图像分割(Image Segmentation) - 杨亮亮的博文

本文介绍了OpenCV中三种图像分割算法:分水岭分割、金字塔分割和均值漂移分割。分水岭算法效果最佳但依赖于手动标记,金字塔分割效率低,均值漂移分割效率高但效果次于分水岭。通过代码示例展示了如何在C#中使用这三种算法,并提供了性能比较。
摘要由CSDN通过智能技术生成

图像分割(Image Segmentation)

已有 5449 次阅读

2010-10-19 13:44

|

图像分割(Image Segmentation)

作者:王先荣

From:

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()

{

//分水岭算法分割

ImageimageMarkers2 = 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整除的次数决定了金字塔的最大层数。下面的代码演示了如果校验金字塔层数:

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

/// /// 当改变金字塔分割的参数“金字塔层数”时,对参数进行校验

///

///

///

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, "注意:您输入的金字塔层数不符合要求,计算结果可能会无效。", "金字塔层数错误");

}

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

使用金字塔分割算法

/// /// 金字塔分割算法

///

///

private string PrySegmentation()

{

//准备参数

ImageimageDest = new Image(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]之间,您可以用类似下面的代码来使用它:

使用均值漂移分割算法

/// /// 均值漂移分割算法

///

///

private string PryMeanShiftFiltering()

{

//准备参数

ImageimageDest = new Image(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中没有实现,我们可以用下面的方式来使用:

调用均值漂移分割

分割效果及性能对比

上述三种分割算法的效果如何呢?下面我们以它们的默认参数,对一幅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 ImageimageSource = null; //源图像

private ImageimageSourceClone = null; //源图像的克隆

private ImageimageMarkers = 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(sourceImageFileName);

if (imageSourceClone != null)

imageSourceClone.Dispose();

imageSourceClone = imageSource.Copy();

pbSource.Image = imageSourceClone.Bitmap;

if (imageMarkers != null)

imageMarkers.Dispose();

imageMarkers = new Image(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();

}

/// /// 分水岭算法图像分割

///

/// 返回用时

private string Watershed()

{

//分水岭算法分割

ImageimageMarkers2 = 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);

}

/// /// 金字塔分割算法

///

///

private string PrySegmentation()

{

//准备参数

ImageimageDest = new Image(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);

}

/// /// 均值漂移分割算法

///

///

private string PryMeanShiftFiltering()

{

//准备参数

ImageimageDest = new Image(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);

}

/// /// 当改变金字塔分割的参数“金字塔层数”时,对参数进行校验

///

///

///

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, "注意:您输入的金字塔层数不符合要求,计算结果可能会无效。", "金字塔层数错误");

}

/// /// 当改变均值漂移分割的参数“金字塔层数”时,对参数进行校验

///

///

///

private void txtPMSFNaxLevel_TextChanged(object sender, EventArgs e)

{

int maxLevel = int.Parse(txtPMSFNaxLevel.Text);

if (maxLevel < 0 || maxLevel > 8)

MessageBox.Show(this, "注意:均值漂移分割的金字塔层数只能在0至8之间。", "金字塔层数错误");

}

}

}

转载本文请联系原作者获取授权,同时请注明本文来自杨亮亮科学网博客。

链接地址:http://blog.sciencenet.cn/blog-242300-374856.html

上一篇:北海道大学之生活-雨后北大农学部

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值