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.workinprogress;
031
032import java.util.ArrayList;
033import java.util.Collections;
034import java.util.Comparator;
035import java.util.List;
036
037import org.openimaj.image.DisplayUtilities;
038import org.openimaj.image.FImage;
039import org.openimaj.image.MBFImage;
040import org.openimaj.image.analysis.watershed.feature.MomentFeature;
041import org.openimaj.image.colour.ColourSpace;
042import org.openimaj.image.colour.RGBColour;
043import org.openimaj.image.connectedcomponent.ConnectedComponentLabeler;
044import org.openimaj.image.feature.local.interest.EllipticInterestPointData;
045import org.openimaj.image.feature.local.interest.InterestPointDetector;
046import org.openimaj.image.pixel.ConnectedComponent;
047import org.openimaj.image.pixel.ConnectedComponent.ConnectMode;
048import org.openimaj.image.pixel.IntValuePixel;
049import org.openimaj.image.pixel.Pixel;
050import org.openimaj.image.renderer.MBFImageRenderer;
051import org.openimaj.math.geometry.shape.Ellipse;
052import org.openimaj.math.geometry.shape.Rectangle;
053
054public class LOCKY implements InterestPointDetector<EllipticInterestPointData> {
055        private static float DEFAULT_THRESHOLD_PERCENTAGE = 0.24f;
056        protected BrightnessClusteringTransform bct = new BrightnessClusteringTransform();
057        protected ConnectedComponentLabeler ccl = new ConnectedComponentLabeler(ConnectMode.CONNECT_4);
058        protected int shiftx = 0;
059        protected int shifty = 0;
060        private float threshold;
061        private ArrayList<EllipticInterestPointData> regions;
062
063        public LOCKY() {
064                this(DEFAULT_THRESHOLD_PERCENTAGE);
065        }
066
067        public LOCKY(float threshold) {
068                this.threshold = threshold;
069        }
070
071        @Override
072        public void findInterestPoints(FImage image) {
073                final FImage bctImage = image.process(bct);
074                bctImage.normalise();
075                final FImage thresholdImage = bctImage.clone().threshold(this.threshold);
076                final List<ConnectedComponent> components = ccl.findComponents(thresholdImage);
077
078                this.regions = new ArrayList<EllipticInterestPointData>(components.size());
079                for (final ConnectedComponent cc : components) {
080                        final EllipticInterestPointData ipd = new EllipticInterestPointData();
081                        ipd.score = findMaxValue(cc, bctImage);
082
083                        // TODO: utility method to get best fit ellipse of concomp
084                        final MomentFeature mf = new MomentFeature();
085                        final IntValuePixel tmp = new IntValuePixel(0, 0);
086                        for (final Pixel p : cc) {
087                                tmp.x = p.x;
088                                tmp.y = p.y;
089                                mf.addSample(tmp);
090                        }
091                        final Ellipse e = mf.getEllipse(2);
092                        // FIXME: need to factor out the scale
093                        ipd.transform = e.transformMatrix().getMatrix(0, 1, 0, 1);
094                        ipd.scale = 10;
095
096                        final Pixel centroid = cc.calculateCentroidPixel();
097                        ipd.x = centroid.x;
098                        ipd.y = centroid.y;
099
100                        regions.add(ipd);
101                }
102
103                DisplayUtilities.display(bctImage);
104                DisplayUtilities.display(thresholdImage);
105
106                Collections.sort(regions, new Comparator<EllipticInterestPointData>() {
107                        @Override
108                        public int compare(EllipticInterestPointData o1, EllipticInterestPointData o2) {
109                                return -Float.compare(o1.score, o2.score);
110                        }
111                });
112        }
113
114        private float findMaxValue(ConnectedComponent cc, FImage bctImage) {
115                float max = -1;
116                for (final Pixel p : cc) {
117                        max = Math.max(max, bctImage.pixels[p.y][p.x]);
118                }
119                return max;
120        }
121
122        @Override
123        public void findInterestPoints(FImage image, Rectangle window) {
124                findInterestPoints(image.extractROI(window));
125                shiftx = (int) window.x;
126                shifty = (int) window.y;
127        }
128
129        @Override
130        public List<EllipticInterestPointData> getInterestPoints(int npoints) {
131                return regions.subList(0, Math.min(npoints, regions.size()));
132        }
133
134        @Override
135        public List<EllipticInterestPointData> getInterestPoints(float threshold) {
136                for (int i = regions.size(); i > 0; i++) {
137                        if (regions.get(i - 1).score < threshold)
138                                return regions.subList(0, i);
139                }
140
141                return new ArrayList<EllipticInterestPointData>(0);
142        }
143
144        @Override
145        public List<EllipticInterestPointData> getInterestPoints() {
146                return regions;
147        }
148
149        public static void main(String[] args) {
150                final MBFImage image = new MBFImage(400, 400, ColourSpace.RGB);
151                final MBFImageRenderer renderer = image.createRenderer();
152
153                image.fill(RGBColour.BLACK);
154                final List<Ellipse> ellipses = new ArrayList<Ellipse>();
155                ellipses.add(new Ellipse(200, 100, 10, 8, Math.PI / 4));
156                ellipses.add(new Ellipse(200, 300, 5, 3, -Math.PI / 4));
157                ellipses.add(new Ellipse(100, 300, 3, 5, -Math.PI / 3));
158
159                for (final Ellipse ellipse : ellipses) {
160                        renderer.drawShapeFilled(ellipse, RGBColour.WHITE);
161                }
162
163                final LOCKY locky = new LOCKY();
164                locky.findInterestPoints(image.flatten());
165                final List<EllipticInterestPointData> pts = locky.getInterestPoints();
166                for (final EllipticInterestPointData pt : pts) {
167                        image.drawShape(pt.getEllipse(), RGBColour.RED);
168                }
169
170                DisplayUtilities.display(image);
171        }
172}