Photo
interface. It defines methods for loading a photo from a file, and retreiving size and pixel information.enum
called Pixel
which defines the values WHITE
, GRAY
, and BLACK
.
PhotoLoader
, PhotoMatcher
, and PhotoLocator
which have methods for working with images: loading sets of images, finding the quality of a match between two images, and finding the best match for a set of images.
PhotoProcessor
which implements PhotoLoader
, PhotoMatcher
, and PhotoLocator
.
ExtraCredit
interface to implement which uses a more flexible notion of matching images.
BLACK
, GRAY
, and WHITE
. Furthermore, assume that all photos are the same dimensions (for extra credit, you can handle photos which can be different dimensions and which are not necessarily perfectly aligned). The quality of a match is based on the percent of pixels which match or somewhat match (more on that later).
You will need to implement four interfaces, at the very least: Photo
, which lets you load a photo and read its pixel data; plus the three PhotoProcessor
-related interfaces providing the methods necessary to match pairs of photos.
Rules
private
or protected
. This will be verified manually.
PhotoProcessor
class which implements PhotoLoader
, PhotoMatcher
, and PhotoLocator
.
public enum Pixel
This enumeration must contain the three values, BLACK
, GRAY
, and WHITE
.
Photo: public interface Photo
Photos are made up of a rectangle of 3-color (black/gray/white) pixels. The upper left corner of the photo is (0, 0)
. The x-value increases when moving to the right, and the y-value increases going downwards. When the load function is called with a filename, a photo is read from a text file. Once an image is loaded, the interface's methods can be used to access the image's pixel data. Below is an example of what a photo file might look like:
. ...
..x..
..xxx..
...x..
.
... . .x
.x.x
.x
.x
Photo
, your PhotoProcessor
must be partially functional as well, because it's the only way we can load photos using your code.Scanner
to read from a file as well. You just have to make sure that when you first create the Scanner
object, you should open it with a File
or a FileInputStream
.public void load(String filename) throws Exception
load image data from the named text file. Throws an exception if there was a problem opening the file (does not exist, etc), or if it's badly formatted (lines which are different length). Each line in the file represents one row of pixels, and each character represents a single pixel. Specifically, a ' '
(blank space) represents a white pixel, a '.'
(period) represents a gray pixel, and any other character (for example a 'x'
) represents a black pixel.
public String getName()
returns the name of the photo, i.e. the filename which was used the last time the photo was loaded. You may return null if no photo has been loaded.
public int getHeight()
returns the height of the image in pixels. You may return zero if no photo has been loaded.
public int getWidth()
returns the width of the image in pixels. You may return zero if no photo has been loaded.
public Pixel getPixel(int x, int y)
returns the pixel value located at the requested image coordinates.
public interface PhotoLoader
This interface provides all of the image loading functionality which we need for this project. You will also need to write a class called PhotoProcessor
which implements this functionality, so that we have something which we can instantiate and use. The PhotoProcessor
must have a default constructor (i.e. it must be possible to instantiate it with no arguments).
The role of this interface is for loading images. It should be possible to load a set of background images, as well as a set of foreground images. The background images represent a set of locations, and the goal is to figure out which foreground images correspond to which locations.
A list of background images will be specified by a file. So for example, we may have a file bgplaces.txt
containing:
johnson_center.txt
washington_monument.txt
fair_oaks_mall.txt
"bgplaces.txt"
, the load function will look inside this file, take the three filenames there, and load each of the three as photos. A similar process would be used for foreground images. Once the photo files are loaded, we also have methods to retrieve the photos by name or as a collection.
Tip 1: a Collection
is an interface which is implemented by a number of different types of lists. An ArrayList
is a Collection
and a HashMap
contains a Collection
, for instance. In order to get back an array from a collection (if you prefer to work with it that way, you can use toArray()
. For example:
ArrayList<String> list = new ArrayList<String>();
// build the list
String[] array = list.toArray(new String[0]);
getPhoto()
method should retrieve photos whether they have already been loaded or not. It may make sense to keep some kind of list of photos which have already been loaded (as foreground or background photos, or independently), and if it's not already part of that list, then load it separately.
public Photo getPhoto(String photoname) throws Exception
returns a Photo
corresponding to the given filename, which can be either a foreground or background photo, or one we haven't already loaded.
public void loadBGManifest(String filename) throws Exception
load all of the background photos which are listed in the specified file.
public void loadFGManifest(String filename) throws Exception
load all of the foreground photos which are listed in the specified file.
public Collection<? extends Photo> getBGPhotos()
get a list of all background photos which have been loaded.
public Collection<? extends Photo> getFGPhotos()
get a list of all foreground photos which have been loaded.
public interface PhotoMatcher
Another role of the PhotoProcessor
(which implements this interface) is to find matches between photos. Given two photos, the quality of a match is defined as follows:
For a pair of pixels, if they both have the same value (WHITE
and WHITE
, GRAY
and GRAY
, or BLACK
and BLACK
), then the resulting score is 1.0.GRAY
(thus, the other one is either WHITE
or BLACK
), then the resulting score is 0.5.(0, 0)
pixel in the first image vs the (0, 0)
pixel in the second image, the (1, 0)
pixel vs the (1, 0)
pixel, etc). Assume that both images are the same size and and are aligned with one another.
As an example, suppose that we have the following background picture:
xxxxx
xx.xx
x. .x
xx.xx
xxxxx
xxxxx
x. .x
x.x.x
x. .x
xxxxx
(18*1.0 + 4*0.5 + 2*0.5 + 1*0.0)/25 = 0.84
.
public double getMatch(Photo foreground, Photo background)
finds the quality of match score between two different photos.
public interface PhotoLocator
Finally, you will need to be able to match your foreground photos up with the background photos to see which came from where. Assume that the number of foreground photos is no greater than the number of background photos, and that at most one foreground photo matches with each background photo. The goal is to find the mapping of foreground-to-background photos which has the best overall score (i.e. mapping produces the largest possible sum of quality scores). This interface should be implemented by PhotoProcessor
as well.
Since only one foreground photo is allowed for every background photo, you will most likey need to backtrack to find the best solution. This sort of problem lends itself to a recursive solution (try to match a pair, then reduce it to the subproblem of matching the remaining photos). In practice, this would be a very slow process if a many photos are involved, but as long as there are just a few, it should be doable. Also, consider saving or precomputing your match scores to save some time.
As an example, suppose that we have two background photos, bg1
and bg2
, and two foreground photos, fg1
and fg2
. Let's say that fg1
has a match score of 0.93 with bg1
and 0.31 with bg2
. Meanwhile, fg2
has a score of 0.13 with bg1
and 0.72 with bg2
. Then the best match will place fg1
with bg1
and fg2
with bg2
. The overall score of this choice is 0.93 + 0.72 = 1.65
, which is far better than the alternative choice's overall score, 0.31 + 0.13 = 0.44
Let's try a slightly different example. Suppose that fg1
with bg1
has a score of 0.85, while with bg2
it has a score of 0.65. Meanwhile, suppose that fg2
has a score of 0.80 with bg1
and 0.12 with bg2
. Then the matching of fg1
with bg1
and fg2
with bg2
has an overall score of 0.85 + 0.12 = 0.97
, while matching fg1
with bg2
and fg2
with bg1
has an overall score of 0.65 + 0.80 = 1.45
. Thus, the latter matching is better, despite the fact that the best independent matching for fg1
is bg1
.
If there are three foreground and three background photos, then there are six possible matchings to pick from, and so on.
Tip 1: if the number of photos is not too large, then a recursive solution may make sense. For example, consider trying to match foreground pictures fg1
, fg2
, and fg3
to background pictures bg1
, bg2
, and bg3
. We could try matching fg1
with bg1
, then use the recursive subproblem of finding the best match of fg2
and fg3
with bg2
and bg3
. After doing that, we can try matching fg1
with bg2
and recursively finding the best match of fg2
and fg3
with bg1
and bg3
. And so on.
public double getBestMatch(HashMap matches)
produces a map of foreground to background image names which results in the best matching score. If more than one matching produces the same best score, then any one of the best options may be returned. The return value is the total score of the chosen matching.
public interface ExtraCredit
public double getMatch(Photo foreground, Photo background, int x, int y)
finds the quality of match score, assuming that the upper left corner of the foreground photo is aligned to position (x, y)
in the background photo, and that the two photos may be different sizes. The quality of match score is only averaged over the overlapping pixels.
public double getMatch(Photo foreground, Photo background, int n)
finds the quality of match score between two photos, but the result is taken as the best possible score over all possible choices of (x, y)
which overlap by at least n
pixels. That is, the first version of getMatch()
is used, but the value of the first version depends on the (x, y)
offset which is chosen, so we want to pick the (x, y)
which gives us the best possible score. The reason for the n
is that you can sometimes get an easy perfect match by just matching a single pixel in the corner - we can avoid this by requiring that two photos overlap by at least n
pixels if we want the score to count.
public double getBestMatch(HashMap<String, String> matches, int n)
same as the normal version of getBestMatch()
, except that it uses the extra-credit version of getMatch()
with the given n
value.
> import java.util.HashMap;
> PhotoProcessor pp = new PhotoProcessor();
> pp.loadFGManifest("fgphotos.txt");
> pp.loadBGManifest("bgplaces.txt");
> System.out.println(pp.getFGPhotos().size());
3
> System.out.println(pp.getBGPhotos().size());
3
> HashMap<String,String> best = new HashMap<String,String>();
> System.out.println(best);
{}
> pp.getBestMatch(best);
> System.out.println(best);
{p3.txt=washington_monument.txt, p2.txt=johnson_center.txt, p1.txt=fair_oaks_mall.txt}
> Photo fg1 = pp.getPhoto("p1.txt");
> Photo bg1 = pp.getPhoto("johnson_center.txt");
> System.out.println(pp.getMatch(fg1, bg1));
0.818115234375
> System.out.println(pp.getMatch(fg1, fg1));
1.0
> Photo test = pp.getPhoto("testphoto.txt");
> System.out.println(test.getName());
testphoto.txt
> System.out.println(test.getWidth());
5
> System.out.println(test.getHeight());
5
> System.out.println(test.getPixel(3,3));
WHITE
> System.out.println(test.getPixel(0,4));
GRAY
Photo
interface)PhotoLoader
interface)PhotoMatcher
interface)PhotoLocator
interface)ExtraCredit
interface)Pixel
enum.
Photo
interface.
PhotoLoader
interface.
PhotoMatcher
interface.
PhotoLocator
interface.
ExtraCredit
interface.
Submission instructions are as follows.
xxx_yyyyyyyy_P5/
ID.txt
in the format shown below, containing your name, userid, G#, lecture section
and lab section, and add it to the directory.
Full Name: Donald Knuth