/* * 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.shapeFeatures; import ij.process.ImageProcessor; import java.awt.Point; import java.util.ArrayList; import org.apache.log4j.Logger; /** * Utility class for 2d image PCA. * * As long as it is not fully clear that this class is used somewhere else as * well, we'll keep it backage private. * * @author Johannes Stadler * @since 09/29/2012 */ class ImagePCA { private static final Logger log = Logger.getLogger(ImagePCA.class.getName()); private ImageProcessor ip2; private double angle = 0; public ImagePCA(ImageProcessor ip, int backgroundColor) { ip2 = ip; ip = ip.convertToByte(true); ArrayList<Point> points = new ArrayList<>(); double xx = 0, xy = 0, yx = 0, yy = 0; double counter = 0; double meanX = 0, meanY = 0; //1) retrieve relevant points for (int i = 0; i < ip.getWidth(); i++) { for (int j = 0; j < ip.getHeight(); j++) { if (ip.getPixel(i, j) != backgroundColor) { points.add(new Point(i, j)); counter++; meanX = ((counter - 1.0) / counter) * meanX + 1.0 / counter * i; meanY = ((counter - 1.0) / counter) * meanY + 1.0 / counter * j; } } } //2) compute covariances double c = 0; for (Point p : points) { c++; xx = ((c - 1) / c) * xx + (1 / c) * (p.getX() - meanX) * (p.getX() - meanX); yy = ((c - 1) / c) * yy + (1 / c) * (p.getY() - meanY) * (p.getY() - meanY); xy = ((c - 1) / c) * xy + (1 / c) * (p.getY() - meanY) * (p.getX() - meanX); } yx = xy; //3) compute eigenvalues double lambda1 = 0.5 * yy + 0.5 * xx + 0.5 * Math.sqrt(yy * yy - 2 * yy * xx + xx * xx + 4 * xy * xy); double lambda2 = 0.5 * yy + 0.5 * xx - 0.5 * Math.sqrt(yy * yy - 2 * yy * xx + xx * xx + 4 * xy * xy); double[] eigenVector1 = new double[]{(-xy / (lambda2 - yy)), 1}; double[] eigenVector2 = new double[]{(-xy / (lambda1 - yy)), 1}; double[] ev; if (lambda2 > lambda1) { ev = eigenVector2; } else { ev = eigenVector1; } double evlen = Math.sqrt(ev[0] * ev[0] + ev[1] * ev[1]); double cosa = ev[0] * 1.0 / evlen; angle = Math.acos(cosa); log.debug("PCA2D-Angle: " + (-(angle / Math.PI) * 180)); //uncomment me if you want to see the rotated image... ip2 = ip2.duplicate(); ip2.invert(); ip2.rotate(-(angle / Math.PI) * 180); ip2.invert(); } /*rotate the image counter clockwise by the returned angle and the resulting images principal component is parallel to the x-axis...*/ public double getAngle() { return this.angle; } public ImageProcessor getResultImage() { return this.ip2; } }