HaarFeatureFinder.java
package ac.essex.ooechs.imaging.commons.test;
import ac.essex.ooechs.imaging.commons.HaarRegions;
import ac.essex.ooechs.imaging.commons.StatisticsSolver;
import ac.essex.ooechs.imaging.commons.PixelLoader;
import ac.essex.ooechs.imaging.commons.fast.FastStatistics;
import ac.essex.ooechs.imaging.commons.util.ImageFilenameFilter;
import java.util.Vector;
import java.util.Collections;
import java.text.DecimalFormat;
import java.io.File;
import java.io.Serializable;
/**
* Uses a heuristic technique to find good Haar Features.
* It works by finding an exhaustive list of possible Haar features and
* then runs them all on a set of images. It finds the standard deviation
* of the answer given and thus finds the regions on the image where there
* is high variability between different images (not so useful), and regions
* on the image where there is low variability across images (more useful). The
* regions with the lowest variability are selected as an initial set of weak
* classifiers, and may then be used in a machine learning environment to discover
* a good classifier of the image set.
* <p/>
* It can also be used to discover if false training data is really that representative
* or whether there is a common feature linking the false training data that the
* learned program could be exploiting.
*
* @author Olly Oechsle, University of Essex, Date: 07-Nov-2006
* @version 1.01 14th July 2007
*/
public class HaarFeatureFinder {
protected int windowBlocksX;
protected int windowBlocksY;
protected double maxAreaPercentagePermitted;
// define a border around the image.
// this is so we can locate the areas of the image that
// are not so useful.
int xPosMin = 5;
int yPosMin = 4;
int xPosMax = 11;
int yPosMax = 16;
public static void main(String[] args) {
// specify where the images are
final File imageDirectory = new File("/home/ooechs/ecj-training/faces/essex/mit/test/scaled48x60/");
// create the finder specifying the grid dimensions
HaarFeatureFinder featureFinder = new HaarFeatureFinder(16, 20, 50.0);
// locate regions and print them to standard out.
featureFinder.findFeatures(featureFinder.getImages(imageDirectory), 1, 2, 5);
}
/**
* @param windowBlocksX The blocks in X into which the Haar Regions are divided.
* @param windowBlocksY The blocks in Y into which the Haar Regions are divided.
* @param maxAreaPercentagePermitted The maximum percentage of the image are that found regions should inhabit. This stops the finder from locating large areas that have an overall low variability, but are nonetheless not useful in performing good classification. Values accepted between 0d-100d
*/
public HaarFeatureFinder(int windowBlocksX, int windowBlocksY, double maxAreaPercentagePermitted) {
this.windowBlocksX = windowBlocksX;
this.windowBlocksY = windowBlocksY;
this.maxAreaPercentagePermitted = maxAreaPercentagePermitted;
}
/**
* Loads all the images (As HaarRegion objects) in a given directory. The
* ImageFilenameFilter class is used to determine what is an image, and what
* isnt.
*/
public Vector<HaarRegions> getImages(File imageDirectory) {
File[] images = imageDirectory.listFiles(new ImageFilenameFilter());
Vector<HaarRegions> imageSet = new Vector<HaarRegions>(images.length);
for (int i = 0; i < images.length; i++) {
try {
File image = images[i];
HaarRegions haar = new HaarRegions(new PixelLoader(image));
imageSet.add(haar);
} catch (Exception e) {
e.printStackTrace();
}
}
return imageSet;
}
public void findOneRectFeatures(Vector<HaarRegions> images, int howMany) {
findFeatures(images, 1, 1, howMany);
}
public void findHorizontalTwoRectFeatures(Vector<HaarRegions> images, int howMany) {
findFeatures(images, 2, 1, howMany);
}
public void findVerticalTwoRectFeatures(Vector<HaarRegions> images, int howMany) {
findFeatures(images, 1, 2, howMany);
}
public void findHorizontalThreeRectFeatures(Vector<HaarRegions> images, int howMany) {
findFeatures(images, 3, 1, howMany);
}
public void findVerticalThreeRectFeatures(Vector<HaarRegions> images, int howMany) {
findFeatures(images, 1, 3, howMany);
}
public void findFourRectFeatures(Vector<HaarRegions> images, int howMany) {
findFeatures(images, 2, 2, howMany);
}
public Vector<HaarDefinition> findFeatures(Vector<HaarRegions> images, int widthMultiple, int heightMultiple, int howMany) {
Vector<HaarDefinition> results = new Vector<HaarDefinition>(1000);
System.out.println("Autodiscovering weak classifiers (this may take some time)");
int counter = 0;
int totalArea = windowBlocksX * windowBlocksY;
double highestAreaPermitted = totalArea * (maxAreaPercentagePermitted / 100);
// every width
for (int width = 1; width < windowBlocksX - 1; width++) {
// every height
for (int height = 1; height < windowBlocksY - 1; height++) {
// only use shape if it is no larger than the maximum permitted area
if (width * height >= highestAreaPermitted) continue;
// every position
for (int yPos = 0; yPos < (windowBlocksY - (height * heightMultiple)); yPos++) {
for (int xPos = 0; xPos < (windowBlocksX - (width * widthMultiple)); xPos++) {
// Stay within border
if (xPos < xPosMin) continue;
if (xPos + (width * widthMultiple) > xPosMax) continue;
if (yPos < yPosMin) continue;
if (yPos + (height & heightMultiple) > yPosMax) continue;
// Use StatisticsSolver to calculate the Standard Deviation
FastStatistics solver = new FastStatistics();
// on every image
for (int i = 0; i < images.size(); i++) {
HaarRegions image = images.elementAt(i);
image.makeWindowFillImage(windowBlocksX, windowBlocksY);
if (widthMultiple == 1 && heightMultiple == 1)
solver.addData(image.getOneRectangleFeature(xPos, yPos, width, height));
if (widthMultiple == 2 && heightMultiple == 1)
solver.addData(image.getTwoRectangleFeature(xPos, yPos, width, height, HaarRegions.HORIZONTALLY_ADJACENT, HaarRegions.FIRST_SHAPE));
if (widthMultiple == 1 && heightMultiple == 2)
solver.addData(image.getTwoRectangleFeature(xPos, yPos, width, height, HaarRegions.VERTICALLY_ADJACENT, HaarRegions.FIRST_SHAPE));
if (widthMultiple == 3 && heightMultiple == 1)
solver.addData(image.getThreeRectangleFeature(xPos, yPos, width, height, HaarRegions.HORIZONTALLY_ADJACENT));
if (widthMultiple == 1 && heightMultiple == 3)
solver.addData(image.getThreeRectangleFeature(xPos, yPos, width, height, HaarRegions.VERTICALLY_ADJACENT));
if (widthMultiple == 2 && heightMultiple == 2)
solver.addData(image.getFourRectangleFeature(xPos, yPos, width, height, HaarRegions.FIRST_SHAPE));
}
// Now Calculate the Variance
double stdDeviation = solver.getStandardDeviation();
if (widthMultiple == 1 && heightMultiple == 1) {
results.add(new HaarDefinition(HaarDefinition.ONE_RECT_FEATURE, width, height, xPos, yPos, stdDeviation));
}
if (widthMultiple == 2 && heightMultiple == 1) {
results.add(new HaarDefinition(HaarDefinition.HORIZONTAL_TWO_RECT_FEATURE, width, height, xPos, yPos, stdDeviation));
}
if (widthMultiple == 1 && heightMultiple == 2) {
results.add(new HaarDefinition(HaarDefinition.VERTICAL_TWO_RECT_FEATURE, width, height, xPos, yPos, stdDeviation));
}
if (widthMultiple == 3 && heightMultiple == 1) {
results.add(new HaarDefinition(HaarDefinition.HORIZONTAL_THREE_RECT_FEATURE, width, height, xPos, yPos, stdDeviation));
}
if (widthMultiple == 1 && heightMultiple == 3) {
results.add(new HaarDefinition(HaarDefinition.VERTICAL_THREE_RECT_FEATURE, width, height, xPos, yPos, stdDeviation));
}
if (widthMultiple == 2 && heightMultiple == 2) {
results.add(new HaarDefinition(HaarDefinition.FOUR_RECT_FEATURE, width, height, xPos, yPos, stdDeviation));
}
}
}
System.out.print(".");
counter++;
if (counter > 50) {
counter = 0;
System.out.println();
}
} // next height
} // next width
Collections.sort(results);
// Now find the X best achievers
Vector<HaarDefinition> best = new Vector<HaarDefinition>(10);
for (int i = 0; i < results.size(); i++) {
HaarDefinition result = results.elementAt(i);
// see if anythig similar already there
boolean tooSimilar = false;
for (int j = 0; j < best.size(); j++) {
HaarDefinition existingResult = best.elementAt(j);
if (existingResult.similarTo(result)) tooSimilar = true;
}
if (!tooSimilar) {
best.add(result);
if (best.size() >= howMany) break;
}
}
System.out.println("\nResults: ");
System.out.println("#\tStdDev\tFeature");
DecimalFormat format = new DecimalFormat("0.00");
for (int i = 0; i < best.size(); i++) {
HaarDefinition result = best.elementAt(i);
System.out.println((i+1) + "\t" + format.format(result.stdDeviation) + "\t" + result);
}
return best;
}
}