## 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

(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.

(a) Original Image
(b) Image Filtered Using a Horizontal Sobel Kernel
(c) Gradient Magnitude of the Image

### 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.

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

## 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.

(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.