As an elective during my senior year of college, I chose to take Introduction to Geospatial Technologies. A mildly interesting class, but one assignment really got on my nerves. The assignment was to digitize the following image
It’s a square-mile slice of Rochester NY, just north of my campus, showing specifications for that regions sewer system. The image above is scaled down considerably; The original is 11249×9152 pixels. Our assignment was to digitize the entirety of the sewer details in this image. If you look closely, you’ll see lines (pipes) with dots (manholes) running down the centers of each street. Yup, we were supposed to digitize all of that, including all associated numbers, by hand.
I don’t like being used as a click farm under the guise of “education.” I learned how to plot points and draw lines in middle school, thank you very much. Besides, whenever I see lots of repetitive work, my automation alarms start going off. So I decided to try and tackle the problem with computer vision.
Along with this image, we were given a vector centerlines file describing the configuration of the roads. Since most of the sewer pipes stay inline with the roads, I thought it best to locate the manholes first, and use them to split the centerlines into segments.
Before going into computer vision, it’s worth a little time to pre-process the image. I brought the massive TIFF into gimp, where I applied a slight blur, and tweaked the levels until it was truly black & white. The purpose of the blur was to fight significant noise in the image that was akin to dithering patterns. Since I’ll be doing blob detection later, it helps to have everything be as contiguous as possible.
Then, in an OpenCV application, the image is inverted, and a distance transform is applied. In a nutshell, a distance transform is an operation that leaves thicker (lit) regions shaded brighter, causing thin features to receed into the dark background. Its effect, from a color-value standpoint, is extremely subtle, but with some simple ranging, you can use it to produce images such as this:
You can see that it does a decent job at locating the manhole dots I was after. With some additional ranging, you can eleminate the darker background information, leaving only the bright manhole dots. These can then be fed into OpenCV’s SimpleBlobDetector to get pixel coordinates.
After some value-tweaking, I got this working suprisingly well, and sucessfully located 689 blobs. However, it would still produce the occassional false positives, ussually having to do with the arrows on the lines between manholes. To weed these out, I wrote another quick application that shows me each potential manhole, and allows me to accept or reject it. This went quicker than I thought it would (about 5 minutes), and gave me a final list of 519 manholes. Not a great false-positive rate, but I intentionally set the detector with wider margins, hoping to miss less true positives.
After putting these pixel coordinates into a GeoJSON file as “Features”, I then took to ArcMap for its georeferencing facilities. Georeferencing is the act of matching up the pixel values of an image with the coordinates (feet) of the real world (which are specified in the centerlines file). Using 4 anchor points, you can compute the affine transform that maps your image onto the world. After aligning and finding these anchor points in ArcMap, I used GDAL’s ogr2ogr utility to process my GeoJSON file.
ogr2ogr -f "GeoJSON" world_nodes.json \ -gcp 2431 241 1396527.486 1156707.409 \ -gcp 10592 247 1402186.618 1156770.797 \ -gcp 2396 8909 1396559.438 1150970.182 \ -gcp 10931 8908 1402226.101 1151037.144 \ nodes.json
Now, with my manholes in world-space, I wrote a small python script to slice up the centerlines file into individual segments of sewer pipe. This involved finding the manholes on a given line (using perpendicular distance), and subdividing that line into smaller segments, starting or ending on a manhole.