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.feature.ltp; 031 032import java.io.DataInput; 033import java.io.DataOutput; 034import java.io.IOException; 035import java.util.ArrayList; 036import java.util.List; 037 038import org.openimaj.citation.annotation.Reference; 039import org.openimaj.citation.annotation.ReferenceType; 040import org.openimaj.image.FImage; 041import org.openimaj.image.ImageUtilities; 042import org.openimaj.image.analysis.algorithm.EuclideanDistanceTransform; 043import org.openimaj.image.feature.dense.binarypattern.LocalTernaryPattern; 044import org.openimaj.image.feature.dense.binarypattern.UniformBinaryPattern; 045import org.openimaj.image.pixel.Pixel; 046import org.openimaj.image.processing.algorithm.DifferenceOfGaussian; 047import org.openimaj.image.processing.algorithm.GammaCorrection; 048import org.openimaj.image.processing.algorithm.MaskedRobustContrastEqualisation; 049import org.openimaj.image.processing.face.feature.FacialFeature; 050import org.openimaj.io.wrappers.ReadableListBinary; 051import org.openimaj.io.wrappers.WriteableArrayBinary; 052import org.openimaj.io.wrappers.WriteableListBinary; 053 054/** 055 * Base class for LTP based features using a 056 * truncated Euclidean distance transform 057 * to estimate the distances within each slice. 058 * 059 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 060 */ 061@Reference( 062 type = ReferenceType.Article, 063 author = { "Tan, Xiaoyang", "Triggs, Bill" }, 064 title = "Enhanced local texture feature sets for face recognition under difficult lighting conditions", 065 year = "2010", 066 journal = "Trans. Img. Proc.", 067 pages = { "1635", "1650" }, 068 url = "http://dx.doi.org/10.1109/TIP.2010.2042645", 069 month = "June", 070 number = "6", 071 publisher = "IEEE Press", 072 volume = "19" 073 ) 074public class AbstractLtpDtFeature implements FacialFeature { 075 /** 076 * The pixels forming the binary patterns in each slice 077 */ 078 public List<List<Pixel>> ltpPixels; 079 080 private int width; 081 private int height; 082 private LTPWeighting weighting; 083 084 private FImage[] cachedDistanceMaps; 085 086 /** 087 * Construct with given parameters. 088 * @param width the face patch width 089 * @param height the face patch height 090 * @param weighting the weighting scheme 091 * @param ltpPixels the pattern 092 */ 093 public AbstractLtpDtFeature(int width, int height, LTPWeighting weighting, List<List<Pixel>> ltpPixels) { 094 this.width = width; 095 this.height = height; 096 this.weighting = weighting; 097 this.ltpPixels = ltpPixels; 098 } 099 100 /** 101 * Get the Euclidean distance maps for each slice. The 102 * maps are cached internally and are generated on the 103 * first call to this method. 104 * 105 * @return the distance maps 106 */ 107 public FImage[] getDistanceMaps() { 108 if (cachedDistanceMaps == null) 109 cachedDistanceMaps = extractDistanceTransforms(constructSlices(ltpPixels, width, height), weighting); 110 111 return cachedDistanceMaps; 112 } 113 114 protected static FImage normaliseImage(FImage image, FImage mask) { 115 if (mask == null) { 116 return image.process(new GammaCorrection()) 117 .processInplace(new DifferenceOfGaussian()) 118 .processInplace(new MaskedRobustContrastEqualisation()); 119 } 120 121 return image.process(new GammaCorrection()) 122 .processInplace(new DifferenceOfGaussian()) 123 .processInplace(new MaskedRobustContrastEqualisation(mask)) 124 .multiply(mask); 125 } 126 127 protected static List<List<Pixel>> extractLTPSlicePixels(FImage image) { 128 LocalTernaryPattern ltp = new LocalTernaryPattern(2, 8, 0.1f); 129 image.analyseWith(ltp); 130 131 List<List<Pixel>> positiveSlices = UniformBinaryPattern.extractPatternPixels(ltp.getPositivePattern(), 8); 132 List<List<Pixel>> negativeSlices = UniformBinaryPattern.extractPatternPixels(ltp.getNegativePattern(), 8); 133 134 positiveSlices.addAll(negativeSlices); 135 136 return positiveSlices; 137 } 138 139 protected FImage[] extractDistanceTransforms(FImage [] slices, LTPWeighting weighting) { 140 FImage [] dist = new FImage[slices.length]; 141 int width = slices[0].width; 142 int height = slices[0].height; 143 int [][] indices = new int[height][width]; 144 145 for (int i=0; i<slices.length; i++) { 146 if (slices[i] == null) 147 continue; 148 149 dist[i] = new FImage(width, height); 150 151 EuclideanDistanceTransform.squaredEuclideanDistanceBinary(slices[i], dist[i], indices); 152 153 for (int y=0; y<height; y++) { 154 for (int x=0; x<width; x++) { 155 dist[i].pixels[y][x] = weighting.weightDistance((float)Math.sqrt(dist[i].pixels[y][x])); 156 } 157 } 158 } 159 160 return dist; 161 } 162 163 protected FImage [] constructSlices(List<List<Pixel>> ltpPixels, int width, int height) { 164 FImage[] slices = new FImage[ltpPixels.size()]; 165 166 for (int i=0; i<slices.length; i++) { 167 List<Pixel> pixels = ltpPixels.get(i); 168 169 if (pixels == null) 170 continue; 171 172 slices[i] = new FImage(width, height); 173 for (Pixel p : pixels) { 174 slices[i].pixels[p.y][p.x] = 1; 175 } 176 } 177 178 return slices; 179 } 180 181 @Override 182 public void readBinary(DataInput in) throws IOException { 183 new ReadableListBinary<List<Pixel>>(ltpPixels) { 184 185 @Override 186 protected List<Pixel> readValue(DataInput in) throws IOException { 187 List<Pixel> pixels = new ArrayList<Pixel>(); 188 189 new ReadableListBinary<Pixel>(pixels) { 190 @Override 191 protected Pixel readValue(DataInput in) throws IOException { 192 Pixel p = new Pixel(); 193 p.readBinary(in); 194 return p; 195 } 196 }.readBinary(in); 197 198 return pixels; 199 } 200 201 }.readBinary(in); 202 203 List<FImage> images = new ArrayList<FImage>(); 204 new ReadableListBinary<FImage>(images) { 205 @Override 206 protected FImage readValue(DataInput in) throws IOException { 207 return ImageUtilities.readF(in); 208 } 209 }.readBinary(in); 210 cachedDistanceMaps = images.size() == 0 ? null : images.toArray(new FImage[images.size()]); 211 } 212 213 @Override 214 public byte[] binaryHeader() { 215 // TODO Auto-generated method stub 216 return null; 217 } 218 219 @Override 220 public void writeBinary(DataOutput out) throws IOException { 221 new WriteableListBinary<List<Pixel>>(ltpPixels) { 222 223 @Override 224 protected void writeValue(List<Pixel> v, DataOutput out) throws IOException { 225 new WriteableListBinary<Pixel>(v) { 226 227 @Override 228 protected void writeValue(Pixel v, DataOutput out) throws IOException { 229 v.writeBinary(out); 230 } 231 }.writeBinary(out); 232 } 233 234 }.writeBinary(out); 235 236 new WriteableArrayBinary<FImage>(cachedDistanceMaps) { 237 @Override 238 protected void writeValue(FImage v, DataOutput out) throws IOException { 239 ImageUtilities.write(v, "png", out); 240 } 241 }.writeBinary(out); 242 } 243}