Example Image Filter
Introduction
This tutorial introduces basic BoofCV concepts through different examples which produce the same output, but use different techniques to do so. 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. Often times these procedural functions are low level implementations which allow more flexibility, 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 always start with the letter 'G'. Inside the children of 'boofcv.abst' package are abstracted filters. These classes provide an OOP interface and handle much of the memory management, but are less flexible.
Example File: ImageFilterExample
Tutorial Concepts:
- Invoke procedural functions.
- Invoke generalized functions using Java generics.
- Create and invoke abstracted filters.
- Use a Factory.
- Display images.
Procedural
Here an image is passed in of a the type ImageUInt8, which is then blurred, has its derivative computed and displayed in a window.
The input is an image of type ImageUInt8. ImageUInt8 are single band images with pixels that are unsigned integer 8 bits. Using the same naming scheme, the derivative is stored in images that are of type ImageSInt16. ImageSInt16 are single band images with signed 16 bit integer pixels. Different types of images are required for the derivative because the derivative can take on positive and negative values over a wider range.
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 a filter 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 the 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()); }