Computer Vision with OpenCV
Use the OpenCV for Processing library to detect faces, subtract backgrounds, trace contours, and attract a particle swarm to live webcam shapes.
The OpenCV for Processing library, written by Greg Borenstein, wraps the OpenCV 2.x C++ library in a Processing-friendly Java API. It gives you face detection, background subtraction, contour finding, and optical flow without touching native code directly.
Installation
Open the Contribution Manager (Sketch > Import Library > Manage Libraries), search for OpenCV for Processing, and install. The library installs native binaries for the current platform automatically.
After installation, verify your setup:
import gab.opencv.*;
import processing.video.*;
Capture cam;
OpenCV opencv;
void setup() {
size(640, 480);
cam = new Capture(this, 640, 480);
opencv = new OpenCV(this, 640, 480);
cam.start();
}
void draw() {
if (cam.available()) {
cam.read();
opencv.loadImage(cam);
image(opencv.getOutput(), 0, 0);
}
}
opencv.getOutput() returns a PImage of whatever the last OpenCV operation produced. opencv.getInput() returns the unmodified source.
Grayscale Conversion
Many CV algorithms require a single-channel image. Call opencv.gray() after loading:
opencv.loadImage(cam);
opencv.gray();
image(opencv.getOutput(), 0, 0); // grayscale PImage
Chain operations; each modifies the internal buffer in place. Use opencv.getSnapshot() to capture the intermediate state as a PImage before the next operation overwrites it.
Face Detection
OpenCV ships Haar cascade classifiers. opencv.detect() returns an array of Rectangle objects (java.awt.Rectangle):
import gab.opencv.*;
import processing.video.*;
import java.awt.Rectangle;
Capture cam;
OpenCV opencv;
Rectangle[] faces;
void setup() {
size(640, 480);
cam = new Capture(this, 640, 480);
opencv = new OpenCV(this, 640, 480);
opencv.loadCascade(OpenCV.CASCADE_FRONTALFACE);
cam.start();
}
void draw() {
if (cam.available()) cam.read();
opencv.loadImage(cam);
faces = opencv.detect();
image(cam, 0, 0);
noFill();
stroke(0, 255, 0);
strokeWeight(3);
for (Rectangle r : faces) {
rect(r.x, r.y, r.width, r.height);
}
}
Detection is CPU-heavy; limit to every other frame or reduce the image scale with opencv.setGray(opencv.getGray().get(0, 0, width/2, height/2)) for faster throughput.
Background Subtraction
Background subtraction isolates moving objects from a static scene. OpenCV’s MOG2 algorithm maintains a statistical model of the background:
void setup() {
// ... camera setup as above ...
opencv.startBackgroundSubtraction(5, 3, 0.5);
// args: history frames, variance threshold, shadow detection threshold
}
void draw() {
if (cam.available()) cam.read();
opencv.loadImage(cam);
opencv.updateBackground();
// getOutput() is now the foreground mask (white = motion)
image(opencv.getOutput(), 0, 0);
}
The foreground mask is a binary image you can feed directly into contour finding.
Contour Finding
opencv.findContours() returns a List<Contour>. Each Contour offers getPoints() (a list of PVector) and area/bounding-box accessors:
import gab.opencv.*;
import processing.video.*;
import java.util.List;
Capture cam;
OpenCV opencv;
List<Contour> contours;
void setup() {
size(640, 480);
cam = new Capture(this, 640, 480);
opencv = new OpenCV(this, 640, 480);
opencv.startBackgroundSubtraction(5, 3, 0.5);
cam.start();
}
void draw() {
if (cam.available()) cam.read();
opencv.loadImage(cam);
opencv.updateBackground();
opencv.dilate(); // fill gaps
opencv.erode(); // remove noise
contours = opencv.findContours(true, true);
// args: findHoles, approximateSingleContour
background(0);
noFill();
stroke(255, 100, 0);
strokeWeight(1.5);
for (Contour c : contours) {
if (c.area() < 800) continue; // skip tiny blobs
c.draw(); // draws directly to sketch
}
}
Optical Flow
The opencv.calculateOpticalFlow() method uses Lucas-Kanade sparse optical flow to track feature points. Call it after opencv.loadImage():
opencv.loadImage(cam);
opencv.calculateOpticalFlow();
// Draw flow vectors
PVector[] flow = opencv.getAverageFlow();
// flow is a grid of PVectors — x/y velocity per cell
// (grid size configurable via opencv.setOpticalFlowGridSize(int))
opencv.drawOpticalFlow(); // convenience: draws arrows onto the sketch
Full Example: Contour-Based Particle Attractor
This sketch finds foreground contours, extracts their points, and uses them as attractors for a particle swarm. Particles steer toward the nearest contour vertex each frame.
import gab.opencv.*;
import processing.video.*;
import java.util.List;
import java.util.ArrayList;
Capture cam;
OpenCV opencv;
List<Contour> contours;
ArrayList<Particle> particles;
PVector[] attractors = new PVector[0];
final int NUM_PARTICLES = 800;
void setup() {
size(640, 480, P2D);
cam = new Capture(this, width, height);
opencv = new OpenCV(this, width, height);
opencv.startBackgroundSubtraction(5, 3, 0.5);
cam.start();
particles = new ArrayList<Particle>();
for (int i = 0; i < NUM_PARTICLES; i++) {
particles.add(new Particle(random(width), random(height)));
}
}
void draw() {
if (cam.available()) {
cam.read();
opencv.loadImage(cam);
opencv.updateBackground();
opencv.dilate();
opencv.erode();
contours = opencv.findContours(true, true);
// Rebuild attractor list from all large-enough contour points
ArrayList<PVector> pts = new ArrayList<PVector>();
for (Contour c : contours) {
if (c.area() < 1000) continue;
for (PVector p : c.getPoints()) {
pts.add(p.copy());
}
}
attractors = pts.toArray(new PVector[0]);
}
// Dim previous frame for trail effect
fill(0, 25);
noStroke();
rect(0, 0, width, height);
// Update and draw particles
stroke(255, 160, 50, 200);
strokeWeight(1.2);
noFill();
for (Particle p : particles) {
p.attract(attractors);
p.update();
p.draw();
}
// Overlay faint live camera
tint(255, 40);
image(cam, 0, 0);
noTint();
}
class Particle {
PVector pos, vel, acc;
float maxSpeed = 3.5;
float maxForce = 0.18;
Particle(float x, float y) {
pos = new PVector(x, y);
vel = PVector.random2D().mult(2);
acc = new PVector();
}
void attract(PVector[] targets) {
if (targets.length == 0) {
// Drift to random position when no contours
PVector wander = PVector.random2D().mult(0.5);
acc.add(wander);
return;
}
// Find nearest attractor
PVector nearest = null;
float minDist = Float.MAX_VALUE;
for (PVector t : targets) {
float d = PVector.dist(pos, t);
if (d < minDist) { minDist = d; nearest = t; }
}
PVector desired = PVector.sub(nearest, pos);
float d = desired.mag();
if (d < 5) {
// Arrived — pick a random nearby attractor next frame
acc.add(PVector.random2D().mult(maxSpeed));
return;
}
float speed = map(d, 0, 200, 0.5, maxSpeed);
desired.setMag(speed);
PVector steer = PVector.sub(desired, vel);
steer.limit(maxForce);
acc.add(steer);
}
void update() {
vel.add(acc);
vel.limit(maxSpeed);
pos.add(vel);
acc.mult(0);
// Wrap around edges
if (pos.x < 0) pos.x = width;
if (pos.x > width) pos.x = 0;
if (pos.y < 0) pos.y = height;
if (pos.y > height) pos.y = 0;
}
void draw() {
point(pos.x, pos.y);
}
}
Performance Tuning
findContours() allocates new ArrayList objects every call. If garbage collection causes frame drops, consider running the OpenCV processing in a background thread and synchronizing the attractor array with synchronized blocks. For 60 fps with 1,000+ particles, run OpenCV at 15 fps (every fourth frame) and interpolate attractor positions between updates.
Background subtraction quality depends heavily on lighting consistency. Prevent flickering fluorescent lights from corrupting the background model by increasing the history parameter above 20 frames, or by calling opencv.startBackgroundSubtraction(30, 16, 0.0) to disable shadow detection.