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.tools.vis; 031 032import gnu.trove.list.array.TIntArrayList; 033import gnu.trove.set.hash.TIntHashSet; 034 035import java.io.File; 036import java.io.IOException; 037import java.util.ArrayList; 038import java.util.List; 039 040import org.kohsuke.args4j.Argument; 041import org.kohsuke.args4j.CmdLineException; 042import org.kohsuke.args4j.CmdLineParser; 043import org.kohsuke.args4j.Option; 044import org.openimaj.feature.local.list.LocalFeatureList; 045import org.openimaj.feature.local.list.MemoryLocalFeatureList; 046import org.openimaj.image.FImage; 047import org.openimaj.image.ImageUtilities; 048import org.openimaj.image.feature.local.keypoints.Keypoint; 049import org.openimaj.image.feature.local.keypoints.KeypointVisualizer; 050import org.openimaj.image.feature.local.keypoints.quantised.QuantisedKeypoint; 051 052/** 053 * Tool to extract prototypical quantised SIFT patches from a set of images and their 054 * corresponding quantised SIFT features. 055 * 056 * Patches are extracted by blurring the image to the correct scale and then extracting 057 * the patch using its geometry. It is assumed that the patch size is 2 * 2 * 3 * scale 058 * as per the original geometry suggested in Lowe's SIFT paper. 059 * 060 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 061 * 062 */ 063public class QuantisedSIFTPatchExtractor { 064 @Option(name="--path", aliases="-p", required=true, usage="Path to the directory structure containing the quantised SIFT features", metaVar="path") 065 File featurePath; 066 067 @Option(name="--feature-matches", aliases="-fm", required=true, usage="regex to test whether a file is a feature") 068 String featureMatchesRegex; 069 070 @Option(name="--image-match", aliases="-im", required=true, usage="regex to apply to feature paths to select required parts") 071 String imageMatchRegex; 072 073 @Option(name="--image-replace", aliases="-ir", required=true, usage="regex to build image path from feature path using the --image-match") 074 String imageReplaceRegex; 075 076 @Option(name="--output", aliases="-o", required=false, usage="output directory") 077 File outputDir; 078 079 @Option(name="--output-file", aliases="-of", required=false, usage="output file for aggregated image") 080 File outputImageFile; 081 082 @Argument(required=false, usage="required term identifiers") 083 List<Integer> requiredIds; 084 TIntArrayList requiredIdsList; 085 086 TIntHashSet foundTerms = new TIntHashSet(); 087 088 FImage outputImage; 089 090 void process() throws IOException { 091 if (outputDir != null) outputDir.mkdirs(); 092 093 if (outputImageFile != null && requiredIdsList != null) { 094 outputImage = new FImage(128*requiredIdsList.size(), 128); 095 } 096 097 process(featurePath); 098 } 099 100 protected boolean process(File file) throws IOException { 101 if (file.isDirectory()) { 102 for (File f : file.listFiles()) { 103 boolean ret = process(f); 104 if (ret) return true; 105 } 106 } else { 107 if (file.getAbsolutePath().matches(featureMatchesRegex)) { 108 return processFeatureFile(file); 109 } 110 } 111 return false; 112 } 113 114 protected FImage loadCorrespondingImage(File feature) throws IOException { 115 //get image name/path 116 File imagePath = new File(feature.getAbsolutePath().replaceAll(imageMatchRegex, imageReplaceRegex)); 117 System.out.println(feature + " -> " + imagePath); 118 119 return ImageUtilities.readF(imagePath); 120 } 121 122 protected boolean processFeatureFile(File featureFile) throws IOException { 123 LocalFeatureList<QuantisedKeypoint> qkeys = MemoryLocalFeatureList.read(featureFile, QuantisedKeypoint.class); 124 FImage image = null; 125 126 for (QuantisedKeypoint kpt : qkeys) { 127 if ((requiredIdsList == null || requiredIdsList.contains(kpt.id)) && !foundTerms.contains(kpt.id)) { 128 if (image == null) image = loadCorrespondingImage(featureFile); 129 130 Keypoint key = new Keypoint(); 131 key.y = kpt.location.y; 132 key.x = kpt.location.x; 133 key.scale = kpt.location.scale; 134 key.ori = kpt.location.orientation; 135 136 List<Keypoint> keys = new ArrayList<Keypoint>(); 137 keys.add(key); 138 139 KeypointVisualizer<Float, FImage> viz = new KeypointVisualizer<Float, FImage>(image, keys); 140 FImage patch = viz.getPatches(128).get(key); 141 142 if (outputDir != null) { 143 File patchout = new File(outputDir, "patch-" + kpt.id + ".png"); 144 ImageUtilities.write(patch, "png", patchout); 145 } 146 147 if (outputImage != null) { 148 System.out.println("term: " + kpt.id + " " + requiredIdsList.indexOf(kpt.id)); 149 150 outputImage.createRenderer().drawImage(patch, 128 * requiredIdsList.indexOf(kpt.id), 0); 151 ImageUtilities.write(outputImage, outputImageFile); 152 } 153 154 foundTerms.add(kpt.id); 155 156 if (requiredIdsList != null && foundTerms.size() == requiredIdsList.size()) { 157 return true; 158 } 159 } 160 } 161 162 return false; 163 } 164 165 static QuantisedSIFTPatchExtractor load(String [] args) { 166 QuantisedSIFTPatchExtractor options = new QuantisedSIFTPatchExtractor(); 167 CmdLineParser parser = new CmdLineParser( options ); 168 169 try { 170 parser.parseArgument( args ); 171 if (options.requiredIds != null && options.requiredIds.size() > 0) { 172 options.requiredIdsList = new TIntArrayList(); 173 for (int i : options.requiredIds) options.requiredIdsList.add(i); 174 } 175 } catch( CmdLineException e ) { 176 System.err.println( e.getMessage() ); 177 System.err.println( "java " + QuantisedSIFTPatchExtractor.class.getName() + " options..."); 178 parser.printUsage( System.err ); 179 System.exit(1); 180 } 181 182 return options; 183 } 184 185 /** 186 * The main method of the tool. 187 * @param args 188 * @throws IOException 189 */ 190 public static void main(String [] args) throws IOException { 191 QuantisedSIFTPatchExtractor extr = QuantisedSIFTPatchExtractor.load(args); 192 extr.process(); 193 } 194}