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.objectdetection; 031 032import java.util.ArrayList; 033import java.util.List; 034 035import org.openimaj.image.FImage; 036import org.openimaj.image.Image; 037import org.openimaj.image.processing.resize.ResizeProcessor; 038import org.openimaj.image.processing.transform.ProjectionProcessor; 039import org.openimaj.image.processor.SinglebandImageProcessor; 040import org.openimaj.math.geometry.shape.Rectangle; 041import org.openimaj.math.geometry.shape.Shape; 042import org.openimaj.math.geometry.transforms.TransformUtilities; 043 044import Jama.Matrix; 045 046/** 047 * An {@link ObjectDetector} that wraps another {@link ObjectDetector} and 048 * performs rotation simulations on the images it passes to the internal 049 * detector. This allows a non rotation invariant detector to be able to detect 050 * rotated objects. 051 * <p> 052 * This implementation allows the input image to be scaled in order to reduce 053 * computational complexity, and control over the simulated angles. 054 * 055 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 056 * 057 * @param <IMAGE> 058 * Type of image in which the detection happens 059 * @param <PIXEL> 060 * Type of pixel of the image in which the detection happens 061 * @param <DETECTED_OBJECT> 062 * The type of object emitted by the inner detector 063 */ 064public class RotationSimulationObjectDetector<IMAGE extends Image<PIXEL, IMAGE> & SinglebandImageProcessor.Processable<Float, FImage, IMAGE>, PIXEL, DETECTED_OBJECT> 065 implements 066 ObjectDetector<IMAGE, TransformedDetection<DETECTED_OBJECT>> 067{ 068 private ObjectDetector<IMAGE, DETECTED_OBJECT> detector; 069 private Rectangle roi; 070 private float scalefactor = 1f; 071 private float[] simulationAngles; 072 073 /** 074 * Construct with the given inner detector and number of rotations. 075 * Simulations will occur every 360/numRotations degrees. 076 * 077 * @param detector 078 * the internal detector 079 * @param numRotations 080 * the number of rotations 081 */ 082 public RotationSimulationObjectDetector(ObjectDetector<IMAGE, DETECTED_OBJECT> detector, int numRotations) { 083 this.detector = detector; 084 this.simulationAngles = computeAngles(numRotations); 085 } 086 087 /** 088 * Construct with the given inner detector, number of rotations and scale 089 * factor. Simulations will occur every 360/numRotations degrees. 090 * 091 * @param detector 092 * the internal detector 093 * @param numRotations 094 * the number of rotations 095 * @param scalefactor 096 * the amount by which to scale the input image prior to passing 097 * to the inner detector 098 */ 099 public RotationSimulationObjectDetector(ObjectDetector<IMAGE, DETECTED_OBJECT> detector, int numRotations, 100 float scalefactor) 101 { 102 this(detector, numRotations); 103 this.scalefactor = scalefactor; 104 } 105 106 /** 107 * Construct with the given inner detector, simulation angles and scale 108 * factor. Simulations will occur every 360/numRotations degrees. 109 * 110 * @param detector 111 * the internal detector 112 * @param simulationAngles 113 * the rotation angles to simulate 114 * @param scalefactor 115 * the amount by which to scale the input image prior to passing 116 * to the inner detector 117 */ 118 public RotationSimulationObjectDetector(ObjectDetector<IMAGE, DETECTED_OBJECT> detector, float[] simulationAngles, 119 float scalefactor) 120 { 121 this.detector = detector; 122 this.simulationAngles = simulationAngles; 123 this.scalefactor = scalefactor; 124 } 125 126 private float[] computeAngles(int numRotations) { 127 final float[] angles = new float[numRotations]; 128 129 for (int i = 1; i < numRotations; i++) { 130 angles[i] = (float) (2 * i * Math.PI / numRotations); 131 } 132 133 return angles; 134 } 135 136 @Override 137 public List<TransformedDetection<DETECTED_OBJECT>> detect(IMAGE image) { 138 final List<TransformedDetection<DETECTED_OBJECT>> results = new ArrayList<TransformedDetection<DETECTED_OBJECT>>(); 139 140 Matrix scale; 141 142 if (scalefactor != 1) { 143 image = image.process(new ResizeProcessor(scalefactor)); 144 scale = TransformUtilities.scaleMatrix(scalefactor, scalefactor); 145 } else { 146 scale = Matrix.identity(3, 3); 147 } 148 149 for (final float angle : simulationAngles) { 150 if (angle == 0) { 151 detectObjects(image, scale, results); 152 } else { 153 final Matrix matrix = TransformUtilities.rotationMatrix(angle); 154 final IMAGE rimg = ProjectionProcessor.project(image, matrix); 155 156 final Rectangle actualBounds = image.getBounds(); 157 final Shape transformedActualBounds = actualBounds.transform(matrix); 158 final double tminX = transformedActualBounds.minX(); 159 final double tminY = transformedActualBounds.minY(); 160 161 final int minc = (int) Math.floor(tminX); 162 final int minr = (int) Math.floor(tminY); 163 164 matrix.set(0, 2, -minc); 165 matrix.set(1, 2, -minr); 166 167 detectObjects(rimg, matrix.times(scale), results); 168 } 169 } 170 171 return results; 172 } 173 174 private void detectObjects(IMAGE image, Matrix transform, List<TransformedDetection<DETECTED_OBJECT>> results) { 175 if (this.roi != null) { 176 final Rectangle troi = roi.transform(transform).calculateRegularBoundingBox(); 177 detector.setROI(troi); 178 } 179 180 final List<DETECTED_OBJECT> detections = detector.detect(image); 181 182 if (detections == null) 183 return; 184 185 for (final DETECTED_OBJECT o : detections) { 186 results.add(new TransformedDetection<DETECTED_OBJECT>(o, transform.inverse())); 187 } 188 } 189 190 @Override 191 public void setROI(Rectangle roi) { 192 this.roi = roi; 193 } 194 195 /** 196 * Get the internal detector 197 * 198 * @return the internal detector 199 */ 200 public ObjectDetector<IMAGE, DETECTED_OBJECT> getInnerDetector() { 201 return detector; 202 } 203}