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}