ENGGEN 131: Matlab project

This walks through my submission for the first project of the ENGGEN 131 course taught in 2019. Our task for this project was to implement eight functions described in the project outline, with marks being given for correctness, documentation, and performance. Surprisingly, the last of these requirements turned out the be the most difficult to satisfy.

Function 1: GenerateFrameList

GenerateFrameList generates a list of frames we are interested in which can be used by other functions. In particular it will be useful for determining which frames to extract from a movie file.

It takes three inputs in the following order:

  1. A starting frame number
  2. A step size
  3. The number of frames to generate (n)

It returns a single output, a 1xn 1D array, where n is the desired number of frames (n). The first element of the array will be the starting frame number and each subsequent element will have a frame value that is the step size greater than the last.

Implementation

function [frames] = GenerateFrameList(start,step,n)
frames = start:step:start+(n-1)*step;
end

Trivial.

Function 2: GenerateImageList

Fetch a list of the names of all the images with a particular file extension that are contained in a specified directory.

It takes two inputs in the following order:

  1. A string containing the name of the directory the images are contained in
  2. A string containing the file extension of the images to fetch

It returns one output: A 1xn 1D cell array containing n strings where each element is the filename of an image from the specified directory that has a particular file extension

Implementation

function [files] = GenerateImageList(directory,extension)
files = {dir(fullfile(directory,['*.' extension])).name};
end

There are three parts to this function:

  1. Assembling a pattern from the directory and extension.
  2. Generating a list of files that match this pattern.
  3. Extracting the file names from these files.

(1) is simple matter of concatenating the input strings and (2) is performed exactly by dir. (3) is done by getting the name fields of the files and concatenating them into a cell array with {}.

Function 3: ReadImages

Read in a specified list of images given the filenames and the directory the files are located in.

It takes two inputs in the following order:

  1. A string containing the name of the directory the images are contained in
  2. A 1xn 1D cell array containing n strings where each element is a filename of an image to read

It returns one output A 1xn 1D cell array containing n images, where each element is an RGB image (recall RGB images stored as 3D arrays of uint8 values ranging from 0 to 255). The first image will correspond to the first filename from the list of filenames

Implementation

function [images] = ReadImages(directory,filenames)
images = cellfun(@imread,fullfile(directory,filenames),'UniformOutput',false);
end

Sadly, imread can't be used directly on a cell array so we're forced to manually map it over our files with cellfun.

Function 4: PixelDistance

Calculates the square of the distance between two points in colour space

It takes two inputs in the following order:

  1. An array containing three elements representing a point in 3D colour space
  2. An array containing three elements representing a second point in 3D colour space

A single output, the square of the distance between the two points in 3D colour space.

Implementation

function [distance] = PixelDistance(p1,p2)
distance = sum((double(p1)-double(p2)).^2);
end

It might be faster to take the dot product of p1 - p2 with itself, but I hope the direct solution I used can be optimised better.

Function 5: MedianPixel

MedianPixel calculates the median RGB values from a list of pixels.

It takes one input: A 1xnx3 3D array of RGB values representing a list of pixels (pixel 1 will be in column 1, pixel 2 in column 2 etc). Typically n will be greater than 1 (i.e. there are two or more pixels in the list) but your code should still be able to handle the special case of n being 1.

It returns three outputs in the following order:

  1. The median red value, which will be the median of all the R values from the list of pixels
  2. The median green value, which will be the median of all the G values from the list of pixels
  3. The median blue value, which will be the median of all the B values from the list of pixels

Implementation

function [red,green,blue] = MedianPixel(pixels)
pixel = num2cell(median(pixels,2));
[red,green,blue] = pixel{:};
end

Matlab doesn't allow indexing on the result of a function call, so we have to define an intermediary pixel variable. If only Matlab had a way to assign the values of a matrix to multiple variables we could avoid the num2cell andpixel{:} pattern.

Function 6: MostDistantPixel

MostDistantPixel calculates the pixel from a list that is most distant from the median RGB values of that list. The distance metric to be used is that described in the PixelDistance task (i.e. the squared distance between points in RGB colour space)

It takes one input: A 1xnx3 3D array of RGB values representing a list of pixels (pixel 1 will be in column 1, pixel 2 in column 2 etc). Typically n will be greater than 1 (i.e. there are two or more pixels in the list) but your code should still be able to handle the special case of n being 1.

It returns three outputs in the following order:
1. The red value of the most distant pixel
2. The green value of the most distant pixel
3. The blue value of the most distant pixel

Implementation

function [red,green,blue] = MostDistantPixel(pixels)
[~,I] = max(sum((double(pixels)-double(median(pixels,2))).^2,3));
pixel = num2cell(pixels(:,I,:));
[red,green,blue] = pixel{:};
end

We don't even have to specify 'linear', since we're only dealing with a 1D vector of pixels.

Function 7: RemoveAction

RemoveAction creates an image that has the action removed by applying a median filter (i.e. each pixel in the new image is obtained by taking the median RGB values of the stack of corresponding pixels taken from the source images.

It takes one input: A 1xn 1D cell array containing n images, where each element is an RGB image (recall RGB images stored as 3D arrays of uint8 values ranging from 0 to 255).

It returns one output:
An RGB image (stored as 3D arrays of uint8 values ranging from 0 to 255) that was obtained by taking the median RGB values of the stack of corresponding pixels from the source images.

Implementation

function [frame] = RemoveAction(frames)
frame = median(cat(4,frames{:}),4);
end

This function is both shorter and simpler than MedianPixel. Matlab!

Function 8: ActionShot

ActionShot creates an action shot image by finding the pixels from a stack of images that are most distant from the median RGB values (i.e. each pixel in the new image is obtained by finding the pixel in the same row and column of the source images that is most distant from the median RGB values of the stack of corresponding pixels.

It takes one input: A 1xn 1D cell array containing n images, where each element is an RGB image (recall RGB images stored as 3D arrays of uint8 values ranging from 0 to 255).

It returns one output: An action shot in the form of an RGB image (stored as 3D arrays of uint8 values ranging from 0 to 255)

Implementation

function [frame] = ActionShot(frames)
chonk = cat(4,frames{:});
[~,I] = max(repmat(sum((double(chonk)-double(median(chonk,4))).^2,3),1,1,3),[],4,'linear');
frame = chonk(I);
end

Here we use 'repmat' to repeat the input of the differences matrix for each pixel colour so we can use the 'linear' option of max to give us the indices of the most distant pixels. This means that max does three times as much work as it needs to, though in practice this makes no difference to the runtime.

In any other language the correct way to do this would be to convert the I output of the max function into the linear indices we want, but I can't think of a way to do this conversion cleanly.