热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

32、【opencv入门】GrabCut&FloodFill图像分割

一、GrabCut1、简介OpenCV中的GrabCut算法是Graphcut算法的改进,Graphcut是一种直接基于图割算法的图像分割技术,仅仅需要确认前景和背景输入,该算法就
一、GrabCut

1、简介  

  OpenCV中的GrabCut算法是Graphcut算法的改进, Graphcut是一种直接基于图割算法的图像分割技术, 仅仅需要确认前景和背景输入, 该算法就可以完成前景和背景的最优分割, 算法依据《“GrabCut” - Interactive Foreground Extraction using Iterated Graph Cuts》这篇文章来实现的。该算法利用了图像中的纹理(颜色)信息和边界(反差)信息, 只要少量的用户交互操作即可得到比较好的分割结果, 和分水岭算法比较相似, 但是计算速度比较慢, 得到的结果比较精确。如果要从静态图像中提取前景物体(如从一个图像剪切物体到另一个图像), 采用GrabCut算法是最好的选择。    

  用法很简单: 只需输入一幅图像, 并对一些像素做属于背景或属于前景的标记, 算法会根据这个局部标记, 计算出整个图像中前景和背景的分割线。

2、GrabCut算法---grabCut()

1 CV_EXPORTS_W void grabCut(InputArray img, InputOutArray mask, Rect rect, InputOutputArray bgdModel, 
                  InputOutputArray fgdModel, int iterCount, int mode=GC_EVAL);

  img: 待分割原图像, 需为8位三通道彩色图像

  mask: 8位单通道掩码图像, 如果使用掩码进行初始化, 那么mask保存掩码信息, 在执行分割的时候, 也可以将用户交互所设定的前景与背景保存到mask中,然后再传入grabCut函数, 在处理结束之后,mask中会保存结果。Mask只能取4种可能的值:    

    GC_BGD: 表示明确属于背景的像素    

    GC_FGD: 表示明确属于前景的像素    

    GC_PR_BGD: 表示可能属于背景的像素    

    GC_PR_FGD: 表示可能属于前景的像素    

    如果没有手动标记GC_BGD或者GC_FGD, 那么结果只会有GC_PR_BGD或 GC_PR_FGD

  rect: 包含分割对象的矩形ROI, 矩形外部的像素为背景, 矩形内部的像素为前景,当参数mode=GC_INIT_WITH_RECT使用

  bgdModel: 背景模型(内部使用)

  fgdModel: 前景模型(内部使用)

  iterCount: 迭代次数, 必须大于0

  mode: 用于指示grabCut函数进行什么操作, 可选的值有:    

    GC_INIT_WITH_RECT: 用矩形框初始化GrabCut    

    GC_INIT_WITH_MASK: 用掩码图像初始化GrabCut    

    GC_EVAL: 执行分割

相关所用其他函数:

比较两幅图像对应位置像素值:

CV_EXPORTS_W void compare(InputArray src1, InputArray src2, OutputArray dst, int cmpop);

将矩阵特定区域设置为特定值:

Mat& setTo(InputArray value, InputArray mask=noArray());

【示例1】利用Rect做分割

 1 //利用Rect做分割
 2 #include "opencv2/opencv.hpp"
 3 using namespace cv;
 4 
 5 int main()
 6 {
 7     Mat src = imread("bird.jpg");
 8     Rect rect(84, 84, 406, 318);//左上坐标(X,Y)和长宽
 9     Mat result, bg, fg;
10 
11     grabCut(src, result, rect, bg, fg, 1, GC_INIT_WITH_RECT);
12     imshow("grab", result);
13     /*threshold(result, result, 2, 255, CV_THRESH_BINARY);
14     imshow("threshold", result);*/
15 
16     compare(result, GC_PR_FGD, result, CMP_EQ);//result和GC_PR_FGD对应像素相等时,目标图像该像素值置为255
17     imshow("result",result);
18     Mat foreground(src.size(), CV_8UC3, Scalar(255, 255, 255));
19     src.copyTo(foreground, result);//copyTo有两种形式,此形式表示result为mask
20     imshow("foreground", foreground);
21     waitKey(0);
22     return 0;
23 }

【示例2】利用mask做分割

 1 //利用mask做分割
 2 #include "opencv2/opencv.hpp"
 3 using namespace cv;
 4 
 5 int main()
 6 {
 7     Mat src = imread("bird.jpg");
 8     //Rect rect(84, 84, 406, 318);
 9     Rect rect;
10     Mat bgModel, fgModel;
11     Mat result(src.size(), CV_8U, Scalar(0));
12     Mat ROI(result(Rect(84, 84, 406, 318)));
13     ROI.setTo(GC_PR_FGD);//ROI设置为可能是前景
14 
15     grabCut(src, result, rect, bgModel, fgModel, 1, GC_INIT_WITH_MASK);
16     //threshold(result, result, 2, 255, CV_THRESH_BINARY);
17     imshow("grab", result);
18     compare(result, GC_PR_FGD, result, CMP_EQ);
19     //result = result&1;
20     imshow("result", result);
21     Mat foreground(src.size(), CV_8UC3, Scalar(255, 255, 255));
22     src.copyTo(foreground, result);
23     imshow("foreground", foreground);
24 
25     waitKey(0);
26     return 0;
27 }
二、漫水填充算法——floodFill 1、引言 · 漫水填充的定义

  漫水填充法是一种用特定的颜色填充联通区域,通过设置可连通像素的上下限以及连通方式来达到不同的填充效果的方法。漫水填充经常被用来标记或分离图像的一部分以便对其进行进一步处理或分析,也可以用来从输入图像获取掩码区域,掩码会加速处理过程,或只处理掩码指定的像素点,操作的结果总是某个连续的区域。

2、漫水填充法的基本思想

  所谓漫水填充,简单来说,就是自动选中了和种子点相连的区域,接着将该区域替换成指定的颜色,这是个非常有用的功能,经常用来标记或者分离图像的一部分进行处理或分析。漫水填充也可以用来从输入图像获取掩码区域,掩码会加速处理过程,或者只处理掩码指定的像素点。

  以此填充算法为基础,类似photoshop的魔术棒选择工具就很容易实现了。漫水填充(FloodFill)是查找和种子点联通的颜色相同的点,魔术棒选择工具则是查找和种子点联通的颜色相近的点,将和初始种子像素颜色相近的点压进栈作为新种子。

  在OpenCV中,漫水填充是填充算法中最通用的方法。且在OpenCV 2.X中,使用C++重写过的FloodFill函数有两个版本。一个不带掩膜mask的版本,和一个带mask的版本。这个掩膜mask,就是用于进一步控制哪些区域将被填充颜色(比如说当对同一图像进行多次填充时)。这两个版本的FloodFill,都必须在图像中选择一个种子点,然后把临近区域所有相似点填充上同样的颜色,不同的是,不一定将所有的邻近像素点都染上同一颜色,漫水填充操作的结果总是某个连续的区域。当邻近像素点位于给定的范围(从loDiff到upDiff)内或在原始seedPoint像素值范围内时,FloodFill函数就会为这个点涂上颜色。

3、floodFill函数详解
  在OpenCV中,漫水填充算法由floodFill函数实现,其作用是用我们指定的颜色从种子点开始填充一个连接域。连通性由像素值的接近程度来衡量。OpenCV2.X有两个C++重写版本的floodFill。

 第一个版本的floodFill:

 int floodFill(InputOutputArray image, Point seedPoint, Scalar newVal, Rect* rect=0, Scalar loDiff=Scalar(), Scalar upDiff=Scalar(), int flags=4 )

第二个版本的floodFill:

int floodFill(InputOutputArray image, InputOutputArray mask, Point seedPoint,Scalar newVal, Rect* rect=0, Scalar loDiff=Scalar(), Scalar upDiff=Scalar(), int flags=4 )

参数详解:

    第一个参数,InputOutputArray类型的image, 输入/输出1通道或3通道,8位或浮点图像,具体参数由之后的参数具体指明。
    第二个参数, InputOutputArray类型的mask,这是第二个版本的floodFill独享的参数,表示操作掩模,。它应该为单通道、8位、长和宽上都比输入图像
     image 大两个像素点的图像。第二个版本的floodFill需要使用以及更新掩膜,所以这个mask参数我们一定要将其准备好并填在此处。需要注意的是,
      漫水填充不会填充掩膜mask的非零像素区域。例如,一个边缘检测算子的输出可以用来作为掩膜,以防止填充到边缘。同样的,也可以在多次的函数调用
      中使用同一个掩膜,以保证填充的区域不会重叠。另外需要注意的是,掩膜mask会比需填充的图像大,所以 mask 中与输入图像(x,y)像素点相对应的点的
坐标为(x+1,y+1)。 第三个参数,Point类型的seedPoint,漫水填充算法的起始点。 第四个参数,Scalar类型的newVal,像素点被染色的值,即在重绘区域像素的新值。 第五个参数,Rect*类型的rect,有默认值0,一个可选的参数,用于设置floodFill函数将要重绘区域的最小边界矩形区域。 第六个参数,Scalar类型的loDiff,有默认值Scalar( ),表示当前观察像素值与其部件邻域像素值或者待加入该部件的种子像素之间的亮度或颜色之负差
    (lower brightness/color difference)的最大值。 第七个参数,Scalar类型的upDiff,有默认值Scalar( ),表示当前观察像素值与其部件邻域像素值或者待加入该部件的种子像素之间的亮度或颜色之正差
    (lower brightness/color difference)的最大值。 第八个参数,int类型的flags,操作标志符,此参数包含三个部分,比较复杂,我们一起详细看看。 低八位(第0~7位)用于控制算法的连通性,可取4 (4为缺省值) 或者 8。如果设为4,表示填充算法只考虑当前像素水平方向和垂直方向的相邻点;如果
        设为 8,除上述相邻点外,还会包含对角线方向的相邻点。 高八位部分(16~23位)可以为0 或者如下两种选项标识符的组合: FLOODFILL_FIXED_RANGE - 如果设置为这个标识符的话,就会考虑当前像素与种子像素之间的差,否则就考虑当前像素与其相邻像素的差。也就
         是说,这个范围是浮动的。 FLOODFILL_MASK_ONLY - 如果设置为这个标识符的话,函数不会去填充改变原始图像 (也就是忽略第三个参数newVal), 而是去填充掩模图像
         (mask)。这个标识符只对第二个版本的floodFill有用,因第一个版本里面压根就没有mask参数。 中间八位部分,上面关于高八位FLOODFILL_MASK_ONLY标识符中已经说的很明显,需要输入符合要求的掩码。Floodfill的flags参数的中间八位的值就
        是用于指定填充掩码图像的值的。但如果flags中间八位的值为0,则掩码会用1来填充。

  而所有flags可以用or操作符连接起来,即“|”。例如,如果想用8邻域填充,并填充固定像素值范围,填充掩码而不是填充源图像,以及设填充值为38,那么输入的参数是这样:

flags=8 | FLOODFILL_MASK_ONLY | FLOODFILL_FIXED_RANGE | (38<<8)

【示例】

 1 //漫水填充
 2 #include 
 3 #include 
 4 
 5 using namespace cv;
 6 
 7 int main( )
 8 {
 9     Mat src = imread("1.jpg");
10     imshow("【原始图】",src);
11     Rect ccomp;
12     floodFill(src, Point(50,300), Scalar(155, 255,55), &ccomp, Scalar(20, 20, 20),Scalar(20, 20, 20));
13     imshow("【效果图】",src);
14     imwrite("效果图.jpg", src);
15     waitKey(0);
16     return 0;
17 }

效果展示:

技术分享图片

4、关于SetMouseCallback函数

  因为下面示例程序中有用到SetMouseCallback函数,我们在这里讲一讲。SetMouseCallback函数为指定的窗口设置鼠标回调函数。

C++: void setMouseCallback(conststring& winname, MouseCallback onMouse, void* userdata=0 )
    第一个参数,const string&类型的winname,为窗口的名字。
    第二个参数,MouseCallback类型的onMouse,指定窗口里每次鼠标时间发生的时候,被调用的函数指针。这个函数的原型应该为
        voidFoo(int event, int x, int y, int flags, void* param);其中event是 CV_EVENT_*变量之一, x和y
        是鼠标指针在图像坐标系的坐标(不是窗口坐标系), flags是CV_EVENT_FLAG的组合, param是用户定义的传递到
        cvSetMouseCallback函数调用的参数。 第三个参数,void*类型的userdata,用户定义的传递到回调函数的参数,有默认值0。
三、综合示例
  1 //漫水填充,综合示例
  2 #include "opencv2/imgproc/imgproc.hpp"
  3 #include "opencv2/highgui/highgui.hpp"
  4 #include 
  5 
  6 using namespace cv;
  7 using namespace std;
  8 
  9 Mat g_srcImage, g_dstImage, g_grayImage, g_maskImage;//定义原始图、目标图、灰度图、掩模图
 10 int g_nFillMode = 1;//漫水填充的模式
 11 int g_nLowDifference = 20, g_nUpDifference = 20;//负差最大值、正差最大值
 12 int g_nCOnnectivity= 4;//表示floodFill函数标识符低八位的连通值
 13 int g_bIsColor = true;//是否为彩色图的标识符布尔值
 14 bool g_bUseMask = false;//是否显示掩膜窗口的布尔值
 15 int g_nNewMaskVal = 255;//新的重新绘制的像素值
 16 
 17 static void ShowHelpText()
 18 {
 19     //输出一些帮助信息
 20     cout <<"\n\n\n\t欢迎来到漫水填充示例程序~\n\n";
 21     cout <<"\n\n\t按键操作说明: \n\n"
 22         "\t\t鼠标点击图中区域- 进行漫水填充操作\n"
 23         "\t\t键盘按键【ESC】- 退出程序\n"
 24         "\t\t键盘按键【1】-  切换彩色图/灰度图模式\n"
 25         "\t\t键盘按键【2】- 显示/隐藏掩膜窗口\n"
 26         "\t\t键盘按键【3】- 恢复原始图像\n"
 27         "\t\t键盘按键【4】- 使用空范围的漫水填充\n"
 28         "\t\t键盘按键【5】- 使用渐变、固定范围的漫水填充\n"
 29         "\t\t键盘按键【6】- 使用渐变、浮动范围的漫水填充\n"
 30         "\t\t键盘按键【7】- 操作标志符的低八位使用4位的连接模式\n"
 31         "\t\t键盘按键【8】- 操作标志符的低八位使用8位的连接模式\n"
 32         "\n\n\t\t\t\t\t\t\t\tn\n\n";
 33 }
 34 
 35 static void onMouse( int event, int x, int y, int, void* )
 36 {
 37     // 若鼠标左键没有按下,便返回
 38     if( event != CV_EVENT_LBUTTONDOWN )
 39         return;
 40 
 41     Point seed = Point(x,y);
 42     int LowDifference = g_nFillMode == 0 ? 0 : g_nLowDifference;//空范围的漫水填充,此值设为0,否则设为全局的g_nLowDifference
 43     int UpDifference = g_nFillMode == 0 ? 0 : g_nUpDifference;//空范围的漫水填充,此值设为0,否则设为全局的g_nUpDifference
 44     int flags = g_nConnectivity + (g_nNewMaskVal <<8) +
 45         (g_nFillMode == 1 ? CV_FLOODFILL_FIXED_RANGE : 0);//标识符的0~7位为g_nConnectivity,8~15位为g_nNewMaskVal左移8位的值,16~23位为CV_FLOODFILL_FIXED_RANGE或者0。
 46 
 47     //随机生成bgr值
 48     int b = (unsigned)theRNG() & 255;//随机返回一个0~255之间的值
 49     int g = (unsigned)theRNG() & 255;//随机返回一个0~255之间的值
 50     int r = (unsigned)theRNG() & 255;//随机返回一个0~255之间的值
 51     Rect ccomp;//定义重绘区域的最小边界矩形区域
 52 
 53     Scalar newVal = g_bIsColor ? Scalar(b, g, r) : Scalar(r*0.299 + g*0.587 + b*0.114);//在重绘区域像素的新值,若是彩色图模式,取Scalar(b, g, r);若是灰度图模式,取Scalar(r*0.299 + g*0.587 + b*0.114)
 54 
 55     Mat dst = g_bIsColor ? g_dstImage : g_grayImage;//目标图的赋值
 56     int area;
 57 
 58     if( g_bUseMask )
 59     {
 60         threshold(g_maskImage, g_maskImage, 1, 128, CV_THRESH_BINARY);
 61         area = floodFill(dst, g_maskImage, seed, newVal, &ccomp, Scalar(LowDifference, LowDifference, LowDifference),
 62             Scalar(UpDifference, UpDifference, UpDifference), flags);
 63         imshow( "mask", g_maskImage );
 64     }
 65     else
 66     {
 67         area = floodFill(dst, seed, newVal, &ccomp, Scalar(LowDifference, LowDifference, LowDifference),
 68             Scalar(UpDifference, UpDifference, UpDifference), flags);
 69     }
 70 
 71     imshow("效果图", dst);
 72 
 73     cout <

32、【opencv入门】GrabCut & FloodFill图像分割


推荐阅读
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 高质量SQL书写的30条建议
    本文提供了30条关于优化SQL的建议,包括避免使用select *,使用具体字段,以及使用limit 1等。这些建议是基于实际开发经验总结出来的,旨在帮助读者优化SQL查询。 ... [详细]
  • 本文介绍了指针的概念以及在函数调用时使用指针作为参数的情况。指针存放的是变量的地址,通过指针可以修改指针所指的变量的值。然而,如果想要修改指针的指向,就需要使用指针的引用。文章还通过一个简单的示例代码解释了指针的引用的使用方法,并思考了在修改指针的指向后,取指针的输出结果。 ... [详细]
  • CentOS 7部署KVM虚拟化环境之一架构介绍
    本文介绍了CentOS 7部署KVM虚拟化环境的架构,详细解释了虚拟化技术的概念和原理,包括全虚拟化和半虚拟化。同时介绍了虚拟机的概念和虚拟化软件的作用。 ... [详细]
  • 本文讨论了Alink回归预测的不完善问题,指出目前主要针对Python做案例,对其他语言支持不足。同时介绍了pom.xml文件的基本结构和使用方法,以及Maven的相关知识。最后,对Alink回归预测的未来发展提出了期待。 ... [详细]
author-avatar
lv
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有