Accessor

When resampling images or performing neighborhood operations, we might try to access a pixel at x, y coordinates that are outside the image. To handle such cases gracefully, it is good to write functions that take x, y as input and check them against the bounds of the image before looking up a value.

Multiple options are possible when a pixel is requested outside the bounds, but the two most common are to return a black pixel or the closest available pixel (that of the closest edge). We recommend you implement both. For the latter, just clamp the pixel coordinates to [0..height-1] and [0..width-1] and perform the lookup there.

Black padding is more appropriate for applications such as scale and rotation whereas edge-pixel padding looks better for warping and for convolution.

Blurring

In the following problems, you will implement several functions in which you will convolve an image with a kernel. This will require that you index out of the bounds of the image. Handle these boundary effects by using the smartAccessor from the previous section. Also, process each of the three color channels in an image independently.

We have provided you with a function FloatImage impulseImg(const int &k) that generates a \( k \times k \times 1 \) grayscale image that is black everywhere except for one pixel in the center that is completely white. If you convolve a kernel with this image, you should get a copy of the kernel in the center of the image. An example of this can be seen in testGradient() (in a4_main.cpp).

Box blur

before box blurr
after box blurr
(a) Original Image
(b) Box Blurred Image

General kernels

Now, you will implement a more general convolution function that uses an arbitary kernel. The kernel is an instance of the Filter class. To create a kernel use the constructor Filter(const vector &fData, const int &fWidth, const int &fHeight). This takes in a row-major vector containing the values of the kernel (just like in the FloatImage class), and the width and height of the kernel respectively (fWidth and fHeight must be odd). See a4_main.cpp for example kernels.

Pay attention to indexing, (0, 0) denotes the upper left corner of the Image, but for our kernels we want the center to be in the middle. This means you might need to shift indices by half the kernel size. Test your function with impulseImg(), a constant image and real images.

Gradients

before applying kernel
(a) Original Image
after horizontal sobel kernel
(b) Image Filtered Using a Horizontal Sobel Kernel
after gradient magnitude
(c) Gradient Magnitude of the Image

Gaussian Filtering

1D Horizontal Gaussian filtering

2D Gaussian Filtering

Separable 2D Gaussian Filtering

Verify that you get the same result with the full 2D filtering as with the separable Gaussian filtering. Measure the running times of the separable filtering vs. 2D filtering using testGaussianFilters() in a4_main.cpp.

horizontal gaussian kernel
2D gaussian kernel
(a) Image Filtered Using a Horizontal Gaussian Kernel
(b) Image Filtered Using a 2D Gaussian Kernel

Sharpening

Denoising using bilateral filtering

The range Gaussian on \(I_{in}(x, y) − I_{in}(x', y')\) should be computed using the Euclidean distance in RGB. Try your filter on the provided noisy image lens as well as on simple test cases.

Luminance/Chrominance denoising

We want to avoid chromatic artifacts by filtering chrominance more than luminance. This is because the human visual system is more sensitive to low frequencies in the chrominance components.

In all cases, make sure you compute the range Gaussian with respect to the full YUV coordinates, and not just for the channel you are filtering. We recommend a spatial sigma four times bigger for U and V as for Y.

image before filtering
image after RGB bilateral filtering
image after YUV bilateral filtering
(a) Original Image im
(b) RGB Bilaterial Filtering
(b) YUV Bilaterial Filtering

Extra credit

Here are ideas for extensions you could attempt, for up to 5% extra credit each:

  • Median filter
  • Create fancier kernels for your Filter class that mimic different types of camera apertures (e.g. circular, pentagonal, hexagonal). Create these either parametrically/programmatically, or create the kernels in an image editing program and extend your Filter class to load kernels from a PNG file.
  • Implement a fake tilt-shift effect by blurring an image with a spatially-varying kernel size.
  • Fast incremental and separable box filter. Evaluate the speed improvements.
  • Fast Gaussian filter approximation using repeated fast box blurs (your solution should be asympotically independent of kernel size, and should automatically determine the box blur sizes from the desired Gaussian sigma). Evaluate the performance and accuracy relative to your separable Gaussian implementation.
  • Use a look-up table to accelerate the computation of Gaussians for bilateral filtering. Evaluate the performance difference.
  • Denoising using NL means http://www.math.ens.fr/culturemath/maths/mathapli/imagerie-Morel/Buades-Coll-Morel-movies.pdf

Submission

Turn in your files using Canvas and make sure all your files are in the a4 directory under the root of the zip file. Include all sources (.cpp and .h) files, any of your images, and the output of your program. Don't include your actual executable (we don't need your _build directory), and remove any superfluous files before submission.

In your readme.txt file, you should also answer the following questions:

Acknowledgments: This assignment is based off of one designed by Frédo Durand, Katherine L. Bouman, Gaurav Chaurasia, Adrian Vasile Dalca, and Neal Wadhwa for MIT's Digital & Computational Photography class.