当前位置: 首页 > news >正文

OpenCV的floodFill(漫水填充)分割

OpenCV计算机视觉开发实践:基于Qt C++ - 商品搜索 - 京东

9.8.1  基本概念

Flood Fill(漫水填充)算法是一种在许多图形绘制软件中常用的填充算法。通常情况下,该算法会自动选中与种子像素相连的区域,并利用指定颜色对该区域进行填充。这个算法常用于标记或分离图像的特定部分,以便进行进一步的分析和处理。Windows画图工具中的油漆桶功能和Photoshop的魔术棒选择工具,都是Flood Fill算法的改进和延伸。

漫水填充算法的原理很简单,就是从一个点开始遍历附近的像素点,并填充成新的颜色,直到封闭区域内所有像素点都被填充成新颜色为止。floodFill的实现方法常见的有4邻域像素填充法、8邻域像素填充法、基于扫描线的像素填充方法等。

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

9.8.2  floodFill函数

在OpenCV中,漫水填充算法由floodFill函数实现,其作用是用指定的颜色从种子点开始填充一个连接域,连通性由像素值的接近程度来衡量。OpenCV中使用C++重写的两个floodFill函数的声明如下:

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

这两个函数除了第二个参数外,其他的参数都是共用的。其中参数image是一个输入/输出参数,表示一通道或三通道、8位或浮点图像,具体取值由之后的参数指明。参数mask也是输入/输出参数,是第二个版本的floodFill独享的,表示操作掩膜,它应该为单通道、8位、长和宽上都比输入图像image大两个像素点的图像。第二个版本的floodFill需要使用以及更新掩膜,所以这个mask参数我们一定要准备好并填在此处。需要注意的是,漫水填充不会填充mask的非零像素区域。例如,一个边缘检测算子的输出可以用来作为掩膜,以防止填充到边缘。同样地,也可以在多次的函数调用中使用同一个掩膜,以保证填充的区域不会重叠。

另外需要注意的是,mask会比需填充的图像大,所以mask中与输入图像(x,y)像素点相对应的点的坐标为(x+1,y+1)。参数seedPoint表示漫水填充算法的起始点。参数Scalar类型的newVal表示像素点被染色的值,即像素在重绘区域的新值。参数rect的有默认值为0,一个可选的参数,用于设置floodFill函数将要重绘区域的最小边界矩形区域;参数loDiff的默认值为Scalar(),表示当前观察像素值与其部件邻域像素值或者待加入该部件的种子像素之间的亮度或颜色之负差(lower brightness/color difference)的最大值。参数upDiff的默认值为Scalar(),表示当前观察像素值与其部件邻域像素值或者待加入该部件的种子像素之间的亮度或颜色之正差(lower brightness/color difference)的最大值。参数flags表示操作标志符,此参数包含3个部分:

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

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

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

(3)中间八位部分(8~15位)用于指定填充掩码图像的值。如果flags中间八位的值为0,则掩码会用1来填充。而所有flags可以用or操作符(即“|”)连接起来。例如,如果想用8邻域填充,并填充固定像素值范围,即填充掩码而不是填充原图像,以及设填充值为38,那么输入的参数是这样的:

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

9.8.3  floodFill函数示例

下面来看一个关于floodfill的简单的调用范例。

【例9.7】利用floodfill进行图像分割

   新建一个控制台工程,工程名是test。

   在vc中打开main.cpp,并输入如下代码:

#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <opencv2/imgproc/types_c.h>
#include <iostream>
using namespace std;
using namespace cv;
int main()
{Mat src = imread("mysky.jpg");imshow("src", src);Rect ccomp;floodFill(src, Point(50, 300), Scalar(155, 255, 55), &ccomp, Scalar(20, 20, 20), Scalar(20, 20, 20));imshow("res", src);waitKey(0);return 0;
}

代码很简单,主要就是floodFill的调用,读者可以根据实际参数对照floodFill的原型调用方法。

   保存工程并运行,结果如图9-17所示。

图9-17

下面再来看一个综合例子,它可以根据用户鼠标的单击和滑杆调节来实现不同区域的图像分割。

【例9.8】功能更强大的floodFill分割

   新建一个控制台工程,工程名是test。

   在vc中打开main.cpp,并输入如下代码:

#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <opencv2/imgproc/types_c.h>
#include <QDebug>
#include <iostream>
using namespace std;
using namespace cv;
// 全局变量声明部分
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函数标识符低八位的连通值
bool g_bIsColor = true;		// 是否为彩色图的标识符布尔值
bool g_bUseMask = false;	// 是否显示掩膜窗口的布尔值
int g_nNewMaskVal = 255;	// 新的重新绘制的像素值
// ===============【onMouse()函数】=======================
static void onMouse(int event, int x, int y, int, void *) {// 若鼠标左键没有按下,便返回if (event != EVENT_LBUTTONDOWN)return;// -----------------【<1>调用floodFill函数之前的参数准备部分】-------------Point seed = Point(x, y);int LowDifference = g_nFillMode == 0 ? 0 : g_nLowDifference;int UpDifference = g_nFillMode == 0 ? 0 : g_nUpDifference;// 标识符的0~7位为g_nConnectivity,8~15位为g_nNewMaskVal左移8位的值,16~23位为CV_FLOODFILL_FIXED_RANGE或者0int flags = g_nConnectivity + (g_nNewMaskVal << 8) + (g_nFillMode == 1 ? FLOODFILL_FIXED_RANGE : 0);// 随机生成BGR值int b = (unsigned)theRNG() & 255;// 随机返回一个0~255的值int g = (unsigned)theRNG() & 255;int r = (unsigned)theRNG() & 255;Rect ccomp;// 定义重绘区域的最小边界矩阵区域Scalar newVal = g_bIsColor ? 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, 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("res", dst);qDebug()<< area << " 个像素被重新绘制\n";
}// main()函数
int main(int argc, char** argv) {// 载入原图g_srcImage = imread("study.jpg", 1);if (!g_srcImage.data) {printf("Fail to open file.!\n");return false;}g_srcImage.copyTo(g_dstImage);// 复制原图到目标图cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);// 转为灰度图到g_grayImageg_maskImage.create(g_srcImage.rows + 2, g_srcImage.cols + 2, CV_8UC1);// 用原图尺寸初始化masknamedWindow("res", WINDOW_AUTOSIZE);// 创建TrackbarcreateTrackbar("负差最大值", "res", &g_nLowDifference, 255, 0);createTrackbar("正差最大值", "res", &g_nUpDifference, 255, 0);// 鼠标回调函数setMouseCallback("res", onMouse, 0);// 循环轮询按键while (1) {// 先显示效果图imshow("res", g_bIsColor ? g_dstImage : g_grayImage);// 获取按键键盘int c = waitKey(0);// 判断ESC是否被按下,若按下则退出if (c == 27) {cout << "game over.\n";break;}// 根据不同按键进行不同操作switch ((char)c) {// 如果键盘1被按下,效果图在灰度图和彩色图之间转换case '1':if (g_bIsColor) {// 若原来为彩色图,则转换为灰度图,并将mask所有元素设置为0qDebug() << "键盘‘1’被按下,切换彩色/灰度模式,当前操作将【彩色模式】切换为【灰度模式】";cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);g_maskImage = Scalar::all(0);// 将mask的所有元素设置为0g_bIsColor = false;}else {qDebug()<< "键盘‘1’被按下,切换彩色/灰度模式,当前操作将【灰度模式】切换为【彩色模式】";g_srcImage.copyTo(g_dstImage);g_maskImage = Scalar::all(0);g_bIsColor = true;}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;case '3':// 如果键盘3被按下,恢复原始图像qDebug()<< "按下键盘‘3’,恢复原始图像\n";g_srcImage.copyTo(g_dstImage);cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);g_maskImage = Scalar::all(0);break;case '4':qDebug()<< "键盘‘4’被按下,使用空范围的漫水填充\n";g_nFillMode = 0;break;case '5':qDebug() << "键盘‘5’被按下,使用渐变、固定范围的漫水填充\n";g_nFillMode = 1;break;case '6':qDebug() << "键盘‘6’被按下,使用渐变、浮动范围的漫水填充\n";g_nFillMode = 2;break;case '7':qDebug() << "键盘‘7’被按下,操作标识符的低八位使用4位的连接模式\n";g_nConnectivity = 4;break;case '8':qDebug() << "键盘‘8’被按下,操作标识符的低八位使用8位的连接模式\n";g_nConnectivity = 8;break;}}return 0;
}

这个例子本质上也是floodFill的调用,但输入根据用户而定。

   保存工程并运行,通过鼠标滚轮,可以放大或缩小图像,按住鼠标左键可以移动图像,运行结果如图9-18所示。

​​​​​​​图9-18

http://www.xdnf.cn/news/309421.html

相关文章:

  • 静态NAT
  • C++23 新利器:深入解析栈踪迹库 (P0881R7)
  • HTTP协议网络读卡器通讯报文
  • 无法解析导入“pybulletgym”
  • C# System.Text.Json实现高效JSON序列化与反序列化
  • 基于Java多线程实现简单图片下载
  • SLAM算法工程师面经大全:2025年面试真题解析与实战指南
  • 美信监控易:全栈式自主可控的底层架构优势
  • 使用 Poco C++ 库构建轻量级 HTTP 服务器
  • LeetCode 1128. 等价多米诺骨牌对的数量 题解
  • 【了解】通感算一体化网络
  • 深入理解 Web 架构:从基础到实践
  • 【大模型面试每日一题】Day 10:混合精度训练如何加速大模型训练?可能出现什么问题?如何解决?
  • 数据库复习
  • 面试常问系列(一)-神经网络参数初始化-之自注意力机制为什么除以根号d而不是2*根号d或者3*根号d
  • 最新版Google浏览器加载ActiveX控件之VLC五一节特别版
  • Marin说PCB之1000-BASE-T1的PCB设计总结--04
  • DeepSeek:突破AI搜索的无限可能
  • ProteinTools辅助探索蛋白稳定性、动态调控以及结构关系
  • 优化04-选择率和直方图
  • 意识场方程与道函数赋能的三智双融体系建构
  • Xcode16提交App Store审核时提示bitcode报错
  • vue3 computed方法传参数
  • Java泛型深度解析与电商场景应用
  • 手写Promise.all
  • USB接口的PCB设计
  • vue3在使用@import “./index.scss“报错
  • 互联网法院在NFT、元宇宙等新兴领域的规则创新
  • 江苏正力新能Verify认知能力测评笔试已通知 | SHL测评题库预测题 | 华东同舟求职讲求职
  • 园区网的发展