HaarRegions.java
package ac.essex.ooechs.imaging.commons;
import ac.essex.ooechs.imaging.commons.fast.FastStatistics;
import java.io.File;
/**
* <p/>
* Allows certain Haar Statistics about an image to be returned.
* The class uses optimisations as described by Viola and Jones's
* Integral Image which means that while the constructor has a slight
* time delay in preparing the optimisation, the imaging functions themselves
* should be extremely fast.
* </p>
* <p/>
* <p/>
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version,
* provided that any use properly credits the author.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details at http://www.gnu.org
* </p>
*
* @author Olly Oechsle, University of Essex, Date: 07-Sep-2006
* @version 1.0
*/
public class HaarRegions implements SimpleImage {
protected PixelLoader image;
protected int imageWidth, imageHeight;
public static void main(String[] args) throws Exception {
// tests haar regions
// create a pixel loader
final File trueDirectory2 = new File("/home/ooechs/Data/faces");
PixelLoader image = new PixelLoader(new File(trueDirectory2, "blended.bmp"));
HaarRegions haar = new HaarRegions(image);
haar.makeWindowFillImage(19,19);
System.out.println(haar.getOneRectangleFeature(16, 0, 1, 2));
}
/**
* Allows access to the original image pixels.
*/
public PixelLoader getPixelLoader() {
return image;
}
public static final int HORIZONTALLY_ADJACENT = 1;
public static final int VERTICALLY_ADJACENT = 2;
public static final int FIRST_SHAPE = 1;
public static final int SECOND_SHAPE = 2;
public static final int DEFAULT_WINDOW_WIDTH = 32;
public static final int DEFAULT_WINDOW_HEIGHT = 40;
public static final int DEFAULT_BLOCKS_X = 16;
public static final int DEFAULT_BLOCKS_Y = 20;
/**
* The number of blocks in the window in the X direction
*/
protected int windowBlocksX = DEFAULT_BLOCKS_X;
/**
* The number of blocks in the window in the Y direction
*/
protected int windowBlocksY = DEFAULT_BLOCKS_Y;
/**
* The width of the window in pixels. This number should be evenly
* divisible by windowBlocksX
*/
protected int windowWidth = DEFAULT_WINDOW_WIDTH;
/**
* The height of the window in pixels. This number should be evenly
* divisible by windowBlocksY
*/
protected int windowHeight = DEFAULT_WINDOW_HEIGHT;
/**
* The X position of the window's top left corner.
*/
protected int windowX = 0;
/**
* The Y position of the window's top left corner.
*/
protected int windowY = 0;
public void makeWindowFillImage(int windowBlocksX, int windowBlocksY) {
setWindowPosition(0, 0, image.getWidth(), image.getHeight(), windowBlocksX, windowBlocksY);
}
public void setWindowPosition(int windowX, int windowY) {
if (windowX > (image.getWidth() - windowWidth)) {
throw new RuntimeException("WindowX is too large - the window is outside the image boundary. (windowX=" + windowX + ", imageWidth=" + image.getWidth() + ", windowWidth=" + windowWidth + ")");
}
if (windowY > (image.getHeight() - windowHeight)) {
throw new RuntimeException("WindowY is too large - the window is outside the image boundary. (windowY=" + windowY + ", imageHeight=" + image.getHeight() + ", windowHeight=" + windowHeight + ")");
}
this.windowX = windowX;
this.windowY = windowY;
}
/**
* Function to set the position of the window. Calls to the various get
* rectangle functions are made relative to this window position and
* size.
* <p/>
* An exception is thrown if the window is placed outside the boundaries of the
* image, or if the window is larger than the image.
*
* @param windowBlocksX The number of "blocks" in the X direction
* @param windowBlocksY The number of "blocks" in the Y direction
*/
public void setWindowPosition(int windowX, int windowY, int windowWidth, int windowHeight, int windowBlocksX, int windowBlocksY) {
// ensure that the width and height chosen divide evenly into the chosen number of blocks
if (windowWidth % windowBlocksX != 0) {
throw new RuntimeException(image.getFile().getName() + ": The window's width (" + windowWidth + ") does not divide evenly into " + windowBlocksX + " vertical segments.");
}
if (windowHeight % windowBlocksY != 0) {
throw new RuntimeException(image.getFile().getName() + ": The window's height (" + windowHeight + ") does not divide evenly into " + windowBlocksY + " horizontal segments.");
}
if (windowWidth > image.getWidth()) {
throw new RuntimeException(image.getFile().getName() + ": Window is wider (" + windowWidth + ") than the image (" + getWidth() + ")");
}
if (windowHeight > image.getHeight()) {
throw new RuntimeException(image.getFile().getName() + ": Window is taller (" + windowHeight + ") than the image (" + getHeight() + ")");
}
this.windowWidth = windowWidth;
this.windowHeight = windowHeight;
setWindowPosition(windowX, windowY);
this.windowBlocksX = windowBlocksX;
this.windowBlocksY = windowBlocksY;
this.blockWidth = windowWidth / windowBlocksX;
this.blockHeight = windowHeight / windowBlocksY;
}
/**
* The width of a block in pixels, which is windowWidth / windowBlocksX
*/
private int blockWidth;
/**
* The height of a block in pixels, which is windowHeight / windowBlocksY
*/
private int blockHeight;
/**
* Finds the actual position on the integral image when we are presented with a block's X position.
*/
public int getActualPositionX(int blocksLeft) {
return windowX + (blocksLeft * blockWidth);
}
/**
* Finds the actual position on the integral image when we are presented with a block's Y position.
*/
public int getActualPositionY(int blocksTop) {
return windowY + (blocksTop * blockHeight);
}
/**
* Initialises the Haar Regions object, and performs preliminary calculations
* on the image.
* <p/>
* MaxX and MaxY define the maximum size of the window being used.
* <p/>
* ResX and ResY define how many blocks across the image are to be used in
* each direction. If resX and ResY are the same as MaxX and MaxY then
* an image recognition program is created, but if you want to use this image
* in a sliding window approach, then ResX and ResY will be larger than MaxX
* and windowBlocksY.
*
* @param image An image from which we are allowed access to its width, height, and greyscale pixel values (0-255)
*/
public HaarRegions(PixelLoader image) {
this.integralImage = getIntegralImage(image, 1, 1);
this.image = image;
this.imageWidth = image.getWidth();
this.imageHeight = image.getHeight();
}
protected int[][] integralImage;
/**
* Calculates the sum of pixels at any given point on an image. An implementation of the algorithm as
* described by Viola and Jones. By storing sums of all regions relative to the origin (0,0), we can use
* basic arithmetic to find the sum of any arbitrary region.
*
* @param image An image from which we are allowed access to its width, height, and greyscale pixel values (0-255)
* @return A 2D array of the pixel sums at any pixel, relative to the origin at (0,0)
*/
private int[][] getIntegralImage(PixelLoader image, int baseResolutionX, int baseResolutionY) {
// Prepare an array to store the results in
int integralImage[][] = new int[image.getWidth() / baseResolutionX + 1][image.getHeight() / baseResolutionY + 1];
// The array is at a different position to the image
// If the baseResolution is more than 1. A bit messy but
// avoids unnecessary multiplications
int arrX = 1;
// Move through each column in the image
for (int x = 0; x < image.getWidth(); x += baseResolutionX) {
int columnTotal = 0;
int arrY = 1;
// Move down the column
for (int y = 0; y < image.getHeight(); y += baseResolutionY) {
// Find the sum of pixels in this column, to this particular Y value
// This involves evaluating a square of baseResolution x baseResolution pixels.
for (int dx = 0; dx < baseResolutionX; dx++)
for (int dy = 0; dy < baseResolutionY; dy++)
columnTotal += image.getGreyValue(x + dx, y + dy);
// sum total = value of row to left + columnTotal
integralImage[arrX][arrY] = (arrX == 0 ? 0 : integralImage[arrX - 1][arrY]) + columnTotal;
// results Y index
arrY++;
}
// results X index
arrX++;
}
return integralImage;
}
/**
* Gets the sum total of pixels in a given region.
*
* @param x
* @param y
* @param width
* @param height
*/
public int getRegionSum(int x, int y, int width, int height) {
int val1 = integralImage[x][y];
int val2 = integralImage[x + width][y];
int val3 = integralImage[x][y + height];
int val4 = integralImage[x][y];
return val4 - val2 - val3 + val1;
}
public float getOneRectangleFeature(int x, int y, int width, int height) {
if (x + width > windowBlocksX) {
if (width > windowBlocksX) {
System.err.println("Width is too much!" + width + " > " + windowBlocksX);
} else {
x = windowBlocksX - width;
}
}
if (y + height > windowBlocksY) {
if (height > windowBlocksY) {
System.err.println("Height is too much!" + height + " > " + windowBlocksY);
} else {
y = windowBlocksY - height;
}
}
// average function
float pixelsPerSquare = (width * blockWidth) * (height * blockHeight);
int val1 = integralImage[getActualPositionX(x)][getActualPositionY(y)];
int val2 = integralImage[getActualPositionX(x + width)][getActualPositionY(y)];
int val4 = integralImage[getActualPositionX(x)][getActualPositionY(y + height)];
int val5 = integralImage[getActualPositionX(x + width)][getActualPositionY(y + height)];
int firstSum = val5 - val2 - val4 + val1;
return firstSum / pixelsPerSquare;
}
public double getOneRectangleMin(int x, int y, int width, int height) {
if (x + width > windowBlocksX) {
if (width > windowBlocksX) {
System.err.println("Width is too much!");
} else {
x = windowBlocksX - width;
}
}
if (y + height > windowBlocksY) {
if (height > windowBlocksY) {
System.err.println("Height is too much!");
} else {
y = windowBlocksY - height;
}
}
x = getActualPositionX(x);
y = getActualPositionY(y);
// how many pixels
int min = 256;
for (int dY = y; dY < y + (height * blockHeight); dY++) {
for (int dX = x; dX < x + (width * blockWidth); dX++) {
if (image.getGreyValue(dX, dY) < min) {
min = image.getGreyValue(dX, dY);
}
}
}
return min;
}
public double getOneRectangleMax(int x, int y, int width, int height) {
if (x + width > windowBlocksX) {
if (width > windowBlocksX) {
System.err.println("Width is too much!");
} else {
x = windowBlocksX - width;
}
}
if (y + height > windowBlocksY) {
if (height > windowBlocksY) {
System.err.println("Height is too much!");
} else {
y = windowBlocksY - height;
}
}
x = getActualPositionX(x);
y = getActualPositionY(y);
// how many pixels
int max = 0;
for (int dY = y; dY < y + (height * blockHeight); dY++) {
for (int dX = x; dX < x + (width * blockWidth); dX++) {
if (image.getGreyValue(dX, dY) > max) {
max = image.getGreyValue(dX, dY);
}
}
}
return max;
}
public double getOneRectangleStdDev(int x, int y, int width, int height) {
if (x + width > windowBlocksX) {
if (width > windowBlocksX) {
System.err.println("Width is too much!");
} else {
x = windowBlocksX - width;
}
}
if (y + height > windowBlocksY) {
if (height > windowBlocksY) {
System.err.println("Height is too much!");
} else {
y = windowBlocksY - height;
}
}
x = getActualPositionX(x);
y = getActualPositionY(y);
// how many pixels
FastStatistics solver = new FastStatistics();
for (int dY = y; dY < y + (height * blockHeight); dY++) {
for (int dX = x; dX < x + (width * blockWidth); dX++) {
solver.addData(image.getGreyValue(dX, dY));
}
}
return solver.getStandardDeviation();
}
/**
* @param x A Zero Indexed value
* @param y A Zero Indexed value
* @param width
* @param height
* @param adjacency
* @param shape
* @return The difference of sums between two adjacent rectangles.
*/
public float getTwoRectangleFeature(int x, int y, int width, int height, int adjacency, int shape) {
if (adjacency == HORIZONTALLY_ADJACENT) {
if (x + width + width > windowBlocksX) {
if (width + width > windowBlocksX) {
System.err.println("Width is too much!");
} else {
x = windowBlocksX - (2 * width);
}
}
if (y + height > windowBlocksY) {
if (height > windowBlocksY) {
System.err.println("Height is too much!");
} else {
y = windowBlocksY - height;
}
}
/**
* 1-----2-----3
* | 1st | 2nd |
* 4-----5-----6
*/
int val1 = integralImage[getActualPositionX(x)][getActualPositionY(y)];
int val2 = integralImage[getActualPositionX(x + width)][getActualPositionY(y)];
int val3 = integralImage[getActualPositionX(x + width + width)][getActualPositionY(y)];
int val4 = integralImage[getActualPositionX(x)][getActualPositionY(y + height)];
int val5 = integralImage[getActualPositionX(x + width)][getActualPositionY(y + height)];
int val6 = integralImage[getActualPositionX(x + width + width)][getActualPositionY(y + height)];
int firstSum = val5 - val2 - val4 + val1;
int secondSum = val6 - val3 - val5 + val2;
// average function
float pixelsPerSquare = (width * blockWidth) * (height * blockHeight);
return (shape == FIRST_SHAPE ? firstSum - secondSum : secondSum - firstSum) / (1 * pixelsPerSquare);
} else {
if (x + width > windowBlocksX) {
if (width > windowBlocksX) {
System.err.println("Width is too much!");
} else {
x = windowBlocksX - width;
}
}
if (y + height + height > windowBlocksY) {
if (height + height > windowBlocksY) {
System.err.println("Height is too much!");
} else {
y = windowBlocksY - (2 * height);
}
}
/**
* 1-----2
* | 1st |
* 3-----4
* | 2nd |
* 5-----6
*/
int val1 = integralImage[getActualPositionX(x)][getActualPositionY(y)];
int val2 = integralImage[getActualPositionX(x + width)][getActualPositionY(y)];
int val3 = integralImage[getActualPositionX(x)][getActualPositionY(y + height)];
int val4 = integralImage[getActualPositionX(x + width)][getActualPositionY(y + height)];
int val5 = integralImage[getActualPositionX(x)][getActualPositionY(y + height + height)];
int val6 = integralImage[getActualPositionX(x + width)][getActualPositionY(y + height + height)];
int firstSum = val4 - val2 - val3 + val1;
int secondSum = val6 - val4 - val5 + val3;
// average function
float pixelsPerSquare = (width * blockWidth) * (height * blockHeight);
return (shape == FIRST_SHAPE ? firstSum - secondSum : secondSum - firstSum) / (1 * pixelsPerSquare);
}
}
public float getThreeRectangleFeature(int x, int y, int width, int height, int adjacency) {
//System.out.println("x: " + x + ", y: " + y + ", width: " + width + ", height: " + height);
if (adjacency == HORIZONTALLY_ADJACENT) {
if ((width * 3) > getWindowBlocksX()) {
width = getWindowBlocksX() / 3;
}
if (x + (3 * width) > windowBlocksX) {
x = windowBlocksX - (3 * width);
}
if (y + height + 1 > windowBlocksY) {
y = windowBlocksY - height;
}
//System.out.println("x: " + x + ", y: " + y + ", width: " + width + ", height: " + height);
/**
* 1-----2-----3-----4
* | 1st | 2nd | 3rd |
* 5-----6-----7-----8
*/
int val1 = integralImage[getActualPositionX(x)][getActualPositionY(y)];
int val2 = integralImage[getActualPositionX(x + width)][getActualPositionY(y)];
int val3 = integralImage[getActualPositionX(x + width + width)][getActualPositionY(y)];
int val4 = integralImage[getActualPositionX(x + width + width + width)][getActualPositionY(y)];
int val5 = integralImage[getActualPositionX(x)][getActualPositionY(y + height)];
int val6 = integralImage[getActualPositionX(x + width)][getActualPositionY(y + height)];
int val7 = integralImage[getActualPositionX(x + width + width)][getActualPositionY(y + height)];
int val8 = integralImage[getActualPositionX(x + width + width + width)][getActualPositionY(y + height)];
int firstRegion = val6 - val2 - val5 + val1;
int secondRegion = val7 - val3 - val6 + val2;
int thirdRegion = val8 - val4 - val7 + val3;
// average function
float pixelsPerSquare = (width * blockWidth) * (height * blockHeight);
return (firstRegion + thirdRegion - secondRegion) / pixelsPerSquare;
} else {
// System.out.println("VERTICALLY_ADJACENT");
if ((height * 3) > windowBlocksY) {
height = getWindowBlocksY() / 3;
}
if (x + width + 1 > windowBlocksX) {
x = windowBlocksX - width;
}
if (y + (3 * height) + 1 > windowBlocksY) {
y = windowBlocksY - (3 * height);
}
/**
* 1-----2
* | 1st |
* 3-----4
* | 2nd |
* 5-----6
* | 2nd |
* 7-----8
*/
int val1 = integralImage[getActualPositionX(x)][getActualPositionY(y)];
int val2 = integralImage[getActualPositionX(x + width)][getActualPositionY(y)];
int val3 = integralImage[getActualPositionX(x)][getActualPositionY(y + height)];
int val4 = integralImage[getActualPositionX(x + width)][getActualPositionY(y + height)];
int val5 = integralImage[getActualPositionX(x)][getActualPositionY(y + height + height)];
int val6 = integralImage[getActualPositionX(x + width)][getActualPositionY(y + height + height)];
int val7 = integralImage[getActualPositionX(x)][getActualPositionY(y + height + height + height)];
int val8 = integralImage[getActualPositionX(x + width)][getActualPositionY(y + height + height + height)];
int firstRegion = val4 - val2 - val3 + val1;
int secondRegion = val6 - val4 - val5 + val3;
int thirdRegion = val8 - val6 - val7 + val5;
// average function
float pixelsPerSquare = (width * blockWidth) * (height * blockHeight);
return (firstRegion + thirdRegion - secondRegion) / (2 * pixelsPerSquare);
}
}
public float getFourRectangleFeature(int x, int y, int width, int height, int shape) {
if (x + (2 * width) + 1 > windowBlocksX) {
x = windowBlocksX - (2 * width);
}
if (y + (2 * height) + 1 > windowBlocksY) {
y = windowBlocksY - (2 * height);
}
/**
* 1-----2-----3
* | 1st | 2nd |
* 4-----5-----6
* | 2nd | 1st |
* 7-----8-----9
*/
int val1 = integralImage[getActualPositionX(x)][getActualPositionY(y)];
int val2 = integralImage[getActualPositionX(x + width)][getActualPositionY(y)];
int val3 = integralImage[getActualPositionX(x + width + width)][getActualPositionY(y)];
int val4 = integralImage[getActualPositionX(x)][getActualPositionY(y + height)];
int val5 = integralImage[getActualPositionX(x + width)][getActualPositionY(y + height)];
int val6 = integralImage[getActualPositionX(x + width + width)][getActualPositionY(y + height)];
int val7 = integralImage[getActualPositionX(x)][getActualPositionY(y + height + height)];
int val8 = integralImage[getActualPositionX(x + width)][getActualPositionY(y + height + height)];
int val9 = integralImage[getActualPositionX(x + width + width)][getActualPositionY(y + height + height)];
int largeSquare = val9 - val3 - val7 + val1;
// average function
float pixelsPerSquare = (width * blockWidth) * (height * blockHeight);
if (shape == FIRST_SHAPE) {
int topLeft = val5 - val2 - val4 + val1;
int bottomRight = val9 - val6 - val8 + val5;
return (largeSquare - (2 * (topLeft + bottomRight))) / (2 * pixelsPerSquare);
} else {
int topRight = val6 - val3 - val5 + val2;
int bottomLeft = val8 - val5 - val7 + val4;
return (largeSquare - (2 * (topRight + bottomLeft))) / (2 * pixelsPerSquare);
}
}
// More Getter Functions Below
public int getWindowBlocksX() {
return windowBlocksX;
}
/**
* There are a certain number of blocks
*/
public int getWindowBlocksY() {
return windowBlocksY;
}
public int getWindowWidth() {
return windowWidth;
}
public int getWindowHeight() {
return windowHeight;
}
public int getWindowX() {
return windowX;
}
public int getWindowY() {
return windowY;
}
public int getWidth() {
return imageWidth;
}
public int getHeight() {
return imageHeight;
}
public File getFile() {
return image.getFile();
}
public String getFilename() {
return getFile().getName();
}
}