com.github.ojil.algorithm.Gray8DetectHaarMultiScale Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ojil-core Show documentation
Show all versions of ojil-core Show documentation
Open Java Imaging Library.
package com.github.ojil.algorithm;
/*
* Gray8DetectHaarMultiScale.java
*
* Created on August 19, 2007, 7:33 PM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*
* Copyright 2007 by Jon A. Webb
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the Lesser GNU General Public License
* along with this program. If not, see .
*
*/
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import com.github.ojil.core.Gray8Image;
import com.github.ojil.core.Gray8MaskedImage;
import com.github.ojil.core.Gray8OffsetImage;
import com.github.ojil.core.Image;
import com.github.ojil.core.ImageError;
import com.github.ojil.core.PipelineStage;
/**
* DetectHaar applies a Haar cascade at multiple locations and multiple scales
* to an input Gray8Image. The result is a mask with the masked
* (non-Byte.MIN_VALUE) locations indicating the areas where the feature was
* detected.
* The Haar cascade is applied at multiple scales, starting with the coarsest
* scale, and working down to the finest scale. At each scale, the cascade is
* applied to subimages spread across the image. If the cascade detects a
* feature, the area of the mask corresponding to that subimage is set to
* Byte.MAX_VALUE. When a subimage is to be tested, the mask is first examined
* to see if the central pixel in the mask area corresponding to that subimage
* is masked. If it is, the subimage is skipped. When transitioning to a finer
* scale, the mask is stretched to the new size. This results in areas where
* features have been detected at a coarser scale not being re-searched at a
* finer scale.
* Gray8DetectHaarMultiScale is structured as a pipeline stage so push'ing an
* image results in a new mask being available on getFront. The mask can be
* further processed by doing connected component detection to determine the
* feature characteristics, or the mask can be displayed in an overlay on the
* original image to show the feature areas.
*
* @author webb
*/
public class Gray8DetectHaarMultiScale extends PipelineStage {
private final HaarClassifierCascade hcc;
// maximum scale is the largest factor the image is divided by
private int nMaxScale = 10;
// minimum scale is the smallest factor the image is divided by
private int nMinScale = 5;
// scale change is the change in scale from one search to the next
// times 256
private final int nScaleChange = (12 * 256) / 10;
/**
* Creates a new instance of Gray8DetectHaarMultiScale. The scale parameters
* correspond to the size of a square area in the original input image that
* are averaged to create a single pixel in the image used for detection. A
* scale factor of 1 would do detection at full image resolution.
*
* @param is
* Input stream containing the Haar cascade. This input stream is
* created by the Haar2J2me program (run on a PC) from a Haar
* cascade that has been trained using the OpenCV. See
* {http://sourceforge.net/projects/opencv} for more information
* about the OpenCV. The Haar2J2me program should be available
* wherever you got this code from.
* @param nMinScale
* Minimum (finest) scale at which features will be detected.
* @param nMaxScale
* Maximum (coarsest) scale at which features will be detected.
* @throws ImageError
* if there is an error in the input file.
* @throws java.io.IOException
* if there is an I/O error reading the input file.
*/
public Gray8DetectHaarMultiScale(final InputStream is, final int nMinScale, final int nMaxScale) throws ImageError, IOException {
this.nMinScale = nMinScale;
this.nMaxScale = nMaxScale;
// load Haar classifier cascade
final InputStreamReader isr = new InputStreamReader(is);
hcc = HaarClassifierCascade.fromStream(isr);
}
/**
* Apply multi-scale Haar cascade and prepare a mask image showing where
* features were detected.
*
* @param image
* Input Gray8Image.
* @throws ImageError
* if the input is not a Gray8Image or is too small.
*/
@Override
public void push(final Image> image) throws ImageError {
Gray8Image> imGray;
if (image instanceof Gray8Image) {
imGray = (Gray8Image>) image;
} else {
throw new ImageError(ImageError.PACKAGE.ALGORITHM, AlgorithmErrorCodes.IMAGE_NOT_GRAY8IMAGE, image.toString(), null, null);
}
if ((image.getWidth() < hcc.getWidth()) || (image.getHeight() < hcc.getHeight())) {
throw new ImageError(ImageError.PACKAGE.ALGORITHM, AlgorithmErrorCodes.IMAGE_TOO_SMALL, image.toString(), hcc.toString(), null);
}
int nScale = Math.min(nMaxScale, Math.min(image.getWidth() / hcc.getWidth(), image.getHeight() / hcc.getHeight()));
// Zero the mask
Gray8Image> imMask = new Gray8Image<>(1, 1, Byte.MIN_VALUE);
while (nScale >= nMinScale) {
// shrink the input image
final int nTargetWidth = imGray.getWidth() / nScale;
final int nTargetHeight = imGray.getHeight() / nScale;
final Gray8Shrink gs = new Gray8Shrink(nTargetWidth, nTargetHeight);
gs.push(imGray);
final Gray8Image> imShrunk = (Gray8Image>) gs.getFront();
// scale the mask to the new size
final Gray8RectStretch grs = new Gray8RectStretch(nTargetWidth, nTargetHeight);
grs.push(imMask);
imMask = (Gray8Image>) grs.getFront();
// combine the image and mask to make a masked image
final Gray8MaskedImage> gmi = new Gray8MaskedImage<>(imShrunk, imMask);
// pass the masked image to a subimage generator
final MaskedGray8SubImgGen mgsi = new MaskedGray8SubImgGen(hcc.getWidth(), hcc.getHeight(), Math.max(1, gmi.getWidth() / 30), Math.max(1, gmi.getHeight() / 30));
mgsi.push(gmi);
// now run Haar detection on each scaled image
int nxLastFound = -hcc.getWidth();
int nyLastFound = -hcc.getHeight();
while (!mgsi.isEmpty()) {
final Gray8OffsetImage> imSub = (Gray8OffsetImage>) mgsi.getFront();
// if we've found a feature recently we skip forward until
// we're outside the masked region. There's no point rerunning
// the detector
if ((imSub.getXOffset() > (nxLastFound + hcc.getWidth())) && (imSub.getYOffset() > (nyLastFound + hcc.getHeight()))) {
if (hcc.eval(imSub)) {
// Found something.
nxLastFound = imSub.getXOffset();
nyLastFound = imSub.getYOffset();
// assign Byte.MAX_VALUE to the feature area so we don't
// search it again
final Gray8Rect gr = new Gray8Rect(nxLastFound, nyLastFound, hcc.getWidth(), hcc.getHeight(), Byte.MAX_VALUE);
gr.push(imMask);
imMask = (Gray8Image>) gr.getFront();
}
}
}
nScale = (nScale * 256) / nScaleChange;
}
// Stretch imMask to original image size; this is the result
final Gray8RectStretch grs = new Gray8RectStretch(image.getWidth(), image.getHeight());
grs.push(imMask);
super.setOutput(grs.getFront());
}
/**
* Set minimum and maximum scale.
*
* @param nMinScale
* The finest scale -- a scale factor of 1 corresponds to the
* full image resolution.
* @param nMaxScale
* The coarsest scale. A scale factor equal to the image width
* (for a square image) would mean the entire image is reduced to
* a single pixel.
* Note. The maximum scale actually used is the maximum of
* this number and the scale which would reduce the image size to
* the smallest size that the image used in the Haar cascade
* would fit inside.
*/
public void setScale(final int nMinScale, final int nMaxScale) {
this.nMinScale = nMinScale;
this.nMaxScale = nMaxScale;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy