zoukankan      html  css  js  c++  java
  • [转载]OpenCV(EmguCV)2.1新特性介绍之图像分割GrabCut(GrabCut Of OpenCV 2.1)

    OpenCV(EmguCV)2.1新特性介绍之图像分割GrabCut(GrabCut Of OpenCV 2.1)

    作者:王先荣
        前不久OpenCV和EmguCV相继发布了2.1版,增加了一些新的特性,本文关注的是其中的图像分割部分——GrabCut。GrabCut主要用于图像编辑中的抠图,作用跟Photoshop中的魔法棒、套索类似,但是更加强大。由于没有GrabCut的文档,探索具体的用法花费了不少时间和精力,仔细看了论文,大致看了源代码。

     

    GrabCut简介
        OpenCV中的GrabCut算法是依据《"GrabCut" - Interactive Foreground Extraction using Iterated Graph Cuts》这篇文章来实现的。该算法利用了图像中的纹理(颜色)信息和边界(反差)信息,只要少量的用户交互操作即可得到比较好的分割结果。如果前景和背景之间的颜色反差不大,分割的效果不好;不过,这种情况下允许手工标记一些前景或背景区域,这样能得到较好的结果。经我测试,GrabCut算法的效率不高,初始化341x326大小的矩形窗大约需要20秒,处理需要9秒;而论文中宣称初始化450x300大小的矩形窗仅0.9秒,处理只要0.12秒;虽然矩形大小和测试环境稍有区别,但是结果却相差太多。

    GrabCut函数说明
    函数原型:
        void cv::grabCut( const Mat& img, Mat& mask, Rect rect,
                 Mat& bgdModel, Mat& fgdModel,
                 int iterCount, int mode )
    其中:
    img——待分割的源图像,必须是8位3通道(CV_8UC3)图像,在处理的过程中不会被修改;
    mask——掩码图像,如果使用掩码进行初始化,那么mask保存初始化掩码信息;在执行分割的时候,也可以将用户交互所设定的前景与背景保存到mask中,然后再传入grabCut函数;在处理结束之后,mask中会保存结果。mask只能取以下四种值:
    GCD_BGD(=0),背景;
    GCD_FGD(=1),前景;
    GCD_PR_BGD(=2),可能的背景;
    GCD_PR_FGD(=3),可能的前景。
    如果没有手工标记GCD_BGD或者GCD_FGD,那么结果只会有GCD_PR_BGD或GCD_PR_FGD;
    rect——用于限定需要进行分割的图像范围,只有该矩形窗口内的图像部分才被处理;
    bgdModel——背景模型,如果为null,函数内部会自动创建一个bgdModel;bgdModel必须是单通道浮点型(CV_32FC1)图像,且行数只能为1,列数只能为13x5;
    fgdModel——前景模型,如果为null,函数内部会自动创建一个fgdModel;fgdModel必须是单通道浮点型(CV_32FC1)图像,且行数只能为1,列数只能为13x5;
    iterCount——迭代次数,必须大于0;
    mode——用于指示grabCut函数进行什么操作,可选的值有:
    GC_INIT_WITH_RECT(=0),用矩形窗初始化GrabCut;
    GC_INIT_WITH_MASK(=1),用掩码图像初始化GrabCut;
    GC_EVAL(=2),执行分割。

    GrabCut的用法
        您可以按以下方式来使用GrabCut函数:
    (1)用矩形窗或掩码图像初始化grabCut;
    (2)执行分割;
    (3)如果对结果不满意,在掩码图像中设定前景和(或)背景,再次执行分割;
    (4)使用掩码图像中的前景或背景信息。

    从上述图片中可以看出,用更多的迭代次数,或者更多的用户交互都能得到更好的结果。

    示例
        下面是一个使用GrabCut进行图像分割的例子,其中用了P/INVOKE形式的CvGrabCut函数,以及封装在Image<TColor,TDepth>类中的GrabCut方法。封装的方法便于使用,但是缺少一些功能,灵活性不足。

     

    使用GrabCut
    复制代码
    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;

    namespace NewFeaturesOfOpenCV2._1
    {
    public partial class FormGrabCut : Form
    {
    //常量
    private static readonly Bgr Blue = new Bgr(255d, 0d, 0d); //蓝色,用于绘制矩形
    private static readonly Bgr Green = new Bgr(0d, 255d, 0d); //绿色,用于绘制前景曲线
    private static readonly Bgr Red = new Bgr(0d, 0d, 255d); //红色,用于绘制背景曲线
    private const int LineWidth = 5; //绘制线条的宽度
    private const int GC_BGD = 0; //背景标志
    private const int GC_FGD = 1; //前景标志
    private const int GC_PR_BGD = 2; //可能的背景标志
    private const int GC_PR_FGD = 3; //可能的前景标志
    //成员变量
    private string sourceImageFileName = "wky_tms_2272x1704.jpg";//源图像文件名
    private Image<Bgr, Byte> imageSource = null; //源图像
    private Image<Bgr, Byte> imageSourceClone = null; //源图像的克隆
    private Image<Gray, Byte> imageMask = null; //掩码图像:保存初始化之后的掩码信息及用户绘制的信息
    private Matrix<Single> foregroundModel = null; //前景模型
    private Matrix<Single> backgroundModel = null; //背景模型
    private double xScale = 1d; //原始图像与PictureBox在x轴方向上的缩放
    private double yScale = 1d; //原始图像与PictureBox在y轴方向上的缩放
    private Point previousMouseLocation = new Point(-1, -1); //上次绘制线条时,鼠标所处的位置
    private Rectangle rect; //初始化矩形窗口
    private bool initialized = false; //是否已经初始化过GrabCut

    public FormGrabCut()
    {
    InitializeComponent();
    }

    //加载窗体时
    private void FormGrabCut_Load(object sender, EventArgs e)
    {
    //设置提示
    toolTip.SetToolTip(rbRect, "使用鼠标在源图像绘制矩形窗口,在图像分割之前使用矩形窗口所在的区域进行初始化。");
    toolTip.SetToolTip(rbMask,
    "使用鼠标在源图像绘制掩码,左键绘制前景掩码,邮件绘制背景掩码,在图像分割之前使用掩码图像进行初始化。");
    //初始化前景模型和背景模型
    foregroundModel = new Matrix<float>(1, 13 * 5);
    backgroundModel
    = new Matrix<float>(1, 13 * 5);
    //加载默认图像
    LoadImage();
    }

    //关闭窗体前,释放资源
    private void FormGrabCut_FormClosing(object sender, FormClosingEventArgs e)
    {
    if (imageSource != null)
    imageSource.Dispose();
    if (imageSourceClone != null)
    imageSourceClone.Dispose();
    if (imageMask != null)
    imageMask.Dispose();
    if (foregroundModel != null)
    foregroundModel.Dispose();
    if (backgroundModel != null)
    backgroundModel.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 btnReload_Click(object sender, EventArgs e)
    {
    LoadImage();
    }

    //加载源图像
    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 (imageMask != null)
    imageMask.Dispose();
    imageMask
    = new Image<Gray, byte>(imageSource.Size);
    imageMask.SetZero();
    xScale
    = 1d * imageSource.Width / pbSource.Width;
    yScale
    = 1d * imageSource.Height / pbSource.Height;
    rect
    = new Rectangle(-1, -1, 1, 1);
    initialized
    = false;
    }

    //鼠标在源图像上按下时
    private void pbSource_MouseDown(object sender, MouseEventArgs e)
    {
    if (rbRect.Checked)
    rect
    = new Rectangle((int)(e.X * xScale), (int)(e.Y * yScale), 1, 1);
    else
    previousMouseLocation
    = new Point((int)(e.X * xScale), (int)(e.Y * yScale));
    }

    //鼠标在源图像上移动时
    private void pbSource_MouseMove(object sender, MouseEventArgs e)
    {
    //绘制矩形
    if (rbRect.Checked && e.Button != MouseButtons.None)
    {
    rect
    = new Rectangle(rect.Left, rect.Top, (int)(e.X * xScale - rect.Left), (int)(e.Y * yScale - rect.Top));
    imageSourceClone.Dispose();
    imageSourceClone
    = imageSource.Clone();
    imageSourceClone.Draw(rect, Blue, LineWidth);
    pbSource.Image
    = imageSourceClone.Bitmap;
    return;
    }
    //绘制线条,用于手工标记前景或者背景
    if (rbMask.Checked && (e.Button == MouseButtons.Left || e.Button == MouseButtons.Right))
    {
    if (previousMouseLocation.X == -1 && previousMouseLocation.Y == -1)
    {
    previousMouseLocation.X
    = (int)(e.X * xScale);
    previousMouseLocation.Y
    = (int)(e.Y * yScale);
    }
    else
    {
    LineSegment2D line
    = new LineSegment2D(previousMouseLocation, new Point((int)(e.X * xScale), (int)(e.Y * yScale)));
    if (e.Button == MouseButtons.Left)
    {
    imageMask.Draw(line,
    new Gray((double)GC_FGD), LineWidth);
    imageSourceClone.Draw(line, Green, LineWidth);
    }
    else
    {
    imageMask.Draw(line,
    new Gray((double)GC_BGD), LineWidth);
    imageSourceClone.Draw(line, Red, LineWidth);
    }
    pbSource.Image
    = imageSourceClone.Bitmap;
    previousMouseLocation
    = line.P2;
    }

    }
    }

    //鼠标在源图像上松开时
    private void pbSource_MouseUp(object sender, MouseEventArgs e)
    {
    if (rbRect.Checked && e.Button != MouseButtons.None)
    {
    rect
    = new Rectangle(rect.Left, rect.Top, (int)(e.X * xScale - rect.Left), (int)(e.Y * yScale - rect.Top));
    imageSourceClone.Dispose();
    imageSourceClone
    = imageSource.Clone();
    imageSourceClone.Draw(rect, Blue, LineWidth);
    pbSource.Image
    = imageSourceClone.Bitmap;
    //绘制矩形结束之后,初始化掩码图像
    imageMask.SetZero();
    imageMask.Draw(rect,
    new Gray((double)GC_PR_FGD), 0);
    return;
    }
    if (rbMask.Checked)
    previousMouseLocation
    = new Point(-1, -1);
    }

    //开始图像分割
    private void btnStartSegment_Click(object sender, EventArgs e)
    {
    if (rect != new Rectangle(-1, -1, 1, 1)) //必须指定矩形窗
    {
    Stopwatch sw
    = new Stopwatch();
    Image
    <Gray, Byte> mask = null;
    if (rbRect.Checked)
    {
    //用矩形窗初始化
    sw.Reset();
    sw.Start();
    mask
    = imageSource.GrabCut(rect, (int)nudIterCount.Value); //注:Image.GrabCut等价于先用矩形初始化CvGrabCut(....,GRABCUT_INIT_TYPE.INIT_WITH_RECT),然后再计算CvGrabCut(....,GRABCUT_INIT_TYPE.INIT_WITH_EVAL)
    sw.Stop();
    imageMask
    = mask.Clone();
    initialized
    = true;
    ShowResult(
    "用矩形窗初始化GrabCut并计算", sw.ElapsedMilliseconds);
    }
    else
    {
    //用掩码初始化
    mask = imageMask.Clone();
    if (!initialized)
    {
    sw.Reset();
    sw.Start();
    CvInvoke.CvGrabCut(imageSource.Ptr, mask.Ptr,
    ref rect, backgroundModel.Ptr, foregroundModel.Ptr, 1, GRABCUT_INIT_TYPE.INIT_WITH_MASK);
    sw.Stop();
    initialized
    = true;
    ShowResult(
    "用掩码初始化GrabCut", sw.ElapsedMilliseconds);
    }
    sw.Reset();
    sw.Start();
    CvInvoke.CvGrabCut(imageSource.Ptr, mask.Ptr,
    ref rect, backgroundModel.Ptr, foregroundModel.Ptr, (int)nudIterCount.Value, GRABCUT_INIT_TYPE.EVAL);
    sw.Stop();
    ShowResult(
    "计算GrabCut", sw.ElapsedMilliseconds);
    }
    CvInvoke.cvAndS(mask.Ptr,
    new MCvScalar(1d), mask.Ptr, IntPtr.Zero); //将掩码图像和1进行按位“与”操作,这样背景及可能的背景将变为0;而前景及可能的前景将变成1
    Image<Bgr, Byte> result = imageSource.Copy(mask);
    pbResult.Image
    = result.Bitmap;
    mask.Dispose();
    //result.Dispose();
    }
    else
    MessageBox.Show(
    this, "在开始分割之前,请在源图像上绘制一个矩形窗口。", "缺少矩形窗", MessageBoxButtons.OK, MessageBoxIcon.Information);
    }

    /// <summary>
    /// 显示结果
    /// </summary>
    /// <param name="prompt">提示</param>
    /// <param name="elapsedMilliseconds">耗时</param>
    private void ShowResult(string prompt, double elapsedMilliseconds)
    {
    txtResult.Text
    += string.Format("{0},耗时:{1:F04}毫秒,参数(矩形窗起点:{2},大小:{3}X{4},迭代次数:{5})。\r\n",
    prompt, elapsedMilliseconds, rect.Location, rect.Width, rect.Height, nudIterCount.Value);
    }
    }
    }
    复制代码

    点击这里下载本文源代码

     

  • 相关阅读:
    poj 1088 滑雪
    位运算与bitset
    hdu 4607 Park Visit
    树的直径
    codeforces 495D Sonya and Matrix
    German Collegiate Programming Contest 2015(第三场)
    BAPC 2014 Preliminary(第一场)
    Benelux Algorithm Programming Contest 2014 Final(第二场)
    E. Reachability from the Capital(tarjan+dfs)
    poj2104 K-th Number(划分树)
  • 原文地址:https://www.cnblogs.com/fx2008/p/2724627.html
Copyright © 2011-2022 走看看