ofxCv contourFinder.draw()

So I am still working on my multicam project. I have almost finished the tracking with ofxCv, which is truly incredible. However, I am becoming frustrated by the draw() function for the contourFinder. As I have multiple cameras I am scaling them so that they all fit on the screen. I have been able to scale all of the actual tracking elements, except for the contourFinder.draw() function, which continues to draw at the full 1920x1080 frame size. Does anybody have any tips for quickly scaling down the output of draw for the contours and their rects? I have looked all over and haven’t found a solution yet. I am currently experimenting with the example for ofxCv_contoursAdvanced:

All i’ve done so far that works is add a scale variable that allows me to scale down the tracked outputs.

    #include "ofApp.h"

using namespace ofxCv;
using namespace cv;

void ofApp::setup() {
    camWidth = 1920;
    camHeight = 1080;
    ofSetVerticalSync(false);
	ofSetFrameRate(120);
    cam.setDeviceID(0);
	cam.setDesiredFrameRate(15);
	cam.initGrabber(camWidth,camHeight);
	cam.setUseTexture(true);
	contourFinder.setMinAreaRadius(10);
	contourFinder.setMaxAreaRadius(150);
	//contourFinder.setInvert(true); // find black instead of white
	trackingColorMode = TRACK_COLOR_RGB;
	scale = 2.0;
}

void ofApp::update() {
	cam.update();
	if(cam.isFrameNew()) {
		threshold = ofMap(mouseX, 0, ofGetWidth()/scale, 0, 255);
		contourFinder.setThreshold(threshold);
		contourFinder.findContours(cam);
	}
}

void ofApp::draw() {
	ofSetColor(255);
	cam.draw(0, 0, cam.width/scale, cam.height/scale);

	ofSetLineWidth(2);
	contourFinder.draw();

	ofNoFill();
	int n = contourFinder.size();
	for(int i = 0; i < n; i++) {
		// smallest rectangle that fits the contour
		ofSetColor(cyanPrint);
		ofPolyline minAreRect = toOf(contourFinder.getMinAreaRect(i));
		minAreRect.resize(1/scale);
		minAreRect.draw();

		// ellipse that best fits the contour
		ofSetColor(magentaPrint);
		cv::RotatedRect ellipse = contourFinder.getFitEllipse(i);
		ofPushMatrix();
		ofVec2f ellipseCenter = toOf(ellipse.center);
		ofVec2f ellipseSize = toOf(ellipse.size);
		ofTranslate(ellipseCenter.x/scale, ellipseCenter.y/scale);
		ofRotate(ellipse.angle);
		ofEllipse(0, 0, ellipseSize.x/scale, ellipseSize.y/scale);
		ofPopMatrix();

		// minimum area circle that encloses the contour
		ofSetColor(cyanPrint);
		float circleRadius;
		ofVec2f circleCenter = toOf(contourFinder.getMinEnclosingCircle(i, circleRadius));
		ofCircle(circleCenter/scale, circleRadius/scale);

		// convex hull of the contour
		ofSetColor(yellowPrint);
		ofPolyline convexHull = toOf(contourFinder.getConvexHull(i));
		convexHull.resize(1/scale);
		convexHull.draw();

		// defects of the convex hull
		vector<cv::Vec4i> defects = contourFinder.getConvexityDefects(i);
		for(int j = 0; j < defects.size(); j++) {
			ofLine(defects[j][0]/scale, defects[j][1]/scale, defects[j][2]/scale, defects[j][3]/scale);
		}

		// some different styles of contour centers
		ofVec2f centroid = toOf(contourFinder.getCentroid(i));
		ofVec2f average = toOf(contourFinder.getAverage(i));
		ofVec2f center = toOf(contourFinder.getCenter(i));
		ofSetColor(cyanPrint);
		ofCircle(centroid/scale, 1/scale);
		ofSetColor(magentaPrint);
		ofCircle(average/scale, 1/scale);
		ofSetColor(yellowPrint);
		ofCircle(center/scale, 1/scale);

		// you can also get the area and perimeter using ofPolyline:
		// ofPolyline::getArea() and ofPolyline::getPerimeter()
		double area = contourFinder.getContourArea(i)/scale;
		double length = contourFinder.getArcLength(i)/scale;

		// balance is useful for detecting when a shape has an "arm" sticking out
		// if balance.length() is small, the shape is more symmetric: like I, O, X...
		// if balance.length() is large, the shape is less symmetric: like L, P, F...
		ofVec2f balance = toOf(contourFinder.getBalance(i));
		ofPushMatrix();
		ofTranslate(centroid.x/scale, centroid.y/scale);
		ofScale(5/scale, 5/scale);
		ofLine(0, 0, balance.x/scale, balance.y/scale);
		ofPopMatrix();
	}

	ofSetColor(255);
	drawHighlightString(ofToString((int) ofGetFrameRate()) + " fps", 10, 10);
	drawHighlightString(ofToString((int) threshold) + " threshold", 10, 30);
	drawHighlightString(trackingColorMode == TRACK_COLOR_RGB ? "RGB tracking" : "hue tracking", 10, 50);

	ofTranslate(8/scale, 75/scale);
	ofFill();
	ofSetColor(0);
	ofRect(-3/scale, -3/scale, (64+6)/scale, (64+6)/scale);
	ofSetColor(targetColor);
	ofRect(0, 0, 64/scale, 64/scale);
}

void ofApp::mousePressed(int x, int y, int button) {
	targetColor = cam.getPixelsRef().getColor(x/scale, y/scale);
	contourFinder.setTargetColor(targetColor, trackingColorMode);
}

void ofApp::keyPressed(int key) {
	if(key == 'h') {
		trackingColorMode = TRACK_COLOR_HS;
	}
	if(key == 'r') {
		trackingColorMode = TRACK_COLOR_RGB;
	}
	contourFinder.setTargetColor(targetColor, trackingColorMode);
}

Any help is greatly appreciated.

Hi, You can do, at the beginning of draw():

ofPushMatrix();
ofScale( scale, scale );

ofSetColor(255);
cam.draw(0, 0);

ofSetLineWidth(2);
contourFinder.draw();
// [...]

And insert the ofPopMatrix() just after the loop wich draw all the detection objects:

    // [...]
    ofScale(5, 5);
    ofLine(0, 0, balance.x, balance.y);
    ofPopMatrix();
}

// here:
ofPopMatrix();

ofSetColor(255);
drawHighlightString(ofToString((int) ofGetFrameRate()) + " fps", 10, 10);
drawHighlightString(ofToString((int) threshold) + " threshold", 10, 30);

without modify anything else to the example code (just declare and initialize “scale”).

1 Like

Ooops, you also must touch mousePressed() :

void testApp::mousePressed(int x, int y, int button) {
    targetColor = cam.getPixelsRef().getColor(x / scale, y / scale);

Wow, such great help and so quick.
I changed the top to

 ofPushMatrix();
    ofScale( 1/scale, 1/scale );
    ofTranslate(1920,0);

This worked out very well, i am no able to easily move the contours to whichever video feed it is being applied to.
I would like to have the mousePressed function work, I did try the scale, as mentioned, but that didn’t seem to work. Is there a way to have the cam.getPixelsRef.getColor, understand that the position of the video may have shifted to any of the 4 corners?

If not, could I not sample from the cam pixels, but rather sample from the pixels drawn to the screen directly?

Thanks for solving the primary problem for me so quickly.

This works for getting the color from any of the resized videos on the screen, the only problem is that it can also grab the colors of the tracking elements. If anybody knows of a way to getColor from a moved and scaled down cam that would still be pretty slick.

void ofApp::mousePressed(int x, int y, int button) {
    ofImage imgTmp;
    ofColor tmpColor;

    imgTmp.grabScreen(x,y,1,1);
    unsigned char *pixels=imgTmp.getPixels();

	targetColor = imgTmp.getColor(0,0);
	contourFinder.setTargetColor(targetColor, trackingColorMode);
}

Usually, the value of a scale variable multiply the size of the drawn objects. For example:
scale = 2 for double size.
scale = 0.5 for half size.
That’s why I wrote:

ofScale( scale, scale );

Doing that, my modified mousePressed method works for me:

void testApp::mousePressed(int x, int y, int button) {
    targetColor = cam.getPixelsRef().getColor(x / scale, y / scale);

If you want to stick to your scale interpretation, I think you have to invert the operations in both the draw and the mousePressed methods:

// draw
ofScale( 1 / scale, 1 / scale );

// mousePressed
targetColor = cam.getPixelsRef().getColor(x * scale, y * scale);

Avoid using grabScreen(), which is a slow method (I read that many times, and I believe that even for just one pixel, it’s better not to use it).

1 Like

lilive, you are so right. Your logic is much cleaner. I was basing it off of my existing arithmetic based on total cameras, it’s an easy fix in the scale var to make it a float 1/numCameras. That allows me to keep the cam.getPixelsRef().getColor(x/scale,y/scale);

Changed scale to 0.5, works like a charm, handles the cam placement based on cam.draw(cam.widthscale, 0, cam.widthscale, cam.height*scale);

just had to change a few things to *scale instead of /scale…works great.

Thanks for everything.