Blackout Blob
This week, New York magazine had a series of striking aerial photographs of the city’s blackout in the aftermath of Superstorm/Hurricane Sandy. Since the blackout covers such a large area, it works well for blob detection.
Principle Component Analysis (PCA) can be used to determine the orientation of an object in space, i.e. what direction it is pointing. One of Greg’s examples used blog detection and PCA to show the direction of a pen on a table. I thought it might be interesting to apply this code to the image of the blackout to see what would happen.
I ended up tweaking the brightness, contrast, and threshold values quite a bit before I was able to get a result I was happy with. When the brightness and contrast are set too high, it detects the lit area instead of the dark area. Higher threshold values change the location of the blob’s centroid. A few minor changes to these values can also change the perceived orientation of the blob. The tricky part is that I’m not entirely sure what the “right” answer is. I ended up settling on the up orientation (where the heaviest part of the blob is considered the “top”), since it is closest to the north/south orientation of a map. It is tilted in space a bit as a result of the way the photo was taken, and ideally this would probably have made more sense with a photo taken directly from above. Either way, the result is interesting to look at.
The code is below. Additional components on Github.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | //Kim Ash //Makematics - Fall 2012 //pca_nyc - takes map of NYC blackout and analyzes orientation of unlit area import Jama.Matrix; import pca_transform.*; import hypermedia.video.*; PCA pca; PVector axis1; PVector axis2; PVector mean; OpenCV opencv; PImage img; int imgWidth = 1200/4; int imgHeight = 800/4; void setup() { size(1200, 800); opencv = new OpenCV(this); opencv.loadImage("data/NYCsandy.jpg", imgWidth, imgHeight); noLoop(); } Matrix toMatrix(PImage img) { ArrayList<PVector> points = new ArrayList<PVector>(); for (int x = 0; x < img.width; x++) { for (int y = 0; y < img.height; y++) { int i = y*img.width + x; if (brightness(img.pixels[i]) == 0) { points.add(new PVector(x, y)); } } } println("nBlackPixels: " + points.size() + "/" + img.width*img.height); Matrix result = new Matrix(points.size(), 2); for (int i = 0; i < points.size(); i++) { result.set(i, 0, points.get(i).x); result.set(i, 1, points.get(i).y); } return result; } int currX = 0; int currY = 0; void imageInGrid(PImage img, String message) { image(img, currX, currY); fill(255, 0, 0); text(message, currX + 5, currY + imgHeight - 5); currX += img.width; if (currX > width - img.width) { currX = 0; currY += img.height; } } void draw() { //background(255); opencv.convert(GRAY); imageInGrid(opencv.image(), "GRAY"); opencv.brightness(53); imageInGrid(opencv.image(), "BRIGHTNESS: 53"); opencv.contrast(90); imageInGrid(opencv.image(), "CONTRAST: 90"); opencv.threshold(115); imageInGrid(opencv.image(), "THRESHOLD: 115"); Matrix m = toMatrix(opencv.image()); pca = new PCA(m); Matrix eigenVectors = pca.getEigenvectorsMatrix(); eigenVectors.print(10, 2); axis1 = new PVector(); axis2 = new PVector(); axis1.x = (float)eigenVectors.get(0, 0); axis1.y = (float)eigenVectors.get(1, 0); axis2.x = (float)eigenVectors.get(0, 1); axis2.y = (float)eigenVectors.get(1, 1); axis1.mult((float)pca.getEigenvalue(0)); axis2.mult((float)pca.getEigenvalue(1)); image(opencv.image(), 0, opencv.image().height, opencv.image().width*3, opencv.image().height*3); Blob[] blobs = opencv.blobs(10, imgWidth*imgHeight/2, 100, true, OpenCV.MAX_VERTICES*4 ); noFill(); stroke(200); pushMatrix(); translate(0, imgHeight); scale(3, 3); for ( int i=0; i<blobs.length; i++ ) { beginShape(); for ( int j=0; j<blobs[i].points.length; j++ ) { vertex( blobs[i].points[j].x, blobs[i].points[j].y ); } endShape(CLOSE); } PVector centroid = new PVector(blobs[0].centroid.x, blobs[0].centroid.y); translate(centroid.x, centroid.y); stroke(0, 255, 0); line(0, 0, axis1.x, axis1.y); stroke(255, 0, 0); line(0, 0, axis2.x, axis2.y); popMatrix(); fill(255,0,0); text("PCA Object Axes:\nFirst two principle components centered at blob centroid", 10, height - 20); } void draw(Matrix m) { double[][] a = m.getArray(); for (int i = 0; i < m.getRowDimension(); i++) { ellipse((float)a[i][0], (float)a[i][1], 3, 3); } } |