文章目录
前言
仅仅是记录与分享自己在学习过程中遇到的问题与解决办法,可能有一些错误的观点或理解等,如果有说错的地方麻烦请大家指正,谢谢。该代码仅仅是从rgb角度进行操作,没有使用opencv等第三方库。
一、分线的目的是什么?
在数字图像处理中,如何知道图片中有多少个物体呢?这个前提之一就是需要了解图片中物体边缘的线条数。知道有几个边缘的线条数,我们就知道有多少个物体啦。这就是我们做分线的目的,话不多说,往下看。
二、大致思路
就以黑色的线条为例吧,我们要做的事情如下:
1.既然我们要找到不同的线,那么我们不妨就把线当作一个类,为了后续的方便,我们给出以下属性和方法
class AloneLine
{
int id;
ArrayList<Integer> xpoints = new ArrayList<>();
ArrayList<Integer> ypoints = new ArrayList<>();
Color color;
int r,g,b;
public AloneLine(){}
public AloneLine(int ordinal,int height,int width)
{
id = ordinal;
Random random = new Random();
r = random.nextInt(255);
g = random.nextInt(255);
b = random.nextInt(255);
}
public void setPoints(int x,int y)
{
xpoints.add(x);
ypoints.add(y);
}
public void Coloring(BufferedImage bufferedImage,int x,int y)
{
color = new Color(r,g,b);
bufferedImage.setRGB(x,y,color.getRGB());
setPoints(x,y);
}
}
为什么会用到随机数呢,因为不想写属于颜色的管理(懒),但是实际上用id来区分,颜色来标记,除非是连续两条生成的线颜色都相同,即1/256*256*256的概率,应该是不太可能的吧。setPoints呢就是记录一条线段的坐标点,对于后续的合并操作和其它数字图像处理都有帮助。
2.横向扫描(当然也可以改成纵向的),当我们扫到一个黑色像素点时,即r,g,b为(0,0,0)的点,进行判断这个点是否是一条线段的起始点。我们引入变量times,来表示周围的白色像素点。
Color white = new Color(255,255,255);
Color black = new Color(0,0,0);
public void Traverse()
{
for(int y = 0;y < height;y++)
{
for(int x = 0;x < width;x++)
{
color = new Color(bufferedImage.getRGB(x,y));
if(color.getRGB() == black.getRGB())
{
Scan(x,y);
}
}
}
}
public void GetColorful(ArrayList<AloneLine> temp,int times,int x,int y)
{
if(times == 4)
{
al = new AloneLine(ordinal,height,width);
arraylist.add(al);
ordinal++;
arraylist.get(arraylist.size()-1).Coloring(bufferedImage,x,y);
al = null;
}
else if (times == 3)
temp.get(0).Coloring(bufferedImage,x,y);
else if(times <= 2)
{
temp.get(0).Coloring(bufferedImage,x,y);
if(temp.get(0) != temp.get(1))
Assimilation(temp);
}
}
部分代码如上,可以浅浅的看一下,暂时不需要看懂,通过times的不同,我们进行不同的染色操作。
3.染色
我们用染色来表示我们已经处理了该像素点。由于我们是横向扫描,那么对于一个点是否是一条线段的起始点,就很容易能想到,如果该点的上方三个点和左侧的一个点如果都是白色像素点,那么它一定是一条线段的起始点。那么我们用一个集合ArrayList<AloneLine> arraylist来记录它。
这就是GetColorful中的times = 4的情况
如果说,我们发现该像素点的周围四个点中有一个点是已经被染色过的点,即我们得到的times = 3,那么我们就把该点染色成为与邻点相同颜色的点。所以,在我们获取times大小的时候呢,申请一个集合temp来记录周围四个点的情况。如果出现了有颜色的点,那么就去和arraylist中的所有对象进行一一匹配,如果找到了,就返回该对象并记录到临时集合temp中。这样我们就可以拿出来使用啦。
public AloneLine Classification(Color color,int x,int y)
{
for(AloneLine line : arraylist)
{
if(color.getRGB() == line.color.getRGB())
{
return line;
}
}
return null;
}
当然,说完了简单的两种情况,我们来看下面几种情况,当该像素点的四个邻点中有两个被染色的情况,就得细细分析一下,这两个被染色的点是否是相邻的呢?
①.如果是相邻的,我们就可以相信,第二个邻点一定是被第一个邻点所提前染色过了,所以我们直接提出temp集合中的第一个对象进行染色即可。
else if(times <= 2)
{
temp.get(0).Coloring(bufferedImage,x,y);
if(temp.get(0) != temp.get(1))
Assimilation(temp);
}
②.如果这两个点不是相邻的呢?同样也有可能是颜色相同的两个点,因为可能是由同一个起点出发分叉后又交汇到该点处,所以依然需要提出temp集合中的第一个对象进行染色。而且就算这两个邻点颜色不同,我们也可以提前对该点进行染色,合并只需要将另一种颜色的所有点遍历并染色即可。
说完两个邻点的可能性,我们仔细想想,三邻点和四邻点是不是一定会有邻点本身也相邻?答案显而易见,这些邻点再该点被扫描前就被处理过了,所以此处合并times<=2的所有情况。
4.合并
前面简单的提到了合并的思路,我们现在具体说说看。
public void Assimilation(ArrayList<AloneLine> temp)
{
for(int i = 0;i < temp.get(1).xpoints.size();i++)
temp.get(0).Coloring(bufferedImage,temp.get(1).xpoints.get(i),temp.get(1).ypoints.get(i));
arraylist.remove(temp.get(1));
}
其实合并很简单,三邻点四邻点都会因为邻点本身也有相邻点,会被提前处理过,所以我们的temp集合其实最多只会记录下两种颜色的对象。那么我们只需要将其中一个对象的所有点进行处理即可啦。最多就是注意一下前后严格对应,不然一定会串颜色导致分线失败。
因为我们的类里面记录了线段的坐标,所以用for循环即可,用temp集合中的一号对象的颜色对二号对象的所有坐标点进行染色即可。在染色方法中,已经调用了记录坐标的方法,所以最后只要记得在arraylist中删去这个不再被需要的对象即可啦。(忘记arraylist作用是啥的拉上去再看看)
5.细节处理
最后就是一些细节了,我们要考虑到图像的边缘点嘛,毕竟边缘点也可能有点需要处理的。
public void Scan(int x,int y)
{
int times = 0;
ArrayList<AloneLine> temp = new ArrayList<>();
if(x == 0 && y == 0)
times = 4;
else if(x == 0 && y != 0)
{
times = 2;
for(int i = 0;i < 2;i++)
{
color = new Color(bufferedImage.getRGB(x+i,y-1));
if(color.getRGB() == white.getRGB())
times++;
else
temp.add(Classification(color,x+i,y-1));
}
}
else if(x != 0 && y == 0)
{
times = 3;
color = new Color(bufferedImage.getRGB(x-1,y));
if(color.getRGB() == white.getRGB())
times++;
else
temp.add(Classification(color,x-1,y));
}
else if(x == width-1 && y != 0)
{
times = 1;
for(int i = -1;i < 1;i++)
{
color = new Color(bufferedImage.getRGB(x+i,y-1));
if(color.getRGB() == white.getRGB())
times++;
else
temp.add(Classification(color,x+i,y-1));
}
color = new Color(bufferedImage.getRGB(x-1,y));
if(color.getRGB() == white.getRGB())
times++;
else
temp.add(Classification(color,x-1,y));
}
else
{
for(int i = -1;i < 2;i++)
{
color = new Color(bufferedImage.getRGB(x+i,y-1));
if(color.getRGB() == white.getRGB())
times++;
else
temp.add(Classification(color,x+i,y-1));
}
color = new Color(bufferedImage.getRGB(x-1,y));
if(color.getRGB() == white.getRGB())
times++;
else
temp.add(Classification(color,x-1,y));
}
GetColorful(temp,times,x,y);
}
所以最后一块方法拼图也亮出来了。简单来说只需要分为最左侧的一条线,开始的坐标原点,最上方的一条线和最右侧的一条线和其它剩余的所有点。
三、出现的失误提醒
1.数组还是集合
我建议还是用集合来记录对象和坐标,之前因为用数组记录了对象和坐标,因为没办法动态调整数组大小,所以每一个数组我都给开了很大的空间导致了JAVA HEAP SPACE的情况。当时苦恼了半天没查到哪里有问题,后来发现是数组给的空间太大了,改成集合后就解决啦,尝试过跑13000条线是没问题的。
2.关于递归
之前用过递归来进行染色,这样就避免了合并的过程,因为递归会从第一个点开始自己寻找周围八邻点主动染色。确实少了很多代码但是会因为线段过长而引发递归爆栈(学到了Java不适合用递归)
四、代码与总结
大概这些就是我写分线时候遇到的问题和思路。最后贴一下全部的代码吧,如果有可以优化的点欢迎大家帮忙指出!看到这里了点个赞吧!感谢!
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Random;
import java.util.ArrayList;
import javax.imageio.ImageIO;
public class Continuous1
{
ArrayList<AloneLine> arraylist = new ArrayList<>();
AloneLine al;
int ordinal = 0;
BufferedImage bufferedImage;
int height,width;
Color color;
Color white = new Color(255,255,255);
Color black = new Color(0,0,0);
public Continuous1(String path)
{
try
{
bufferedImage = ImageIO.read(new File(path));
}
catch (IOException e)
{
e.printStackTrace();
}
height = bufferedImage.getHeight();
width = bufferedImage.getWidth();
}
public Continuous1(BufferedImage bufferedImage)
{
this.bufferedImage = bufferedImage;
height = bufferedImage.getHeight();
width = bufferedImage.getWidth();
}
public void Traverse()
{
for(int y = 0;y < height;y++)
{
for(int x = 0;x < width;x++)
{
color = new Color(bufferedImage.getRGB(x,y));
if(color.getRGB() == black.getRGB())
{
Scan(x,y);
}
}
}
}
public void Scan(int x,int y)
{
int times = 0;
ArrayList<AloneLine> temp = new ArrayList<>();
if(x == 0 && y == 0)
times = 4;
else if(x == 0 && y != 0)
{
times = 2;
for(int i = 0;i < 2;i++)
{
color = new Color(bufferedImage.getRGB(x+i,y-1));
if(color.getRGB() == white.getRGB())
times++;
else
temp.add(Classification(color,x+i,y-1));
}
}
else if(x != 0 && y == 0)
{
times = 3;
color = new Color(bufferedImage.getRGB(x-1,y));
if(color.getRGB() == white.getRGB())
times++;
else
temp.add(Classification(color,x-1,y));
}
else if(x == width-1 && y != 0)
{
times = 1;
for(int i = -1;i < 1;i++)
{
color = new Color(bufferedImage.getRGB(x+i,y-1));
if(color.getRGB() == white.getRGB())
times++;
else
temp.add(Classification(color,x+i,y-1));
}
color = new Color(bufferedImage.getRGB(x-1,y));
if(color.getRGB() == white.getRGB())
times++;
else
temp.add(Classification(color,x-1,y));
}
else
{
for(int i = -1;i < 2;i++)
{
color = new Color(bufferedImage.getRGB(x+i,y-1));
if(color.getRGB() == white.getRGB())
times++;
else
temp.add(Classification(color,x+i,y-1));
}
color = new Color(bufferedImage.getRGB(x-1,y));
if(color.getRGB() == white.getRGB())
times++;
else
temp.add(Classification(color,x-1,y));
}
GetColorful(temp,times,x,y);
}
public AloneLine Classification(Color color,int x,int y)
{
for(AloneLine line : arraylist)
{
if(color.getRGB() == line.color.getRGB())
{
return line;
}
}
return null;
}
public void GetColorful(ArrayList<AloneLine> temp,int times,int x,int y)
{
if(times == 4)
{
al = new AloneLine(ordinal,height,width);
arraylist.add(al);
ordinal++;
arraylist.get(arraylist.size()-1).Coloring(bufferedImage,x,y);
al = null;
}
else if (times == 3)
temp.get(0).Coloring(bufferedImage,x,y);
else if(times <= 2)
{
temp.get(0).Coloring(bufferedImage,x,y);
if(temp.get(0) != temp.get(1))
Assimilation(temp);
}
}
public void Assimilation(ArrayList<AloneLine> temp)
{
for(int i = 0;i < temp.get(1).xpoints.size();i++)
temp.get(0).Coloring(bufferedImage,temp.get(1).xpoints.get(i),temp.get(1).ypoints.get(i));
arraylist.remove(temp.get(1));
}
public void Counting()
{
System.out.println("一共有: " + arraylist.size() + "条线,ID为: ");
for (AloneLine al : arraylist)
{
System.out.println(al.id);
}
}
public void OutPicture(String path)
{
try
{
ImageIO.write(bufferedImage,"png",new File(path));
}
catch (IOException e)
{
e.printStackTrace();
}
}
public static void main(String[] args)
{
Continuous1 ctn = new Continuous1("去背景图片/de2_0.png");
ctn.Traverse();
ctn.Counting();
ctn.OutPicture("去背景图片/de2_0_.png");
}
}
class AloneLine
{
int id;
ArrayList<Integer> xpoints = new ArrayList<>();
ArrayList<Integer> ypoints = new ArrayList<>();
Color color;
int r,g,b;
public AloneLine(){}
public AloneLine(int ordinal,int height,int width)
{
id = ordinal;
Random random = new Random();
r = random.nextInt(255);
g = random.nextInt(255);
b = random.nextInt(255);
}
public void setPoints(int x,int y)
{
xpoints.add(x);
ypoints.add(y);
}
public void Coloring(BufferedImage bufferedImage,int x,int y)
{
color = new Color(r,g,b);
bufferedImage.setRGB(x,y,color.getRGB());
setPoints(x,y);
}
}
文章评论