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