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}