Example Image Filter

From BoofCV
Revision as of 10:32, 28 October 2011 by Peter (talk | contribs)
Jump to navigationJump to search

Introduction

This tutorial introduces basic BoofCV programming concepts through three examples. In BoofCV there are often three ways to invoke a function; 1) procedural, 2) generalized, and 3) abstracted.

Procedural functions are contained in children of the boofcv.alg package, where 'alg' stands for algorithms. These procedural functions are low level implementations which allow greater customization, tighter memory control, and have strong typing. In the same package are generalized versions of procedural functions that provide weaker type checking, but allow generic images to be passed in. Classes which contain generalized functions start with the letter 'G'. For example, BlurImageOps contains a set of functions for each image type and operation pair it supports and GBlurImageOps has a single function for each operation it supports.

Inside the children of 'boofcv.abst' package are abstracted filters. These classes provide an Object Oriented Programming (OOP) interface and handle much of the memory management, but are less flexible. Some times the ability to configure low level parameters has been removed from the algorithms that they are wrappers around to make development more manageable.

Example File: ImageFilterExample

Tutorial Concepts:

  1. Invoke procedural functions.
  2. Invoke generalized functions using Java generics.
  3. Create and invoke abstracted filters.
  4. Use a Factory.
  5. Display images.

Relevant Applets:

  1. Image Blur
  2. Gradient

Procedural

Here an ImageUInt8 image is passed in, which is then blurred, has its derivative computed and displayed in a window. In BoofCV, unlike many other vision libraries, to provide strong type checking at compile time, images with different primitive data types are a different type. The name 'UInt8' indicates that its elements are unsigned 8-bit integers and that it has only a single band. Image derivative require a different data type because the domain of each element includes negative numbers and has a larger domain, which is why they are stored in a ImageSInt16. ImageSInt16 are single band images with signed 16-bit integer pixels.

	public static void procedural( ImageUInt8 input )
	{
		ImageUInt8 blurred = new ImageUInt8(input.width,input.height);
		ImageSInt16 derivX = new ImageSInt16(input.width,input.height);
		ImageSInt16 derivY = new ImageSInt16(input.width,input.height);

		// Gaussian blur: Convolve a Gaussian kernel with a width of 5 pixels
		BlurImageOps.gaussian(input,blurred,-1,2,null);

		// Calculate image's derivative
		GradientSobel.process(blurred, derivX, derivY, FactoryImageBorder.extend(input));

		// display the results
		BufferedImage outputImage = VisualizeImageData.colorizeSign(derivX,null,-1);
		ShowImages.showWindow(outputImage,"Procedural Fixed Type");
	}

Notice how in the gaussian() function (-1,2) are passed in. The negative number indicates that the procedure should select the Gaussian's sigma itself using the provided kernel radius of 2. A null is also passed into that function. When computing the Gaussian blur a temporary image is required, since null was passed in for the last parameter it will internally declare that memory. Declaring temporary memory each time a function is invoked is wasteful, but easier to program.

When convolving a kernel across an image its not always clear how image borders should be handled where only part of the image intersects the kernel. In this example, when computing the gradient using a Sobel operator, it is creating a function that will handle the border by extending the outside pixels.

Finally the output is computed by rendering the image derivative into a BufferedImage. BufferedImages come from Java Swing and is Java's standard way for storing image data. They are not used internally in BoofCV due to their complexity and poor performance. Once rendered, it is displayed in an image using the convenient showWindow() function.

Generalized

The following example is very similar to the first, but it now takes in a generalized image type. Type checking is weaker, but many different images can be passed in now.

	public static <T extends ImageBase, D extends ImageBase>
	void generalized( T input )
	{
		Class<T> inputType = (Class<T>)input.getClass();
		Class<D> derivType = GImageDerivativeOps.getDerivativeType(inputType);

		T blurred = GeneralizedImageOps.createImage(inputType,input.width, input.height);
		D derivX = GeneralizedImageOps.createImage(derivType,input.width, input.height);
		D derivY = GeneralizedImageOps.createImage(derivType,input.width, input.height);

		// Gaussian blur: Convolve a Gaussian kernel with a width of 5 pixels
		GBlurImageOps.gaussian(input, blurred, -1, 2, null);

		// Calculate image's derivative
		GImageDerivativeOps.sobel(blurred, derivX, derivY, BorderType.EXTENDED);

		// display the results
		BufferedImage outputImage = VisualizeImageData.colorizeSign(derivX,null,-1);
		ShowImages.showWindow(outputImage,"Generalized "+inputType.getSimpleName());
	}

Temporary images are created using GeneralizedImageOps.createImage() sicne the type is not known at compile time. Even though the specific type of image isn't know at runtime, Java generics allow consistency checks to make sure images which should be the same type are the same type. Notice that blurring and image derivatives are computed using classes that begin with the letter 'G', which signifies they are generalized.

Abstracted

Instead of invoking functions, filter classes are created here. Filters are even more flexible and allow greater abstraction.

	public static <T extends ImageBase, D extends ImageBase>
	void filter( T input )
	{
		Class<T> inputType = (Class<T>)input.getClass();
		Class<D> derivType = GImageDerivativeOps.getDerivativeType(inputType);

		T blurred = GeneralizedImageOps.createImage(inputType, input.width, input.height);
		D derivX = GeneralizedImageOps.createImage(derivType, input.width, input.height);
		D derivY = GeneralizedImageOps.createImage(derivType, input.width, input.height);

		// declare image filters
		BlurFilter<T> filterBlur = FactoryBlurFilter.gaussian(inputType, -1, 2);
		ImageGradient<T,D> gradient = FactoryDerivative.sobel(inputType, derivType);

		// process the image
		filterBlur.process(input,blurred);
		gradient.process(blurred,derivX,derivY);

		// display the results
		BufferedImage outputImage = VisualizeImageData.colorizeSign(derivX,null,-1);
		ShowImages.showWindow(outputImage,"Filter "+inputType.getSimpleName());
	}

Notice how the filters are created using a Factory class. Factories provide an easy to use interface for constructing filters and other algorithms. Often they will handle all the little details and only let the user specify major parameters. Notice how FactoryDerivative.sobel() does not allow the user to specify the border type, that's because it assumes it knows the best type and might as well spare the developer from that level of detail.

No Generics

For sake of completeness, it is demonstrated that Java generics are optional when writing generalized code. No type checking is done here, but the code is less verbose.

	public static void nogenerics( ImageBase input )
	{
		Class inputType = input.getClass();
		Class derivType = GImageDerivativeOps.getDerivativeType(inputType);

		ImageBase blurred = GeneralizedImageOps.createImage(inputType,input.width, input.height);
		ImageBase derivX = GeneralizedImageOps.createImage(derivType,input.width, input.height);
		ImageBase derivY = GeneralizedImageOps.createImage(derivType,input.width, input.height);

		// Gaussian blur: Convolve a Gaussian kernel with a width of 5 pixels
		GBlurImageOps.gaussian(input, blurred, -1, 2, null);

		// Calculate image's derivative
		GImageDerivativeOps.sobel(blurred, derivX, derivY, BorderType.EXTENDED);

		// display the results
		BufferedImage outputImage = VisualizeImageData.colorizeSign(derivX,null,-1);
		ShowImages.showWindow(outputImage,"Generalized "+inputType.getSimpleName());
	}