/* * 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.Progress; import ij.process.ByteProcessor; import ij.process.ImageProcessor; import java.util.EnumSet; /** * Patch-based local binary patterns. * <p> * Constructs histogram of binary patterns based on comparing the mean intensity * of the 8-neighborhood of each pixel N<sub>i</sub> in the patch to the mean * intensity of the 8-neighborhood around the pixel in the center N<sub>C</sub>. * Each of the 8 mean intensities N<sub>i</sub> is compared to the mean intensity * N<sub>C</sub> in the center, if N<sub>i</sub> is smaller than N<sub>C</sub>, * N<sub>i</sub> is assigned weight 0, else 1. The weights form a binary pattern * by contactinating them in counter clockwise order, starting with the weight * to the left of the pixel in the center. * </p> * <p><strong>Example:</strong><br/> * Mean intensity patch: * <pre> * | 2 | 25 | 32 | * | 24 | 23 | 9 | * | 19 | 99 | 13 | * </pre> * Weights: * <pre> * | 0 | 1 | 1 | * | 1 | | 0 | * | 0 | 1 | 0 | * </pre> * Binary pattern: <code>0b10100110</code> * </p> * <p> * All pixels part of the 2-pixel wide margin are not considered due to incomplete * 8-neighborhoods of these pixels. * </p> * <p> * References: * <pre> * @inproceedings{ * author = {Ojala, Timo and Pietikäinen, Matti and Harwood, David}, * title = {Performance Evaluation of Texture Measures with Classification * Based on Kullback Disrimination of Distributions}, * booktitle = {Proceedings of the 12th IAPR International Conference on * Pattern Recognition (ICPR 1994)}, * year = {1994}, * pages = {582--585} * } * </pre> * </p> * * @author sebp */ public class MeanIntensityLocalBinaryPatterns extends AbstractFeatureDescriptor { protected int m_bins; protected double m_histMin; protected double m_histMax; protected MeanPatchIntensityHistogram m_meanDescriptor; public MeanIntensityLocalBinaryPatterns() { } @Override public String getDescription() { return "Mean Intensity Local Binary Patterns"; } @Override public EnumSet<Supports> supports() { return EnumSet.of( Supports.Masking, Supports.NoChanges, Supports.DOES_8G, Supports.DOES_8C, Supports.DOES_RGB); } @Override public void run(ImageProcessor ip) { firePropertyChange(Progress.START); createPatchDescriptor(ip); final int yEnd = ip.getHeight() - 2; final int xEnd = ip.getWidth() - 2; final int width = xEnd - 2; final int height = yEnd - 2; final byte[] mask = ip.getMaskArray(); byte[] data = new byte[width * height]; byte[] histMask = new byte[width * height]; // do not process pixels at border, which don't have a complete 8-neighborhood int k = 0; for (int y=2; y < yEnd; y++) { for (int x=2; x < xEnd; x++) { if (mask == null || mask[k] != 0) { data[k] = getBinaryPattern(x, y); histMask[k] = -1; // -1 = 0xFF } k++; } } ByteProcessor lbpImage = new ByteProcessor(width, height, data); lbpImage.setHistogramSize(256); lbpImage.setHistogramRange(0, 256); int[] hist; if (mask == null) { hist = lbpImage.getHistogram(); } else { ByteProcessor maskIp = new ByteProcessor(width, height, histMask); hist = lbpImage.getHistogram(maskIp); } addData(hist); m_meanDescriptor = null; firePropertyChange(Progress.END); } protected void createPatchDescriptor(ImageProcessor ip) { if (!ByteProcessor.class.isAssignableFrom(ip.getClass())) { ImageProcessor mask = ip.getMask(); ip = ip.convertToByte(true); ip.setMask(mask); } m_meanDescriptor = new MeanPatchIntensityHistogram(); m_meanDescriptor.setSize(1); m_meanDescriptor.createIntegralImage((ByteProcessor) ip); } protected byte getBinaryPattern(final int x, final int y) { final double meanCenter = m_meanDescriptor.getMeanIntensity(x, y); // define 8-neighborhood clockwise starting from (x-1, y-1) final int[] yp = new int[] {y-1, y-1, y-1, y, y+1, y+1, y+1, y}; final int[] xp = new int[] {x-1, x, x+1, x+1, x+1, x, x-1, x-1}; byte bit = 0; byte result = 0; // iterate over 8-neighborhood of pixel at (x, y) for (int i=0; i<xp.length; i++) { double meanPixel = m_meanDescriptor.getMeanIntensity(xp[i], yp[i]); if (meanPixel >= meanCenter) { result |= (1 << bit); } bit++; } return result; } public int getNumberOfBins() { return m_bins; } /** * Set the number of bins of the histogram. * * @param bins * @throws IllegalArgumentException if <code>bins <= 0</code> */ public void setNumberOfBins(int bins) { if (bins <= 0) throw new IllegalArgumentException("number of bins must be greater zero, but got " + bins); m_bins = bins; } public double getHistogramMin() { return m_histMin; } public double getHistogramMax() { return m_histMax; } /** * Set the minimum and maximum value that the histogram should consider. * * If both min and max are set to zero (default), limits are retrieved from data. * * @throws IllegalArgumentException if <code>min > max</code> */ public void setHistogramRange(double min, double max) { if (min > max) throw new IllegalArgumentException("min must be smaller than or equal to max"); m_histMin = min; m_histMax = max; } }