热门标签 | 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图像分割


推荐阅读
  • [c++基础]STL
    cppfig15_10.cppincludeincludeusingnamespacestd;templatevoidprintVector(constvector&integer ... [详细]
  • malloc 是 C 语言中的一个标准库函数,全称为 memory allocation,即动态内存分配。它用于在程序运行时申请一块指定大小的连续内存区域,并返回该区域的起始地址。当无法预先确定内存的具体位置时,可以通过 malloc 动态分配内存。 ... [详细]
  • Python多线程详解与示例
    本文介绍了Python中的多线程编程,包括僵尸进程和孤儿进程的概念,并提供了具体的代码示例。同时,详细解释了0号进程和1号进程在系统中的作用。 ... [详细]
  • 本文详细介绍了Linux系统中用于管理IPC(Inter-Process Communication)资源的两个重要命令:ipcs和ipcrm。通过这些命令,用户可以查看和删除系统中的消息队列、共享内存和信号量。 ... [详细]
  • NX二次开发:UFUN点收集器UF_UI_select_point_collection详解
    本文介绍了如何在NX中使用UFUN库进行点收集器的二次开发,包括必要的头文件包含、初始化和选择点集合的具体实现。 ... [详细]
  • 本文介绍了如何在 ASP.NET 中设置 Excel 单元格格式为文本,获取多个单元格区域并作为表头,以及进行单元格合并、赋值、格式设置等操作。 ... [详细]
  • LDAP服务器配置与管理
    本文介绍如何通过安装和配置SSSD服务来统一管理用户账户信息,并实现其他系统的登录调用。通过图形化交互界面配置LDAP服务器,确保用户账户信息的集中管理和安全访问。 ... [详细]
  • 如果应用程序经常播放密集、急促而又短暂的音效(如游戏音效)那么使用MediaPlayer显得有些不太适合了。因为MediaPlayer存在如下缺点:1)延时时间较长,且资源占用率高 ... [详细]
  • 网络爬虫的规范与限制
    本文探讨了网络爬虫引发的问题及其解决方案,重点介绍了Robots协议的作用和使用方法,旨在为网络爬虫的合理使用提供指导。 ... [详细]
  • 自定义滚动条美化页面内容
    当页面内容超出显示范围时,为了提升用户体验和页面美观,通常会添加滚动条。如果默认的浏览器滚动条无法满足设计需求,我们可以自定义一个符合要求的滚动条。本文将详细介绍自定义滚动条的实现过程。 ... [详细]
  • 微软推出Windows Terminal Preview v0.10
    微软近期发布了Windows Terminal Preview v0.10,用户可以在微软商店或GitHub上获取这一更新。该版本在2月份发布的v0.9基础上,新增了鼠标输入和复制Pane等功能。 ... [详细]
  • 本文介绍了多种开源数据库及其核心数据结构和算法,包括MySQL的B+树、MVCC和WAL,MongoDB的tokuDB和cola,boltDB的追加仅树和mmap,levelDB的LSM树,以及内存缓存中的一致性哈希。 ... [详细]
  • 本文详细介绍了Java代码分层的基本概念和常见分层模式,特别是MVC模式。同时探讨了不同项目需求下的分层策略,帮助读者更好地理解和应用Java分层思想。 ... [详细]
  • 网站访问全流程解析
    本文详细介绍了从用户在浏览器中输入一个域名(如www.yy.com)到页面完全展示的整个过程,包括DNS解析、TCP连接、请求响应等多个步骤。 ... [详细]
  • [转]doc,ppt,xls文件格式转PDF格式http:blog.csdn.netlee353086articledetails7920355确实好用。需要注意的是#import ... [详细]
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社区 版权所有