Example Tracker Mean Shift

From BoofCV
Jump to navigationJump to search

BoofCV provides two mean-shift trackers, histogram and pixel likelihood, this example is for the second. It is demonstrated how to create a custom pixel likelihood fuction to track a color ball. Mean-shift performs a local search that attempts to maximize the similarity of the color in its kernel to the color in its model. It is a fairly robust and fast tracker. If you are lucky, it will sometimes even reaquire a track after it has been lost.

Example Code:

Concepts:

  • Object tracking
  • Color histogram
  • Mean-shift

Example Code

/**
 * Example of how to use the low level implementation of mean-shift to track a specific color provided by the user.
 * The weights of each pixel is computed by RgbLikelihood, see below, and track regions are rectangular. This
 * tracker works very well since the ball is almost uniformly blue. If the light varried then a HSV color
 * model might be better.
 *
 * @author Peter Abeles
 */
public class ExampleTrackerMeanShiftLikelihood {
	/**
	 * Very simple implementation of PixelLikelihood. Uses linear distance to compute how close
	 * a color is to the target color.
	 */
	public static class RgbLikelihood implements PixelLikelihood<Planar<GrayU8>> {
		int targetRed, targetGreen, targetBlue;
		float radius = 35;
		Planar<GrayU8> image;

		public RgbLikelihood( int targetRed, int targetGreen, int targetBlue ) {
			this.targetRed = targetRed;
			this.targetGreen = targetGreen;
			this.targetBlue = targetBlue;
		}

		@Override public void setImage( Planar<GrayU8> image ) { this.image = image; }

		@Override public boolean isInBounds( int x, int y ) { return image.isInBounds(x, y); }

		/**
		 * This function is used to learn the target's model from the select image region. Since the
		 * model is provided in the constructor it isn't needed or used.
		 */
		@Override
		public void createModel( RectangleLength2D_I32 target ) { throw new RuntimeException("Not supported"); }

		@Override
		public float compute( int x, int y ) {
			int pixelR = image.getBand(0).get(x, y);
			int pixelG = image.getBand(1).get(x, y);
			int pixelB = image.getBand(2).get(x, y);

			// distance along each color band
			float red = Math.max(0, 1.0f - Math.abs(targetRed - pixelR)/radius);
			float green = Math.max(0, 1.0f - Math.abs(targetGreen - pixelG)/radius);
			float blue = Math.max(0, 1.0f - Math.abs(targetBlue - pixelB)/radius);

			// multiply them all together
			return red*green*blue;
		}
	}

	public static void main( String[] args ) {
		MediaManager media = DefaultMediaManager.INSTANCE;
		String fileName = UtilIO.pathExample("tracking/balls_blue_red.mjpeg");
		RectangleLength2D_I32 location = new RectangleLength2D_I32(394, 247, 475 - 394, 325 - 247);

		ImageType<Planar<GrayU8>> imageType = ImageType.pl(3, GrayU8.class);

		SimpleImageSequence<Planar<GrayU8>> video = media.openVideo(fileName, imageType);

		// Return a higher likelihood for pixels close to this RGB color
		var likelihood = new RgbLikelihood(64, 71, 69);

		TrackerMeanShiftLikelihood<Planar<GrayU8>> tracker =
				new TrackerMeanShiftLikelihood<>(likelihood, 50, 0.1f);

		// specify the target's initial location and initialize with the first frame
		Planar<GrayU8> frame = video.next();
		// Note that the tracker will not automatically invoke RgbLikelihood.createModel() in its initialize function
		tracker.initialize(frame, location);

		// For displaying the results
		var gui = new TrackerObjectQuadPanel(null);
		gui.setPreferredSize(new Dimension(frame.getWidth(), frame.getHeight()));
		gui.setImageUI(video.getGuiImage());
		gui.setTarget(location, true);
		ShowImages.showWindow(gui, "Tracking Results", true);

		// Track the object across each video frame and display the results
		while (video.hasNext()) {
			frame = video.next();

			boolean visible = tracker.process(frame);

			gui.setImageUI(video.getGuiImage());
			gui.setTarget(tracker.getLocation(), visible);
			gui.repaint();

			BoofMiscOps.pause(20);
		}
	}
}