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();
}
}
}