I thought of a thing that I wanted, found that someone else had already done it, and sold it on Etsy, but then stopped. Using a photo of it, and some light hacking skills, I made another one for myself.
I like words, optical illusions, and art. An ambigram is a clever combination of all three of these, so of course I like these too. There are a few different forms, and while they're all neat, I am particularly fond of the type that incorporates two wholly different words, with a 180-degree rotation separating the two, like the true/false image above.
One day, I noticed one of those dishwasher clean/dirty magnets, and thought "Hey! that would be a perfect way to use an ambigram!" Then, realizing my limited artistic skill would prevent me from inventing it myself, I turned to the web. I found a handful of examples, and one that I really liked on Etsy - this one was actually designed to be used as a dishwasher magnet! Unfortunately, it not only unavailable, but it's the lone product in a defunct shop, so I was not hopeful about contacting the creator. Thanks for the design, Nick, if you ever see this!
So that would have been a dead end, except the image was available in a halfway decent resolution. That means I should be able to hack it back into existence, so I set out to do just that.
570x443 pixels isn't great, but for a simple, monochrome pattern with sharp lines, it might be good enough. So, there is this rectangular object, and I have a moderate-resolution photograph of it from an oblique angle. What do I need to do to reproduce the pattern at high resolution? It's simple:
- Undo the perspective projection
- Trace the edges to generate a vector image
- Clean up the messy edges
Normally, I'm happy to dive into coding this stuff from scratch, just for the hell of it. In this case I was more interested in the destination than the journey, so I went for some proven solutions.
Undoing the perspective projection
Generally speaking, extracting a high-quality representation of an object, from a photo in uncontrolled conditions, is a tricky problem. Photographing something means, among other things, applying a perspective projection to a 3D scene. That projection removes a lot of information from the scene to reduce it to an image - the exact shape, size, and location of the object - and that's exactly what we want. If the object has any sort of complicated shape, it might be necessary to use some clever heuristics, or maybe multiple photos from different angles. On the other hand, a planar rectangle with known dimensions (4"x2.5" conveniently given in the product description), is just about the easiest thing to work with.
In the case of a known rectangle, there is a simple way to estimate the original perspective projection, invert it, and apply that inverse to the photo. This effectively maps whatever pattern is on the rectangle in the photo, to a new, properly rectangular region, like so:
Perspective projection inverted
As part of my perpetual Matlab-to-Python transition, I decided to ignore some old Matlab code I wrote to solve this problem, and redo it in Python. As it turns out, with the OpenCV bindings available in Python, the solution is trivial. All I had to do was manually pick a handful of keypoints (the rectangle corners), and pass them to a single function to generate the required transformation. The output of that can then be applied directly to the image. In short, something like this:
h, status = cv2.findHomography(pin, pout)
im_dst = cv2.warpPerspective(im_src, h, dim)
"Homography" is more or less another word for perspective projection. If I needed to do this for more than a few images, I would have wanted to automate the keypoint-picking step (I've used SIFT for that in the past, I wonder if that's still in fashion?), but I'll save that for a time when I need it.
Here is the full python code, because it's easy enough to embed here:
Generating a vector image
The "hard" part is done - I have something pretty close to what I want for the final product - that just leaves the grunt work. The end goal is to produce a clean, black and white image, and there are different ways to go about that. Experience tells me that in this case, vectorizing the image will give the best result. I've used a few tools for this task, including some free web services, potrace, and Corel Draw. Here, Illustrator did the best job:
This looks pretty good, at least around the letters. The finer details in the decorative border didn't turn out so well, but that's ok - I'm more into the minimal design with letters only. I removed all the border stuff, and did a little manual repair work on some of the letter edges.
Final high-quality raster image
Perfect! All I had to do was get it printed on a magnet. I used purebuttons, which was great for a low-volume order. The final product: