You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

456 lines
11 KiB
Plaintext

/**
* Image Filtering
* This sketch will help us to adjust the filter values to optimize blob detection
*
* Persistence algorithm by Daniel Shifmann:
* http://shiffman.net/2011/04/26/opencv-matching-faces-over-time/
*
* @author: Jordi Tost (@jorditost)
* @url: https://github.com/jorditost/ImageFiltering/tree/master/ImageFilteringWithBlobPersistence
*
* University of Applied Sciences Potsdam, 2014
*
* It requires the ControlP5 Processing library:
* http://www.sojamo.de/libraries/controlP5/
*/
import gab.opencv.*;
import java.awt.Rectangle;
import processing.video.*;
import controlP5.*;
OpenCV opencv;
Capture video;
PImage src, preProcessedImage, processedImage, contoursImage;
ArrayList<Contour> contours;
// List of detected contours parsed as blobs (every frame)
ArrayList<Contour> newBlobContours;
// List of my blob objects (persistent)
ArrayList<Blob> blobList;
// Number of blobs detected over all time. Used to set IDs.
int blobCount = 0;
float contrast = 1.35;
int brightness = 0;
int threshold = 75;
boolean useAdaptiveThreshold = false; // use basic thresholding
int thresholdBlockSize = 489;
int thresholdConstant = 45;
int blobSizeThreshold = 20;
int blurSize = 4;
// Control vars
ControlP5 cp5;
int buttonColor;
int buttonBgColor;
void setup() {
frameRate(15);
video = new Capture(this, 640, 480);
//video = new Capture(this, 640, 480, "USB2.0 PC CAMERA");
video.start();
opencv = new OpenCV(this, 640, 480);
contours = new ArrayList<Contour>();
// Blobs list
blobList = new ArrayList<Blob>();
size(opencv.width + 200, opencv.height, P2D);
// Init Controls
cp5 = new ControlP5(this);
initControls();
// Set thresholding
toggleAdaptiveThreshold(useAdaptiveThreshold);
}
void draw() {
// Read last captured frame
if (video.available()) {
video.read();
}
// Load the new frame of our camera in to OpenCV
opencv.loadImage(video);
src = opencv.getSnapshot();
///////////////////////////////
// <1> PRE-PROCESS IMAGE
// - Grey channel
// - Brightness / Contrast
///////////////////////////////
// Gray channel
opencv.gray();
//opencv.brightness(brightness);
opencv.contrast(contrast);
// Save snapshot for display
preProcessedImage = opencv.getSnapshot();
///////////////////////////////
// <2> PROCESS IMAGE
// - Threshold
// - Noise Supression
///////////////////////////////
// Adaptive threshold - Good when non-uniform illumination
if (useAdaptiveThreshold) {
// Block size must be odd and greater than 3
if (thresholdBlockSize%2 == 0) thresholdBlockSize++;
if (thresholdBlockSize < 3) thresholdBlockSize = 3;
opencv.adaptiveThreshold(thresholdBlockSize, thresholdConstant);
// Basic threshold - range [0, 255]
} else {
opencv.threshold(threshold);
}
// Invert (black bg, white blobs)
opencv.invert();
// Reduce noise - Dilate and erode to close holes
opencv.dilate();
opencv.erode();
// Blur
opencv.blur(blurSize);
// Save snapshot for display
processedImage = opencv.getSnapshot();
///////////////////////////////
// <3> FIND CONTOURS
///////////////////////////////
detectBlobs();
// Passing 'true' sorts them by descending area.
//contours = opencv.findContours(true, true);
// Save snapshot for display
contoursImage = opencv.getSnapshot();
// Draw
pushMatrix();
// Leave space for ControlP5 sliders
translate(width-src.width, 0);
// Display images
displayImages();
// Display contours in the lower right window
pushMatrix();
scale(0.5);
translate(src.width, src.height);
// Contours
//displayContours();
//displayContoursBoundingBoxes();
// Blobs
displayBlobs();
popMatrix();
popMatrix();
}
///////////////////////
// Display Functions
///////////////////////
void displayImages() {
pushMatrix();
scale(0.5);
image(src, 0, 0);
image(preProcessedImage, src.width, 0);
image(processedImage, 0, src.height);
image(src, src.width, src.height);
popMatrix();
stroke(255);
fill(255);
textSize(12);
text("Source", 10, 25);
text("Pre-processed Image", src.width/2 + 10, 25);
text("Processed Image", 10, src.height/2 + 25);
text("Tracked Points", src.width/2 + 10, src.height/2 + 25);
}
void displayBlobs() {
for (Blob b : blobList) {
strokeWeight(1);
b.display();
}
}
void displayContours() {
// Contours
for (int i=0; i<contours.size(); i++) {
Contour contour = contours.get(i);
noFill();
stroke(0, 255, 0);
strokeWeight(3);
contour.draw();
}
}
void displayContoursBoundingBoxes() {
for (int i=0; i<contours.size(); i++) {
Contour contour = contours.get(i);
Rectangle r = contour.getBoundingBox();
if (//(contour.area() > 0.9 * src.width * src.height) ||
(r.width < blobSizeThreshold || r.height < blobSizeThreshold))
continue;
stroke(255, 0, 0);
fill(255, 0, 0, 150);
strokeWeight(2);
rect(r.x, r.y, r.width, r.height);
}
}
////////////////////
// Blob Detection
////////////////////
void detectBlobs() {
// Contours detected in this frame
// Passing 'true' sorts them by descending area.
contours = opencv.findContours(true, true);
newBlobContours = getBlobsFromContours(contours);
//println(contours.length);
// Check if the detected blobs already exist are new or some has disappeared.
// SCENARIO 1
// blobList is empty
if (blobList.isEmpty()) {
// Just make a Blob object for every face Rectangle
for (int i = 0; i < newBlobContours.size(); i++) {
println("+++ New blob detected with ID: " + blobCount);
blobList.add(new Blob(this, blobCount, newBlobContours.get(i)));
blobCount++;
}
// SCENARIO 2
// We have fewer Blob objects than face Rectangles found from OpenCV in this frame
} else if (blobList.size() <= newBlobContours.size()) {
boolean[] used = new boolean[newBlobContours.size()];
// Match existing Blob objects with a Rectangle
for (Blob b : blobList) {
// Find the new blob newBlobContours.get(index) that is closest to blob b
// set used[index] to true so that it can't be used twice
float record = 50000;
int index = -1;
for (int i = 0; i < newBlobContours.size(); i++) {
float d = dist(newBlobContours.get(i).getBoundingBox().x, newBlobContours.get(i).getBoundingBox().y, b.getBoundingBox().x, b.getBoundingBox().y);
//float d = dist(blobs[i].x, blobs[i].y, b.r.x, b.r.y);
if (d < record && !used[i]) {
record = d;
index = i;
}
}
// Update Blob object location
used[index] = true;
b.update(newBlobContours.get(index));
}
// Add any unused blobs
for (int i = 0; i < newBlobContours.size(); i++) {
if (!used[i]) {
println("+++ New blob detected with ID: " + blobCount);
blobList.add(new Blob(this, blobCount, newBlobContours.get(i)));
//blobList.add(new Blob(blobCount, blobs[i].x, blobs[i].y, blobs[i].width, blobs[i].height));
blobCount++;
}
}
// SCENARIO 3
// We have more Blob objects than blob Rectangles found from OpenCV in this frame
} else {
// All Blob objects start out as available
for (Blob b : blobList) {
b.available = true;
}
// Match Rectangle with a Blob object
for (int i = 0; i < newBlobContours.size(); i++) {
// Find blob object closest to the newBlobContours.get(i) Contour
// set available to false
float record = 50000;
int index = -1;
for (int j = 0; j < blobList.size(); j++) {
Blob b = blobList.get(j);
float d = dist(newBlobContours.get(i).getBoundingBox().x, newBlobContours.get(i).getBoundingBox().y, b.getBoundingBox().x, b.getBoundingBox().y);
//float d = dist(blobs[i].x, blobs[i].y, b.r.x, b.r.y);
if (d < record && b.available) {
record = d;
index = j;
}
}
// Update Blob object location
Blob b = blobList.get(index);
b.available = false;
b.update(newBlobContours.get(i));
}
// Start to kill any left over Blob objects
for (Blob b : blobList) {
if (b.available) {
b.countDown();
if (b.dead()) {
b.delete = true;
}
}
}
}
// Delete any blob that should be deleted
for (int i = blobList.size()-1; i >= 0; i--) {
Blob b = blobList.get(i);
if (b.delete) {
blobList.remove(i);
}
}
}
ArrayList<Contour> getBlobsFromContours(ArrayList<Contour> newContours) {
ArrayList<Contour> newBlobs = new ArrayList<Contour>();
// Which of these contours are blobs?
for (int i=0; i<newContours.size(); i++) {
Contour contour = newContours.get(i);
Rectangle r = contour.getBoundingBox();
if (//(contour.area() > 0.9 * src.width * src.height) ||
(r.width < blobSizeThreshold || r.height < blobSizeThreshold))
continue;
newBlobs.add(contour);
}
return newBlobs;
}
//////////////////////////
// CONTROL P5 Functions
//////////////////////////
void initControls() {
// Slider for contrast
cp5.addSlider("contrast")
.setLabel("contrast")
.setPosition(20,50)
.setRange(0.0,6.0)
;
// Slider for threshold
cp5.addSlider("threshold")
.setLabel("threshold")
.setPosition(20,110)
.setRange(0,255)
;
// Toggle to activae adaptive threshold
cp5.addToggle("toggleAdaptiveThreshold")
.setLabel("use adaptive threshold")
.setSize(10,10)
.setPosition(20,144)
;
// Slider for adaptive threshold block size
cp5.addSlider("thresholdBlockSize")
.setLabel("a.t. block size")
.setPosition(20,180)
.setRange(1,700)
;
// Slider for adaptive threshold constant
cp5.addSlider("thresholdConstant")
.setLabel("a.t. constant")
.setPosition(20,200)
.setRange(-100,100)
;
// Slider for blur size
cp5.addSlider("blurSize")
.setLabel("blur size")
.setPosition(20,260)
.setRange(1,20)
;
// Slider for minimum blob size
cp5.addSlider("blobSizeThreshold")
.setLabel("min blob size")
.setPosition(20,290)
.setRange(0,60)
;
// Store the default background color, we gonna need it later
buttonColor = cp5.getController("contrast").getColor().getForeground();
buttonBgColor = cp5.getController("contrast").getColor().getBackground();
}
void toggleAdaptiveThreshold(boolean theFlag) {
useAdaptiveThreshold = theFlag;
if (useAdaptiveThreshold) {
// Lock basic threshold
setLock(cp5.getController("threshold"), true);
// Unlock adaptive threshold
setLock(cp5.getController("thresholdBlockSize"), false);
setLock(cp5.getController("thresholdConstant"), false);
} else {
// Unlock basic threshold
setLock(cp5.getController("threshold"), false);
// Lock adaptive threshold
setLock(cp5.getController("thresholdBlockSize"), true);
setLock(cp5.getController("thresholdConstant"), true);
}
}
void setLock(Controller theController, boolean theValue) {
theController.setLock(theValue);
if (theValue) {
theController.setColorBackground(color(150,150));
theController.setColorForeground(color(100,100));
} else {
theController.setColorBackground(color(buttonBgColor));
theController.setColorForeground(color(buttonColor));
}
}