001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030package org.openimaj.image.processing.face.alignment; 031 032import java.io.DataInput; 033import java.io.DataOutput; 034import java.io.IOException; 035 036import org.openimaj.image.FImage; 037import org.openimaj.image.ImageUtilities; 038import org.openimaj.image.processing.face.detection.keypoints.FKEFaceDetector; 039import org.openimaj.image.processing.face.detection.keypoints.FacialKeypoint; 040import org.openimaj.image.processing.face.detection.keypoints.FacialKeypoint.FacialKeypointType; 041import org.openimaj.image.processing.face.detection.keypoints.KEDetectedFace; 042import org.openimaj.math.geometry.transforms.TransformUtilities; 043 044import Jama.Matrix; 045 046/** 047 * Attempt to align a face by rotating and scaling it. Facial Keypoints are used 048 * to judge the alignment. Specifically, the distance between the eyes is 049 * normalised by scaling, and the eyes are rotated to be level. The face is then 050 * translated to a known position (again, based on the eyes). 051 * 052 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 053 * 054 */ 055public class RotateScaleAligner implements FaceAligner<KEDetectedFace> { 056 private static final FImage DEFAULT_MASK = loadDefaultMask(); 057 058 // Define the geometry 059 private int eyeDist = 68; 060 private int eyePaddingLeftRight = 6; 061 private int eyePaddingTop = 20; 062 063 private FImage mask = DEFAULT_MASK; 064 065 /** 066 * Default constructor with no mask. 067 */ 068 public RotateScaleAligner() { 069 } 070 071 /** 072 * Default constructor with no mask. 073 * 074 * @param targetSize 075 * target aligned image size 076 */ 077 public RotateScaleAligner(int targetSize) { 078 final int canonicalSize = 2 * eyePaddingLeftRight + eyeDist; 079 080 final double sf = targetSize / canonicalSize; 081 082 eyeDist = (int) (eyeDist * sf); 083 eyePaddingLeftRight = (targetSize - eyeDist) / 2; 084 eyePaddingTop = (int) (eyePaddingTop * sf); 085 } 086 087 /** 088 * Construct with a mask (in the canonical frame) to apply after alignment. 089 * 090 * @param mask 091 * The mask. 092 */ 093 public RotateScaleAligner(FImage mask) { 094 this.mask = mask; 095 } 096 097 @Override 098 public FImage align(KEDetectedFace descriptor) { 099 final FacialKeypoint lefteye = descriptor.getKeypoint(FacialKeypointType.EYE_LEFT_LEFT); 100 final FacialKeypoint righteye = descriptor.getKeypoint(FacialKeypointType.EYE_RIGHT_RIGHT); 101 102 final float dx = righteye.position.x - lefteye.position.x; 103 final float dy = righteye.position.y - lefteye.position.y; 104 105 final float rotation = (float) Math.atan2(dy, dx); 106 final float scaling = (float) (eyeDist / Math.sqrt(dx * dx + dy * dy)); 107 108 final float tx = lefteye.position.x - eyePaddingLeftRight / scaling; 109 final float ty = lefteye.position.y - eyePaddingTop / scaling; 110 111 final Matrix tf0 = TransformUtilities.scaleMatrix(scaling, scaling) 112 .times(TransformUtilities.translateMatrix(-tx, -ty)) 113 .times(TransformUtilities.rotationMatrixAboutPoint(-rotation, lefteye.position.x, lefteye.position.y)); 114 final Matrix tf = tf0.inverse(); 115 116 final FImage J = FKEFaceDetector.pyramidResize(descriptor.getFacePatch(), tf); 117 return FKEFaceDetector.extractPatch(J, tf, 2 * eyePaddingLeftRight + eyeDist, 0); 118 } 119 120 private static FImage loadDefaultMask() { 121 try { 122 return ImageUtilities.readF(FaceAligner.class.getResourceAsStream("affineMask.png")); 123 } catch (final IOException e) { 124 e.printStackTrace(); 125 } 126 127 return null; 128 } 129 130 @Override 131 public FImage getMask() { 132 return mask; 133 } 134 135 @Override 136 public void readBinary(DataInput in) throws IOException { 137 eyeDist = in.readInt(); 138 eyePaddingLeftRight = in.readInt(); 139 eyePaddingTop = in.readInt(); 140 141 mask = ImageUtilities.readF(in); 142 } 143 144 @Override 145 public byte[] binaryHeader() { 146 return this.getClass().getName().getBytes(); 147 } 148 149 @Override 150 public void writeBinary(DataOutput out) throws IOException { 151 out.writeInt(eyeDist); 152 out.writeInt(eyePaddingLeftRight); 153 out.writeInt(eyePaddingTop); 154 155 ImageUtilities.write(mask, "png", out); 156 } 157}