zoukankan      html  css  js  c++  java
  • 转:图像分割

     

     

     

    作者:王先荣
    前言
        图像分割指的是将数字图像细分为多个图像子区域的过程,在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之间。", "金字塔层数错误");
    }
    }
    }
  • 相关阅读:
    计数
    表单验证
    获取位置
    jq事件
    jq选择器
    PHP超文本预处理器(通用开源脚本语言)
    文字超出两行显示省略号
    Vim配置:在win10下用vim编译运行C/C++(异步插件管理,一键运行)
    实验楼HTML基础入门学习
    博客园美化:添加目录,标题设置,代码高亮,主题设置
  • 原文地址:https://www.cnblogs.com/hedengfeng/p/3440071.html
Copyright © 2011-2022 走看看