Description
I created a website that automatically makes a spot-the-difference challenge from various images. When given an image, I first find the specific items within the picture using ImageContents. I then remove or edit each of those contents and put the edited subimage back onto the original image, or in the case of removal, use InPaint to cover the gap.
When the user arrives at the site, they choose a difficulty level, whether they would like one image or all of the images in that difficulty range, and an image. They can click to the very right in order to reveal the correct solution.
General Image Functions
The heart of the functionality in my project is ImageContents, a function which uses machine learning to find and identify things in a given image. Though ImageContents uses the same idea as ImageIdentify, it can find items in subimages and not only the entire image. For example, lets take the ImageContents of this elephant image. It finds each elephant separately.
One of the first problems I ran into was that Inpaint will sometimes use parts of the original image which contain other objects: for example, Inpaint might successfully cover the large elephant, but catch the small elephant in its mask, covering the large elephant with an unsettling floating baby elephant head.
In order to solve this, I created my first function: mask
which solves the issue of not being able to auto-generate masks based solely on their bounding boxes.
boundingboxes[image_]:=Quiet[Check[
List @@@ Flatten@
Values@Normal@ImageContents[image, All, "BoundingBox"], {}]]
The next function I created is called expunge
, which utilizes mask
to Inpaint the image contents.
mask[{{col1_, row1_}, {col2_, row2_}}, {nrows_, ncols_},
previous_: None] :=
Table[If[nrows - row2 < row <= nrows - row1 + 1 &&
col1 < col <= col2, 1,
If[previous === None, 0, previous[[row, col]]]], {row,
nrows}, {col, ncols}]
The next function I made is called ImageMapAt. ImageTrim trims the contents out of the image and applies the given function to the trimmed contents. ImageMapAt uses ImageCompose to shove the edited contents back into the original image, one at a time. Also, ImageMapAt uses the parameters {Left,Top} to align the edited contents correctly when pasting them back into the image, so it doesn't return outputs like this:
Image Difference Functions
Once all my general functions were working, my next step was to create functions to be applied to the images to automate the differences. These functions were based on built-in functions in the Wolfram Language, but specified to visible yet not too obvious variations. These include blurred
, which blurs an image by 2 units, darker
which darkens dominant colors, aquarecolor
which replaces a dominant color with part of the gradient Aquamarine, and various others.
Displaying Image Differences
Once I had created differences, my next step was to display them. The idea is simple, merely subtract the original image from the one with differences. The difficult part was finding a way to display them nicely as the default had the differences placed on a black background making them hard to see and the ColorNegated version of it was too bright, so I used ImageAdjust. Here are some of the results.
Image Distance Sorting
Now that I had created and displayed the differences, I sorted the images into the order of larger changes to smaller ones using ImageDistance, because the larger the distance is, the bigger the change. For example, compare these two distances. Wouldnt you agree the first one is roughly ten times easier than the second?
Website Code
I then created LibraryAdd, a function that is used to add images into my curated image library. Lastly, I created my website. The basic idea is that next to the original (left) and the new image (right), there will be a blank/white image that I refer to as "blank." The user clicks on the "empty space" once they think they know the difference, and then the correct difference is shown. If it is clicked again, the image returns to its original blank state. The code is shown below.
CloudDeploy[
FormFunction[{"difficulty" -> {"easy" -> {0, 1/4},
"medium" -> {1/4, 1/2}, "hard" -> {1/2, 3/4},
"impossible" -> {3/4, 1}}, "selection" -> {"random", "all"},
"image" -> (CloudImport[First@# <> "/1.png"] -> First[#] & /@
Select[CloudObjects[
"gallery"], ! StringContainsQ[First@#, ".png"] &])},
With[{total = Length[CloudObjects[#image <> "/"]]/2,
original = #image <> "/1"},
Grid[With[{orderly =
Table[With[{new = #image <> "/" <> ToString[i]},
"<img height=\"200px\" src=\"" <> If[#2, blank, #] <> "\"" <>
If[#2,
" onclick=\"javascript:this.src=(this.src=='" <> # <>
"' ? '" <> blank <> "' : '" <> # <> "');\"", ""] <>
">" & @@@ {original <> ".png" -> False,
new <> ".png" -> False, new <> "a.png" -> True}],
{i, TakeQuantile[Range[2, total], #difficulty]}]},
If[#["selection"] === "random", {RandomChoice[orderly]},
orderly]]]] &,
AppearanceRules -> <|"Title" -> "Spot the Difference",
"Description" ->
"Choose a difficulty, whether you would like to see the \
solutions or not, and an image, then hit submit to try to spot the \
difference between the original image on the left and the edited one \
on the right. <i><a \
href=\"mailto:stella@maymin.com\">stella@maymin.com</a></i>"|>], \
"spot.me", Permissions -> "Public"]
Future Work
In the future, as an extension of my website, I would like to find a way to create more of a game, perhaps by using dynamics. Another extension may be to create a timer counting in seconds how long it took the user to click the correct difference, or a counter of how many incorrect clicks on the Locator there were. Using those high scores, I think it might be possible to create a Leaderboard across all users. This would create a competition between users all over the world, so they can try to beat each others high scores in the race to the top. Lastly, a simpler extension is to create the option of allowing the user to upload their own images, which would be saved in my master library. This would let users play with images others have uploaded.
Acknowledgements
I would like to thank my mentor, Rory Fougler, for helping me with my code every step of the way. It really made a huge difference. I would also like to thank Chip Hurst for providing various insights and helpful functions throughout my project. Lastly, I would like to thank Mads Bahrami, Kyle Keane, and Anna Musser for making this entire experience truly unforgettable. Overall, I am so grateful for my knowledge and capabilities now, thanks to my stay here at the Wolfram High School Summer Camp.
Attachments: