2015-05-17 22:22:17 w12345_ww 阅读数 1848
  • 1.14.ARM裸机第十四部分-LCD显示器

    本期课程主要讲解LCD。首先讲了LCD显示原理、颜色原理、LCD显示器的主要性能参数、概念等,然后进入了LCD编程实战部分,用6节课从零开始带大家编写程序在LCD上显示像素、背景、线条、文字、图片等内容。本课程的学习目标是掌握LCD显示相关的概念和编程方法,对显存的作用和填充有本质的了解,对图像显示有一定的认识。

    8429 人正在学习 去看看 朱有鹏

【漫水填充法】

首先,漫水填充在图像处理中是做什么的?

漫水填充,经常被用来标记或分离图像中的一部分,以便对其进行进一步的处理或者分析。漫水填充也可以用来从输入图像获取掩码区域,掩码会加速处理过程,或只处理掩码指定的像素点。

漫水填充的过程是怎样的?

漫水填充操作的结果总是在某个连续的区域。当邻近像素点位于给定的范围(从loDiff到upDiff)内或在原始seedPoint像素值范围内事,cvFloodFill()函数将为这个点涂上颜色。可以选参数mask控制漫水法填充。OpenCV中给出了函数cvFloodFill(),下面简介下这个函数。

void cvFloodFill(
    IplImage*     img,//输入图像,8位或者浮点类型的单通道或三通道图
    CvPoint       seedPoint, //种子坐标
    CvScalar      newVal,//像素点被染色的值
    CvScalar      loDiff= cvScalarAll(0),//像素值的下限差值
    CvScalar      upDiff= cvScalarAll(0),//像素值的上限差值
    CvConnectedComp* comp=NULL,
    int           flags=4,//这个参数略复杂,下文介绍下

    //mask是掩码,既可以作为输入,也可以作为输出,若mask非空,
    //那么它必须为一个单通道、8位、源图像为width * height,
    //则掩码大小为 (width+2) * (height+2)
    CvArr*        mask=NULL 
)

关于flags参数,低八位可以被设置为4或者8,这个参数控制着填充算法的连通性,4表示在4个方向考虑连通性(上下左右), 8表示在8个方向考虑连通性(加上4个对角线方向),高八位可以设置CV_FLOODFILL_FIXED_RANGE,或者 CV_FLOODFILL_MASK_ONLY(如果设置只考虑填充MASK),flags的中间比特(8-15位)的值可以设置填充掩码的值 flags = 8 | CV_FLOODFILL_MASK_ONLY | CV_FLOODFILL_FIXED_RANGE | (43<<8)

下面给出漫水填充法函数应用的示例:

#include<iostream>
#include<opencv2/opencv.hpp>
using namespace std;

int main()
{
    IplImage* src = cvLoadImage("1.jpg", CV_LOAD_IMAGE_UNCHANGED);
    cvShowImage("source", src);
    IplImage* image1 = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 3);
    IplImage* image2 = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 3);

    cvCopy(src, image1);
    cvCopy(src, image2);

    cvFloodFill(
        image1,
        cvPoint(54, 82),
        CV_RGB(255, 0, 0),
        cvScalar(20, 30, 40, 0),
        cvScalar(20, 30, 40, 0),
        NULL,
        4,
        NULL
        );
    cvShowImage("dst1", image1);

    cvFloodFill(
        image2,
        cvPoint(88,88),
        CV_RGB(255, 0, 0),
        cvScalar(20, 30, 40, 0),
        cvScalar(20, 30, 40, 0),
        NULL,
        4,
        NULL
        );
    cvShowImage("dst2", image2);

    cvWaitKey();
    cvReleaseImage(&src);
    cvReleaseImage(&image1);
    cvReleaseImage(&image2);
    cvDestroyAllWindows();

    return 0;

}

【尺寸调整】

首先,尺寸调整,顾名思义,就是将某种尺寸的图像转换为其他尺寸的图像。在OpenCV中有函数cvResize(),此函数可以将源图像精确转换为目标图像的尺寸。若源图像设置了ROI,那么函数会对ROI调整尺寸,以匹配目标图像。函数比较简单。

void cvResize(
    const  CvArr*  src,
    CvArr*         dst,
    int            interpolation=CV_INTER_LINEAR //插值方法
)

关于interpolation,有如下列表:

这里写图片描述

一般情况下,我们期望源图像和重采样后的目标图像之间的映射尽可能平滑,而参数 interpolation就是控制如何映射。当缩小图像时,目标图像的像素会映射为源图像中的多个像素,这时需要插值。当放大图像时,目标图像上的像素可能无法在源图像中找到精确对应的像素,也需要进行插值。

CV_INTER_NN: 将目标图像各点的像素值设为源图像中与其距离最近点的像素值
CV_INTER_LINEAR: 将根据图像附近的4个(2×2范围)邻近像素的线性加权计算得出,权重由4个像素到精确目标点的距离决定
CV_INTER_AREA: 新的像素点覆盖原来的像素点,然后求取覆盖区域的平均值
CV_INTER_CUBIC: 首先对源图像附近的4×4个邻近像素进行三次样条拟合,然后将目标图像对应的三次样条值作为目标图像对应像素点的值

2016-06-30 19:18:13 u013162930 阅读数 7131
  • 1.14.ARM裸机第十四部分-LCD显示器

    本期课程主要讲解LCD。首先讲了LCD显示原理、颜色原理、LCD显示器的主要性能参数、概念等,然后进入了LCD编程实战部分,用6节课从零开始带大家编写程序在LCD上显示像素、背景、线条、文字、图片等内容。本课程的学习目标是掌握LCD显示相关的概念和编程方法,对显存的作用和填充有本质的了解,对图像显示有一定的认识。

    8429 人正在学习 去看看 朱有鹏

漫水填充算法,是根据选定的种子点,用一种自定义的颜色填充种子点的联通区域,通过设置可连通像素的上下限以及连通方式来达到不同的填充效果。
漫水填充经常被用来标记或分离图像的一部分以便对其进行进一步处理或分析。
所谓漫水填充,简单来说,就是自动选中了和种子点相连的区域,接着将该区域替换成指定的颜色。
漫水填充也可以用来从输入图像获取掩码区域,掩码会加速处理过程,或者只处理掩码指定的像素点。
在OpenCV中,漫水填充是填充算法中最通用的方法。且在OpenCV 2.X中,使用C++重写过的FloodFill函数有两个版本。一个不带掩膜mask的版本,和一个带mask的版本。这个掩膜mask,就是用于进一步控制哪些区域将被填充颜色(比如说当对同一图像进行多次填充时)。这两个版本的FloodFill,都必须在图像中选择一个种子点,然后把临近区域所有相似点填充上同样的颜色,不同的是,不一定将所有的邻近像素点都染上同一颜色,漫水填充操作的结果总是某个连续的区域。当邻近像素点位于给定的范围(从loDiff到upDiff)内或在原始seedPoint像素值范围内时,FloodFill函数就会为这个点涂上颜色。

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

OpenCV中的函数原型如下:

int floodFill(InputOutputArray image, Point seedPoint, Scalar newVal, Rect* rect=0, Scalar loDiff=Scalar(), Scalar upDiff=Scalar(), int flags=4 )
int floodFill(InputOutputArray image, 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会比需填充的图像大2个像素,所以 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,操作标志符,此参数包含三个部分,比较复杂,我们一起详细看看。
                    a。低八位(第0~7位)用于控制算法的连通性,可取4 (4为缺省值) 或者 8。如果设为4,表示填充算法只考虑当前像素水平方向和垂直方向的相邻点;如果设为 8,除上述相邻点外,还会包含对角线方向的相邻点。
                    b。中间八位部分,上面关于高八位FLOODFILL_MASK_ONLY标识符中已经说的很明显,需要输入符合要求的掩码。Floodfill的flags参数的中间八位的值就是用于指定填充掩码图像的值的。但如果flags中间八位的值为0,则掩码会用1来填充。
                    c。高八位部分(16~23位)可以为0 或者如下两种选项标识符的组合:
                    FLOODFILL_FIXED_RANGE - 如果设置为这个标识符的话,就会考虑当前像素与种子像素之间的差,否则就考虑当前像素与其相邻像素的差。也就是说,这个范围是浮动的。
                    FLOODFILL_MASK_ONLY - 如果设置为这个标识符的话,函数不会去填充改变原始图像 (也就是忽略第三个参数newVal), 而是去填充掩模图像(mask)。这个标识符只对第二个版本的floodFill有用,因第一个版本里面压根就没有mask参数。
而所有flags可以用or操作符连接起来,即“|”。例如,如果想用8邻域填充,并填充固定像素值范围,填充掩码而不是填充源图像,以及设填充值为47,那么输入的参数是这样:
flags=8 | FLOODFILL_MASK_ONLY | FLOODFILL_FIXED_RANGE | (47<<8)


eg。

Mat srcImage=imread("M:/图像处理实验/floodFill/test_.bmp");
Rect ccomp;
floodFill(srcImage,  Point(1, 1), CV_RGB(205, 205, 205), &ccomp, Scalar(15, 15, 15), Scalar(15, 15, 15), 8 | FLOODFILL_FIXED_RANGE );
imwrite("M:/图像处理实验/floodFill/test_dst.bmp", srcImage);
种子点为(1,1)。下面为原图与漫水填充后的结果对比:

  

eg。

Mat srcImage=imread("M:/图像处理实验/floodFill/云.bmp");
Mat mask;
mask.create(((srcImage).rows + 2), ((srcImage).cols + 2), CV_8UC1);
mask = Scalar::all(0);
Mat ImageROI;
ImageROI = mask(Rect(1, 1, (srcImage).cols, (srcImage).rows));
Mat dstImage;
Mat dstImage_canny;
srcImage.copyTo(dstImage_canny);
cvtColor(dstImage_canny, dstImage_canny, CV_RGB2GRAY);
medianBlur(dstImage_canny, dstImage_canny, 7);
Canny(dstImage_canny, dstImage, 3, 3 * 3, 3);
dstImage.copyTo(ImageROI);
Rect ccomp;
//选择了三个种子点,分别赋予了三种填充颜色。
//第一次调用floodFill时,未添加canny边缘检测后的掩模,所以云的边缘被腐蚀掉了一部分。结果就是部分云消失了。
//后两次调用floodFill时,添加了canny边缘检测后的掩模,云的边缘得到了很好的保留,但是部分颜色变化较大的区域,canny后,检验出了边缘,边缘的原来颜色得到了保留
floodFill(srcImage, Point(223, 184), CV_RGB(88,123,165), &ccomp, Scalar(35, 35, 35), Scalar(30, 30, 30), 8 | FLOODFILL_FIXED_RANGE );
floodFill(srcImage, mask,  Point(48, 507), CV_RGB(108,148,184), &ccomp, Scalar(25, 25, 25), Scalar(15, 15, 15), 8 | FLOODFILL_FIXED_RANGE );
floodFill(srcImage, mask,  Point(609, 582), CV_RGB(137,173,197), &ccomp, Scalar(25, 25, 25), Scalar(15, 15, 15), 8 | FLOODFILL_FIXED_RANGE );
imwrite("M:/图像处理实验/floodFill/云_dst.bmp", srcImage);

下面为原图与漫水填充后的输出结果:

 
canny边缘检测后的输出,也是floodFill函数中的掩模。黑色区域可以被填充,白色部分保留原来的颜色。


EmguCV中的函数原型如下:

Public Shared Function FloodFill(src As Emgu.CV.IInputOutputArray, mask As Emgu.CV.IInputOutputArray, seedPoint As System.Drawing.Point, newVal As Emgu.CV.Structure.MCvScalar, ByRef rect As System.Drawing.Rectangle, loDiff As Emgu.CV.Structure.MCvScalar, upDiff As Emgu.CV.Structure.MCvScalar, Optional connectivity As Emgu.CV.CvEnum.Connectivity = FourConnected, Optional flags As Emgu.CV.CvEnum.FloodFillType = Default) As Integer
  • 第一个参数,Emgu.CV.IInputOutputArray类型的image。
  • 第二个参数, Emgu.CV.IInputOutputArray类型的mask,表示操作掩模,。它应该为单通道、8位、长和宽上都比输入图像 image 大两个像素点的图像。与OpenCV不同,emguCV貌似没有第二个版本的FloodFill函数,emgu提供的FloodFill与OpenCV的第二个函数作用相似。漫水填充算法不会填充掩膜mask的非零像素区域。例如,一个边缘检测算子的输出可以用来作为掩膜,以防止填充到边缘。同样的,也可以在多次的函数调用中使用同一个掩膜,以保证填充的区域不会重叠。另外需要注意的是,掩膜mask会比需填充的图像大2个像素,所以 mask 中与输入图像(x,y)像素点相对应的点的坐标为(x+1,y+1)。
  • 第三个参数,Point类型的seedPoint,漫水填充算法的起始点。
  • 第四个参数,Emgu.CV.Structure.MCvScalar类型的newVal,像素点被染色的值,即在重绘区域像素的新值。
  • 第五个参数,Rect*类型的rect,用于设置floodFill函数将要重绘区域的最小边界矩形区域。与OpenCV不同,没有默认值。
  • 第六个参数,Emgu.CV.Structure.MCvScalar类型的loDiff,表示当前观察像素值与其部件邻域像素值或者待加入该部件的种子像素之间的亮度或颜色之负差(lower brightness/color difference)的最大值。 
  • 第七个参数,Emgu.CV.Structure.MCvScalar类型的upDiff,表示当前观察像素值与其部件邻域像素值或者待加入该部件的种子像素之间的亮度或颜色之正差(lower brightness/color difference)的最大值。
  • 第八个参数,Emgu.CV.CvEnum.Connectivity类型的connectivity。控制算法的连通性,可取4 (4为缺省值) 或者 8。如果设为4,表示填充算法只考虑当前像素水平方向和垂直方向的相邻点;如果设为 8,除上述相邻点外,还会包含对角线方向的相邻点。
  • 第九个参数,Emgu.CV.CvEnum.FloodFillType类型的flags。可以为0(0为缺省值) 或者以下两种选项标识符的组合:
                        FLOODFILL_FIXED_RANGE - 如果设置为这个标识符的话,就会考虑当前像素与种子像素之间的差,否则就考虑当前像素与其相邻像素的差。也就是说,这个范围是浮动的。
                        FLOODFILL_MASK_ONLY - 如果设置为这个标识符的话,函数不会去填充改变原始图像 (也就是忽略第三个参数newVal), 而是去填充掩模图像(mask)。
eg。
Dim bkGrayWhite As New Gray(255)
Dim bkGrayBlack As New Gray(0)
Dim img As Image(Of Bgr, Byte) = New Image(Of Bgr, Byte)("M:\图像处理实验\FloodFill\云1.bmp")
Dim img_MedianBlur As Image(Of Bgr, Byte) = New Image(Of Bgr, Byte)(img.Width, img.Height)
img.CopyTo(img_MedianBlur)
CvInvoke.MedianBlur(img, img_MedianBlur, 7)
Dim mask As Image(Of Gray, Byte) = New Image(Of Gray, Byte)(img.Width + 2, img.Height + 2, bkGrayBlack)
'BGR
CvInvoke.FloodFill(img,
                   mask,
                   New System.Drawing.Point(2, 2),
                   New MCvScalar(165, 123, 88),
                   New System.Drawing.Rectangle(0, 0, 0, 0),
                   New MCvScalar(5, 5, 5),
                   New MCvScalar(5, 5, 5),
                   Emgu.CV.CvEnum.Connectivity.EightConnected,
                   Emgu.CV.CvEnum.FloodFillType.FixedRange
                   )
CvInvoke.cvSetImageROI(mask, New System.Drawing.Rectangle(1, 1, img.Width, img.Height))
Dim img_canny As Image(Of Gray, Byte) = New Image(Of Gray, Byte)(img.Width, img.Height, bkGrayBlack)
CvInvoke.Canny(img_MedianBlur, img_canny, 5, 5 * 3)
img_canny.CopyTo(mask)
CvInvoke.cvResetImageROI(mask)
CvInvoke.FloodFill(img,
                   mask,
                   New System.Drawing.Point(668, 570),
                   New MCvScalar(197, 173, 137),
                   New System.Drawing.Rectangle(0, 0, 0, 0),
                   New MCvScalar(25, 25, 25),
                   New MCvScalar(5, 5, 5),
                   Emgu.CV.CvEnum.Connectivity.EightConnected,
                   Emgu.CV.CvEnum.FloodFillType.FixedRange
                   )
img.Save("M:\图像处理实验\FloodFill\云1_result.bmp")


本文中的OpenCv与EmguCV均用的是3.0以上的版本。
参考文献:
Bradski & Kaebler ·《学习OpenCV(中文版)》· 清华大学出版社 · 2009

2014-06-03 15:39:21 zhmxy555 阅读数 99025
  • 1.14.ARM裸机第十四部分-LCD显示器

    本期课程主要讲解LCD。首先讲了LCD显示原理、颜色原理、LCD显示器的主要性能参数、概念等,然后进入了LCD编程实战部分,用6节课从零开始带大家编写程序在LCD上显示像素、背景、线条、文字、图片等内容。本课程的学习目标是掌握LCD显示相关的概念和编程方法,对显存的作用和填充有本质的了解,对图像显示有一定的认识。

    8429 人正在学习 去看看 朱有鹏



本系列文章由@浅墨_毛星云 出品,转载请注明出处。  

文章链接: http://blog.csdn.net/poem_qianmo/article/details/28261997

作者:毛星云(浅墨)    微博:http://weibo.com/u/1723155442

知乎:http://www.zhihu.com/people/mao-xing-yun

邮箱: happylifemxy@163.com

写作当前博文时配套使用的OpenCV版本: 2.4.9



本篇文章中,我们一起探讨了OpenCV填充算法中漫水填充算法相关的知识点,以及了解了OpenCV中实现漫水填充算法的两个版本的floodFill函数的使用方法。此篇博文一共有两个个配套的示例程序,其详细注释过的代码都在文中贴出,且文章最后提供了综合示例程序的下载。

 

先尝鲜一下最终示例程序的运行截图吧,这个示例程序实现了类似于PhotoShop中魔棒的图像处理效果,我们可以用鼠标对窗口中的图片一顿狂点,得到一片一片的彩色区域:

   

                                           效果图                                                                                              原始





一、引言 · 漫水填充的定义



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

另外,floodfill官方译作“漫水填充”,但是浅墨总是喜欢说成“水漫填充”,因为受从小到大深入骨髓的“水漫金山”这个成语的影响,囧。

 


 

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



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

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

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

 

 


三、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)



接着,来看一个关于Floodfill的简单的调用范例:

 

//-----------------------------------【头文件包含部分】---------------------------------------  
//      描述:包含程序所依赖的头文件  
//----------------------------------------------------------------------------------------------   
#include <opencv2/opencv.hpp>  
#include <opencv2/imgproc/imgproc.hpp>  

//-----------------------------------【命名空间声明部分】---------------------------------------  
//      描述:包含程序所使用的命名空间  
//-----------------------------------------------------------------------------------------------   
using namespace cv;  
//-----------------------------------【main( )函数】--------------------------------------------  
//      描述:控制台应用程序的入口函数,我们的程序从这里开始  
//----------------------------------------------------------------------------------------------- 

int main( )
{    
	Mat src = imread("1.jpg"); 
	imshow("【原始图】",src);
	Rect ccomp;
	floodFill(src, Point(50,300), Scalar(155, 255,55), &ccomp, Scalar(20, 20, 20),Scalar(20, 20, 20));
	imshow("【效果图】",src);
	waitKey(0);
	return 0;    
}  

 运行截图,原始图:



效果图:


 

 

 

五、floodFill函数在OpenCV中的实现源代码



这个部分贴出OpenCV中本文相关函数的源码实现细节,来给想了解实现细节的小伙伴们参考。浅墨暂时不在源码的细节上挖深作详细注释。

 


5.1 OpenCV2.X中两个版本的floodFill函数源码

第一个,不带mask版本的floodFill:

int cv::floodFill( InputOutputArray _image,Point seedPoint,
                   Scalar newVal, Rect* rect,
                   Scalar loDiff, ScalarupDiff, int flags )
{
   CvConnectedComp ccomp;
   CvMat c_image = _image.getMat();
   cvFloodFill(&c_image, seedPoint, newVal, loDiff, upDiff, &ccomp,flags, 0);
   if( rect )
       *rect = ccomp.rect;
   return cvRound(ccomp.area);
}

 

第二个,带mask版本的floodFill:

int cv::floodFill( InputOutputArray _image,InputOutputArray _mask,
                   Point seedPoint, ScalarnewVal, Rect* rect,
                   Scalar loDiff, ScalarupDiff, int flags )
{
   CvConnectedComp ccomp;
   CvMat c_image = _image.getMat(), c_mask = _mask.getMat();
    cvFloodFill(&c_image, seedPoint, newVal,loDiff, upDiff, &ccomp, flags, c_mask.data.ptr ? &c_mask : 0);
   if( rect )
       *rect = ccomp.rect;
   return cvRound(ccomp.area);
}


我们依然可以发现,其内部实现是基于OpenCV 1.X旧版的cvFloodFill函数,我们再来看看其旧版函数的源码。



5.2 OpenCV2.X中cvFloodFill()函数的实现源码

 

CV_IMPL void
cvFloodFill( CvArr* arr, CvPointseed_point,
            CvScalar newVal, CvScalar lo_diff, CvScalar up_diff,
            CvConnectedComp* comp, int flags, CvArr* maskarr )
{
   cv::Ptr<CvMat> tempMask;
   std::vector<CvFFillSegment> buffer;
 
   if( comp )
       memset( comp, 0, sizeof(*comp) );
 
   int i, type, depth, cn, is_simple;
   int buffer_size, connectivity = flags & 255;
   union {
       uchar b[4];
       int i[4];
       float f[4];
       double _[4];
    }nv_buf;
   nv_buf._[0] = nv_buf._[1] = nv_buf._[2] = nv_buf._[3] = 0;
 
   struct { cv::Vec3b b; cv::Vec3i i; cv::Vec3f f; } ld_buf, ud_buf;
   CvMat stub, *img = cvGetMat(arr, &stub);
   CvMat maskstub, *mask = (CvMat*)maskarr;
   CvSize size;
 
   type = CV_MAT_TYPE( img->type );
   depth = CV_MAT_DEPTH(type);
   cn = CV_MAT_CN(type);
 
   if( connectivity == 0 )
       connectivity = 4;
   else if( connectivity != 4 && connectivity != 8 )
       CV_Error( CV_StsBadFlag, "Connectivity must be 4, 0(=4) or 8");
 
   is_simple = mask == 0 && (flags & CV_FLOODFILL_MASK_ONLY) ==0;
 
   for( i = 0; i < cn; i++ )
    {
       if( lo_diff.val[i] < 0 || up_diff.val[i] < 0 )
           CV_Error( CV_StsBadArg, "lo_diff and up_diff must benon-negative" );
       is_simple &= fabs(lo_diff.val[i]) < DBL_EPSILON &&fabs(up_diff.val[i]) < DBL_EPSILON;
    }
 
   size = cvGetMatSize( img );
 
   if( (unsigned)seed_point.x >= (unsigned)size.width ||
        (unsigned)seed_point.y >=(unsigned)size.height )
       CV_Error( CV_StsOutOfRange, "Seed point is outside of image");
 
   cvScalarToRawData( &newVal, &nv_buf, type, 0 );
   buffer_size = MAX( size.width, size.height ) * 2;
   buffer.resize( buffer_size );
 
   if( is_simple )
    {
       int elem_size = CV_ELEM_SIZE(type);
       const uchar* seed_ptr = img->data.ptr + img->step*seed_point.y +elem_size*seed_point.x;
 
       for(i = 0; i < elem_size; i++)
           if (seed_ptr[i] != nv_buf.b[i])
                break;
 
       if (i != elem_size)
       {
           if( type == CV_8UC1 )
               icvFloodFill_CnIR(img->data.ptr, img->step, size, seed_point,nv_buf.b[0],
                                  comp, flags, &buffer);
           else if( type == CV_8UC3 )
               icvFloodFill_CnIR(img->data.ptr, img->step, size, seed_point,cv::Vec3b(nv_buf.b),
                                  comp, flags,&buffer);
           else if( type == CV_32SC1 )
               icvFloodFill_CnIR(img->data.ptr, img->step, size, seed_point,nv_buf.i[0],
                                  comp, flags,&buffer);
           else if( type == CV_32FC1 )
               icvFloodFill_CnIR(img->data.ptr, img->step, size, seed_point,nv_buf.f[0],
                                  comp, flags,&buffer);
           else if( type == CV_32SC3 )
               icvFloodFill_CnIR(img->data.ptr, img->step, size, seed_point,cv::Vec3i(nv_buf.i),
                                  comp, flags, &buffer);
           else if( type == CV_32FC3 )
               icvFloodFill_CnIR(img->data.ptr, img->step, size, seed_point,cv::Vec3f(nv_buf.f),
                                  comp, flags,&buffer);
           else
                CV_Error(CV_StsUnsupportedFormat, "" );
           return;
       }
    }
 
   if( !mask )
    {
       /* created mask will be 8-byte aligned */
       tempMask = cvCreateMat( size.height + 2, (size.width + 9) & -8,CV_8UC1 );
       mask = tempMask;
    }
   else
    {
       mask = cvGetMat( mask, &maskstub );
       if( !CV_IS_MASK_ARR( mask ))
           CV_Error( CV_StsBadMask, "" );
 
       if( mask->width != size.width + 2 || mask->height != size.height +2 )
           CV_Error( CV_StsUnmatchedSizes, "mask must be 2 pixel wider "
                                   "and 2pixel taller than filled image" );
    }
 
   int width = tempMask ? mask->step : size.width + 2;
   uchar* mask_row = mask->data.ptr + mask->step;
    memset(mask_row - mask->step, 1, width );
 
   for( i = 1; i <= size.height; i++, mask_row += mask->step )
    {
       if( tempMask )
           memset( mask_row, 0, width );
       mask_row[0] = mask_row[size.width+1] = (uchar)1;
    }
   memset( mask_row, 1, width );
 
   if( depth == CV_8U )
       for( i = 0; i < cn; i++ )
       {
           int t = cvFloor(lo_diff.val[i]);
           ld_buf.b[i] = CV_CAST_8U(t);
           t = cvFloor(up_diff.val[i]);
           ud_buf.b[i] = CV_CAST_8U(t);
       }
   else if( depth == CV_32S )
       for( i = 0; i < cn; i++ )
       {
           int t = cvFloor(lo_diff.val[i]);
           ld_buf.i[i] = t;
           t = cvFloor(up_diff.val[i]);
           ud_buf.i[i] = t;
       }
    else if( depth == CV_32F )
       for( i = 0; i < cn; i++ )
       {
           ld_buf.f[i] = (float)lo_diff.val[i];
           ud_buf.f[i] = (float)up_diff.val[i];
       }
   else
       CV_Error( CV_StsUnsupportedFormat, "" );
 
   if( type == CV_8UC1 )
       icvFloodFillGrad_CnIR<uchar, int, Diff8uC1>(
                              img->data.ptr,img->step, mask->data.ptr, mask->step,
                              size, seed_point,nv_buf.b[0],
                              Diff8uC1(ld_buf.b[0],ud_buf.b[0]),
                              comp, flags,&buffer);
   else if( type == CV_8UC3 )
       icvFloodFillGrad_CnIR<cv::Vec3b, cv::Vec3i, Diff8uC3>(
                              img->data.ptr,img->step, mask->data.ptr, mask->step,
                              size, seed_point,cv::Vec3b(nv_buf.b),
                             Diff8uC3(ld_buf.b, ud_buf.b),
                              comp, flags,&buffer);
   else if( type == CV_32SC1 )
       icvFloodFillGrad_CnIR<int, int, Diff32sC1>(
                              img->data.ptr,img->step, mask->data.ptr, mask->step,
                              size, seed_point,nv_buf.i[0],
                             Diff32sC1(ld_buf.i[0], ud_buf.i[0]),
                              comp, flags, &buffer);
   else if( type == CV_32SC3 )
       icvFloodFillGrad_CnIR<cv::Vec3i, cv::Vec3i, Diff32sC3>(
                              img->data.ptr,img->step, mask->data.ptr, mask->step,
                              size, seed_point,cv::Vec3i(nv_buf.i),
                             Diff32sC3(ld_buf.i, ud_buf.i),
                              comp, flags,&buffer);
   else if( type == CV_32FC1 )
       icvFloodFillGrad_CnIR<float, float, Diff32fC1>(
                              img->data.ptr,img->step, mask->data.ptr, mask->step,
                              size, seed_point,nv_buf.f[0],
                             Diff32fC1(ld_buf.f[0], ud_buf.f[0]),
                              comp, flags,&buffer);
   else if( type == CV_32FC3 )
       icvFloodFillGrad_CnIR<cv::Vec3f, cv::Vec3f, Diff32fC3>(
                              img->data.ptr,img->step, mask->data.ptr, mask->step,
                              size, seed_point,cv::Vec3f(nv_buf.f),
                             Diff32fC3(ld_buf.f,ud_buf.f),
                              comp, flags,&buffer);
   else
       CV_Error(CV_StsUnsupportedFormat, "");
}




四、关于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。

 

 

 

五、综合示例部分

 

本次的综合示例为OpenCV文档中自带的一个程序。浅墨将其做了适当的修改并详细注释,放出来供大家消化理解。

操作说明如下:


 可以看到,此程序着不少的按键功能。而我们拿着鼠标对窗口中的图形一顿狂点,就可以得到类似PhotoShop中魔棒的效果,当然,就这短短的两百来行代码写出来的东西,体验是比不上PS的魔棒工具的。

废话不多说,程序详细注释的源码如下:

//-----------------------------------【程序说明】----------------------------------------------
//		程序名称::《【OpenCV入门教程之十五】水漫金山:OpenCV漫水填充算法(Floodfill)》 博文配套源码 
//		开发所用IDE版本:Visual Studio 2010
//   		开发所用OpenCV版本:	2.4.9
//		2014年6月3日 Created by 浅墨
//		浅墨的微博:@浅墨_毛星云 http://weibo.com/1723155442/profile?topnav=1&wvr=5&user=1
//		浅墨的知乎:http://www.zhihu.com/people/mao-xing-yun
//		浅墨的豆瓣:http://www.douban.com/people/53426472/
//----------------------------------------------------------------------------------------------

//-----------------------------------【头文件包含部分】---------------------------------------  
//      描述:包含程序所依赖的头文件  
//----------------------------------------------------------------------------------------------
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>

//-----------------------------------【命名空间声明部分】---------------------------------------  
//      描述:包含程序所使用的命名空间  
//-----------------------------------------------------------------------------------------------   
using namespace cv;
using namespace std;


//-----------------------------------【全局变量声明部分】--------------------------------------  
//      描述:全局变量声明  
//-----------------------------------------------------------------------------------------------  
Mat g_srcImage, g_dstImage, g_grayImage, g_maskImage;//定义原始图、目标图、灰度图、掩模图
int g_nFillMode = 1;//漫水填充的模式
int g_nLowDifference = 20, g_nUpDifference = 20;//负差最大值、正差最大值
int g_nConnectivity = 4;//表示floodFill函数标识符低八位的连通值
int g_bIsColor = true;//是否为彩色图的标识符布尔值
bool g_bUseMask = false;//是否显示掩膜窗口的布尔值
int g_nNewMaskVal = 255;//新的重新绘制的像素值


//-----------------------------------【ShowHelpText( )函数】----------------------------------  
//      描述:输出一些帮助信息  
//----------------------------------------------------------------------------------------------  
static void ShowHelpText()  
{  
	//输出一些帮助信息  
	printf("\n\n\n\t欢迎来到漫水填充示例程序~\n\n");  
	printf( "\n\n\t按键操作说明: \n\n"  
		"\t\t鼠标点击图中区域- 进行漫水填充操作\n"  
		"\t\t键盘按键【ESC】- 退出程序\n"  
		"\t\t键盘按键【1】-  切换彩色图/灰度图模式\n"  
		"\t\t键盘按键【2】- 显示/隐藏掩膜窗口\n"  
		"\t\t键盘按键【3】- 恢复原始图像\n"  
		"\t\t键盘按键【4】- 使用空范围的漫水填充\n"  
		"\t\t键盘按键【5】- 使用渐变、固定范围的漫水填充\n"  
		"\t\t键盘按键【6】- 使用渐变、浮动范围的漫水填充\n"  
		"\t\t键盘按键【7】- 操作标志符的低八位使用4位的连接模式\n"  
		"\t\t键盘按键【8】- 操作标志符的低八位使用8位的连接模式\n"  
		"\n\n\t\t\t\t\t\t\t\t by浅墨\n\n\n"  
		);  
}  


//-----------------------------------【onMouse( )函数】--------------------------------------  
//      描述:鼠标消息onMouse回调函数
//---------------------------------------------------------------------------------------------
static void onMouse( int event, int x, int y, int, void* )
{
	// 若鼠标左键没有按下,便返回
	if( event != CV_EVENT_LBUTTONDOWN )
		return;

	//-------------------【<1>调用floodFill函数之前的参数准备部分】---------------
	Point seed = Point(x,y);
	int LowDifference = g_nFillMode == 0 ? 0 : g_nLowDifference;//空范围的漫水填充,此值设为0,否则设为全局的g_nLowDifference
	int UpDifference = g_nFillMode == 0 ? 0 : g_nUpDifference;//空范围的漫水填充,此值设为0,否则设为全局的g_nUpDifference
	int flags = g_nConnectivity + (g_nNewMaskVal << 8) +
		(g_nFillMode == 1 ? CV_FLOODFILL_FIXED_RANGE : 0);//标识符的0~7位为g_nConnectivity,8~15位为g_nNewMaskVal左移8位的值,16~23位为CV_FLOODFILL_FIXED_RANGE或者0。

	//随机生成bgr值
	int b = (unsigned)theRNG() & 255;//随机返回一个0~255之间的值
	int g = (unsigned)theRNG() & 255;//随机返回一个0~255之间的值
	int r = (unsigned)theRNG() & 255;//随机返回一个0~255之间的值
	Rect ccomp;//定义重绘区域的最小边界矩形区域

	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)

	Mat dst = g_bIsColor ? g_dstImage : g_grayImage;//目标图的赋值
	int area;

	//--------------------【<2>正式调用floodFill函数】-----------------------------
	if( g_bUseMask )
	{
		threshold(g_maskImage, g_maskImage, 1, 128, CV_THRESH_BINARY);
		area = floodFill(dst, g_maskImage, seed, newVal, &ccomp, Scalar(LowDifference, LowDifference, LowDifference),
			Scalar(UpDifference, UpDifference, UpDifference), flags);
		imshow( "mask", g_maskImage );
	}
	else
	{
		area = floodFill(dst, seed, newVal, &ccomp, Scalar(LowDifference, LowDifference, LowDifference),
			Scalar(UpDifference, UpDifference, UpDifference), flags);
	}

	imshow("效果图", dst);
	cout << area << " 个像素被重绘\n";
}


//-----------------------------------【main( )函数】--------------------------------------------  
//      描述:控制台应用程序的入口函数,我们的程序从这里开始  
//-----------------------------------------------------------------------------------------------  
int main( int argc, char** argv )
{
	//改变console字体颜色  
	system("color 2F");    

	//载入原图
	g_srcImage = imread("1.jpg", 1);

    if( !g_srcImage.data ) { printf("Oh,no,读取图片image0错误~! \n"); return false; }  

	//显示帮助文字
	ShowHelpText();

	g_srcImage.copyTo(g_dstImage);//拷贝源图到目标图
	cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);//转换三通道的image0到灰度图
	g_maskImage.create(g_srcImage.rows+2, g_srcImage.cols+2, CV_8UC1);//利用image0的尺寸来初始化掩膜mask

	namedWindow( "效果图",CV_WINDOW_AUTOSIZE );

	//创建Trackbar
	createTrackbar( "负差最大值", "效果图", &g_nLowDifference, 255, 0 );
	createTrackbar( "正差最大值" ,"效果图", &g_nUpDifference, 255, 0 );

	//鼠标回调函数
	setMouseCallback( "效果图", onMouse, 0 );

	//循环轮询按键
	while(1)
	{
		//先显示效果图
		imshow("效果图", g_bIsColor ? g_dstImage : g_grayImage);

		//获取键盘按键
		int c = waitKey(0);
		//判断ESC是否按下,若按下便退出
		if( (c & 255) == 27 )
		{
			cout << "程序退出...........\n";
			break;
		}

		//根据按键的不同,进行各种操作
		switch( (char)c )
		{
		//如果键盘“1”被按下,效果图在在灰度图,彩色图之间互换
		case '1':
			if( g_bIsColor )//若原来为彩色,转为灰度图,并且将掩膜mask所有元素设置为0
			{
				cout << "键盘“1”被按下,切换彩色/灰度模式,当前操作为将【彩色模式】切换为【灰度模式】\n";
				cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);
				g_maskImage = Scalar::all(0);	//将mask所有元素设置为0
				g_bIsColor = false;	//将标识符置为false,表示当前图像不为彩色,而是灰度
			}
			else//若原来为灰度图,便将原来的彩图image0再次拷贝给image,并且将掩膜mask所有元素设置为0
			{
				cout << "键盘“1”被按下,切换彩色/灰度模式,当前操作为将【彩色模式】切换为【灰度模式】\n";
				g_srcImage.copyTo(g_dstImage);
				g_maskImage = Scalar::all(0);
				g_bIsColor = true;//将标识符置为true,表示当前图像模式为彩色
			}
			break;
		//如果键盘按键“2”被按下,显示/隐藏掩膜窗口
		case '2':
			if( g_bUseMask )
			{
				destroyWindow( "mask" );
				g_bUseMask = false;
			}
			else
			{
				namedWindow( "mask", 0 );
				g_maskImage = Scalar::all(0);
				imshow("mask", g_maskImage);
				g_bUseMask = true;
			}
			break;
		//如果键盘按键“3”被按下,恢复原始图像
		case '3':
			cout << "按键“3”被按下,恢复原始图像\n";
			g_srcImage.copyTo(g_dstImage);
			cvtColor(g_dstImage, g_grayImage, COLOR_BGR2GRAY);
			g_maskImage = Scalar::all(0);
			break;
		//如果键盘按键“4”被按下,使用空范围的漫水填充
		case '4':
			cout << "按键“4”被按下,使用空范围的漫水填充\n";
			g_nFillMode = 0;
			break;
		//如果键盘按键“5”被按下,使用渐变、固定范围的漫水填充
		case '5':
			cout << "按键“5”被按下,使用渐变、固定范围的漫水填充\n";
			g_nFillMode = 1;
			break;
		//如果键盘按键“6”被按下,使用渐变、浮动范围的漫水填充
		case '6':
			cout << "按键“6”被按下,使用渐变、浮动范围的漫水填充\n";
			g_nFillMode = 2;
			break;
		//如果键盘按键“7”被按下,操作标志符的低八位使用4位的连接模式
		case '7':
			cout << "按键“7”被按下,操作标志符的低八位使用4位的连接模式\n";
			g_nConnectivity = 4;
			break;
		//如果键盘按键“8”被按下,操作标志符的低八位使用8位的连接模式
		case '8':
			cout << "按键“8”被按下,操作标志符的低八位使用8位的连接模式\n";
			g_nConnectivity = 8;
			break;
		}
	}

	return 0;
}




一些运行截图,首先是运行后的原始图:


点鼠标啊点鼠标:


滑滚动条啊滑滚动条:



地球已经阻止不了我们的鼠标了,点出来的图,已经有点恐怖。。。。。看妹子的手。。。。


 如果鼠标点到妹子脸上的话。。。。呃,更多惊悚的图,浅墨不放出了,免得。。。。。

随着我们鼠标的点击,程序会记下我们的操作:



接着看一张灰度图模式的漫水填充效果和掩码图:


再来一张彩色窗户:


程序功能还是很多的,有鼠标操作,键盘8个按键的操作,还可以调滚动条:



好了,更多的功能我们就不在这里示范了,大家下载了程序自己回去玩吧。


本篇文章的配套源代码请点击这里下载:

 

【浅墨OpenCV入门教程之十五】配套源代码下载



OK,今天的内容大概就是这些,我们下篇文章见:)



2020-01-27 11:45:21 weixin_43645790 阅读数 160
  • 1.14.ARM裸机第十四部分-LCD显示器

    本期课程主要讲解LCD。首先讲了LCD显示原理、颜色原理、LCD显示器的主要性能参数、概念等,然后进入了LCD编程实战部分,用6节课从零开始带大家编写程序在LCD上显示像素、背景、线条、文字、图片等内容。本课程的学习目标是掌握LCD显示相关的概念和编程方法,对显存的作用和填充有本质的了解,对图像显示有一定的认识。

    8429 人正在学习 去看看 朱有鹏

漫水填充的定义

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

漫水填充的基本思想

自动选中和种子点相连的区域,接着将该区域替换成指定的颜色,经常利用这个功能来标记或者分离图像一部分进行处理分析。

在OpenCV中,漫水填充是填充算法中最通用的方法,且有两个floodFill函数版本:一个不带掩膜mask的版本,和一个带mask的版本。
此掩膜mask

floodFill函数实现漫水填充算法

#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

using namespace std;
using namespace cv;

int main()
{
	Mat src = imread("a.jpg");
	imshow("【原始图】", src);
	Rect ccomp;
	floodFill(src, Point(50, 300), Scalar(155, 255, 55), &ccomp, Scalar(20, 20, 20), Scalar(20, 20, 20));
	imshow("【效果图】", src);
	waitKey(0);
	return 0;
}

运行效果如图:
在这里插入图片描述

2019-10-31 00:35:41 deliberate_cha 阅读数 23
  • 1.14.ARM裸机第十四部分-LCD显示器

    本期课程主要讲解LCD。首先讲了LCD显示原理、颜色原理、LCD显示器的主要性能参数、概念等,然后进入了LCD编程实战部分,用6节课从零开始带大家编写程序在LCD上显示像素、背景、线条、文字、图片等内容。本课程的学习目标是掌握LCD显示相关的概念和编程方法,对显存的作用和填充有本质的了解,对图像显示有一定的认识。

    8429 人正在学习 去看看 朱有鹏

漫水填充的定义及基本思想

相信大家都玩过计算机中画图工具里的油漆桶,鼠标一点,一片相同颜色的区域就会被油漆桶中颜色覆盖,这便是漫水填充实现的。漫水填充的定义是用特定的颜色填充联通区域,通过设置可以连通像素正负差的上下限,或者改变连通的方式,以达到不同的填充效果的方法。漫水填充经常被用来标记或者分离图像的一部分,以便于对下一部分进行处理或分析,也可以用来输入图像获取掩码区域,掩码会加速处理过程,或只处理掩码指定的像素点,操作的结果总是某个连续的区域。
通俗来讲,漫水填充就是输入种子点,自动选择与种子点相连通的区域,接着将该区域替换成指定的颜色或者灰度,这是一个非常有用的功能。

floodFill()函数

在OpenCV的imgproc组件中,提供了2种漫水填充函数floodFill(),其API接口及定义如下:

第一种无mask版本:

int floodFill( InputOutputArray image,
                          Point seedPoint, Scalar newVal, CV_OUT Rect* rect=0,
                          Scalar loDiff=Scalar(), Scalar upDiff=Scalar(),
                          int flags=4 );
  • 第一个参数:输入与输出的Mat类变量image,表示输入要进行漫水填充的图像,并且图像漫水填充后再以该变量输出。
  • 第二个参数:Point类的seedPoint,表示种子点,即以该点为中心开始计算连通区域。简单来说,就是油漆桶所点的位置(像素点)
  • 第三个参数:Scalar类的newVal,表示通过种子点确认的目标连通区域将要填充的颜色值或者灰度值。
  • 第四个参数:Rect*类型的rect,有默认值0,一个可选的参数,用于设置floodFill函数将要重绘区域的最小边界矩形区域。
  • 第五个参数:Scalar类型的loDiff,有默认值Scalar(),表示当前观察像素值与其部件邻域像素值或者待加入该部件的种子像素之间的亮度或颜色之负差的最大值。
  • 第六个参数:Scalar类型的upDiff,有默认值Scalar(),表示当前观察像素值与其部件邻域像素值或者待加入该部件的种子像素之间的亮度或颜色之正差的最大值。
  • 第七个参数:nt类型的flags,操作表示符,此参数包含三个部分,比较复杂

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

高八位(16-23位)可以为0或者如下两种选项标识符的结合

  1. FLOODFILL_FIXED_RANGE:如果设置这个标识符,就会考虑当前像素与种子像素之间的差,否则就考虑当前像素与其相邻像素的差。也就是说,这个范围是浮动的。
  2. FLOODFILL_MASK_ONLY,如果设置这个标志符,函数不会去填充改变原始图像(也就是忽略第三个参数newVal),而是去填充掩膜图像(mask)。这个标识符支队第二个版本的floodFill有用,因为第一版的参数内没有mask。

中间8位,上面关于高八位FLOODFILL_MASK_ONLY标识符中已经说了,选择输入符合要求的掩码。Floodfill的flags参数的中间八位的值就是用于指定填充图像的值的。但如果flags中间8位是0,则掩码会用1来填充。
而所有flags可以用or操作符连接起来,即‘ | ’。例如,如果想用8邻域填充,并填充固定像素值范围,填充掩码而不是填充原图像,以及设置填充值为38,那么输入的参数如下:

flags = 8+(38<<8)+ FLOODFILL_MASK_ONLY;

第二种有mask的版本:

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

除了第二个参数代表掩膜区域,其他参数定义都与第一个版本相同。
关于mask参数,有如下说明:
mask应该位单通道,8位,长宽都比输入图像image大两个像素点的图像。需要注意的是,漫水填充不会填充mask的非零像素区域。例如,一个边缘检测算子的输出可以用来作为掩膜,以防止填充到边缘。同样的,也可以在多此的函数调用中使用同一个掩膜,以保证填充的区域不会重叠。还有需要注意的是,掩膜mask会比需填充的图像大,所以mask中与输入图像(x,y)像素点对应的点坐标为(x+1,y+1).
下面举例说明最后标志位flags的用法:
第一个版本的没有mask参数的floodFill()函数
设置flags = 4+(255<<8)+ FLOODFILL_FIXED_RANGE.
意思是:

  • 填充算法只考虑当前像素水平方向和垂直方向的相邻点;
  • 掩码区的填充数据为1111_1111(对于第一个版本的floodFill()并没有实质性的的用途,因为没有mask区域)
  • 考虑当前像素与种子像素之间的差,确认需要填充的范围。(通俗来讲,填充范围是固定的)
    源码以及效果图如下:
#include<opencv2\imgproc\imgproc.hpp>
#include<opencv2\highgui\highgui.hpp>
#include<core\core.hpp>
using namespace cv;
using namespace std;

void main()
{
	Mat image = imread("1.jpg");
	imshow("原图", image);
	
	//参数准备
	Rect ccomp;
	int a = 255;
	int flags = 8 + (a << 8) + FLOODFILL_FIXED_RANGE;

	floodFill(image,Point(50, 300), Scalar(155 * 0.299, 255 * 0.587, 55 * 0.114), &ccomp, Scalar(30, 30, 30), Scalar(30, 30, 30), flags);
	
	imshow("效果图", image);
	
	waitKey(0);
}

在这里插入图片描述
可以看到鼬神的鼻子被绿了。哈哈啊。
而当设置flags = 4+(255<<8)+ 0;
如图:在这里插入图片描述
整个图像几乎都绿了,,可见动态范围的漫水填充比固定范围的漫水填充的范围会大很多。
第二版的floodFill()函数,由于存在mask参数,所以flags的设置比较复杂。
源码如下:

#include<opencv2\imgproc\imgproc.hpp>
#include<opencv2\highgui\highgui.hpp>
#include<core\core.hpp>
using namespace cv;
using namespace std;

void main()
{
	Mat image = imread("1.jpg");
	Mat mask;
	imshow("原图", image);
	//参数准备
	mask.create(image.rows + 2, image.cols + 2, CV_8UC1);
	mask = Scalar::all(0);
	Rect ccomp;
	int a = 255;
	int flags = 8 + (a << 8) + FLOODFILL_FIXED_RANGE;


	floodFill(image,mask, Point(50, 300), Scalar(155 * 0.299, 255 * 0.587, 55 * 0.114), &ccomp, Scalar(10, 10, 10), Scalar(10, 10, 10), flags);
	
	imshow("效果图", image);
	imshow("mask", mask);
	waitKey(0);
}

flags = 8 + (255 << 8) + FLOODFILL_FIXED_RANGE; 时,意思是:

  1. 除上下左右相邻的点外,判断填充时,还需考虑两个对角线的像素点。
  2. 对与mask区域(灰度图)填充时,填充的颜色为纯白色(255)。
  3. 动态填充,填充时会将源图像以及mask图像一起填充。
    效果如下:在这里插入图片描述
    鼬神的鼻梁变绿了,右侧的mask图记录了鼬神鼻梁的形状。前面定义时说过,mask区域需要准备,漫水填充不会填充非零像素区域,所以在准备时,将mask全部置为0,即黑色区,所以mask就记录了鼬神鼻子的形状了。

flags = 8 + (255 << 8) + FLOODFILL_MASK_ONLY;
函数不会去填充源图像,只会去填充mask图像。效果图如下:
在这里插入图片描述
很明显,第二章图片没有被填充,而mask图像有点小帅。
flags = 8 + (a << 8) + 0;
图像为固定范围的满水填充,效果图如下:
在这里插入图片描述
和上种效果相同,但是由于没有设置FLOODFILL_MASK_ONLY,所以鼬神的脸被绿了。
第四种情况,我们讨论一下中间8位参数的值。
当 int flags = 8 + (150 << 8) + 0;
效果图如下:
在这里插入图片描述
很明显,鼬神的脸颜色没有变,而记录的mask中填充的颜色编程了灰色,所以flags的中间8位,是用来填充mask图像的灰度值。
以上便是flags参数的基本用法,感谢鼬神友情客串,晚安。

没有更多推荐了,返回首页