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}