package com.hygenics.facialrec; import imagetools.Blur; import imagetools.Darken; import imagetools.Denoise; import imagetools.Deskew; import imagetools.Greyscale; import imagetools.Image; import imagetools.Rotate; import imagetools.Sharpen; import java.awt.Color; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.imageio.ImageIO; import org.slf4j.*; import curves.BSpline; /** * This class takes in a silhouete fpath and a new face fpath and checks to see if they are * silhouettes. The algorithm first looks for faces, then eyes, ears, and a mouth, and finally * checks each image against a set of features if they are found (texture, spline map). * * * In image match detection, if the splines and texture of the face are not exactly the same or within a spec. cutoff, * then there is no match. This is not facial recognition which like, record linkage is a multi-classification syste * and much more complex. I try to avoid a lot of the complexity but o(n^2) is not uncommon here. For speed, keep images * small. * * The class will hopefully error on the side of not finding a silhouette. * * @author aevans * */ public class ImageMatch { private ArrayList<PointObject> splineobj=new ArrayList<PointObject>(); private ArrayList<PointObject> silobj=new ArrayList<PointObject>(); private boolean quickcheck=true; /** * Should use standard deviation of covariance, if you set euclideanDist to false, you must set this. * The cutoff will also become the zscore of the two group difference. */ private double sd; private boolean euclideanDist=true; private Double cutoff; private Double leeway; private Logger log; private String silpath; private String imgpath; private int numsteps; private BufferedImage silimage; private BufferedImage image; //set to false so less information is lost List<String> imgmanips; List<String> silmanips; public ImageMatch(){ } public int getNumsteps() { return numsteps; } public void setNumsteps(int numsteps) { this.numsteps = numsteps; } public boolean isQuickcheck() { return quickcheck; } public void setQuickcheck(boolean quickcheck) { this.quickcheck = quickcheck; } public double getSd() { return sd; } public void setSd(double sd) { this.sd = sd; } public boolean isEuclideanDist() { return euclideanDist; } public void setEuclideanDist(boolean euclideanDist) { this.euclideanDist = euclideanDist; } public void setLeeway(Double leeway) { this.leeway = leeway; } public Double getCutoff() { return cutoff; } public void setCutoff(Double cutoff) { this.cutoff = cutoff; } public Double getLeeway() { return leeway; } public void setLeeweay(Double leeway) { this.leeway = leeway; } public Logger getLog() { return log; } public void setLog(Logger log) { this.log = log; } public BufferedImage getSilimage() { return silimage; } public void setSilimage(BufferedImage silimage) { this.silimage = silimage; } public BufferedImage getImage() { return image; } public void setImage(BufferedImage image) { this.image = image; } public List<String> getImgmanips() { return imgmanips; } public void setImgmanips(List<String> imgmanips) { this.imgmanips = imgmanips; } public List<String> getSilmanips() { return silmanips; } public void setSilmanips(List<String> silmanips) { this.silmanips = silmanips; } public String getSilpath() { return silpath; } public void setSilpath(String silpath) { this.silpath = silpath; } public String getImgpath() { return imgpath; } public void setImgpath(String imgpath) { this.imgpath = imgpath; } /** * Perform a series of image manipulations */ private void performManips(boolean silhouette){ //TODO Perform a list of manipulations if(log != null){ log.info("Beginning Manipulations @ "+Calendar.getInstance().getTime().toString()+" | UTC "+Calendar.getInstance().getTimeInMillis()); } //initialize List<String> manips; BufferedImage bi; Pattern p; Matcher m; if(silhouette){ manips=silmanips; bi=silimage; } else{ manips=imgmanips; bi=image; } //this integer kicks out to the slf4j logger if provided and also helps in using the proxy pattern right int i=0; for(String manip : manips){ if(manip.trim().toLowerCase().compareTo("greyscale")==0){ Greyscale g=new Greyscale(); if(i==0){ g.setImage(bi); } g.convert_image(); } else if(manip.trim().toLowerCase().contains("blur")){ p=Pattern.compile("\\d+"); m=p.matcher(manip); Blur b=new Blur(); if(i==0){ b.setImage(bi); } if(m.find()){ b.blurImage(Double.parseDouble(m.group().trim())); } else{ b.average_blur(); } b=null; } else if(manip.trim().toLowerCase().contains("denoise")){ Denoise dn=new Denoise(); if(manip.contains("laplace")){ dn.laplace_denoise(); } if(manip.contains("average")){ dn.average_denoise(); } dn=null; } else if(manip.trim().toLowerCase().contains("sharpen")){ p=Pattern.compile("\\d+"); m=p.matcher(manip); Sharpen s=new Sharpen(); if(i==0){ s.setImage(bi); } if(m.find()){ s.setSharpWeight(Integer.parseInt(m.group().trim())); } s.sharpenImage(); s=null; } else if(manip.trim().toLowerCase().contains("darken")){ Darken d=new Darken(); if(i==0){ d.setImage(bi); } d.darkenImage(); d=null; } else if(manip.trim().toLowerCase().contains("deskew")){ Deskew dsk=new Deskew(); if(i==0){ dsk.setImage(bi); } dsk.run(); } else if(manip.trim().toLowerCase().contains("rotate")){ p=Pattern.compile("\\d+"); m=p.matcher(manip); Rotate rotate=new Rotate(); if(i==0){ rotate.setImage(bi); } if(m.find()){ rotate.setTheta(Double.parseDouble(m.group().trim())); } if(manip.contains("crop")){ rotate.setCrop(true); } rotate.run(); } //the future may include declutter but it may be better to preserve noise here i++; } //reset image if(silhouette){ silimage=Image.getInstance().getImage(); } else{ image=Image.getInstance().getImage(); } if(log != null){ log.info("Manipulations Complete @ "+Calendar.getInstance().getTime().toString()+" | UTC "+Calendar.getInstance().getTimeInMillis()); log.info("Performed "+i+" manipulations"); } } /** * Gets the match distance. This will be useful if everything needs to be 0. * * @return */ private double checkSplinesEuclide(){ //TODO check to spline points for a match percentage using a less memory intensive distance for an exact match PointObject po; int xtot=0; int ytot=0; //iterate down and compare //get a euclidean distance between the points taking image as + and silhouette as - //this would be a quick and dirty matching process //get the normal image total for(int i=0;i<splineobj.size();i++){ po=splineobj.get(i); xtot+=po.getX(); ytot+=po.getY(); } //subtract the silhouette total for(int i=0;i<silobj.size();i++){ po=silobj.get(i); xtot-=po.getX(); ytot-=po.getY(); } //return the Euclidean distance (this is really dirty so try to keep to near or exact matches) return (Math.sqrt(Math.pow(xtot, 2)+Math.pow(ytot, 2))); } /** * This is for if the equation keeps yielding different images. It may even be useful * for facial recog. It gets the covariance of the splines [the change of a spline with another change]. * Use it by passing individual splines to it and getting the point difference. This will be much * slower than just throwing everything at a distance calculator and finding how different things are. * * @param objs * @return */ private double getCovariance(ArrayList<PointObject> objs){ //return the covariance double meanx=0; double meany=0; double cov=0; //get the means for(int i=0;i<objs.size();i++){ //get the x mean meanx+=objs.get(i).getX(); //get the y mean meany+=objs.get(i).getY(); } meanx=meanx/objs.size(); meany=meany/objs.size(); //get the covariance (sum((x-mean)*(y-mean)) for(int i=0;i<objs.size();i++){ //get add to the covariance coeff. cov+=((objs.get(i).getX()-meanx)*(objs.get(i).getY()-meany)); } return cov; } /** * Compare the texturepoints to each other * @return */ private double checkTexture(){ //TODO check against two texture maps return 0.0; } /** * Get the Matching percentage with a simple even weight * @param splineperc * @param txtperc * @return */ private double getMatchPerc(double splineperc,double txtperc){ //TODO simple add and divide for perc return ((splineperc+txtperc)/2.0); } /** * Get a weighted percentage of match. * Make sure the weights are less than 1 (added and passed) and the percs are less than 1 each. * * @param splineperc * @param txtperc * @param splinewght * @param txtwght * @return */ private double getWeightedMatchPerc(double splineperc,double txtperc,double splinewght,double txtwght){ //TODO return a weighted average double total=0.0; double totalspline=splineperc*splinewght; double totaltxt=txtperc*txtwght; total=totalspline+totaltxt; return total; } /** * Check to see if an image is a match * @return */ private boolean isMatch(Double matchpercent){ //TODO using 2 classification system, check for a match against the cutoff if(matchpercent<=(cutoff+leeway) & matchpercent>=(cutoff-leeway)){ return true; } return false; } /** * Get the Texture for the image */ private void getTexture(){ //TODO Find a texture map for the image } /** * Get and set the Spline */ private void getSpline(){ //TODO get the splines for the two images BSpline bs=new BSpline(); if(log != null) { log.info("Getting Spline Points for Image @ "+Calendar.getInstance().getTime().toString()+" | UTC "+Calendar.getInstance().getTimeInMillis()); } //set points and get spline //bs.setPtarr(ptarr); //bs.calculate(); //get the new points if(log != null) { log.info("Spline Points Obtained form Image @ "+Calendar.getInstance().getTime().toString()+" | UTC "+Calendar.getInstance().getTimeInMillis()); } if(log != null) { log.info("Getting Spline Points for Silhouette @ "+Calendar.getInstance().getTime().toString()+" | UTC "+Calendar.getInstance().getTimeInMillis()); } //set points and get spline //get new points if(log != null) { log.info("Spline Points Obtained form Silhouette @ "+Calendar.getInstance().getTime().toString()+" | UTC "+Calendar.getInstance().getTimeInMillis()); } } /** * Perform the check for a match either statistically or looking for an exact match of features. Pixels have been * found to be off for some reason or another at times so this is where this is handy. */ private boolean checkIntensive() { boolean match=false; if(imgmanips != null){ if(imgmanips.size()>0){ performManips(true); } } if(silmanips != null){ if(silmanips.size()>0){ performManips(false); } } //get manips //get splines //get textures //perform check //if asked for distance see if the distance is within the cutoff (using absolute value) //if asked for covar. check, get the number of splines, and get the covariance for each part and use them //first, get the covar. //get the average covariance of the silhouette //get the average covariance of the image //perform our two group test //check to see if the two group test is within the provided z-score //return the match return match; } /** * Hopefully, most sources only require looking at the pixel differences but I have been told this is not * the case in all circumstances. This method just checks each image and pixel for any difference. It may * be a good idea to edge detect or greyscale if the colors are getting screwy. * * @return */ private boolean quickCheck(){ //TODO check to see if the images are an exact match if((silimage.getWidth() != image.getWidth()) | (silimage.getHeight() != image.getHeight())){ return false; } else{ Color c1; Color c2; for(int i=0;i<silimage.getWidth();i++) { for(int j=0;j<image.getWidth();j++){ c1=new Color(silimage.getRGB(i, j)); c2=new Color(image.getRGB(i, j)); if((c1.getRed() != c2.getRed()) & (c1.getBlue() != c2.getBlue()) & (c1.getGreen() != c2.getGreen())){ //rbg suckers :) return false; } } } } return true; } /** * Run a boolean representing the match. There are plenty of options * to match the different images that may be returned. Either check * each pixel and property or go for a more intensive approach if * and only if the other approach proves too fruitless as I've been * told can happen. The intensive approach can be used to check a distance * between a texture map and splines or get an average covariance for testing * between each to see if hypoth. testing shows the two groups to be statistically * the same. Classifications can be used to mark potential, definite, and non matches. * * @return */ public boolean run(){ boolean match=false; if(imgpath != null & silpath != null){ //perform manips. on both images. to ensure symmetry, the same manips. are used on each performManips(false); performManips(true); } else{ if(imgpath == null){ try{ throw new NullPointerException("No Image path provided!\n"); }catch(NullPointerException e){ e.printStackTrace(); } } else{ try{ throw new NullPointerException("No silhouette provided!\n"); }catch(NullPointerException e){ e.printStackTrace(); } } } if(quickcheck){ //perform a quick check on the objects return quickCheck(); } else{ //perform an intensive check on each return checkIntensive(); } } }