OpenCV

CV stands for Computer Vision. Its a set of operations to manipulate and analyze images. Its available in a variety of programing languages.

Setup

For using OpenCV you need to add two libraries to your folder structure. From this website download opencv.js and p5.cv.min.js (click on raw and then save) and add it to code/libraries/ and code/libraries/p5js/libraries/ . In your index.html file update to:

<script src="../libraries/p5js/p5.min.js"></script>
<script src="../libraries/p5js/libraries/p5.svg.js"></script>
<script src="../libraries/opencv.js"></script>
<script src="../libraries/p5js/libraries/p5.cv.min.js"></script>

The example image can be found here.

Inputs

We can apply opencv commands to images and videos. To actually work witht he input, the inputs are transformed into matrices, a matrix is basically a multidimensional array (in opencv in short: mat).

Inputs - Images

let img;

function preload() {
img = loadImage('bunny.png');
}

function draw() {
// make sure opencv is ready
if (p5.cv.isReady) {
let matrixImage = p5.cv.imageToNewMat(img);
// ... more opencv
}
}

Inputs - Videos

Webcam as input:

let myCapture;

function setup() {
myCapture = createCapture(VIDEO);
myCapture.size(320, 240);
myCapture.hide();

p5.cv.onComplete = setupCV;
}

let myCVCapture;
let matrixImage;

function setupCV() {
// create a CV capture helper
myCVCapture = p5.cv.getCvVideoCapture(myCapture);
// empty matrix to hold video frames (same size as video)
myMat = p5.cv.getRGBAMat(320, 240);
}

function hasSameSize(capture, mat){
if (!capture) return false;
return capture.video.width === mat.cols && capture.video.height === mat.rows;
}

function draw() {
// sometimes it can take a while before the video capture kicks in
if (p5.cv.isReady && hasSameSize(myCVCapture, matrixImage)) {
// read from CV Capture into matrixImage
myCVCapture.read(matrixImage);
// ... more opencv
}
}

Video as input:

let myCapture;

function setup() {
myCapture = createVideo(['test.mov'], myVideoLoaded);
p5.cv.onComplete = setupCV;
}

function myVideoLoaded() {
myCapture.loop();
myCapture.volume(0);
}

let myCVCapture;
let matrixImage;

function setupCV() {
// create a CV capture helper
myCVCapture = p5.cv.getCvVideoCapture(myCapture);
// empty matrix to hold video frames (same size as video)
myMat = p5.cv.getRGBAMat(320, 240);
}

function hasSameSize(capture, mat){
if (!capture) return false;
return capture.video.width === mat.cols && capture.video.height === mat.rows;
}

function draw() {
// sometimes it can take a while before the video capture kicks in
if (p5.cv.isReady && hasSameSize(myCVCapture, matrixImage)) {
// read from CV Capture into matrixImage
myCVCapture.read(matrixImage);
// ... more opencv
}
}

Print Matrix

When we want to display a matrix, we can either print it directly or convert it back to an image:

p5.cv.drawMat(matrixImage, xPosition, yPosition);

let outputImage;
p5.cv.matToImage(matrixImage, outputImage);
image(outputImage, xPosition, yPosition);

Modifying images (matrices)

Change from color to grayscale:

p5.cv.convertColor(matrixImage, matrixImage, cv.COLOR_RGBA2GRAY);

Threshold:

p5.cv.autothreshold(matrixImage);

Blurring:

p5.cv.GaussianBlur(matrixImage, 50);
p5.cv.blur(matrixImage, 50);

Bayer filter:

// COLOR_BayerBG2RGB, COLOR_BayerGB2RGB, COLOR_BayerRG2RGB, COLOR_BayerGR2RGB
cv.cvtColor(matrixImage, matrixImage, cv.COLOR_RGBA2GRAY);
cv.cvtColor(matrixImage, matrixImage, cv.COLOR_BayerGR2RGB);

Note that the cvtColor function comes directy from openCV, not the p5js-library

Analysing Images

Find the brightest point in an image (matrix):

const brightestPoint = p5.cv.findMaxLocation(matrixImage);
noFill();
stroke('red');
circle(brightestPoint.x, brightestPoint.y, 30);

Find contours in the image (matrix). To increase the performance you can define a target color for contour identification. Experiment with the threshold, min- and max-area:

const sketchWidth = 400;
const sketchHeight = 400;

let img;

function preload() {
img = loadImage('bunny.png');
}

let myContourFinder;
let targetColor;

function setup () {
createCanvas(sketchWidth * 2, sketchHeight);

myContourFinder = new ContourFinder();
myContourFinder.setMinAreaRadius(10);
myContourFinder.setMaxAreaRadius(200);
myContourFinder.setThreshold(128);
myContourFinder.setFindHoles(true); // false

targetColor = color(255, 0, 0);
}

let matrixImage;
let quads = [];
let threshold;

function draw() {
background(255);
// make sure opencv is ready
if (p5.cv.isReady) {
matrixImage = p5.cv.imageToNewMat(img);

// search for contours in the latest frame
myContourFinder.findContours(matrixImage);

// count contours
let n = myContourFinder.size();
// clear previos quads
quads.length = 0;
// for each contour
for(let i = 0; i < n; i++) {
// fit a quad
quads[i] = myContourFinder.getFitQuad(i);
}

// display Mat
p5.cv.drawMat(matrixImage, 0, 0);
// draw contours
stroke(0);
myContourFinder.draw();
// draw quads
stroke('#ec008c');
noFill();
for (let i = 0; i < n; i++) {
p5.cv.drawContour(quads[i]);
}

translate(8, 75);
fill(0);
rect(-3, -3, 64 + 6, 64 + 6);
fill(targetColor);
rect(0, 0, 64, 64);
}
}

// select a target color from the image
function mousePressed() {
targetColor = img.get(mouseX, mouseY);
myContourFinder.setTargetColor(targetColor, p5.cv.TrackingColorMode.TRACK_COLOR_RGB);
}

Detect changes in an image. You can use this for example to create a background state and then when something changes you can easily extract that part, similar to how virtual backgrounds work in zoom or skype for example. There is a manual version for grayscale and a special RunningBackground for a color version:

let myCapture;
let myCVCapture;
let myMat;
let myMatGrayscale;
let myBackground;
let myThresholdedMat;
let myMatRGB;
let myBackgroundMat;
let differenceMat;

function setup() {
createCanvas(640, 240);
stroke(0, 192, 0);
strokeWeight(3);
myCapture = createCapture(VIDEO);
myCapture.size(320, 240);
myCapture.hide();
p5.cv.onComplete = setupCV;
}

function setupCV() {
// create a CV capture helper
myCVCapture = p5.cv.getCvVideoCapture(myCapture);
// create a CV Mat to read new color frames into
myMat = p5.cv.getRGBAMat(320, 240);
// create a CV Mat for color to grayscale conversion
myMatGrayscale = new cv.Mat();
myMatRGB = p5.cv.getRGBMat(320, 240);
myBackgroundMat = p5.cv.getRGBMat(320, 240);
differenceMat = p5.cv.getRGBMat(320, 240);
// create a CV Mat to accumulate threshold into
myThresholdedMat = p5.cv.getGrayscaleMat(320, 240);
// init Running Background
myBackground = new RunningBackground(320, 240);
myBackground.setLearningTime(30);
myBackground.setThresholdValue(10);
}

function hasSameSize(capture, mat){
if (!capture) return false;
return capture.video.width === mat.cols && capture.video.height === mat.rows;
}

function draw() {
if (p5.cv.isReady && hasSameSize(myCVCapture, myMat)) {
// read from CV Capture into myMat
myCVCapture.read(myMat);
// convert Mat to grayscale
p5.cv.copyGray(myMat, myMatGrayscale);
// update Running Background
myBackground.update(myMatGrayscale, myThresholdedMat);
// display Mat
p5.cv.drawMat(myThresholdedMat, 0, 0);

let presenceSum = 0;
// // convert to from RGBA to RGB
p5.cv.convertColor(myMat, myMatRGB, cv.COLOR_RGBA2RGB);
// // Compute the absolute difference of the red, green, and blue channels
// // subtract myBackgroundMat from myMat and store result
cv.absdiff(myMatRGB, myBackgroundMat, differenceMat);
// // display difference Mat
p5.cv.drawMat(differenceMat, 320, 0);
// // Add these differences to the running tally
presenceSum = p5.cv.sumData(differenceMat.data);
// // Print out the total amount of movement
console.log(presenceSum / (differenceMat.total() * 255 * 3));
}
}

// reset the background
function keyPressed() {
if (p5.cv.isReady && hasSameSize(myCVCapture, myMat)) {
p5.cv.copyRGB(myMatRGB, myBackgroundMat);
}
}

Zurück zum Anfang