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


本文作者:小嗷

微信公众号:aoxiaoji

吹比QQ群:736854977

简书链接:https://www.jianshu.com/u/45da1fbce7d0


简介:

漫水填充(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. 处理完数组,退出

效果如下:(十字形)

即:东、南、西、北、

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

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

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

2.2 堆栈

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

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

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

堆:

堆栈空间分配

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

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

堆栈缓存方式

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

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

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

参数详解:

  • 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 或者如下两种选项标识符的组合:

  • 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)

接着就是理论运用:

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

  • 在灰度图像和浮动范围的情况下:

  • 在灰度图像和固定范围的情况下:

  • 在彩色图像和浮动范围的情况下:

  • 在彩色图像和固定范围的情况下:

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

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

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

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

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

  • 固定范围

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

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

即:180《像素点《250

  • 浮动范围

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

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

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

代码如下:

//videoio:视频流的输入和输入
//imgcodecs:用于图像文件的载入(imread)和输出(imwrite)
//imgproc:图像处理模块
//highgui:高层图形用户界面(GUI),包括媒体输入输出、视频捕捉、图像交互界面接口、图像和视频的编码解码等
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/videoio.hpp"
#include "opencv2/highgui.hpp"
#include 
using namespace cv;
using namespace std;
static void help()
{
    cout << "\nThis program demonstrated the floodFill() function\n"
            "Call:\n"
            "./ffilldemo [image_name -- Default: ../data/fruits.jpg]\n" << endl;
    cout << "Hot keys: \n"
            "\tESC - quit the program\n"
            "\tc - switch color/grayscale mode\n"
            "\tm - switch mask mode\n"
            "\tr - restore the original image\n"
            "\ts - use null-range floodfill\n"
            "\tf - use gradient floodfill with fixed(absolute) range\n"
            "\tg - use gradient floodfill with floating(relative) range\n"
            "\t4 - use 4-connectivity mode\n"
            "\t8 - use 8-connectivity mode\n" << endl;
}
Mat image0, image, gray, mask;
int ffillMode = 1;
int loDiff = 20, upDiff = 20;
int connectivity = 4;
int isColor = true;
bool useMask = false;
int newMaskVal = 255;
static void onMouse( int event, int x, int y, int, void* )
{
    if( event != EVENT_LBUTTONDOWN )
        return;
    Point seed = Point(x,y);
    int lo = ffillMode == 0 ? 0 : loDiff;
    int up = ffillMode == 0 ? 0 : upDiff;
    int flags = connectivity + (newMaskVal << 8) +
                (ffillMode == 1 ? FLOODFILL_FIXED_RANGE : 0);
    int b = (unsigned)theRNG() & 255;
    int g = (unsigned)theRNG() & 255;
    int r = (unsigned)theRNG() & 255;
    Rect ccomp;
    Scalar newVal = isColor ? Scalar(b, g, r) : Scalar(r*0.299 + g*0.587 + b*0.114);
    Mat dst = isColor ? image : gray;
    int area;
    if( useMask )
    {
        threshold(mask, mask, 1, 128, THRESH_BINARY);
        area = floodFill(dst, mask, seed, newVal, &ccomp, Scalar(lo, lo, lo),
                  Scalar(up, up, up), flags);
        imshow( "mask", mask );
    }
    else
    {
        area = floodFill(dst, seed, newVal, &ccomp, Scalar(lo, lo, lo),
                  Scalar(up, up, up), flags);
    }
    imshow("image", dst);
    cout << area << " pixels were repainted\n";
}
int main( int argc, char** argv )
{
    cv::CommandLineParser parser (argc, argv,
        "{help h | | show help message}{@image|../data/fruits.jpg| input image}"
    );
    if (parser.has("help"))
    {
        parser.printMessage();
        return 0;
    }
    string filename = parser.get("@image");
    image0 = imread(filename, 1);
    if( image0.empty() )
    {
        cout << "Image empty\n";
        parser.printMessage();
        return 0;
    }
    help();
    image0.copyTo(image);
    cvtColor(image0, gray, COLOR_BGR2GRAY);
    mask.create(image0.rows+2, image0.cols+2, CV_8UC1);
    namedWindow( "image", 0 );
    createTrackbar( "lo_diff", "image", &loDiff, 255, 0 );
    createTrackbar( "up_diff", "image", &upDiff, 255, 0 );
    setMouseCallback( "image", onMouse, 0 );
    for(;;)
    {
        imshow("image", isColor ? image : gray);
        char c = (char)waitKey(0);
        if( c == 27 )
        {
            cout << "Exiting ...\n";
            break;
        }
        switch( c )
        {
        case 'c':
            if( isColor )
            {
                cout << "Grayscale mode is set\n";
                cvtColor(image0, gray, COLOR_BGR2GRAY);
                mask = Scalar::all(0);
                isColor = false;
            }
            else
            {
                cout << "Color mode is set\n";
                image0.copyTo(image);
                mask = Scalar::all(0);
                isColor = true;
            }
            break;
        case 'm':
            if( useMask )
            {
                destroyWindow( "mask" );
                useMask = false;
            }
            else
            {
                namedWindow( "mask", 0 );
                mask = Scalar::all(0);
                imshow("mask", mask);
                useMask = true;
            }
            break;
        case 'r':
            cout << "Original image is restored\n";
            image0.copyTo(image);
            cvtColor(image, gray, COLOR_BGR2GRAY);
            mask = Scalar::all(0);
            break;
        case 's':
            cout << "Simple floodfill mode is set\n";
            ffillMode = 0;
            break;
        case 'f':
            cout << "Fixed Range floodfill mode is set\n";
            ffillMode = 1;
            break;
        case 'g':
            cout << "Gradient (floating range) floodfill mode is set\n";
            ffillMode = 2;
            break;
        case '4':
            cout << "4-connectivity mode is set\n";
            connectivity = 4;
            break;
        case '8':
            cout << "8-connectivity mode is set\n";
            connectivity = 8;
            break;
        }
    }
    return 0;
}

原图:

效果图:

  1. 本人是抱着玩一玩的心态,学习opencv(其实深度学习没有外界说的这么高深,小嗷是白板,而且有工作在身并且于代码无关)
  2. 大家可以把我的数学水平想象成初中水平,毕竟小嗷既不是代码靠吃饭又不是靠数学吃饭,毕业N年
  3. 写文章主要是为了后人少走点弯路,多交点朋友,一起学习
  4. 如果有好的图像识别群拉我进去QQ:631821577
  5. 就我一个白板,最后还是成的,你们别怕,慢慢来把

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

  • 邮箱:[email protected]
  • QQ群:736854977
  • 有什么疑问公众号提问,下班或者周六日回答,ths

推荐文章:

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

感言

很多知识点都是串起来,怎么说呢?可以理解之前二十几篇文章的知识布局,才理解这篇或者做到这篇文章。

实在有点不好意思,小嗷因为个人原因(工资低老想着跳槽,哈哈哈)中断了一期。一周5篇实在吃不消。

还有1开始几篇文章布局什么真难看,小嗷会重新群发(微信规矩:不群发,上不了链接地址)

代码地址:

链接:

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

密码:nq8r

你可能感兴趣的:(OpenCv,-)