View Javadoc

1   /**
2    * Copyright (c) 2011, The University of Southampton and the individual contributors.
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without modification,
6    * are permitted provided that the following conditions are met:
7    *
8    *   * 	Redistributions of source code must retain the above copyright notice,
9    * 	this list of conditions and the following disclaimer.
10   *
11   *   *	Redistributions in binary form must reproduce the above copyright notice,
12   * 	this list of conditions and the following disclaimer in the documentation
13   * 	and/or other materials provided with the distribution.
14   *
15   *   *	Neither the name of the University of Southampton nor the names of its
16   * 	contributors may be used to endorse or promote products derived from this
17   * 	software without specific prior written permission.
18   *
19   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22   * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
23   * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26   * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29   */
30  package org.openimaj.demos;
31  
32  import gnu.trove.map.hash.TIntObjectHashMap;
33  import gnu.trove.map.hash.TObjectIntHashMap;
34  import gnu.trove.procedure.TObjectIntProcedure;
35  
36  import java.io.IOException;
37  import java.net.MalformedURLException;
38  import java.net.URL;
39  import java.util.ArrayList;
40  import java.util.Collections;
41  import java.util.Comparator;
42  import java.util.HashSet;
43  import java.util.List;
44  import java.util.Set;
45  
46  import org.openimaj.citation.annotation.Reference;
47  import org.openimaj.citation.annotation.ReferenceType;
48  import org.openimaj.feature.local.filter.ByteEntropyFilter;
49  import org.openimaj.image.FImage;
50  import org.openimaj.image.ImageUtilities;
51  import org.openimaj.image.MBFImage;
52  import org.openimaj.image.colour.Transforms;
53  import org.openimaj.image.feature.local.engine.DoGSIFTEngine;
54  import org.openimaj.image.feature.local.keypoints.Keypoint;
55  import org.openimaj.image.processing.resize.ResizeProcessor;
56  import org.openimaj.lsh.functions.DoubleGaussianFactory;
57  import org.openimaj.lsh.sketch.IntLSHSketcher;
58  import org.openimaj.util.filter.FilterUtils;
59  import org.openimaj.util.hash.HashFunction;
60  import org.openimaj.util.hash.HashFunctionFactory;
61  import org.openimaj.util.hash.modifier.LSBModifier;
62  import org.openimaj.util.pair.ObjectIntPair;
63  
64  import cern.jet.random.engine.MersenneTwister;
65  
66  /**
67   * <p>
68   * An in-memory image duplicate database. Stores hashes of features as the basis
69   * of determining duplicates.
70   * </p>
71   * <p>
72   * This is a basic in-memory implementation of the approach described by Dong,
73   * Wang and Li for high-confidence duplicate detection. It does not currently
74   * implement the query expansion phase based on a graph cut of the duplicity
75   * graph, but instead relies directly on a simple thresholded count of
76   * collisions.
77   * </p>
78   *
79   * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
80   *
81   * @param <T>
82   *            type of metadata being stored
83   *
84   * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
85   */
86  @Reference(
87  		author = { "Wei Dong", "Zhe Wang", "Kai Li" },
88  		title = "High-Confidence Near-Duplicate Image Detection",
89  		type = ReferenceType.Inproceedings,
90  		year = "2012",
91  		booktitle = "ACM International Conference on Multimedia Retrieval",
92  		customData = { "location", "Hong Kong, China" })
93  public class BasicDuplicateImageDatabase<T> {
94  	private final int ndims = 128;
95  	private final double w = 6.0;
96  	private final int nbits = 128;
97  	private final float LOG_BASE = 0.001f;
98  	private final int maxImageSize = 150;
99  	private final int seed = 0;
100 	private int minScore = 10;
101 
102 	private DoGSIFTEngine engine;
103 	final ByteEntropyFilter filter;
104 	private IntLSHSketcher<double[]> sketcher;
105 	protected List<TIntObjectHashMap<Set<T>>> database;
106 
107 	public BasicDuplicateImageDatabase() {
108 		engine = new DoGSIFTEngine();
109 		engine.getOptions().setDoubleInitialImage(false);
110 
111 		filter = new ByteEntropyFilter();
112 
113 		final MersenneTwister rng = new MersenneTwister(seed);
114 		final DoubleGaussianFactory gauss = new DoubleGaussianFactory(this.ndims, rng, w);
115 		final HashFunctionFactory<double[]> factory = new HashFunctionFactory<double[]>() {
116 			@Override
117 			public HashFunction<double[]> create() {
118 				return new LSBModifier<double[]>(gauss.create());
119 			}
120 		};
121 
122 		sketcher = new IntLSHSketcher<double[]>(factory, nbits);
123 		database = new ArrayList<TIntObjectHashMap<Set<T>>>(sketcher.arrayLength());
124 
125 		for (int i = 0; i < sketcher.arrayLength(); i++)
126 			database.add(new TIntObjectHashMap<Set<T>>());
127 	}
128 
129 	/**
130 	 * Perform log-scaling element-wise on a feature
131 	 *
132 	 * @param v
133 	 *            the feature
134 	 * @return the scaled feature
135 	 */
136 	private double[] logScale(double[] v) {
137 		final double[] dfv = new double[v.length];
138 		final double s = -Math.log(LOG_BASE);
139 
140 		for (int i = 0; i < v.length; i++) {
141 			double d = (v[i] + 128.0) / 256.0;
142 
143 			if (d < LOG_BASE)
144 				d = LOG_BASE;
145 			d = (Math.log(d) + s) / s;
146 			if (d > 1.0)
147 				d = 1.0;
148 
149 			dfv[i] = d;
150 		}
151 		return dfv;
152 	}
153 
154 	/**
155 	 * Extract hashed features from an image
156 	 *
157 	 * @param image
158 	 *            the image to index
159 	 * @return the features
160 	 */
161 	public int[][] extractFeatures(FImage image) {
162 		image = ResizeProcessor.resizeMax(image, maxImageSize);
163 
164 		final List<Keypoint> features = engine.findFeatures(image);
165 		final List<Keypoint> filtered = FilterUtils.filter(features, filter);
166 
167 		final int[][] sketches = new int[filtered.size()][];
168 		for (int i = 0; i < filtered.size(); i++) {
169 			final Keypoint k = filtered.get(i);
170 			final double[] fv = logScale(k.getFeatureVector().asDoubleVector());
171 			sketches[i] = sketcher.createSketch(fv);
172 		}
173 
174 		return sketches;
175 	}
176 
177 	/**
178 	 * Extract hashed features from an image
179 	 *
180 	 * @param image
181 	 *            the image to index
182 	 * @return the features
183 	 */
184 	public int[][] extractFeatures(MBFImage image) {
185 		return extractFeatures(Transforms.calculateIntensityNTSC(image));
186 	}
187 
188 	/**
189 	 * Index a new image based on its features
190 	 *
191 	 * @param features
192 	 *            the image to index
193 	 * @param metadata
194 	 *            the metadata for the image
195 	 */
196 	public synchronized void indexImage(int[][] features, T metadata) {
197 		for (final int[] sketch : features) {
198 			for (int i = 0; i < sketch.length; i++) {
199 				final int sk = sketch[i];
200 				Set<T> s = database.get(i).get(sk);
201 				if (s == null)
202 					database.get(i).put(sk, s = new HashSet<T>());
203 
204 				s.add(metadata);
205 			}
206 		}
207 	}
208 
209 	/**
210 	 * Search for a given image (represented by its features) in the database
211 	 *
212 	 * @param features
213 	 *            the features
214 	 * @return the list of matching images and their scores (bigger == more
215 	 *         chance of match)
216 	 */
217 	public List<ObjectIntPair<T>> search(int[][] features) {
218 		final TObjectIntHashMap<T> counter = new TObjectIntHashMap<>();
219 
220 		for (final int[] sketch : features) {
221 			for (int i = 0; i < sketch.length; i++) {
222 				final int sk = sketch[i];
223 				final Set<T> s = database.get(i).get(sk);
224 				if (s != null) {
225 					for (final T key : s) {
226 						counter.adjustOrPutValue(key, 1, 1);
227 					}
228 				}
229 			}
230 		}
231 
232 		final List<ObjectIntPair<T>> result = new ArrayList<>();
233 
234 		counter.forEachEntry(new TObjectIntProcedure<T>() {
235 			@Override
236 			public boolean execute(T a, int b) {
237 				if (b > minScore)
238 					result.add(ObjectIntPair.pair(a, b));
239 				return false;
240 			}
241 		});
242 
243 		Collections.sort(result, new Comparator<ObjectIntPair<T>>() {
244 			@Override
245 			public int compare(ObjectIntPair<T> o1, ObjectIntPair<T> o2) {
246 				return Integer.compare(o2.second, o1.second);
247 			}
248 		});
249 
250 		return result;
251 	}
252 
253 	public static void main(String[] args) throws MalformedURLException, IOException {
254 		// Construct DB
255 		final BasicDuplicateImageDatabase<String> database = new BasicDuplicateImageDatabase<>();
256 
257 		// add some images
258 		final int[][] f1 = database.extractFeatures(ImageUtilities.readF(new URL(
259 				"http://comp3204.ecs.soton.ac.uk/cw/dog.jpg")));
260 		database.indexImage(f1, "dog");
261 
262 		final int[][] f2 = database.extractFeatures(ImageUtilities.readF(new URL(
263 				"http://comp3204.ecs.soton.ac.uk/cw/cat.jpg")));
264 		database.indexImage(f2, "cat");
265 
266 		// test to see that the images can be found
267 		System.out.println(database.search(f1).get(0).first); // should be dog
268 		System.out.println(database.search(f2).get(0).first); // should be cat
269 
270 		// test with an unindexed image
271 		final int[][] f3 = database.extractFeatures(ImageUtilities.readF(new URL(
272 				"http://comp3204.ecs.soton.ac.uk/cw/hybrid_image.jpg")));
273 		System.out.println(database.search(f3)); // should be an empty list
274 	}
275 }