/* * This file is part of the JFeatureLib project: https://github.com/locked-fg/JFeatureLib * JFeatureLib is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * JFeatureLib is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with JFeatureLib; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * You are kindly asked to refer to the papers of the according authors which * should be mentioned in the Javadocs of the respective classes as well as the * JFeatureLib project itself. * * Hints how to cite the projects can be found at * https://github.com/locked-fg/JFeatureLib/wiki/Citation */ package de.lmu.ifi.dbs.jfeaturelib.features; import de.lmu.ifi.dbs.jfeaturelib.LibProperties; import de.lmu.ifi.dbs.jfeaturelib.Progress; import de.lmu.ifi.dbs.jfeaturelib.edgeDetector.Canny; import de.lmu.ifi.dbs.jfeaturelib.utils.GradientImage; import de.lmu.ifi.dbs.jfeaturelib.utils.GradientSource; import de.lmu.ifi.dbs.jfeaturelib.utils.Interpolated1DHistogram; import de.lmu.ifi.dbs.utilities.Arrays2; import de.lmu.ifi.dbs.utilities.Math2; import de.lmu.ifi.dbs.utilities.Vectors; import ij.process.ByteProcessor; import ij.process.ImageProcessor; import java.awt.Rectangle; import java.io.IOException; import java.util.EnumSet; import org.apache.log4j.Logger; /** * Generates Pyramid Histograms of Oriented Gradients (PHoG). * * Phogs were first introduced in "Representing shape with a spatial pyramid kernel" (2007). By Anna Bosch, Andrew * Zisserman, Xavier Munoz * * See also http://www.robots.ox.ac.uk/~vgg/publications/2007/Bosch07/ and http://dl.acm.org/citation.cfm?id=1282340 for * further information. * * In the original paper, canny edge extraction is applied prior to extracting the gradients. As edge detection can be * performed in several ways, it is not hard wired. It can be enabled in the properties by setting features.phog.canny * to true. The parameters for the canny operator are also taken from the properties file. * * If another edge detection operator should be used, simple set the canny parameter to false and call * {@link #run(ij.process.ImageProcessor)} with an pre processed image. * * @author graf * @since 11/4/2011 */ public class PHOG extends AbstractFeatureDescriptor { private static final Logger log = Logger.getLogger(PHOG.class.getName()); /* * Amount of bins for each histogram */ private int bins = 8; /* * Amount of recursions for this descriptor. 0 means only the root level. */ private int recursions = 1; /** * dynamic array holding the feature */ private double[] feature = new double[0]; /** * the wrapper class to extract the gradient information from */ private GradientSource gradientSource = new GradientImage(); private Interpolated1DHistogram histogram; boolean useCanny; private LibProperties properties; @Override public void setProperties(LibProperties properties) throws IOException { this.properties = properties; bins = properties.getInteger(LibProperties.PHOG_BINS, 8); recursions = properties.getInteger(LibProperties.PHOG_RECURSIONS, 1); useCanny = properties.getBoolean(LibProperties.PHOG_CANNY, true); } @Override public void run(ImageProcessor ip) { firePropertyChange(Progress.START); setMask(ip); if (useCanny) { ip = applyCanny(ip); } if (!(ip instanceof ByteProcessor)) { ip = ip.convertToByte(true); } gradientSource.setIp(ip); histogram = new Interpolated1DHistogram(0, Math.PI, bins); buildHistogramRecursively(ip.getRoi(), 0); // release memory gradientSource = null; Vectors.normalize(feature); addData(feature); firePropertyChange(Progress.END); } ImageProcessor applyCanny(ImageProcessor ip) throws IllegalStateException { try { Canny canny = new Canny(); canny.setProperties(properties); canny.run(ip); } catch (IOException ex) { log.error("error in canny config", ex); throw new IllegalStateException(ex); } return ip; } private void buildHistogramRecursively(Rectangle r, int recursion) { histogram.clear(); final int borderRight = r.x + r.width; final int borderBottom = r.y + r.height; for (int x = r.x; x < borderRight; x++) { for (int y = r.y; y < borderBottom; y++) { double length = gradientSource.getLength(x, y); if (inMask(x, y) && length != 0) { histogram.add(gradientSource.getTheta(x, y), length); } } } feature = Arrays2.append(feature, histogram.getData()); // descend into next recursion if (recursion++ < recursions) { final int w2 = r.width / 2; final int h2 = r.height / 2; Rectangle tl = new Rectangle(r.x, r.y, w2, h2); buildHistogramRecursively(tl, recursion); Rectangle tr = new Rectangle(w2, r.y, w2, h2); buildHistogramRecursively(tr, recursion); Rectangle bl = new Rectangle(r.x, h2, w2, h2); buildHistogramRecursively(bl, recursion); Rectangle br = new Rectangle(w2, h2, w2, h2); buildHistogramRecursively(br, recursion); } } @Override public EnumSet<Supports> supports() { return EnumSet.of(Supports.DOES_8G, Supports.DOES_8C, Supports.DOES_16, Supports.DOES_32, Supports.Masking); } @Override public String getDescription() { return "Pyramid Histograms of Oriented Gradients"; } //<editor-fold defaultstate="collapsed" desc="getters & setters"> public GradientSource getGradientSource() { return gradientSource; } public void setGradientSource(GradientSource gradientSource) { if (gradientSource == null) { throw new NullPointerException("gradientSource must not be null"); } this.gradientSource = gradientSource; } public int getBins() { return bins; } public void setBins(int bins) { if (bins <= 0) { throw new IllegalArgumentException("bins must be > 0 but was " + bins); } this.bins = bins; } public int getRecursions() { return recursions; } public void setRecursions(int recursions) { if (recursions < 0) { throw new IllegalArgumentException("recursions must be >= 0 but was " + recursions); } this.recursions = recursions; } //</editor-fold> }