图像处理之泛洪填充算法(Flood Fill Algorithm)
泛洪填充算法(Flood Fill Algorithm)
泛洪填充算法又称洪水填充算法是在很多图形绘制软件中常用的填充算法,最熟悉不过就是
windows paint的油漆桶功能。算法的原理很简单,就是从一个点开始附近像素点,填充成新
的颜色,直到封闭区域内的所有像素点都被填充新颜色为止。泛红填充实现最常见有四邻域
像素填充法,八邻域像素填充法,基于扫描线的像素填充方法。根据实现又可以分为递归与
非递归(基于栈)。
在介绍算法的三种实现方式之前,首先来看一下测试该算法的UI实现。基本思路是选择一
张要填充的图片,鼠标点击待填充的区域内部,算法会自动填充该区域,然后UI刷新。完
整的UI代码如下:
package com.gloomyfish.paint.fill; import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.MediaTracker;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException; import javax.imageio.ImageIO;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JFrame; public class FloodFillUI extends JComponent implements MouseListener{ /**
*
*/
private static final long serialVersionUID = 1L;
private BufferedImage rawImg;
private MediaTracker tracker;
private Dimension mySize;
FloodFillAlgorithm ffa;
public FloodFillUI(File f)
{
try {
rawImg = ImageIO.read(f);
} catch (IOException e1) {
e1.printStackTrace();
} tracker = new MediaTracker(this);
tracker.addImage(rawImg, 1); // blocked 10 seconds to load the image data
try {
if (!tracker.waitForID(1, 10000)) {
System.out.println("Load error.");
System.exit(1);
}// end if
} catch (InterruptedException e) {
e.printStackTrace();
System.exit(1);
}// end catch mySize = new Dimension(300, 300);
this.addMouseListener(this);
ffa = new FloodFillAlgorithm(rawImg);
JFrame imageFrame = new JFrame("Flood File Algorithm Demo - Gloomyfish");
imageFrame.getContentPane().add(this);
imageFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
imageFrame.pack();
imageFrame.setVisible(true);
} public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.drawImage(rawImg, 10, 10, rawImg.getWidth(), rawImg.getHeight(), null);
}
public Dimension getPreferredSize() {
return mySize;
} public Dimension getMinimumSize() {
return mySize;
} public Dimension getMaximumSize() {
return mySize;
} public static void main(String[] args) {
JFileChooser chooser = new JFileChooser();
chooser.showOpenDialog(null);
File f = chooser.getSelectedFile();
new FloodFillUI(f);
} @Override
public void mouseClicked(MouseEvent e) {
System.out.println("Mouse Clicked Event!!");
int x = (int)e.getPoint().getX();
int y = (int)e.getPoint().getY();
System.out.println("mouse location x = " + x); // column
System.out.println("mouse location y = " + y); // row
System.out.println();
long startTime = System.nanoTime();
// ffa.floodFill4(x, y, Color.GREEN.getRGB(), ffa.getColor(x, y));
// ffa.floodFill8(x, y, Color.GREEN.getRGB(), ffa.getColor(x, y));
// ffa.floodFillScanLine(x, y, Color.GREEN.getRGB(), ffa.getColor(x, y)); //
ffa.floodFillScanLineWithStack(x, y, Color.GREEN.getRGB(), ffa.getColor(x, y)); // - 16660142
long endTime = System.nanoTime() - startTime;
System.out.println("run time = " + endTime);
ffa.updateResult();
this.repaint();
} @Override
public void mousePressed(MouseEvent e) {
// TODO Auto-generated method stub } @Override
public void mouseReleased(MouseEvent e) {
// TODO Auto-generated method stub } @Override
public void mouseEntered(MouseEvent e) {
// TODO Auto-generated method stub } @Override
public void mouseExited(MouseEvent e) {
// TODO Auto-generated method stub } }
首先介绍四邻域的泛洪填充算法,寻找像素点p(x, y)的上下左右四个临近像素点,如果没有
被填充,则填充它们,并且继续寻找它们的四邻域像素,直到封闭区域完全被新颜色填充。
蓝色方格为四个邻域像素, p(x, y)为当前像素点。
基于递归实现代码很简单:
public void floodFill4(int x, int y, int newColor, int oldColor)
{
if(x >= 0 && x < width && y >= 0 && y < height
&& getColor(x, y) == oldColor && getColor(x, y) != newColor)
{
setColor(x, y, newColor); //set color before starting recursion
floodFill4(x + 1, y, newColor, oldColor);
floodFill4(x - 1, y, newColor, oldColor);
floodFill4(x, y + 1, newColor, oldColor);
floodFill4(x, y - 1, newColor, oldColor);
}
}
八邻域的填充算法,则是在四邻域的基础上增加了左上,左下,右上,右下四个相邻像素。
并递归寻找它们的八邻域像素填充,直到区域完全被新颜色填充。
蓝色方格为四个邻域像素,黄色为左上,左下,右上,右下四个像素, p(x, y)为当前像素点。
基于递归实现的代码也很简单:
public void floodFill8(int x, int y, int newColor, int oldColor)
{
if(x >= 0 && x < width && y >= 0 && y < height &&
getColor(x, y) == oldColor && getColor(x, y) != newColor)
{
setColor(x, y, newColor); //set color before starting recursion
floodFill8(x + 1, y, newColor, oldColor);
floodFill8(x - 1, y, newColor, oldColor);
floodFill8(x, y + 1, newColor, oldColor);
floodFill8(x, y - 1, newColor, oldColor);
floodFill8(x + 1, y + 1, newColor, oldColor);
floodFill8(x - 1, y - 1, newColor, oldColor);
floodFill8(x - 1, y + 1, newColor, oldColor);
floodFill8(x + 1, y - 1, newColor, oldColor);
}
}
基于扫描线实现的泛洪填充算法的主要思想是根据当前输入的点p(x, y),沿y方向分别向上
与向下扫描填充,同时向左p(x-1, y)与向右p(x+1, y)递归寻找新的扫描线,直到递归结束。
代码如下:
public void floodFillScanLine(int x, int y, int newColor, int oldColor)
{
if(oldColor == newColor) return;
if(getColor(x, y) != oldColor) return; int y1; //draw current scanline from start position to the top
y1 = y;
while(y1 < height && getColor(x, y1) == oldColor)
{
setColor(x, y1, newColor);
y1++;
} //draw current scanline from start position to the bottom
y1 = y - 1;
while(y1 >= 0 && getColor(x, y1) == oldColor)
{
setColor(x, y1, newColor);
y1--;
} //test for new scanlines to the left
y1 = y;
while(y1 < height && getColor(x, y1) == newColor)
{
if(x > 0 && getColor(x - 1, y1) == oldColor)
{
floodFillScanLine(x - 1, y1, newColor, oldColor);
}
y1++;
}
y1 = y - 1;
while(y1 >= 0 && getColor(x, y1) == newColor)
{
if(x > 0 && getColor(x - 1, y1) == oldColor)
{
floodFillScanLine(x - 1, y1, newColor, oldColor);
}
y1--;
} //test for new scanlines to the right
y1 = y;
while(y1 < height && getColor(x, y1) == newColor)
{
if(x < width - 1 && getColor(x + 1, y1) == oldColor)
{
floodFillScanLine(x + 1, y1, newColor, oldColor);
}
y1++;
}
y1 = y - 1;
while(y1 >= 0 && getColor(x, y1) == newColor)
{
if(x < width - 1 && getColor(x + 1, y1) == oldColor)
{
floodFillScanLine(x + 1, y1, newColor, oldColor);
}
y1--;
}
}
基于递归实现的泛洪填充算法有个致命的缺点,就是对于大的区域填充时可能导致Java栈溢出
错误,对最后一种基于扫描线的算法,实现了一种非递归的泛洪填充算法。
public void floodFillScanLineWithStack(int x, int y, int newColor, int oldColor)
{
if(oldColor == newColor) {
System.out.println("do nothing !!!, filled area!!");
return;
}
emptyStack(); int y1;
boolean spanLeft, spanRight;
push(x, y); while(true)
{
x = popx();
if(x == -1) return;
y = popy();
y1 = y;
while(y1 >= 0 && getColor(x, y1) == oldColor) y1--; // go to line top/bottom
y1++; // start from line starting point pixel
spanLeft = spanRight = false;
while(y1 < height && getColor(x, y1) == oldColor)
{
setColor(x, y1, newColor);
if(!spanLeft && x > 0 && getColor(x - 1, y1) == oldColor)// just keep left line once in the stack
{
push(x - 1, y1);
spanLeft = true;
}
else if(spanLeft && x > 0 && getColor(x - 1, y1) != oldColor)
{
spanLeft = false;
}
if(!spanRight && x < width - 1 && getColor(x + 1, y1) == oldColor) // just keep right line once in the stack
{
push(x + 1, y1);
spanRight = true;
}
else if(spanRight && x < width - 1 && getColor(x + 1, y1) != oldColor)
{
spanRight = false;
}
y1++;
}
} }
运行效果:
算法类源代码如下:
package com.gloomyfish.paint.fill; import java.awt.image.BufferedImage; import com.gloomyfish.filter.study.AbstractBufferedImageOp; public class FloodFillAlgorithm extends AbstractBufferedImageOp { private BufferedImage inputImage;
private int[] inPixels;
private int width;
private int height; // stack data structure
private int maxStackSize = 500; // will be increased as needed
private int[] xstack = new int[maxStackSize];
private int[] ystack = new int[maxStackSize];
private int stackSize; public FloodFillAlgorithm(BufferedImage rawImage) {
this.inputImage = rawImage;
width = rawImage.getWidth();
height = rawImage.getHeight();
inPixels = new int[width*height];
getRGB(rawImage, 0, 0, width, height, inPixels );
} public BufferedImage getInputImage() {
return inputImage;
} public void setInputImage(BufferedImage inputImage) {
this.inputImage = inputImage;
} public int getColor(int x, int y)
{
int index = y * width + x;
return inPixels[index];
} public void setColor(int x, int y, int newColor)
{
int index = y * width + x;
inPixels[index] = newColor;
} public void updateResult()
{
setRGB( inputImage, 0, 0, width, height, inPixels );
} /**
* it is very low calculation speed and cause the stack overflow issue when fill
* some big area and irregular shape. performance is very bad.
*
* @param x
* @param y
* @param newColor
* @param oldColor
*/
public void floodFill4(int x, int y, int newColor, int oldColor)
{
if(x >= 0 && x < width && y >= 0 && y < height
&& getColor(x, y) == oldColor && getColor(x, y) != newColor)
{
setColor(x, y, newColor); //set color before starting recursion
floodFill4(x + 1, y, newColor, oldColor);
floodFill4(x - 1, y, newColor, oldColor);
floodFill4(x, y + 1, newColor, oldColor);
floodFill4(x, y - 1, newColor, oldColor);
}
}
/**
*
* @param x
* @param y
* @param newColor
* @param oldColor
*/
public void floodFill8(int x, int y, int newColor, int oldColor)
{
if(x >= 0 && x < width && y >= 0 && y < height &&
getColor(x, y) == oldColor && getColor(x, y) != newColor)
{
setColor(x, y, newColor); //set color before starting recursion
floodFill8(x + 1, y, newColor, oldColor);
floodFill8(x - 1, y, newColor, oldColor);
floodFill8(x, y + 1, newColor, oldColor);
floodFill8(x, y - 1, newColor, oldColor);
floodFill8(x + 1, y + 1, newColor, oldColor);
floodFill8(x - 1, y - 1, newColor, oldColor);
floodFill8(x - 1, y + 1, newColor, oldColor);
floodFill8(x + 1, y - 1, newColor, oldColor);
}
} /**
*
* @param x
* @param y
* @param newColor
* @param oldColor
*/
public void floodFillScanLine(int x, int y, int newColor, int oldColor)
{
if(oldColor == newColor) return;
if(getColor(x, y) != oldColor) return; int y1; //draw current scanline from start position to the top
y1 = y;
while(y1 < height && getColor(x, y1) == oldColor)
{
setColor(x, y1, newColor);
y1++;
} //draw current scanline from start position to the bottom
y1 = y - 1;
while(y1 >= 0 && getColor(x, y1) == oldColor)
{
setColor(x, y1, newColor);
y1--;
} //test for new scanlines to the left
y1 = y;
while(y1 < height && getColor(x, y1) == newColor)
{
if(x > 0 && getColor(x - 1, y1) == oldColor)
{
floodFillScanLine(x - 1, y1, newColor, oldColor);
}
y1++;
}
y1 = y - 1;
while(y1 >= 0 && getColor(x, y1) == newColor)
{
if(x > 0 && getColor(x - 1, y1) == oldColor)
{
floodFillScanLine(x - 1, y1, newColor, oldColor);
}
y1--;
} //test for new scanlines to the right
y1 = y;
while(y1 < height && getColor(x, y1) == newColor)
{
if(x < width - 1 && getColor(x + 1, y1) == oldColor)
{
floodFillScanLine(x + 1, y1, newColor, oldColor);
}
y1++;
}
y1 = y - 1;
while(y1 >= 0 && getColor(x, y1) == newColor)
{
if(x < width - 1 && getColor(x + 1, y1) == oldColor)
{
floodFillScanLine(x + 1, y1, newColor, oldColor);
}
y1--;
}
} public void floodFillScanLineWithStack(int x, int y, int newColor, int oldColor)
{
if(oldColor == newColor) {
System.out.println("do nothing !!!, filled area!!");
return;
}
emptyStack(); int y1;
boolean spanLeft, spanRight;
push(x, y); while(true)
{
x = popx();
if(x == -1) return;
y = popy();
y1 = y;
while(y1 >= 0 && getColor(x, y1) == oldColor) y1--; // go to line top/bottom
y1++; // start from line starting point pixel
spanLeft = spanRight = false;
while(y1 < height && getColor(x, y1) == oldColor)
{
setColor(x, y1, newColor);
if(!spanLeft && x > 0 && getColor(x - 1, y1) == oldColor)// just keep left line once in the stack
{
push(x - 1, y1);
spanLeft = true;
}
else if(spanLeft && x > 0 && getColor(x - 1, y1) != oldColor)
{
spanLeft = false;
}
if(!spanRight && x < width - 1 && getColor(x + 1, y1) == oldColor) // just keep right line once in the stack
{
push(x + 1, y1);
spanRight = true;
}
else if(spanRight && x < width - 1 && getColor(x + 1, y1) != oldColor)
{
spanRight = false;
}
y1++;
}
} } private void emptyStack() {
while(popx() != - 1) {
popy();
}
stackSize = 0;
} final void push(int x, int y) {
stackSize++;
if (stackSize==maxStackSize) {
int[] newXStack = new int[maxStackSize*2];
int[] newYStack = new int[maxStackSize*2];
System.arraycopy(xstack, 0, newXStack, 0, maxStackSize);
System.arraycopy(ystack, 0, newYStack, 0, maxStackSize);
xstack = newXStack;
ystack = newYStack;
maxStackSize *= 2;
}
xstack[stackSize-1] = x;
ystack[stackSize-1] = y;
} final int popx() {
if (stackSize==0)
return -1;
else
return xstack[stackSize-1];
} final int popy() {
int value = ystack[stackSize-1];
stackSize--;
return value;
} @Override
public BufferedImage filter(BufferedImage src, BufferedImage dest) {
// TODO Auto-generated method stub
return null;
} }
http://blog.csdn.net/jia20003/article/details/8908464
图像处理之泛洪填充算法(Flood Fill Algorithm)的更多相关文章
- 图像处理------泛洪填充算法(Flood Fill Algorithm) 油漆桶功能
泛洪填充算法(Flood Fill Algorithm) 泛洪填充算法又称洪水填充算法是在很多图形绘制软件中常用的填充算法,最熟悉不过就是 windows paint的油漆桶功能.算法的原理很简单,就 ...
- OpenCV---ROI(region of interest)和泛洪填充
一:ROI 感兴趣区(Region of Interest,ROIs) 是图像的一部分,它通过在图像上选择或使用诸如设定阈值(thresholding) 或者从其他文件(如矢量> 转换获得等方法 ...
- 八 ROI(region of interest)和泛洪填充
一.ROI 感兴趣区(Region of Interest,ROIs) 是图像的一部分,它通过在图像上选择或使用诸如设定阈值(thresholding) 或者从其他文件(如矢量> 转换获得等方法 ...
- Python+OpenCV图像处理(六)—— ROI与泛洪填充
一.ROI ROI(region of interest),感兴趣区域.机器视觉.图像处理中,从被处理的图像以方框.圆.椭圆.不规则多边形等方式勾勒出需要处理的区域,称为感兴趣区域,ROI. 代码如下 ...
- 【Leetcode_easy】733. Flood Fill
problem 733. Flood Fill 题意:图像处理中的泛洪填充算法,常见的有四邻域像素填充法.八邻域像素填充法.基于扫描线的像素填充法,实现方法分为递归与非递归(基于栈). 泛洪填充算法原 ...
- CGA填充算法之种子填充算法
CGA填充算法之种子填充算法 平面区域填充算法是计算机图形学领域的一个很重要的算法,区域填充即给出一个区域的边界 (也可以是没有边界,只是给出指定颜色),要求将边界范围内的所有象素单元都修改成指定的颜 ...
- 和同事谈谈Flood Fill 算法
前言 今天忙完了公司的工作后,发现同事在做LeeCode的算法题,顿时来了兴趣,于是王子与同事一起探讨如何能做好算法题,今天在此文章中和大家分享一下. 什么是Flood Fill 算法 我们今天谈论的 ...
- 带你学习Flood Fill算法与最短路模型
一.Flood Fill(连通块问题) 0.简介 Flood Fill(洪水覆盖) 可以在线性的时间复杂内,找到某个点所在的连通块! 注:基于宽搜的思想,深搜也可以做但可能会爆栈 flood fill ...
- LeetCode算法题-Flood Fill(Java实现)
这是悦乐书的第306次更新,第325篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第173题(顺位题号是733).图像由二维整数数组表示,每个整数表示图像的像素值(从0到 ...
随机推荐
- CSS2系列:外边距合并问题(margincollapse)
外边距合并 w3介绍这个问题地址:https://www.w3.org/TR/CSS2/box.html#collapsing-margins 当两个垂直方向外边距相遇,它们将形成一个折叠外边距. 合 ...
- discuz二次开发之后导航无法高亮 $mnid == $nav[navid]解决办法(转)
在 <body>前面加入如下代码:body原来就有一个class,直接在增加一个进行测试 <!--{eval $mnid = getcurrentnav()}--> <! ...
- Javascript 笔记与总结(2-18)正则验证与正则匹配
① 判断 String 是否符合正则要求 patt.test(String); [例]表单提交: a.用户名不能为空,只能是数字及字母,6-11位 b.email 不能为空且格式正确 <!DOC ...
- Irrlicht引擎I 配置
游戏是一个比较大的系统,包含了图形引擎.网络.AI.声音.UI等模块,模块的开发可能会分别进行或者采用开源项目,Irrlicht引擎基本包含了这些模块,不过在使用中也会陆续加入其它的模块.以前开发的程 ...
- 数据库MySql阶段总结
S1数据库中最重要的是查询,对于查询要有一个好的理解模型是很关键的: 1. 每一个查询都会返回一个结果集,这个结果集可能是一个值,一个字段或者一个记录,甚至可能是一个表 返回一个值 SELECT * ...
- 让Storm插上CEP的翅膀 - Siddhi调研和集成
什么是 Siddhi? Siddhi 是一种 lightweight, easy-to-use, open source CEP(Complex Event Processing)引擎,由wso2公司 ...
- 【转】Unity 解析Json字符串
http://blog.csdn.net/qq_15267341/article/details/52013190 LitJSON使用很简单,两个步骤: 1 将LitJSON.dll文件拖动到unit ...
- Xlib 窗口属性
Xlib 窗口属性 转, 无法找到原作者 所有的 InputOutput 窗口都可以有零个或者多个像素的边框宽度,一个可选的背景,一个事件压制掩码(它压制来自孩子的事件传播),和一个 property ...
- 关于cocoa框架,你所要知道的一切(苹果官方文档,cocoa框架核心竞争力,必须收藏!)
https://developer.apple.com/library/ios/documentation/General/Conceptual/DevPedia-CocoaCore/Accessib ...
- sort,ksort,asort的区别
sort--对数组的val进行排序 ksort--对数组的key值进行排序 asort--对数组进行排序,键与值的对应关系不变 1.sort对数组排序 格式如下:bool sort(array &am ...