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

9.7floodFill图像分割

基本概念

Flood Fill(洪水填充)是一种常用的图像处理技术,用于填充一个连通区域的颜色或者灰度值。在OpenCV中,可以使用floodFill函数来实现这一功能。Flood Fill算法常用于图像分割,尤其是在用户交互式标记或自动分割任务中。这个函数常用于图像分割任务,例如填充选定区域、对象分割等。floodFill 函数的工作原理类似于绘画软件中的“油漆桶工具”,它会从给定的起始点开始,查找具有相似颜色的所有相邻像素,并将它们的颜色替换为新颜色。

Flood Fill 原理

Flood Fill算法从一个指定的种子点开始,遍历所有与种子点颜色相近的连通像素,并将这些像素的值修改为目标颜色或灰度值。其主要步骤如下:

1. 选择一个种子点:确定图像中的一个初始点作为填充的起点。

2. 确定目标颜色:定义要填充的新颜色或灰度值。

3. 遍历连通区域:从种子点开始,递归地访问所有与其颜色相近且尚未填充的邻近像素,并将它们的颜色改为新颜色。

OpenCV中的Flood Fill

在OpenCV中,可以使用floodFill函数来实现Flood Fill算法。该函数有多个重载版本,最常用的是以下形式:

floodFill 函数的基本原型


int floodFill(
    InputOutputArray img,
    Point seedPoint,
    Scalar newVal,
    Rect* rect = 0,
    Scalar loDiff = Scalar(),
    Scalar upDiff = Scalar(),
    int connectivity = 4,
    int flags = 0,
    UMat* mask = 0
);
参数说明
•img:输入/输出图像,图像会被修改。要进行填充的图像。
•seedPoint:指定的种子点坐标。填充的起始点坐标。
•newVal:新的颜色值或灰度值。新的颜色值,通常是一个标量或向量,取决于图像的通道数。
•rect:可选参数,指定要填充的矩形区域,如果指定了这个参数,则仅在这个区域内进行填充。
•loDiff:指定颜色差异的下限阈值,用来判断哪些像素应该被填充。
•upDiff:指定颜色差异的上限阈值,用来判断哪些像素应该被填充。
  loDiff 和 upDiff:允许的颜色差异范围,用于确定哪些像素应该被填充。这两个参数可以分别指定每个通道的最大差异。
•connectivity:像素间连通性的定义,通常为4(仅考虑上下左右四个方向)或8(考虑上下左右及对角线八个方向)。
•flags:可以设置一些标志位来改变填充行为。 
   •floodfillFlags = 4 | (255 << 8):使用4连通性,并指定新值为255。
   •floodfillFixedRange = 1 << 16:当设置了这个标志时,loDiff 和 upDiff 只会在当前像素值与种子点的颜色比较时使用,而不是在相邻像素之间。
•mask:掩模图像,可以用来限制填充的范围。

示例代码1

下面是一个使用OpenCV C++实现Flood Fill的示例代码:

#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

// 回调函数用于鼠标事件
static void on_mouse( int event, int x, int y, int, void* )
{
    Mat &img = *static_cast<Mat*>(void*)(&image);
    
    if( event == EVENT_LBUTTONDOWN )
    {
        // 指定种子点
        Point seed_point(x, y);
        
        // 新的颜色值
        Scalar new_val = Scalar(255, 0, 0); // 蓝色
        
        // 颜色差异的上下限
        Scalar lo_diff = Scalar(10, 10, 10);
        Scalar up_diff = lo_diff;
        
        // 进行Flood Fill
        floodFill(img, seed_point, new_val, NULL, lo_diff, up_diff, 4, 1 << 16);
        
        imshow("Flood Fill", img);
    }
}

int main(int argc, char** argv)
{
    // 加载图像
    Mat image = imread("path_to_your_image.jpg");
    if (image.empty())
    {
        cout << "Error loading image" << endl;
        return -1;
    }

    // 创建窗口并注册回调函数
    namedWindow("Flood Fill", WINDOW_AUTOSIZE);
    setMouseCallback("Flood Fill", on_mouse, &image);

    // 显示图像
    imshow("Flood Fill", image);
    
    while (true)
    {
        if (waitKey(0) >= 0) break;
    }
    
    return 0;
}

代码解释
1. 加载图像:使用 imread 函数加载图像。
2. 创建窗口:使用 namedWindow 创建一个名为"Flood Fill"的窗口。
3. 注册回调函数:使用 setMouseCallback 注册一个鼠标事件回调函数。
4. 鼠标事件处理:在回调函数中处理鼠标点击事件,当左键点击时,获取点击点坐标,并执行Flood Fill算法。
5. 更新显示:每次填充后更新图像显示。

注意事项
•颜色空间:确保图像的颜色空间适合Flood Fill操作。对于彩色图像,通常使用BGR格式。
•颜色差异:lo_diff 和 up_diff 参数用于控制填充的敏感度,较大的差异值会导致更大范围的填充。
•连通性:connectivity 参数决定了像素间的连通性,4表示四连通(仅水平和垂直邻居),8表示八连通(包括对角线邻居)。

应用场景
•图像分割:用户可以通过点击图像上的不同区域来进行交互式分割。
•背景替换:在某些情况下,Flood Fill可以用来快速替换图像中的背景。
•缺陷检测:在工业视觉应用中,Flood Fill可用于检测图像中的缺陷或异常区域。

运行结果1

示例代码2

下面是一个使用 C++ 实现 floodFill 的简单示例:




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


int main(int argc, char** argv)
{
	// 加载图像
	cv::Mat image = cv::imread("256.png");
	if (image.empty())
	{
		std::cout << "Error: Image cannot be loaded!" << std::endl;
		return -1;
	}

	// 创建一个复制图像用于显示填充结果
	cv::Mat fillImage = image.clone();

	// 定义种子点
	cv::Point seedPoint(50, 50); // 你可以根据实际图像选择合适的坐标

	// 新的颜色值
	cv::Scalar newVal(255, 0, 0); // BGR格式,这里设置为蓝色

	// 颜色差异范围
	cv::Scalar loDiff(30, 30, 30); // 允许的最大颜色差异
	cv::Scalar upDiff(20, 20, 20);

	// 执行 floodFill 操作
	//int numPixelsFilled = cv::floodFill(fillImage, seedPoint, newVal, nullptr, &loDiff, &upDiff);
	int numPixelsFilled = cv::floodFill(fillImage, seedPoint, newVal, 0, loDiff, upDiff);

	// 显示原始图像和填充后的图像
	cv::namedWindow("Original Image", cv::WINDOW_NORMAL);
	cv::imshow("Original Image", image);
	cv::namedWindow("Filled Image", cv::WINDOW_NORMAL);
	cv::imshow("Filled Image", fillImage);

	// 输出填充了多少像素
	std::cout << "Number of pixels filled: " << numPixelsFilled << std::endl;

	// 等待按键并退出
	cv::waitKey(0);
	system("pause");
	return 0;
}

参数解释
seedPoint:这是填充的起点。
newVal:这是要应用到区域的新颜色。
loDiff 和 upDiff:定义了颜色的差异范围。如果一个像素的颜色值与种子点的颜色值之差在指定的范围内,则该像素将被填充。
floodFill 的模式
floodFill 支持两种模式:固定范围模式(fixed range mode)和基于连通性模式(connectivity-based mode)。默认情况下,floodFill 使用固定范围模式,这意味着它会查找与种子点颜色相差不超过 loDiff 和 upDiff 的像素。如果你希望基于连通性来填充,可以通过传递一个掩码(mask)参数来启用连通性模式。掩码可以帮助跟踪已经访问过的像素,避免重复填充,并且可以使操作更加高效。

注意事项
floodFill 对于颜色差异的判断是基于像素值的绝对差值,而不是感知色彩差异,因此有时可能需要手动调整 loDiff 和 upDiff 的值以获得满意的结果。
如果图像很大或者连通区域很广,floodFill 可能会变得非常耗时。在这种情况下,考虑优化算法或减少图像尺寸。
在使用 floodFill 时,确保图像的边界条件处理得当,防止溢出等问题。

运行结果2

实验代码3

#include "pch.h"
//#pragma comment(lib, "opencv_world450d.lib")  
#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("03.jpeg");
	namedWindow("【原始图】", WINDOW_NORMAL);
	imshow("【原始图】", src);
	Rect ccomp;
	floodFill(src, Point(50, 300), Scalar(155, 255, 55), &ccomp, Scalar(20, 20, 20), Scalar(20, 20, 20));
	namedWindow("【效果图】", WINDOW_NORMAL);
	imshow("【效果图】", src);
	waitKey(0);
	return 0;
}

运行结果3

9.8代码强化

强化代码4

#include "pch.h"
//#pragma comment(lib, "opencv_world450d.lib")   
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <opencv2/imgproc/types_c.h>
#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或者0
	int 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("效果图", dst);
	cout << area << " 个像素被重新绘制\n";
}

//main()函数
int main(int argc, char** argv) {
	//载入原图
	g_srcImage = imread("test.jpg", 1);
	if (!g_srcImage.data) {
		printf("读取g_srcImage错误!\n");
		return false;
	}

	g_srcImage.copyTo(g_dstImage);//复制原图到目标图
	cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);//转为灰度图到g_grayImage

	g_maskImage.create(g_srcImage.rows + 2, g_srcImage.cols + 2, CV_8UC1);//用原图尺寸初始化掩膜mask

	namedWindow("效果图", 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 == 27) {
			cout << "程序退出........、\n";
			break;
		}

		//根据按键不同进行不同的操作
		switch ((char)c) {
			//如果键盘1被按下,效果图在灰度图和彩色图之间转换
		case '1':
			if (g_bIsColor) {//若原来为彩色图,转换为灰度图,并将掩膜mask所有元素设置为0
				cout << "键盘‘1’按下,切换彩色/灰度模式,当前操作将【彩色模式】切换为【灰度模式】" << endl;
				cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);
				g_maskImage = Scalar::all(0);//将mask所有元素设置为0
				g_bIsColor = false;
			}
			else {
				cout << "键盘‘1’按下,切换彩色/灰度模式,当前操作将【灰度模式】切换为【彩色模式】" << endl;
				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被按下,恢复原始图像
			cout << "按下键盘‘3’,恢复原始图像\n";
			g_srcImage.copyTo(g_dstImage);
			cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);
			g_maskImage = Scalar::all(0);
			break;
		case '4':
			cout << "键盘‘4’被按下,使用空范围的漫水填充\n";
			g_nFillMode = 0;
			break;
		case '5':
			cout << "键盘‘5’被按下,使用渐变、固定范围的漫水填充\n";
			g_nFillMode = 1;
			break;
		case '6':
			cout << "键盘‘6’被按下,使用渐变、浮动范围的漫水填充\n";
			g_nFillMode = 2;
			break;
		case '7':
			cout << "键盘‘7’被按下,操作标识符的低八位使用4位的连接模式\n";
			g_nConnectivity = 4;
			break;
		case '8':
			cout << "键盘‘8’被按下,操作标识符的低八位使用8为的连接模式\n";
			g_nConnectivity = 8;
			break;
		}

	}

	return 0;

}

运行结果4


http://www.kler.cn/a/313676.html

相关文章:

  • 【Qt】Macbook M1下载安装
  • Linux手动安装nginx
  • 2-UML概念模型测试
  • 运行springBlade项目历程
  • 一文了解珈和科技在农业遥感领域的服务内容和能力
  • C++笔记---异常
  • 98-策略模式的理解
  • 蓝桥杯—STM32G431RBT6(ADC数模转换,从原理到应用)
  • C++第十一节课 new和delete
  • [Python可视化]数据可视化在医疗领域应用:提高诊断准确性和治疗效果
  • AI视觉算法盒是什么?如何智能化升级网络摄像机,守护全方位安全
  • 机器学习--AlexNet
  • 系统架构设计师:软件架构的演化和维护
  • 【AI视频】Runway:Gen-2 运镜详解
  • 马踏棋盘c++
  • Ubuntu上使用qt和opencv显示图像
  • MySQL —— 事务
  • RabbitMQ Spring客户端使用
  • 多版本node管理工具nvm
  • C#_封装详解
  • 人话学Python-循环语句
  • 数据结构(Day14)
  • 掌握C#中的动态规划技术
  • 监控易监测对象及指标之:全面监控DB2_linux数据库
  • Scikit-learn (`sklearn`) 教程
  • 二级C语言2024-3易错题