30.漫水填充算法(堆栈是什么?/图像维数/(A?B:C))--- OpenCV从零开始到图像(人脸 + 物体)识别系列


本文作者:小嗷

微信公众号:aoxiaoji

吹比QQ群:736854977

微信链接:https://mp.weixin.qq.com/s?__biz=MzU1MTgxNjQyMg==&mid=2247483885&idx=1&sn=bfc8647575f90b06356eb108486dd1a4&chksm=fb8adc64ccfd55725a253d6cf2c3c444a2b7096d2f39b19be9859cd8f4484af4b34c086d5c8e#rd


image
image
image

简介:

漫水填充(Flood fill),也称为种子填充(seed fill),是一种确定多维数组中连接到给定节点的区域的算法。(灰度图是二维,彩色图是三维)

灰度图的二维:一般来说,一维是高(行),一维是宽(列)。

即:char a[3][4] = 246;

a为3*4(3行4列)的像素值为:246

彩色图:多了一维是图像深度


本文你会找到以下问题的答案:

  1. 漫水填充算法

  2. 灰度图是二维,彩色图是三维

  3. 堆栈是什么?

  4. for (;;)等于while(1)

  5. (A?B:C)

A?B:C 这个运算是判断A的真假,若是真就执行B如是假就执行C


2.1 漫水填充算法包含三个参数(自己写算法的话):

开始节点、目标颜色和替换颜色。

该算法查找数组中的所有节点,这些节点通过目标颜色的路径连接到起始节点,并将它们更改为替换颜色。有许多方法可以构造漫水填充算法,但它们都使用队列或堆栈数据结构,显式或隐式地。

根据我们是否考虑连接在角落的节点,我们有两种变化:8路和4路。(即:核是3*3的正方形,还是自定义十字形),取其中一个4路在演示。

一个隐式堆栈的(递归)漫水填充实现(对于一个二维数组)如下所示:

基于堆栈的递归实现(四【十字形】)的思路

  1. 首先。如果目标颜色等于替换颜色。

  2. 如果节点的颜色不等于目标颜色,不处理。

  3. 将节点的颜色设置为替换颜色。

  4. 执行漫水填充(向节点南部的一步,目标颜色,替换颜色)。

  5. 执行漫水填充(向节点以北一步,目标颜色,替换颜色)。

  6. 执行漫水填充(在节点的西面一步,目标颜色,替换颜色)。

  7. 执行漫水填充(在节点的东边一步,目标颜色,替换颜色)。

  8. 处理完数组,退出

效果如下:(十字形)

image

即:东、南、西、北、

8路的效果如下:(核是3*3的正方形)

image

即:东、南、西、北、东南、西南、西北、东北、 8个方向

用给定的颜色填充连接的组件。

2.2 堆栈

在计算机领域,堆栈是一个不容忽视的概念,堆栈是两种数据结构。

堆栈都是一种数据项按序排列的数据结构,只能在一端(称为栈顶(top))对数据项进行插入和删除。

要点:堆,队列优先,先进先出(FIFO—first in first out) [1] 。栈,先进后出(FILO—First-In/Last-Out)。

堆:

image

堆栈空间分配

栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

堆(操作系统): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。

堆栈缓存方式

栈使用的是一级缓存, 他们通常都是被调用时处于存储空间中,调用完毕立即释放。

堆则是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。

image
1 cv::floodFill  (   InputOutputArray    image,2        InputOutputArray    mask,3        Point   seedPoint,4        Scalar      newVal,5        Rect *      rect = 0,6        Scalar      loDiff = Scalar(),7        Scalar      upDiff = Scalar(),8        int     flags = 4 9    )   

参数详解:

  • image:InputOutputArray类型的image, 输入/输出1通道或3通道,8位或浮点图像,具体参数由之后的参数具体指明。

  • mask:InputOutputArray类型的mask,这是第二个版本的floodFill独享的参数,表示操作掩模,。它应该为单通道、8位、长和宽上都比输入图像 image 大两个像素点的图像。第二个版本的floodFill需要使用以及更新掩膜,所以这个mask参数我们一定要将其准备好并填在此处。需要注意的是,漫水填充不会填充掩膜mask的非零像素区域。例如,一个边缘检测算子的输出可以用来作为掩膜,以防止填充到边缘。同样的,也可以在多次的函数调用中使用同一个掩膜,以保证填充的区域不会重叠。另外需要注意的是,掩膜mask会比需填充的图像大,所以 mask 中与输入图像(x,y)像素点相对应的点的坐标为(x+1,y+1)。

  • seedPoint:Point类型的seedPoint,漫水填充算法的起始点。

  • newVal:Scalar类型的newVal,像素点被染色的值,即在重绘区域像素的新值。

  • rect:Rect*类型的rect,有默认值0,一个可选的参数,用于设置floodFill函数将要重绘区域的最小边界矩形区域。

  • loDiff:Scalar类型的loDiff,有默认值Scalar( ),表示当前观察像素值与其部件邻域像素值或者待加入该部件的种子像素之间的亮度或颜色之负差(lower brightness/color difference)的最大值。

  • upDiff:Scalar类型的upDiff,有默认值Scalar( ),表示当前观察像素值与其部件邻域像素值或者待加入该部件的种子像素之间的亮度或颜色之正差(lower brightness/color difference)的最大值。

  • flags:int类型的flags,操作标志符,此参数包含三个部分,比较复杂,我们一起详细看看。

低八位(第0~7位)用于控制算法的连通性,可取4 (4为缺省值) 或者 8。如果设为4,表示填充算法只考虑当前像素水平方向和垂直方向的相邻点;如果设为 8,除上述相邻点外,还会包含对角线方向的相邻点。

(请看上面第二部分的东南西北的解释,哈哈哈)

高八位部分(16~23位)可以为0 或者如下两种选项标识符的组合:

  • FLOODFILLFIXEDRANGE - 如果设置为这个标识符的话,就会考虑当前像素与种子像素之间的差,否则就考虑当前像素与其相邻像素的差。也就是说,这个范围是浮动的。

  • FLOODFILLMASKONLY - 如果设置为这个标识符的话,函数不会去填充改变原始图像 (也就是忽略第三个参数newVal), 而是去填充掩模图像(mask)。这个标识符只对第二个版本的floodFill有用,因第一个版本里面压根就没有mask参数。

中间八位部分,上面关于高八位FLOODFILLMASKONLY标识符中已经说的很明显,需要输入符合要求的掩码。Floodfill的flags参数的中间八位的值就是用于指定填充掩码图像的值的。但如果flags中间八位的值为0,则掩码会用1来填充

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

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

接着就是理论运用:

功能cv::floodFill从种子点开始,以指定的颜色填充连接的组件。连接性是由相邻像素的颜色/亮度接近决定的。在(x,y)处的像素,如果:

  • 在灰度图像和浮动范围的情况下:
image.gif
  • 在灰度图像和固定范围的情况下:
image
  • 在彩色图像和浮动范围的情况下:
image
  • 在彩色图像和固定范围的情况下:
image

src(x′,y′)的值是一个已知的像素邻居属于组件。也就是说,要添加到所连接的组件中,像素的颜色/亮度应该足够接近:

在浮动范围内,它的一个邻居的颜色/亮度已经属于连接组件。

在固定范围内,种子点的颜色/亮度。

使用这些函数可以将连接的组件标记为指定的颜色,或者构建一个掩膜,然后提取轮廓,或者将区域复制到另一个图像,等等。

简单来说公式(灰度图为例):

  • 固定范围
image

(比如起点的像素点是200,loDiff是20,upDiff是50)

像素点必须要要在以下范围内,才可以上色

即:180《像素点《250

  • 浮动范围
image

(比如临近的像素点是200【已知的像素邻居】,loDiff是20,upDiff是50)

像素点必须要要在以下范围内,才可以上色

即:已知的像素邻居的值 - 20 《像素点《 已知的像素邻居的值 - 50

image

代码如下:

  1//videoio:视频流的输入和输入  2//imgcodecs:用于图像文件的载入(imread)和输出(imwrite)  3//imgproc:图像处理模块  4//highgui:高层图形用户界面(GUI),包括媒体输入输出、视频捕捉、图像交互界面接口、图像和视频的编码解码等  5#include "opencv2/imgproc.hpp"  6#include "opencv2/imgcodecs.hpp"  7#include "opencv2/videoio.hpp"  8#include "opencv2/highgui.hpp"  9#include <iostream> 10using namespace cv; 11using namespace std; 12static void help() 13{ 14    cout << "\nThis program demonstrated the floodFill() function\n" 15            "Call:\n" 16            "./ffilldemo [image_name -- Default: ../data/fruits.jpg]\n" << endl; 17    cout << "Hot keys: \n" 18            "\tESC - quit the program\n" 19            "\tc - switch color/grayscale mode\n" 20            "\tm - switch mask mode\n" 21            "\tr - restore the original image\n" 22            "\ts - use null-range floodfill\n" 23            "\tf - use gradient floodfill with fixed(absolute) range\n" 24            "\tg - use gradient floodfill with floating(relative) range\n" 25            "\t4 - use 4-connectivity mode\n" 26            "\t8 - use 8-connectivity mode\n" << endl; 27} 28Mat image0, image, gray, mask; 29int ffillMode = 1; 30int loDiff = 20, upDiff = 20; 31int connectivity = 4; 32int isColor = true; 33bool useMask = false; 34int newMaskVal = 255; 35static void onMouse( int event, int x, int y, int, void* ) 36{ 37    if( event != EVENT_LBUTTONDOWN ) 38        return; 39    Point seed = Point(x,y); 40    int lo = ffillMode == 0 ? 0 : loDiff; 41    int up = ffillMode == 0 ? 0 : upDiff; 42    int flags = connectivity + (newMaskVal << 8) + 43                (ffillMode == 1 ? FLOODFILL_FIXED_RANGE : 0); 44    int b = (unsigned)theRNG() & 255; 45    int g = (unsigned)theRNG() & 255; 46    int r = (unsigned)theRNG() & 255; 47    Rect ccomp; 48    Scalar newVal = isColor ? Scalar(b, g, r) : Scalar(r*0.299 + g*0.587 + b*0.114); 49    Mat dst = isColor ? image : gray; 50    int area; 51    if( useMask ) 52    { 53        threshold(mask, mask, 1, 128, THRESH_BINARY); 54        area = floodFill(dst, mask, seed, newVal, &ccomp, Scalar(lo, lo, lo), 55                  Scalar(up, up, up), flags); 56        imshow( "mask", mask ); 57    } 58    else 59    { 60        area = floodFill(dst, seed, newVal, &ccomp, Scalar(lo, lo, lo), 61                  Scalar(up, up, up), flags); 62    } 63    imshow("image", dst); 64    cout << area << " pixels were repainted\n"; 65} 66int main( int argc, char** argv ) 67{ 68    cv::CommandLineParser parser (argc, argv, 69        "{help h | | show help message}{@image|../data/fruits.jpg| input image}" 70    ); 71    if (parser.has("help")) 72    { 73        parser.printMessage(); 74        return 0; 75    } 76    string filename = parser.get<string>("@image"); 77    image0 = imread(filename, 1); 78    if( image0.empty() ) 79    { 80        cout << "Image empty\n"; 81        parser.printMessage(); 82        return 0; 83    } 84    help(); 85    image0.copyTo(image); 86    cvtColor(image0, gray, COLOR_BGR2GRAY); 87    mask.create(image0.rows+2, image0.cols+2, CV_8UC1); 88    namedWindow( "image", 0 ); 89    createTrackbar( "lo_diff", "image", &loDiff, 255, 0 ); 90    createTrackbar( "up_diff", "image", &upDiff, 255, 0 ); 91    setMouseCallback( "image", onMouse, 0 ); 92    for(;;) 93    { 94        imshow("image", isColor ? image : gray); 95        char c = (char)waitKey(0); 96        if( c == 27 ) 97        { 98            cout << "Exiting ...\n"; 99            break;100        }101        switch( c )102        {103        case 'c':104            if( isColor )105            {106                cout << "Grayscale mode is set\n";107                cvtColor(image0, gray, COLOR_BGR2GRAY);108                mask = Scalar::all(0);109                isColor = false;110            }111            else112            {113                cout << "Color mode is set\n";114                image0.copyTo(image);115                mask = Scalar::all(0);116                isColor = true;117            }118            break;119        case 'm':120            if( useMask )121            {122                destroyWindow( "mask" );123                useMask = false;124            }125            else126            {127                namedWindow( "mask", 0 );128                mask = Scalar::all(0);129                imshow("mask", mask);130                useMask = true;131            }132            break;133        case 'r':134            cout << "Original image is restored\n";135            image0.copyTo(image);136            cvtColor(image, gray, COLOR_BGR2GRAY);137            mask = Scalar::all(0);138            break;139        case 's':140            cout << "Simple floodfill mode is set\n";141            ffillMode = 0;142            break;143        case 'f':144            cout << "Fixed Range floodfill mode is set\n";145            ffillMode = 1;146            break;147        case 'g':148            cout << "Gradient (floating range) floodfill mode is set\n";149            ffillMode = 2;150            break;151        case '4':152            cout << "4-connectivity mode is set\n";153            connectivity = 4;154            break;155        case '8':156            cout << "8-connectivity mode is set\n";157            connectivity = 8;158            break;159        }160    }161    return 0;162}

原图:

image

效果图:

image
image
  1. 本人是抱着玩一玩的心态,学习opencv(其实深度学习没有外界说的这么高深,小嗷是白板,而且有工作在身并且于代码无关)

  2. 大家可以把我的数学水平想象成初中水平,毕竟小嗷既不是代码靠吃饭又不是靠数学吃饭,毕业N年

  3. 写文章主要是为了后人少走点弯路,多交点朋友,一起学习

  4. 如果有好的图像识别群拉我进去QQ:631821577

  5. 就我一个白板,最后还是成的,你们别怕,慢慢来把

image

分享可以无数次,转载成自己文章QQ邮箱通知一下,未经授权请勿转载。

  • 邮箱:631821577@qq.com

  • QQ群:736854977

  • 有什么疑问公众号提问,下班或者周六日回答,ths

推荐文章:

21.失真/低高通/振铃效应/旁瓣泄漏效应/频域滤波/图像深度/频带/线性滤波源码分析(数学篇) - OpenCV从零开始到图像

感言

嗷嗷嗷~~~,喜欢就推荐一下好友。

代码地址:

链接:

https://pan.baidu.com/s/1RESLgnXlwZ74E-Eh1yRaXQ

密码:nq8r

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容