/*
* 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.utils.Histogram;
import ij.process.ByteProcessor;
import ij.process.ImageProcessor;
import java.io.IOException;
import java.util.EnumSet;
/**
* Constructs a histogram of local binary patterns (LBP) for each pixel.
*
* For each pixel a histogram of LBP is constructed from all pixels in the region
* specified by {@link #setNeighborhoodSize(int)}. In this region the LBP is
* calculated for every pixel. The LBP is calculated by comparing the pixel
* intensities of the central pixel to <tt>N</tt> neighboring pixels ({@link #setNumPoints(int)})
* on a circle of radius <tt>r</tt> ({@link #setRadius(double)}). Interpolation
* is used to retrieve the intensity of neighboring pixels.
*
* The first two features represent the x and y coordinates of the pixel, the
* remaining features the histogram of local binary patterns
* (see {@link #setNumberOfHistogramBins(int)}).
*
* In comparison to {@link MeanIntensityLocalBinaryPatterns}, this class computes
* one histogram of each pixel and computes local binary patterns from neighbors
* lying on a circle around the central pixel.
*
* References:
* <pre>
* @article{Heikkla2006,
* author = {Heikklä, Marko and Pietikäinen, Matti},
* title = {A texture-based method for modeling the background and detecting moving objects.},
* journal = {IEEE transactions on pattern analysis and machine intelligence},
* number = {4},
* volume = {28},
* year = {2006},
* pages = {657--62}
* }
* </pre>
*
* @author sebp
* @see MeanIntensityLocalBinaryPatterns
*/
public class LocalBinaryPatterns extends AbstractFeatureDescriptor {
private double m_radius;
private int m_numPoints;
private int m_neighborhoodSize;
private double m_constant;
private int m_histogramSize;
/* The angle between adjacent neighbors */
protected double m_angle;
/* Offset in x and y direction of all neighbors */
protected double[] m_offsets;
protected ImageProcessor m_ip;
public LocalBinaryPatterns() {
}
@Override
public String getDescription() {
return "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 setProperties(LibProperties properties) throws IOException {
setRadius(properties.getDouble(LibProperties.LBP_RADIUS));
setNumPoints(properties.getInteger(LibProperties.LBP_NUM_POINTS));
setNeighborhoodSize(properties.getInteger(LibProperties.LBP_NEIGHBORHOOD_SIZE));
setConstant(properties.getDouble(LibProperties.LBP_CONSTANT));
setNumberOfHistogramBins(properties.getInteger(LibProperties.LBP_HISTOGRAM_SIZE));
}
@Override
public void run(ImageProcessor ip) {
firePropertyChange(Progress.START);
final int width = ip.getWidth();
final int height = ip.getHeight();
setImageProcessor(ip);
byte[] mask = m_ip.getMaskArray();
int k = 0;
for (int y = 0; y < height ; y++) {
for (int x = 0; x < width; x++) {
if (mask == null || mask[k++] != 0)
addData(processPixel(x, y));
}
int p = (int) (y / (double) height * 100);
firePropertyChange(new Progress(p));
}
// free memory
m_ip = null;
m_offsets = null;
firePropertyChange(Progress.END);
}
protected int getMaxBinaryPattern() {
return (int) Math.pow(2, m_numPoints);
}
protected void setImageProcessor(ImageProcessor ip) {
if (!ByteProcessor.class.isAssignableFrom(ip.getClass())) {
ImageProcessor mask = ip.getMask();
ip = ip.convertToByte(true);
ip.setMask(mask);
}
m_ip = ip;
calculateOffsets();
}
/**
* Calculates relative offsets in x and y direction of neighbors with
* respect to central pixel.
*/
protected void calculateOffsets() {
m_offsets = new double[m_numPoints * 2];
for (int i = 0; i < m_numPoints; i++) {
double a = i * m_angle;
m_offsets[i * 2] = m_radius * Math.cos(a);
m_offsets[i * 2 + 1] = m_radius * Math.sin(a);
}
}
protected double[] processPixel(final int x, final int y) {
int xStart = Math.max(x - m_neighborhoodSize, 0);
int xEnd = Math.min(x + m_neighborhoodSize + 1, m_ip.getWidth());
int yStart = Math.max(y - m_neighborhoodSize, 0);
int yEnd = Math.min(y + m_neighborhoodSize + 1, m_ip.getHeight());
Histogram hist = new Histogram(m_histogramSize, getMaxBinaryPattern());
// iterate over neighborhood
for (int yi = yStart; yi < yEnd; yi++) {
for (int xi = xStart; xi < xEnd; xi++) {
hist.add(getBinaryPattern(xi, yi));
}
}
double[] histarr = hist.getHistogramm();
double[] data = new double[histarr.length + 2];
data[0] = x;
data[1] = y;
System.arraycopy(histarr, 0, data, 2, histarr.length);
return data;
}
protected int getBinaryPattern(final int x, final int y) {
final float centerPixel = m_ip.getf(x, y);
int pattern = 0;
for (int i = 0; i < m_numPoints; i++) {
double xi = x + m_offsets[i * 2];
double yi = y + m_offsets[i * 2 + 1];
if (xi < 0 || xi >= m_ip.getWidth() || yi < 0 || yi >= m_ip.getHeight())
return 0;
double val = m_ip.getInterpolatedPixel(xi, yi);
if (val > centerPixel + m_constant) {
pattern |= 1 << i;
}
}
return pattern;
}
/**
* Number of neighbors to consider.
*
* All neighbors lie equally spaced on a circle determined by {@link #setRadius(double)}.
*
* @param numPoints [1,30]
*/
public void setNumPoints(int numPoints) {
if (numPoints > 30 || numPoints < 1)
throw new IllegalArgumentException(
"numPoints must be in [1,30], but is " + numPoints);
m_numPoints = numPoints;
m_angle = 2.0 * Math.PI / numPoints;
}
/**
* Set the radius of the neighborhood to consider.
*
* @param radius in pixels
* @throws IllegalArgumentException if <code>radius <= 0</code>
*/
public void setRadius(double radius) {
if (radius <= 0)
throw new IllegalArgumentException(
"radius must be bigger than zero, but is " + radius);
m_radius = radius;
}
/**
* Set the size of the neighborhood that is considered to construct a histogram
* of binary patterns for each pixel.
* <p>
* For instance, a neighborhood size of 1 considers the 8-neighborhood of each
* pixel, and a neighborhood size of 2 the 25-neighborhood. The neighborhood
* is always quadractic.
* </p>
* @param neighborhoodSize positive number
*/
public void setNeighborhoodSize(int neighborhoodSize) {
if (neighborhoodSize <= 0)
throw new IllegalArgumentException(
"neighborhoodSize must be bigger than zero, but is " + neighborhoodSize);
m_neighborhoodSize = neighborhoodSize;
}
/**
* Constant added to the intensity of the central pixel when comparing it
* to its neighbors.
*/
public void setConstant(double offset) {
m_constant = offset;
}
/**
* Set the number of bins of the LBP histogram of each pixel.
*
* @param numBins a positive number
*/
public void setNumberOfHistogramBins(int numBins) {
if (numBins <= 0)
throw new IllegalArgumentException(
"numBins must be bigger than zero, but is " + numBins);
m_histogramSize = numBins;
}
public double getRadius() {
return m_radius;
}
public int getNumPoints() {
return m_numPoints;
}
public double getConstant() {
return m_constant;
}
public int getNumberOfHistogramBins() {
return m_histogramSize;
}
}