[SOLVED]How to sort contours from contourFinder

I want to highlight specific parts of an image (a music score), in a row. I have created the same image twice, once in black and white and once with some grey parts included. I’m using ofxCvContourFinder to detect contours between the two images. The whole thing works, but the contours appear in an order that looks quite random to me. Maybe they are being sorted by their Y coordinate.

Here’s my code (ofApp.cpp):

#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){
  // ofxOpenCV doesn't have image loading.
	// So first, load the .png file into a temporary ofImage.
	ofImage scoreOfImage;
	scoreOfImage.loadImage("test_score.png");
	scoreOfImage.setImageType(OF_IMAGE_GRAYSCALE);

  ofImage scoreOfImage2;
	scoreOfImage2.loadImage("test_score2.png");
	scoreOfImage2.setImageType(OF_IMAGE_GRAYSCALE);

  // Set the ofxCvImage from the pixels of this ofImage.
	int imgW = scoreOfImage.getWidth();
	int imgH = scoreOfImage.getHeight();
	unsigned char *scorePixels = scoreOfImage.getPixels().getData();
  grayImage.setFromPixels(scorePixels, imgW, imgH);

  imgWglobal = imgW;
  imgHglobal = imgH;

  int imgW2 = scoreOfImage2.getWidth();
	int imgH2 = scoreOfImage2.getHeight();
	unsigned char *scorePixels2 = scoreOfImage2.getPixels().getData();
	grayBg.setFromPixels(scorePixels2, imgW2, imgH2);

  grayDiff.allocate(imgW, imgH);
  grayDiff.absDiff(grayBg, grayImage);
  grayDiff.threshold(30);
  contourFinder.findContours(grayDiff, 5, (imgW*imgH)/4, 4, false, true);

  timeRef = ofGetElapsedTimeMillis();
  timeDiff = 1000;
  idx = 0;
}

//--------------------------------------------------------------
void ofApp::update(){
  timeStamp = ofGetElapsedTimeMillis();
  if ((timeStamp - timeRef) > timeDiff) {
    idx++;
    if (idx >= contourFinder.nBlobs) {
      idx = 0;
    }
    timeRef = ofGetElapsedTimeMillis();
  }
}

//--------------------------------------------------------------
void ofApp::draw(){
  ofSetHexColor(0xffffff);
  grayBg.draw(0, 0, imgWglobal, imgHglobal);
  contourFinder.draw(imgWglobal, 0, imgWglobal, imgHglobal);
  ofColor c(255, 0, 0);
  ofRectangle r = contourFinder.blobs.at(idx).boundingRect;
  ofSetColor(c);
  ofDrawRectangle(r);
}

And here are the two images I’m using:


The code is supposed to highlight the first note, then the first bar, then the rest, and lastly the second bar, but it highlights the first note, the rest, the second bar, and lastly the first bar.

Can someone help me out with sorting the contours in a desired way?

I guess there is getCenter(index) function, so you can sort the contours along with their coordinates.

Thanks for the reply. Where is this function documented? Can’t find it in the ofxCvContourFinder documentation page.
What is the index argument supposed to be, and what does this function return?
Could you provide an example or link one?

I found a solution that’s likely to be not the most elegant, but it seems to work.
Here’s the ofApp.cpp code for anyone interested:

#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){
  // ofxOpenCV doesn't have image loading.
	// So first, load the .png file into a temporary ofImage.
	ofImage scoreOfImage;
	scoreOfImage.loadImage("test_score.png");
	scoreOfImage.setImageType(OF_IMAGE_GRAYSCALE);

  ofImage scoreOfImage2;
	scoreOfImage2.loadImage("test_score2.png");
	scoreOfImage2.setImageType(OF_IMAGE_GRAYSCALE);

  // Set the ofxCvImage from the pixels of this ofImage.
	imgW = scoreOfImage.getWidth();
	imgH = scoreOfImage.getHeight();
	unsigned char *scorePixels = scoreOfImage.getPixels().getData();
  grayImage.setFromPixels(scorePixels, imgW, imgH);

  int imgW2 = scoreOfImage2.getWidth();
	int imgH2 = scoreOfImage2.getHeight();
	unsigned char *scorePixels2 = scoreOfImage2.getPixels().getData();
	grayBg.setFromPixels(scorePixels2, imgW2, imgH2);

  grayDiff.allocate(imgW, imgH);
  grayDiff.absDiff(grayBg, grayImage);
  grayDiff.threshold(30);
  contourFinder.findContours(grayDiff, 2, (imgW*imgH)/4, 96, false, true);

	ofSetWindowShape((imgW* 2), imgH);
  ofSetWindowPosition(10, 10);

	ofRectangle allRects[contourFinder.nBlobs];
	for (int i = 0; i < contourFinder.nBlobs; i++) {
		allRects[i] = contourFinder.blobs.at(i).boundingRect;
	}

	rectHeight = 30;
	// use + (with shift pressed) and - to set a differnt rectOffset
	rectOffset = 35;

	// overflown 2D array to hold the indices of Y coords
	// to be sure we'll track them all, we use nBlobs for both dimensions
	yIdx = new int * [contourFinder.nBlobs];
	for (int i = 0; i < contourFinder.nBlobs; i++) {
		yIdx[i] = new int [contourFinder.nBlobs];
	}
	yIdxSizes = new int [contourFinder.nBlobs];
	allYs = new int [contourFinder.nBlobs];
	for (int i = 0; i < contourFinder.nBlobs; i++) {
		allYs[i] = allRects[i].y;
		yIdxSizes[i] = 0;
	}

	// this will tell us how many rows have data
	numRows = 0;
	// set the number of pixels the contours should be far away from each other
	// to belong to the same group
	thresh = 50;
	int iOffset = 0;
	// run through all Y coords and group their indices according to their position
	for (int i = 0; i < contourFinder.nBlobs; i++) {
		int thisY = allYs[i];
		int colIdx = 0;
		bool idxExists = false;
		for (int j = 0; j < contourFinder.nBlobs; j++) {
			if (i) {
				for (int k = 0; k < i; k++) {
					for (int l = 0; l < yIdxSizes[k]; l++) {
						if (j == yIdx[k][l]) {
							idxExists = true;
							break;
						}
						else {
							idxExists = false;
						}
					}
					if (idxExists) {
						break;
					}
				}
			}
			if (idxExists) {
				continue;
			}
			else if (abs(thisY - allYs[j]) < thresh) {
				yIdx[i+iOffset][colIdx++] = j;
				yIdxSizes[i+iOffset]++;
				numRows = (i+iOffset) + 1;
			}
		}
		// if the current Y coord is the same as the previous one
		// we'll end up with a row of 0 size
		// in this case offset i by -1 to store the next values
		// to the row that got nothing
		if (yIdxSizes[i+iOffset] == 0) {
			iOffset--;
		}
	}

	// once groupped, sort all the X coords of each group
	int maxYsize = 0;
	for (int i = 0; i < numRows; i++) {
		if (yIdxSizes[i] > maxYsize) {
			maxYsize = yIdxSizes[i];
		}
	}

	xSorted = new int * [numRows];
	for (int i = 0; i < numRows; i++) {
		xSorted[i] = new int [maxYsize];
	}
	for (int i = 0; i < numRows; i++) {
		for (int j = 0; j < yIdxSizes[i]; j++) {
			xSorted[i][j] = allRects[yIdx[i][j]].x;
		}
	}
	for (int i = 0; i < numRows; i++) {
		sort(xSorted[i], xSorted[i] + yIdxSizes[i]);
	}

	ySorted = new int [numRows];
	for (int i = 0; i < numRows; i++) {
		ySorted[i] = allYs[yIdx[i][0]];
	}

	sort(ySorted, ySorted + numRows);
	// make a temporary copy of the x-coords so we can sort them by y-coord
	int tempXsorted[numRows][maxYsize];
	// we need to copy the size of each array in the 2D array as well
	int tempYidxSizes[numRows];
	// create an array to hold the indices in order
	int idxInOrder[numRows];
	for (int i = 0; i < numRows; i++) {
		for (int j = 0; j < yIdxSizes[i]; j++) {
			tempXsorted[i][j] = xSorted[i][j];
		}
		tempYidxSizes[i] = yIdxSizes[i];
	}

	for (int i = 0; i < numRows; i++) {
		for (int j = 0; j < numRows; j++) {
			// since the first (grey) note might not be an f, but an e or g
			// better make a difference comparison
			if (abs(ySorted[i] - allYs[yIdx[j][0]]) < 50) {
				idxInOrder[i] = j;
				break;
			}
		}
	}

	for (int i = 0; i < numRows; i++) {
		yIdxSizes[i] = tempYidxSizes[idxInOrder[i]];
		for (int j = 0; j < yIdxSizes[i]; j++) {
			xSorted[i][j] = tempXsorted[idxInOrder[i]][j];
		}
	}

	for (int i = 0; i < numRows; i++) {
		// add a steady offset
		ySorted[i] -= rectOffset;
	}

	whichStave = 0;
	nextBar = 0;
	beginning = xSorted[whichStave][nextBar];
	step = (xSorted[whichStave][nextBar+1] - beginning) / 3;
	pos = beginning;
	pos += 5;

  timeRef = ofGetElapsedTimeMillis();
  timeDiff = 125;
  idx = 0;
	drawRect = 1;
	halfTime = 0;
}

//--------------------------------------------------------------
void ofApp::update(){
  timeStamp = ofGetElapsedTimeMillis();
  if ((timeStamp - timeRef) > timeDiff) {
		halfTime++;
		halfTime %= 2;
		if (!halfTime) {
			idx++;
		  if (idx >= 4) {
		  	idx = 0;
				nextBar += 2;
				if (nextBar >= yIdxSizes[whichStave]) {
					nextBar = 0;
					whichStave++;
					if (whichStave >= numRows) {
						whichStave = 0;
					}
				}
				beginning = xSorted[whichStave][nextBar];
				step = (xSorted[whichStave][nextBar+1] - beginning) / 3;
		   }
			pos = beginning + (step * idx);
			pos += 5;
		}
		drawRect++;
		drawRect %= 2;
    timeRef = ofGetElapsedTimeMillis();
  }
}

//--------------------------------------------------------------
void ofApp::draw(){
  ofSetHexColor(0xffffff);
  grayBg.draw(0, 0, imgW, imgH);
  contourFinder.draw(imgW, 0, imgW, imgH);
	ofColor c(255, 255, 255);
	ofRectangle r(imgW, 0, imgW, imgH);
	ofSetColor(c);
	ofDrawRectangle(r);
  //ofColor c(255, 0, 0);
	c.r = 255;
	c.g = 0;
	c.b = 0;
	ofSetColor(c);
  //ofRectangle indicator = contourFinder.blobs.at(idx).boundingRect;
	ofRectangle indicator(pos, ySorted[whichStave], 5, rectHeight);
  //ofSetColor(c);
  if (drawRect > 0) {
		ofDrawRectangle(indicator);
	}
}

I’m creating two different scores, one with the first and last note of each bar in grey, and one in just black and white. For this to actually work, it would be best for the grey notes and their black counter parts to be the same note every time (in this case it’s F), to provide consistency in the height of the contours.
If the score contains a different note at the beginning and the end of every bar, three scores should be created: the original, and two with Fs (probably replacing the original note, or adding an F alongside the original) at the beginning and end of each bar, one with greyed notes and one with black.
contourFinder should be able to detect number of bars times two contours.

1 Like