All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openimaj.image.processing.face.alignment.AffineAligner Maven / Gradle / Ivy

/**
 * Copyright (c) 2011, The University of Southampton and the individual contributors.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 *   * 	Redistributions of source code must retain the above copyright notice,
 * 	this list of conditions and the following disclaimer.
 *
 *   *	Redistributions in binary form must reproduce the above copyright notice,
 * 	this list of conditions and the following disclaimer in the documentation
 * 	and/or other materials provided with the distribution.
 *
 *   *	Neither the name of the University of Southampton nor the names of its
 * 	contributors may be used to endorse or promote products derived from this
 * 	software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.openimaj.image.processing.face.alignment;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

import org.openimaj.image.FImage;
import org.openimaj.image.ImageUtilities;
import org.openimaj.image.processing.face.detection.keypoints.FKEFaceDetector;
import org.openimaj.image.processing.face.detection.keypoints.FacialKeypoint;
import org.openimaj.image.processing.face.detection.keypoints.KEDetectedFace;

import Jama.Matrix;

/**
 * The {@link AffineAligner} attempts to find an affine transform that will warp
 * the face to the canonical frame by aligning facial keypoints.
 * 
 * @author Jonathon Hare ([email protected])
 * 
 */
public class AffineAligner implements FaceAligner {
	/**
	 * Normalised positions of facial parts
	 */
	protected final static float[][] Pmu = {
			{ 25.0347f, 34.1802f, 44.1943f, 53.4623f, 34.1208f, 39.3564f, 44.9156f, 31.1454f, 47.8747f },
			{ 34.1580f, 34.1659f, 34.0936f, 33.8063f, 45.4179f, 47.0043f, 45.3628f, 53.0275f, 52.7999f } };

	final static int CANONICAL_SIZE = 80;

	int facePatchWidth = 80;
	int facePatchHeight = 80;
	float facePatchBorderPercentage = 0.225f;

	private FImage mask;

	/**
	 * Default Constructor with the default mask (80x80) and default border
	 * percentage (0.225).
	 */
	public AffineAligner() {
		this(loadDefaultMask());
	};

	/**
	 * Construct with a mask (in the canonical frame) to apply after aligning
	 * and default border percentage (0.225).
	 * 
	 * @param mask
	 */
	public AffineAligner(FImage mask) {
		this.mask = mask;
	}

	/**
	 * Construct with a mask (in the canonical frame) to apply after aligning
	 * and default border percentage (0.225).
	 * 
	 * @param mask
	 *            the mask
	 * @param facePatchBorderPercentage
	 *            the proportional size (against the detection patch) of the
	 *            border for the crop. Higher values result in a more zoomed-in
	 *            face.
	 */
	public AffineAligner(FImage mask, float facePatchBorderPercentage) {
		this.mask = mask;
		this.facePatchBorderPercentage = facePatchBorderPercentage;
		this.facePatchHeight = mask.height;
		this.facePatchWidth = mask.width;
	}

	/**
	 * Construct with no mask and the given size and border.
	 * 
	 * @param facePatchWidth
	 *            the width of the desired aligned faces
	 * @param facePatchHeight
	 *            the height of the desired aligned faces
	 * @param facePatchBorderPercentage
	 *            the proportional size (against the detection patch) of the
	 *            border for the crop. Higher values result in a more zoomed-in
	 *            face.
	 */
	public AffineAligner(int facePatchWidth, int facePatchHeight, float facePatchBorderPercentage) {
		this.mask = new FImage(facePatchWidth, facePatchHeight);
		mask.fill(1f);
		this.facePatchBorderPercentage = facePatchBorderPercentage;
		this.facePatchWidth = facePatchWidth;
		this.facePatchHeight = facePatchHeight;
	}

	@Override
	public FImage align(KEDetectedFace descriptor) {
		final int facePatchSize = Math.max(facePatchWidth, facePatchHeight);
		final double size = facePatchSize + 2.0 * facePatchSize * facePatchBorderPercentage;
		final double sc = CANONICAL_SIZE / size;

		// do the scaling to everything but the translation!!!
		final Matrix T = estimateAffineTransform(descriptor);
		T.set(0, 0, T.get(0, 0) * sc);
		T.set(1, 1, T.get(1, 1) * sc);
		T.set(0, 1, T.get(0, 1) * sc);
		T.set(1, 0, T.get(1, 0) * sc);

		final FImage J = FKEFaceDetector.pyramidResize(descriptor.getFacePatch(), T);
		final FImage bigPatch = FKEFaceDetector.extractPatch(J, T, (int) size,
				(int) (facePatchSize * facePatchBorderPercentage));

		return bigPatch.extractCenter(facePatchWidth, facePatchHeight).extractROI(0, 0, facePatchWidth, facePatchHeight)
				.multiplyInplace(mask);
	}

	/**
	 * Estimate the affine transform required to warp a set of facial keypoints
	 * to their canonical coordinates.
	 * 

* Affine transform is from a flat, vertically oriented (canonical) face to * located face space. You'll need to invert this if you want to use it to * extract the face from the image. * * @param face * the face * @return the affine transform matrix */ public static Matrix estimateAffineTransform(KEDetectedFace face) { return estimateAffineTransform(face.getKeypoints()); } protected static Matrix estimateAffineTransform(FacialKeypoint[] pts) { float emin = Float.POSITIVE_INFINITY; Matrix T = null; for (int c = 0; c < 9; c++) { final Matrix A = new Matrix(8, 3); final Matrix B = new Matrix(8, 3); for (int i = 0, j = 0; i < 9; i++) { if (i != 8 - c) { A.set(j, 0, Pmu[0][i]); A.set(j, 1, Pmu[1][i]); A.set(j, 2, 1); B.set(j, 0, pts[i].position.x); B.set(j, 1, pts[i].position.y); B.set(j, 2, 1); j++; } } final Matrix Tc = A.solve(B).transpose(); final Matrix P1 = Tc.times(A.transpose()); final Matrix D = P1.minus(B.transpose()); float e = 0; for (int cc = 0; cc < D.getColumnDimension(); cc++) { float colsum = 0; for (int rr = 0; rr < D.getRowDimension(); rr++) { colsum += D.get(rr, cc) * D.get(rr, cc); ; } e += Math.sqrt(colsum); } if (e < emin) { emin = e; T = Tc; } } return T; } private static FImage loadDefaultMask() { try { return ImageUtilities.readF(FaceAligner.class.getResourceAsStream("affineMask.png")); } catch (final IOException e) { e.printStackTrace(); } return null; } @Override public FImage getMask() { return mask; } @Override public void readBinary(DataInput in) throws IOException { facePatchWidth = in.readInt(); facePatchHeight = in.readInt(); facePatchBorderPercentage = in.readFloat(); mask = ImageUtilities.readF(in); } @Override public byte[] binaryHeader() { return this.getClass().getName().getBytes(); } @Override public void writeBinary(DataOutput out) throws IOException { out.writeInt(facePatchWidth); out.writeInt(facePatchHeight); out.writeFloat(facePatchBorderPercentage); ImageUtilities.write(mask, "png", out); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy