All Downloads are FREE. Search and download functionalities are using the official Maven repository.

boofcv.alg.tracker.meanshift.TrackerMeanShiftComaniciu2003 Maven / Gradle / Ivy

Go to download

BoofCV is an open source Java library for real-time computer vision and robotics applications.

There is a newer version: 1.1.7
Show newest version
/*
 * Copyright (c) 2011-2017, Peter Abeles. All Rights Reserved.
 *
 * This file is part of BoofCV (http://boofcv.org).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package boofcv.alg.tracker.meanshift;

import boofcv.struct.RectangleRotate_F32;
import boofcv.struct.image.ImageBase;
import georegression.struct.point.Point2D_F32;

import java.util.List;

/**
 * 

* Mean shift tracker which adjusts the scale (or bandwidth) to account for changes in scale of the target * and is based off of [1]. The tracker seeks to minimize the histogram error within the sampled region. * The mean-shift region is sampled using an oriented rectangle and weighted using a 2D gaussian. The target * is modeled using a color histogram of the input image, which can be optionally updated after each frame * is processed. It can also be configured to not allow scale changes, which can improve stability. *

* *

* Scale selection is done using sum-of-absolute-difference (SAD) error instead of Bhattacharyya as the paper * suggests. Situations where found that two errors counteracted each other when using Bhattacharyya * and the incorrect scale would be selected even with perfect data. *

* *

* Another difference from the paper is that mean shift records which hypothesis has the best SAD error. After * mean-shift stops iterating it selects the best solution. This is primarily helpful in situations where mean-shift * doesn't converge in time and jumped away from the solution. *

* *

* [1] Dorin Comaniciu, Visvanathan Ramesh, and Peter Meer,"Kernel-Based Object Tracking." IEEE Transactions on * Pattern Analysis and Machine Intelligence 25.4 (2003): 1. *

* @author Peter Abeles */ public class TrackerMeanShiftComaniciu2003> { // computes the histogram inside a rotated rectangle private LocalWeightedHistogramRotRect calcHistogram; // the key-frame histogram which is being compared again protected float keyHistogram[]; // weight each element contributes protected float weightHistogram[]; // the amount the scale can change protected float scaleChange; // most recently select target region private RectangleRotate_F32 region = new RectangleRotate_F32(); // maximum allowed mean-shift iterations private int maxIterations; // minimum change stopping condition private float minimumChange; // storage for the track region at different sizes private RectangleRotate_F32 region0 = new RectangleRotate_F32(); private RectangleRotate_F32 region1 = new RectangleRotate_F32(); private RectangleRotate_F32 region2 = new RectangleRotate_F32(); private float histogram0[]; private float histogram1[]; private float histogram2[]; // weighting factor for change in scale. 0 to 1. 0 is 100% selected region private float gamma; // should it update the histogram after tracking? private boolean updateHistogram; // if true assume the scale is constant private boolean constantScale; // ratio of the original object size that the track can become private float minimumSizeRatio; // stores the minimum width that the object can be private float minimumWidth; /** * Configures tracker. * * @param updateHistogram If true the histogram will be updated using the most recent image. Try true. * @param maxIterations Maximum number of mean-shift iterations. Try 30 * @param minimumChange Mean-shift will stop when the change is below this threshold. Try 1e-4f * @param gamma Scale weighting factor. Value from 0 to 1. Closer to 0 the more it will prefer * the most recent estimate. Try 0.1 * @param minimumSizeRatio Fraction of the original region that the track is allowed to shrink to. Try 0.25 * @param scaleChange The scale can be changed by this much between frames. 0 to 1. 0 = no scale change. 0.1 is * recommended value in paper. no scale change is more stable. * @param calcHistogram Calculates the histogram */ public TrackerMeanShiftComaniciu2003(boolean updateHistogram, int maxIterations, float minimumChange, float gamma , float minimumSizeRatio , float scaleChange, LocalWeightedHistogramRotRect calcHistogram) { if( scaleChange < 0 || scaleChange > 1 ) throw new IllegalArgumentException("Scale change must be >= 0 and <= 1"); this.updateHistogram = updateHistogram; this.maxIterations = maxIterations; this.minimumChange = minimumChange; this.gamma = gamma; this.scaleChange = scaleChange; this.constantScale = scaleChange == 0; this.minimumSizeRatio = minimumSizeRatio; this.calcHistogram = calcHistogram; keyHistogram = new float[ calcHistogram.getHistogram().length ]; weightHistogram = new float[ keyHistogram.length ]; if( updateHistogram ) { histogram0 = new float[ calcHistogram.getHistogram().length ]; histogram1 = new float[ calcHistogram.getHistogram().length ]; histogram2 = new float[ calcHistogram.getHistogram().length ]; } } /** * Specifies the initial image to learn the target description * @param image Image * @param initial Initial image which contains the target */ public void initialize( T image , RectangleRotate_F32 initial ) { this.region.set(initial); calcHistogram.computeHistogram(image,initial); System.arraycopy(calcHistogram.getHistogram(),0,keyHistogram,0,keyHistogram.length); this.minimumWidth = initial.width*minimumSizeRatio; } /** * Used to set the location of the track without changing any appearance history. * @param location new location */ public void setTrackLocation( RectangleRotate_F32 location ) { this.region.set(location); this.minimumWidth = location.width*minimumSizeRatio; } /** * Searches for the target in the most recent image. * @param image Most recent image in the sequence */ public void track( T image ) { // configure the different regions based on size region0.set( region ); region1.set( region ); region2.set( region ); region0.width *= 1-scaleChange; region0.height *= 1-scaleChange; region2.width *= 1+scaleChange; region2.height *= 1+scaleChange; // distance from histogram double distance0=1,distance1,distance2=1; // perform mean-shift at the different sizes and compute their distance if( !constantScale ) { if( region0.width >= minimumWidth ) { updateLocation(image,region0); distance0 = distanceHistogram(keyHistogram, calcHistogram.getHistogram()); if( updateHistogram ) System.arraycopy(calcHistogram.getHistogram(),0,histogram0,0,histogram0.length); } updateLocation(image,region2); distance2 = distanceHistogram(keyHistogram, calcHistogram.getHistogram()); if( updateHistogram ) System.arraycopy(calcHistogram.getHistogram(),0,histogram2,0,histogram2.length); } // update the no scale change hypothesis updateLocation(image,region1); if( !constantScale ) { distance1 = distanceHistogram(keyHistogram, calcHistogram.getHistogram()); } else { // force it to select distance1 = 0; } if( updateHistogram ) System.arraycopy(calcHistogram.getHistogram(),0,histogram1,0,histogram1.length); RectangleRotate_F32 selected = null; float selectedHist[] = null; switch( selectBest(distance0,distance1,distance2)) { case 0: selected = region0; selectedHist = histogram0; break; case 1: selected = region1; selectedHist = histogram1; break; case 2: selected = region2; selectedHist = histogram2; break; default: throw new RuntimeException("Bug in selectBest"); } // Set region to the best scale, but reduce sensitivity by weighting it against the original size // equation 14 float w = selected.width*(1-gamma) + gamma*region.width; float h = selected.height*(1-gamma) + gamma*region.height; region.set(selected); region.width = w; region.height = h; if( updateHistogram ) { System.arraycopy(selectedHist,0,keyHistogram,0,keyHistogram.length); } } /** * Given the 3 scores return the index of the best */ private int selectBest( double a , double b , double c ) { if( a < b ) { if( a < c ) return 0; else return 2; } else if( b <= c ) { return 1; } else { return 2; } } /** * Updates the region's location using the standard mean-shift algorithm */ protected void updateLocation( T image , RectangleRotate_F32 region ) { double bestHistScore = Double.MAX_VALUE; float bestX = -1, bestY = -1; for( int i = 0; i < maxIterations; i++ ) { calcHistogram.computeHistogram(image,region); float histogram[] = calcHistogram.getHistogram(); updateWeights(histogram); // the histogram fit doesn't always improve with each mean-shift iteration // save the best one and use it later on double histScore = distanceHistogram(keyHistogram, histogram); if( histScore < bestHistScore ) { bestHistScore = histScore; bestX = region.cx; bestY = region.cy; } List samples = calcHistogram.getSamplePts(); int sampleHistIndex[] = calcHistogram.getSampleHistIndex(); // Compute equation 13 float meanX = 0; float meanY = 0; float totalWeight = 0; for( int j = 0; j < samples.size(); j++ ) { Point2D_F32 samplePt = samples.get(j); int histIndex = sampleHistIndex[j]; if( histIndex < 0 ) continue; // compute the weight derived from the Bhattacharyya coefficient. Equation 10. float w = weightHistogram[histIndex]; meanX += w*samplePt.x; meanY += w*samplePt.y; totalWeight += w; } meanX /= totalWeight; meanY /= totalWeight; // convert to image pixels calcHistogram.squareToImageSample(meanX, meanY, region); meanX = calcHistogram.imageX; meanY = calcHistogram.imageY; // see if the change is below the threshold boolean done = Math.abs(meanX-region.cx ) <= minimumChange && Math.abs(meanY-region.cy ) <= minimumChange; region.cx = meanX; region.cy = meanY; if( done ) { break; } } // use the best location found region.cx = bestX; region.cy = bestY; } /** * Update the weights for each element in the histogram. Weights are used to favor colors which are * less than expected. */ private void updateWeights(float[] histogram) { for( int j = 0; j < weightHistogram.length; j++ ) { float h = histogram[j]; if( h != 0 ) { weightHistogram[j] = (float)Math.sqrt(keyHistogram[j]/h); } } } /** * Computes the difference between two histograms using SAD. * * This is a change from the paper, which uses Bhattacharyya. Bhattacharyya could give poor performance * even with perfect data since two errors can cancel each other out. For example, part of the histogram * is too small and another part is too large. */ protected double distanceHistogram(float histogramA[], float histogramB[]) { double sumP = 0; for( int i = 0; i < histogramA.length; i++ ) { float q = histogramA[i]; float p = histogramB[i]; sumP += Math.abs(q-p); } return sumP; } public RectangleRotate_F32 getRegion() { return region; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy