Javaで画像処理(その7)

 前回の特徴点抽出を発展させ、ラスターをベクトル化してみる。

 ■ソースコード
 特徴点を探査し、ベクトルに変換するR2VFilterクラスを実装する。
 ベクトル化した画像情報は、createGeneralPathメソッド又はcreateLinesメソッドで取得する。

 ・R2VFilter.java

package test.filter;

import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class R2VFilter {
	private BufferedImage image;
	private int width;
	private int height;
	private int[][] pixel;
	private int space;
	private double rad=Math.toRadians(165);
	
	public R2VFilter(BufferedImage im,int sp){
		image=im;
		width=image.getWidth();
		height=image.getHeight();
		pixel=new int[width][height];
		for(int x=0;x<width;x++){
			for(int y=0;y<height;y++){
				int n=image.getRGB(x,y);
				int val=(n>>16&0xff);
				if(val==255){
					pixel[x][y]=0;
				}else{
					pixel[x][y]=1;
				}
			}
		}
		space=sp;
	}

	public GeneralPath createGeneralPath(){
		Map<String,Feature> node=getFeatureMap(width,height,pixel);
		List<Edge> list=getEdge(node);
		GeneralPath gp=new GeneralPath();
		for(Edge e :list){
			Line2D ll=new Line2D.Float(e.n0.getX(),e.n0.getY(),e.n1.getX(),e.n1.getY());
			gp.append(ll, false);
		}
		gp.closePath();
		return gp;
	}
	
	public Line2D[] createLines(){
		Map<String,Feature> node=getFeatureMap(width,height,pixel);
		List<Edge> list=getEdge(node);
		List<Line2D> lines=new ArrayList<Line2D>();
		for(Edge e :list){
			Line2D ll=new Line2D.Float(
			   e.n0.getX(),e.n0.getY(),e.n1.getX(),e.n1.getY());
			lines.add(ll);
		}
		return lines.toArray(new Line2D[lines.size()]);
	}
	
	public void setThresholdDegree(double deg){
		rad=Math.toRadians(180-deg);
	}

	public List<Feature> proc(){
		Map<String,Feature> node=getFeatureMap(width,height,pixel);
		List<Edge> edge=getEdge(node);
		edge=procCorner(edge,node);
		List<Feature> ret=new ArrayList<Feature>();
		Iterator<Feature> it=node.values().iterator();
		while(it.hasNext()){
			Feature e=(Feature)it.next();
			ret.add(e);
		}
		return ret;
	}

	private Map<String,Feature> getFeatureMap(int w,int h,int[][] b){
		Map<String,Feature> map=new HashMap<String,Feature>();
		for(int y=1;y<h-1;y++){
			for(int x=1;x<w-1;x++){
				if(b[x][y]==1){
				  int val=b[x+1][y]+b[x][y-1]+b[x-1][y]+b[x][y+1];
				  if(val!=0){
				  	Feature f=new Feature(x,y,val);
				  	map.put(getNum(x,y),f);
				  }
				}
			}
		}
		return map;
	}
	
	private boolean checkEdge(Edge e0,Edge e1){	
		return (e0.n1.equals(e1.n0));
	}

	private double getRad(Edge e0,Edge e1){
		double[] v0=new double[]{
		   e0.n0.getX()-e0.n1.getX(),e0.n0.getY()-e0.n1.getY()};
		double[] v1=new double[]{
		   e1.n1.getX()-e0.n1.getX(),e1.n1.getY()-e0.n1.getY()};
		double rc=(v0[0]*v1[0]+v0[1]*v1[1]);
		double rs=Math.sqrt(
		   v0[0]*v0[0]+v0[1]*v0[1])*Math.sqrt(v1[0]*v1[0]+v1[1]*v1[1]);
		return Math.acos(rc/rs);
	}

	private List<Edge> procCorner(List<Edge> edge,Map<String,Feature> node){
		int num=edge.size();
		for(int i=1;i<num;i++){
			Edge e0=(Edge)edge.get(i-1);
			Edge e1=(Edge)edge.get(i);
			if(!checkEdge(e0,e1)){
				if(checkEdge(e1,e0)){
					Edge tmp=e0;
					e0=e1;
					e1=tmp;
				}else{
					continue;
				}
			}
			double r=getRad(e0,e1);
			if(r>rad){
				int v=e0.n1.getOrder();
				if(v!=1||v!=3||v!=4){
					edge.add(i,new Edge(e0.n0,e1.n1));
					edge.remove(e0);
					edge.remove(e1);
					node.remove(getNum(e0.n1.getX(),e0.n1.getY()));
					num--;
					i--;
				}
			}
		}
		return edge;
	}

	private String getNum(int x,int y){
		return Integer.toString(x)+","+Integer.toString(y);		
	}

	private List<Edge> getEdge(Map<String,Feature> node){
		List<Edge> edge=new ArrayList<Edge>();
		Map<String,Feature> tmp=new TreeMap<String,Feature>(node);
		Iterator<Feature> it=tmp.values().iterator();
		int id=0;
		while(it.hasNext()){
			Feature n=(Feature)it.next();
			if(n.isMark())continue;
			n.setMark(true);
			n.setId(id);
			int count=0;
			int x=n.getX();
			int y=n.getY();
			while(true){
				Feature no=null;
				if(x>0&&(no=(Feature)node.get(getNum(x-1,y)))
						!=null&&!no.isMark()){
					x=x-1;
					count++;
					if(no.getOrder()!=2){
						edge.add(new Edge(n,no));
						n=no;
						n.setMark(true);
						n.setId(id);
						count=0;
					}else{
						if(count==space){
							count=0;
							edge.add(new Edge(n,no));
							n=no;
							n.setMark(true);
							n.setId(id);
						}else{
							node.remove(getNum(x,y));
						}
					}
				}else if(x>0&&(no=(Feature)node.get(getNum(x,y-1)))
						!=null&&!no.isMark()){
					y=y-1;
					count++;
					if(no.getOrder()!=2){
						edge.add(new Edge(n,no));
						n=no;
						n.setMark(true);
						n.setId(id);
						count=0;
					}else{
						if(count==space){
							count=0;
							edge.add(new Edge(n,no));
							n=no;
							n.setMark(true);
							n.setId(id);
						}else{
							node.remove(getNum(x,y));
						}
					}
				}else if(x>0&&(no=(Feature)node.get(getNum(x+1,y)))
						!=null&&!no.isMark()){
					x=x+1;
					count++;
					if(no.getOrder()!=2){
						edge.add(new Edge(n,no));
						n=no;
						n.setMark(true);
						n.setId(id);
						count=0;
					}else{
						if(count==space){
							count=0;
							edge.add(new Edge(n,no));
							n=no;
							n.setMark(true);
							n.setId(id);
						}else{
							node.remove(getNum(x,y));
						}
					}
				}else if(x>0&&(no=(Feature)node.get(getNum(x,y+1)))
						!=null&&!no.isMark()){
					y=y+1;
					count++;
					if(no.getOrder()!=2){
						edge.add(new Edge(n,no));
						n=no;
						n.setMark(true);
						n.setId(id);
						count=0;
					}else{
						if(count==space){
							count=0;
							edge.add(new Edge(n,no));
							n=no;
							n.setMark(true);
							n.setId(id);
						}else{
							node.remove(getNum(x,y));
						}
					}
				}else{
					break;
				}
			}
			id++;
		}
		return edge;
	}
	
	private class Edge{
		Feature n0;
		Feature n1;
		Edge(Feature a,Feature b){
			n0=a;
			n1=b;
		}
	}
}

次いで、createMenuBarに以下のコードを付け加える。

	JMenuItem r2v=new JMenuItem("toVector");
	r2v.addActionListener(new ActionListener(){
		@Override
		public void actionPerformed(ActionEvent arg0) {
			Runnable rr=new Runnable(){
				public void run(){
					BufferedImage bi=canvas.getImage();
					R2VFilter ff=new R2VFilter(bi,10);
					ff.setThresholdDegree(10);
					GeneralPath gp=ff.createGeneralPath();
					final VolatileImage vi=
					  canvas.createVolatileImage(bi.getWidth(), bi.getHeight());
					Graphics2D g2=(Graphics2D)vi.createGraphics();
					g2.drawImage(bi,0,0,canvas);
					g2.setColor(Color.RED);
					g2.draw(gp);
					Runnable rx=new Runnable(){
						public void run(){
							canvas.setBaseImage(vi);
						}
					};
					SwingUtilities.invokeLater(rx);
				}
			};
			new Thread(rr).start();
		}
	});
	filter.add(r2v);

 ■実行結果
 実行結果は以下のとおり。(元画像、細線化、ベクトル化)
 黒線が元の画像、赤線がベクトル化した画像だけど、だいたい再現されている。
 なお、細線化等の前処理を行う必要がある。