So cameras, computer vision and cool stuffs! Yay! For this week, I messed around with the Video and OpenCV libraries. this resulted in two programs: 1) a program that taces the webcam capture and runs an edge-identifying algorithm to re-draw the capture in cool, computer-y ways, and 2) an attempt at a function that is able to identify the inside of a shape drawn with a black marker. Fun times.
Program number one was fairly straightforward. I started by opening the OpenCV example that dealt with edge detection to get acquainted with the methods and how to use them. From there, I just hd to slightly modify the sketch: add the Video library and replace the image to be analyzed with the current frame of the camera’s capture. I also added a green tint because that was the first color that came to mind. 😀

Here’s the code (most of the credit goes to the library’s creator):
import processing.video.*;
import gab.opencv.*;
Capture capture;
OpenCV opencv;
PImage src, canny, scharr, sobel;
void setup() {
size(640, 480);
capture = new Capture(this, 640, 480);
capture.start();
}
void draw() {
src = capture;
opencv = new OpenCV(this, src);
opencv.findCannyEdges(30, 85);
canny = opencv.getSnapshot();
opencv.loadImage(src);
opencv.findScharrEdges(OpenCV.HORIZONTAL);
scharr = opencv.getSnapshot();
opencv.loadImage(src);
opencv.findSobelEdges(1, 0);
sobel = opencv.getSnapshot();
pushMatrix();
scale(0.5);
image(src, 0, 0);
tint(0, 255, 0);
image(canny, src.width, 0);
image(scharr, 0, src.height);
image(sobel, src.width, src.height);
popMatrix();
text("Source", 10, 25);
text("Canny", src.width/2 + 10, 25);
text("Scharr", 10, src.height/2 + 25);
text("Sobel", src.width/2 + 10, src.height/2 + 25);
}
void captureEvent(Capture c) {
c.read();
}
And now the painful fun part!
OpenCV comes with a contour-identifying function, which works great, but I couldn’t think of a way to use it to identify only shapes within drawn borders. Instead, it would find many contours that were irrelevant for my purposes.
Thus, I took inspiration from the FindContours example. There, the gray() and threshold() methods are used to turn an image into a grayscale version of itself and then setting all pixels over a certain value of brightness to white, and all others to black. That way, I can work with a simplified version of the image I’m analyzing. (This, however, also introduced some noise, as any shadow dark enough would be turned pitch black, and parts of the outline would be too light to be identified at times).
Then, I wrote I function to look for shapes. It looks at the image pixel by pixel. First, it tries to find an upper left corner. Then it defines that spot as the inside of the shape, and continues marking pixels as inside until it hits an edge, after which it marks the area as outside of the shape. Thus, finding edges while moving through horizontal rows of pixels would trigger the inside/outside of the shape. I also made the program “finish” the shape once it finds a lower right corner.
My algorithm, however, is quite problematic. It can’t deal with (even slightly) concave shapes or shapes whose bottom is thinner than its top. I’ve thought of a couple of ways to address this (noise reduction functions, functions to “smooth” out the analyzed image, making the shape finder function analyze the shape vertically as well as horizontally, etc.), but regardless of the outcome, this was an interesting exercise. 😀
[I’ll insert image here eventually]
And the code. It includes several attempts and upgrades and downgrades (?) and everything. Yay.
import gab.opencv.*;
import processing.video.*;
Capture capture;
OpenCV opencv;
PImage snapshot, analyzed, render;
ArrayList<Contour> contours;
ArrayList<Contour> polygons;
int threshold = 70;
void setup() {
//fullScreen();
size(640, 480);
capture = new Capture(this, 640, 480);
capture.start();
snapshot = capture.get();
opencv = new OpenCV(this, snapshot);
opencv.gray();
opencv.threshold(threshold);
analyzed = opencv.getOutput();
contours = opencv.findContours();
println("found " + contours.size() + " contours");
noFill();
for (Contour contour : contours) {
stroke(0, 255, 0);
contour.draw();
}
render = analyzed;
}
void draw() {
scale(0.5);
image(capture, 0, 0);
//image(snapshot, 640, 0);
image(analyzed, 640, 0);
//image(analyzed, 0, 480);
//for (Contour contour : contours) {
// strokeWeight(3);
// stroke(0, 255, 0);
// pushMatrix();
// translate(640, 0);
// contour.draw();
// stroke(255, 0, 0);
// beginShape();
// for (PVector point : contour.getPolygonApproximation().getPoints()) {
// vertex(point.x, point.y);
// }
// endShape();
// popMatrix();
//}
}
void keyPressed() {
switch (key) {
case ' ':
snapshot = capture.get();
opencv = new OpenCV(this, snapshot);
opencv.gray();
analyzed = opencv.getOutput();
int minVal = 255;
int maxVal = 0;
for (int q = 0; q < analyzed.pixels.length; q ++) {
if (int(red(analyzed.pixels[q])) > minVal) {
minVal = int(red(analyzed.pixels[q]));
}
if (int(red(analyzed.pixels[q])) < maxVal) {
maxVal = int(red(analyzed.pixels[q]));
}
}
threshold = int((minVal + maxVal)*0.3);
opencv.threshold(threshold);
analyzed = opencv.getOutput();
contours = opencv.findContours();
println("found " + contours.size() + " contours");
noFill();
for (Contour contour : contours) {
strokeWeight(3);
stroke(0, 255, 0);
contour.draw();
}
PImage smoothAnalyzed = smoothEdge(analyzed, 5);
detectShape2(smoothAnalyzed, render);
image(smoothAnalyzed, 0, 480);
image(render, 640, 480);
break;
}
}
void captureEvent(Capture c) {
c.read();
}
void detectShape(PImage check, PImage destination) {
boolean shapeFound = false;
boolean inShape = false;
boolean onEdge = false;
boolean checkingCorner = false;
for (int q = 0; q < check.pixels.length; q ++) {
if (red(check.pixels[q]) == 0) { //If black
if (shapeFound == false) { //If you haven't found a shape
checkingCorner = true; //Start checking for corners
} else { //If you have found a shape already
onEdge = true; //That means you are on the edge
}
destination.loadPixels();
destination.pixels[q] = color(255, 255, 255); //All black edges will be white, I only care about inside
destination.updatePixels();
} else { //If white
if (inShape == false && onEdge == true) { //If you were on an edge right before and were outside the shape
onEdge = false; //Then you are not on the edge anymore
inShape = true; //But rather within the shape
} else if (inShape == true && onEdge == true) { //If you were on an edge before and within the shape
onEdge = false; //Then you are ount of the edge now
inShape = false; //And outside of the shape as well
}
if (shapeFound == true && inShape == true) { //If you've found a shape and you are within it
destination.loadPixels();
destination.pixels[q] = color(255, 0, 0); //Mark the pixel
destination.updatePixels();
int corner = 0; //Check if adyacent pixels are black
if (red(check.pixels[q+1]) == 0) { //Check right
corner ++;
}
//if (red(check.pixels[q+1]) == 0) {
// corner ++;
//}
if (q < check.pixels.length - check.width) { //Check down
if (red(check.pixels[q+check.width]) == 0) {
corner ++;
}
}
if (corner >= 2) { //If at least two are black
shapeFound = false; //Then it was a corner and you've found the end of the shape
inShape = false; //therefore you are now outside the shape
}
} else { //Any other white bit that's not in the shape
destination.loadPixels();
destination.pixels[q] = color(255, 255, 255); //Remains white
destination.updatePixels();
}
if (shapeFound == false && checkingCorner == true) { //If you haven't found a shape but are checking for corners
int corner = 0; //Check if adyacent pixels are black
if (red(check.pixels[q-1]) == 0) { //Check left
corner ++;
}
//if (red(check.pixels[q+1]) == 0) {
// corner ++;
//}
if (q >= check.width) {
if (red(check.pixels[q-check.width]) == 0) { //Check up
corner ++;
}
}
boolean lowerEdge = false;
for (int w = floor(q/check.width); w < check.pixels.length/check.width; w ++) {
if (red(check.pixels[q + ((w - floor(q/check.width))*check.width)]) == 0) {
lowerEdge = true;
}
}
if (corner >= 2 && lowerEdge == true) { //If at least two are black
shapeFound = true; //Then it was a corner and you've found a shape
inShape = true; //therefore you are inside the shape
destination.loadPixels();
destination.pixels[q] = color(255, 0, 0); //Mark the pixel!
destination.updatePixels();
} else { //If not, then you still haven't found the inside of the shape
checkingCorner = false; //Stop checking this as a corner
}
}
}
}
}
void detectShape2(PImage check, PImage destination) {
boolean shapeFound = false;
boolean inShape = false;
boolean onEdge = false;
boolean checkingCorner = false;
IntList edgePixels = new IntList();
IntList shapePixels = new IntList();
for (int q = 0; q < check.pixels.length; q ++) {
if (red(check.pixels[q]) == 0) { //If black
if (shapeFound == false) { //If you haven't found a shape
checkingCorner = true; //Start checking for corners
} else { //If you have found a shape already
onEdge = true; //That means you are on the edge
}
edgePixels.append(q); //Add edge pixel to list
} else { //If white
if (inShape == false && onEdge == true) { //If you were on an edge right before and were outside the shape
onEdge = false; //Then you are not on the edge anymore
inShape = true; //But rather within the shape
} else if (inShape == true && onEdge == true) { //If you were on an edge before and within the shape
onEdge = false; //Then you are ount of the edge now
inShape = false; //And outside of the shape as well
}
if (shapeFound == true && inShape == true) { //If you've found a shape and you are within it
shapePixels.append(q); //Add shape pixel to list
int corner = 0; //Check if adyacent pixels are black
if (red(check.pixels[q+1]) == 0) { //Check right
corner ++;
}
//if (red(check.pixels[q+1]) == 0) {
// corner ++;
//}
if (q < check.pixels.length - check.width) { //Check down
if (red(check.pixels[q+check.width]) == 0) {
corner ++;
}
}
if (corner >= 2) { //If at least two are black
shapeFound = false; //Then it was a corner and you've found the end of the shape
inShape = false; //therefore you are now outside the shape
}
} else { //Any other white bit that's not in the shape
destination.loadPixels();
destination.pixels[q] = color(255, 255, 255); //Remains white
destination.updatePixels();
}
if (shapeFound == false && checkingCorner == true) { //If you haven't found a shape but are checking for corners
int corner = 0; //Check if adyacent pixels are black
if (red(check.pixels[q-1]) == 0) { //Check left
corner ++;
}
//if (red(check.pixels[q+1]) == 0) {
// corner ++;
//}
if (q >= check.width) {
if (red(check.pixels[q-check.width]) == 0) { //Check up
corner ++;
}
}
boolean lowerEdge = false;
for (int w = floor(q/check.width); w < check.pixels.length/check.width; w ++) {
if (red(check.pixels[q + ((w - floor(q/check.width))*check.width)]) == 0) {
lowerEdge = true;
}
}
if (corner >= 2 && lowerEdge == true) { //If at least two are black
shapeFound = true; //Then it was a corner and you've found a shape
inShape = true; //therefore you are inside the shape
shapePixels.append(q); //Add shape pixel to the list!
} else { //If not, then you still haven't found the inside of the shape
checkingCorner = false; //Stop checking this as a corner
}
}
}
}
noiseReduction(check, shapePixels, edgePixels);
for (int q = 0; q < destination.pixels.length; q ++) {
destination.loadPixels();
destination.pixels[q] = color(255, 255, 255);
destination.updatePixels();
}
for (int q = 0; q < shapePixels.size(); q ++) {
destination.loadPixels();
destination.pixels[shapePixels.get(q)] = color(255, 0, 0); //Mark the pixel!
destination.updatePixels();
}
for (int q = 0; q < edgePixels.size(); q ++) {
destination.loadPixels();
destination.pixels[edgePixels.get(q)] = color(0, 0, 255);
destination.updatePixels();
}
}
void noiseReduction(PImage check, IntList shape, IntList edge) { //Kill "shapes" that aren't enclosed
boolean edit = false;
int counter = 0;
int initialCount = shape.size();
for (int q = shape.size() - 1; q >= 0; q --) { //For every "shape" pixel...
int analyzedPixel = shape.get(q); //We look at one pixel at a time
if ((analyzedPixel >= check.pixels.length - check.width) //If the pixel belongs to top or bottom row
|| (analyzedPixel <= check.width)) { //By default kill it
shape.remove(q); //BYE
counter ++;
edit = true;
} else { //If it's not on the borders, then the thing gets trickier...
int stay = 0; //Let's count how many "valid" pixels are ther around our buddy
for (int w = 0; w < shape.size(); w ++) { //Valid pixels include those who are also part of the shape
//Check pixel left (get(q)-1), right (get(q)+1), up (get(q)-width), and down (get(q)+width)
//And compare those to all pixels in the shape (get(w))
if (shape.get(q)-1 == shape.get(w) || shape.get(q)+1 == shape.get(w) ||
shape.get(q)-check.width == shape.get(w) || shape.get(q)+check.width == shape.get(w)) {
stay ++; //A match means an adyacent valid pixel
}
}
for (int w = 0; w < edge.size(); w ++) { //As well as those who are part of the edge
//Ditto but now we check if any edge pixel is adyacent
if (shape.get(q)-1 == edge.get(w) || shape.get(q)+1 == edge.get(w) ||
shape.get(q)-check.width == edge.get(w) || shape.get(q)+check.width == edge.get(w)) {
stay ++;
}
}
if (stay < 3) { //Finally, unless the pixel has 4 valid pixels around it...
shape.remove(q); //KILL IT
counter ++;
edit = true;
}
}
}
println(counter + " pixels ignored out of " + initialCount);
if (edit == true) {
//noiseReduction(check, shape, edge);
} else {
return;
}
}
PImage smoothEdge(PImage check) { //To avoid random floating "edge" pixels
IntList edge = new IntList(); //List to store edge pixels
int counter = 0;
for (int w = 0; w < check.pixels.length; w ++) { //Check black pixels for preliminary list
if (red(check.pixels[w]) == 0) {
edge.append(w);
}
}
for (int q = edge.size() - 1; q >= 0; q --) { //For each pixel...
//if ((edge.get(q) >= check.pixels.length - check.width) ||
// (edge.get(q) <= check.width) || (edge.get(q) % check.width == 0) ||
// (edge.get(q) % check.width == check.width - 1)) {
// edge.remove(q);
//}
int nextTo = 0; //Check how many edge pixels are there next to it
for (int e = 0; e < edge.size(); e ++) {
if ((edge.get(e) == edge.get(q) - 1) || (edge.get(e) == edge.get(q) + 1) ||
(edge.get(e) == edge.get(q) - check.width) ||
(edge.get(e) == edge.get(q) + check.width)) {
nextTo ++;
}
}
if (nextTo < 2) { //If there is less than two adyacent edge pixels...
edge.remove(q); //Kill da pixel!
counter ++;
}
}
for (int t = 0; t < check.pixels.length; t ++) { //Now, we blank out the b/w image we had...
check.loadPixels();
check.pixels[t] = color(255, 255, 255);
check.updatePixels();
}
for (int r = 0; r < edge.size(); r ++) { //And write the new black edges on top!
check.loadPixels();
check.pixels[edge.get(r)] = color(0, 0, 0);
check.updatePixels();
}
//for (int q = 0; q < check.pixels.length; q ++) {
// if (q < 20*check.width || q >= check.pixels.length
//}
println(counter + " pixels killed");
return check; //And return the result!
}
PImage smoothEdge(PImage check, int iterations) {
for (int q = 0; q < iterations; q ++) {
check = smoothEdge(check);
}
return check;
}