Files
discoverpixy/emulator/libs/Pixy/src/blob.cpp.notneeded

550 lines
17 KiB
Plaintext

//
// begin license header
//
// This file is part of Pixy CMUcam5 or "Pixy" for short
//
// All Pixy source code is provided under the terms of the
// GNU General Public License v2 (http://www.gnu.org/licenses/gpl-2.0.html).
// Those wishing to use Pixy source code, software and/or
// technologies under different licensing terms should contact us at
// cmucam@cs.cmu.edu. Such licensing terms are available for
// all portions of the Pixy codebase presented here.
//
// end license header
//
//#include <new>
#ifdef PIXY
#include "pixy_init.h"
#include "exec.h"
#else
#include "debug.h"
#endif
#include "blob.h"
#ifdef DEBUG
#ifndef HOST
#include <textdisp.h>
#else
#include <stdio.h>
#endif
#define DBG_BLOB(x) x
#else
#define DBG_BLOB(x)
#endif
bool CBlob::recordSegments= false;
// Set to true for testing code only. Very slow!
bool CBlob::testMoments= false;
// Skip major/minor axis computation when this is false
bool SMoments::computeAxes= false;
int CBlob::leakcheck=0;
#ifdef INCLUDE_STATS
void SMoments::GetStats(SMomentStats &stats) const {
stats.area= area;
stats.centroidX = (float)sumX / (float)area;
stats.centroidY = (float)sumY / (float)area;
if (computeAxes) {
// Find the eigenvalues and eigenvectors for the 2x2 covariance matrix:
//
// | sum((x-|x|)^2) sum((x-|x|)*(y-|y|)) |
// | sum((x-|x|)*(y-|y|)) sum((y-|y|)^2) |
// Values= 0.5 * ((sumXX+sumYY) +- sqrt((sumXX+sumYY)^2-4(sumXXsumYY-sumXY^2)))
// .5 * (xx+yy) +- sqrt(xx^2+2xxyy+yy^2-4xxyy+4xy^2)
// .5 * (xx+yy) +- sqrt(xx^2-2xxyy+yy^2 + 4xy^2)
// sum((x-|x|)^2) =
// sum(x^2) - 2sum(x|x|) + sum(|x|^2) =
// sum(x^2) - 2|x|sum(x) + n|x|^2 =
// sumXX - 2*centroidX*sumX + centroidX*sumX =
// sumXX - centroidX*sumX
// sum((x-|x|)*(y-|y|))=
// sum(xy) - sum(x|y|) - sum(y|x|) + sum(|x||y|) =
// sum(xy) - |y|sum(x) - |x|sum(y) + n|x||y| =
// sumXY - centroidY*sumX - centroidX*sumY + sumX * centroidY =
// sumXY - centroidX*sumY
float xx= sumXX - stats.centroidX*sumX;
float xyTimes2= 2*(sumXY - stats.centroidX*sumY);
float yy= sumYY - stats.centroidY*sumY;
float xxMinusyy = xx-yy;
float xxPlusyy = xx+yy;
float sq = sqrt(xxMinusyy * xxMinusyy + xyTimes2*xyTimes2);
float eigMaxTimes2= xxPlusyy+sq;
float eigMinTimes2= xxPlusyy-sq;
stats.angle= 0.5*atan2(xyTimes2, xxMinusyy);
//float aspect= sqrt(eigMin/eigMax);
//stats.majorDiameter= sqrt(area/aspect);
//stats.minorDiameter= sqrt(area*aspect);
//
// sqrt(eigenvalue/area) is the standard deviation
// Draw the ellipse with radius of twice the standard deviation,
// which is a diameter of 4 times, which is 16x inside the sqrt
stats.majorDiameter= sqrt(8.0*eigMaxTimes2/area);
stats.minorDiameter= sqrt(8.0*eigMinTimes2/area);
}
}
void SSegment::GetMomentsTest(SMoments &moments) const {
moments.Reset();
int y= row;
for (int x= startCol; x <= endCol; x++) {
moments.area++;
moments.sumX += x;
moments.sumY += y;
if (SMoments::computeAxes) {
moments.sumXY += x*y;
moments.sumXX += x*x;
moments.sumYY += y*y;
}
}
}
#endif
///////////////////////////////////////////////////////////////////////////
// CBlob
CBlob::CBlob()
{
DBG_BLOB(leakcheck++);
// Setup pointers
firstSegment= NULL;
lastSegmentPtr= &firstSegment;
// Reset blob data
Reset();
}
CBlob::~CBlob()
{
DBG_BLOB(leakcheck--);
// Free segments, if any
Reset();
}
void
CBlob::Reset()
{
// Clear blob data
moments.Reset();
// Empty bounds
right = -1;
left = top = 0x7fff;
lastBottom.row = lastBottom.invalid_row;
nextBottom.row = nextBottom.invalid_row;
// Delete segments if any
SLinkedSegment *tmp;
while(firstSegment!=NULL) {
tmp = firstSegment;
firstSegment = tmp->next;
delete tmp;
}
lastSegmentPtr= &firstSegment;
}
void
CBlob::NewRow()
{
if (nextBottom.row != nextBottom.invalid_row) {
lastBottom= nextBottom;
nextBottom.row= nextBottom.invalid_row;
}
}
void
CBlob::Add(const SSegment &segment)
{
// Enlarge bounding box if necessary
UpdateBoundingBox(segment.startCol, segment.row, segment.endCol);
// Update next attachment "surface" at bottom of blob
if (nextBottom.row == nextBottom.invalid_row) {
// New row.
nextBottom= segment;
} else {
// Same row. Add to right side of nextBottom.
nextBottom.endCol= segment.endCol;
}
SMoments segmentMoments;
segment.GetMoments(segmentMoments);
moments.Add(segmentMoments);
if (testMoments) {
#ifdef INCLUDE_STATS
SMoments test;
segment.GetMomentsTest(test);
assert(test == segmentMoments);
#endif
}
if (recordSegments) {
// Add segment to the _end_ of the linked list
*lastSegmentPtr= new /*(std::nothrow)*/ SLinkedSegment(segment);
if (*lastSegmentPtr==NULL)
return;
lastSegmentPtr= &((*lastSegmentPtr)->next);
}
}
// This takes futileResister and assimilates it into this blob
//
// Takes advantage of the fact that we are always assembling top to
// bottom, left to right.
//
// Be sure to call like so:
// leftblob.Assimilate(rightblob);
//
// This lets us assume two things:
// 1) The assimilated blob contains no segments on the current row
// 2) The assimilated blob lastBottom surface is to the right
// of this blob's lastBottom surface
void
CBlob::Assimilate(CBlob &futileResister)
{
moments.Add(futileResister.moments);
UpdateBoundingBox(futileResister.left,
futileResister.top,
futileResister.right);
// Update lastBottom
if (futileResister.lastBottom.endCol > lastBottom.endCol) {
lastBottom.endCol= futileResister.lastBottom.endCol;
}
if (recordSegments) {
// Take segments from futileResister, append on end
*lastSegmentPtr= futileResister.firstSegment;
lastSegmentPtr= futileResister.lastSegmentPtr;
futileResister.firstSegment= NULL;
futileResister.lastSegmentPtr= &futileResister.firstSegment;
// Futile resister is left with no segments
}
}
// Only updates left, top, and right. bottom is updated
// by UpdateAttachmentSurface below
void
CBlob::UpdateBoundingBox(int newLeft, int newTop, int newRight)
{
if (newLeft < left ) left = newLeft;
if (newTop < top ) top = newTop;
if (newRight > right) right= newRight;
}
///////////////////////////////////////////////////////////////////////////
// CBlobAssembler
CBlobAssembler::CBlobAssembler()
{
activeBlobs= currentBlob= finishedBlobs= NULL;
previousBlobPtr= &activeBlobs;
currentRow=-1;
maxRowDelta=1;
m_blobCount=0;
}
CBlobAssembler::~CBlobAssembler()
{
// Flush any active blobs into finished blobs
EndFrame();
// Free any finished blobs
Reset();
}
// Call once for each segment in the color channel
int CBlobAssembler::Add(const SSegment &segment) {
if (segment.row != currentRow) {
// Start new row
currentRow= segment.row;
RewindCurrent();
}
// Try to link this to a previous blob
while (currentBlob) {
if (segment.startCol > currentBlob->lastBottom.endCol) {
// Doesn't connect. Keep searching more blobs to the right.
AdvanceCurrent();
} else {
if (segment.endCol < currentBlob->lastBottom.startCol) {
// Doesn't connect to any blob. Stop searching.
break;
} else {
// Found a blob to connect to
currentBlob->Add(segment);
// Check to see if we attach to multiple blobs
while(currentBlob->next &&
segment.endCol >= currentBlob->next->lastBottom.startCol) {
// Can merge the current blob with the next one,
// assimilate the next one and delete it.
// Uncomment this for verbose output for testing
// cout << "Merging blobs:" << endl
// << " curr: bottom=" << currentBlob->bottom
// << ", " << currentBlob->lastBottom.startCol
// << " to " << currentBlob->lastBottom.endCol
// << ", area " << currentBlob->moments.area << endl
// << " next: bottom=" << currentBlob->next->bottom
// << ", " << currentBlob->next->lastBottom.startCol
// << " to " << currentBlob->next->lastBottom.endCol
// << ", area " << currentBlob->next->moments.area << endl;
CBlob *futileResister = currentBlob->next;
// Cut it out of the list
currentBlob->next = futileResister->next;
// Assimilate it's segments and moments
currentBlob->Assimilate(*(futileResister));
// Uncomment this for verbose output for testing
// cout << " NEW curr: bottom=" << currentBlob->bottom
// << ", " << currentBlob->lastBottom.startCol
// << " to " << currentBlob->lastBottom.endCol
// << ", area " << currentBlob->moments.area << endl;
// Delete it
delete futileResister;
BlobNewRow(&currentBlob->next);
}
return 0;
}
}
}
// Could not attach to previous blob, insert new one before currentBlob
CBlob *newBlob= new /*(std::nothrow)*/ CBlob();
if (newBlob==NULL)
{
DBG("blobs %d\nheap full", m_blobCount);
return -1;
}
m_blobCount++;
newBlob->next= currentBlob;
*previousBlobPtr= newBlob;
previousBlobPtr= &newBlob->next;
newBlob->Add(segment);
return 0;
}
// Call at end of frame
// Moves all active blobs to finished list
void CBlobAssembler::EndFrame() {
while (activeBlobs) {
activeBlobs->NewRow();
CBlob *tmp= activeBlobs->next;
activeBlobs->next= finishedBlobs;
finishedBlobs= activeBlobs;
activeBlobs= tmp;
}
}
int CBlobAssembler::ListLength(const CBlob *b) {
int len= 0;
while (b) {
len++;
b=b->next;
}
return len;
}
// Split a list of blobs into two halves
void CBlobAssembler::SplitList(CBlob *all,
CBlob *&firstHalf, CBlob *&secondHalf) {
firstHalf= secondHalf= all;
CBlob *ptr= all, **nextptr= &secondHalf;
while (1) {
if (!ptr->next) break;
ptr= ptr->next;
nextptr= &(*nextptr)->next;
if (!ptr->next) break;
ptr= ptr->next;
}
secondHalf= *nextptr;
*nextptr= NULL;
}
// Merge maxelts elements from old1 and old2 into newptr
void CBlobAssembler::MergeLists(CBlob *&old1, CBlob *&old2,
CBlob **&newptr, int maxelts) {
int n1= maxelts, n2= maxelts;
while (1) {
if (n1 && old1) {
if (n2 && old2 && old2->moments.area > old1->moments.area) {
// Choose old2
*newptr= old2;
newptr= &(*newptr)->next;
old2= *newptr;
--n2;
} else {
// Choose old1
*newptr= old1;
newptr= &(*newptr)->next;
old1= *newptr;
--n1;
}
}
else if (n2 && old2) {
// Choose old2
*newptr= old2;
newptr= &(*newptr)->next;
old2= *newptr;
--n2;
} else {
// Done
return;
}
}
}
#ifdef DEBUG
void len_error() {
printf("len error, wedging!\n");
while(1);
}
#endif
// Sorts finishedBlobs in order of descending area using an in-place
// merge sort (time n log n)
void CBlobAssembler::SortFinished() {
// Divide finishedBlobs into two lists
CBlob *old1, *old2;
if(finishedBlobs == NULL) {
return;
}
DBG_BLOB(int initial_len= ListLength(finishedBlobs));
DBG_BLOB(printf("BSort: Start 0x%x, len=%d\n", finishedBlobs,
initial_len));
SplitList(finishedBlobs, old1, old2);
// First merge lists of length 1 into sorted lists of length 2
// Next, merge sorted lists of length 2 into sorted lists of length 4
// And so on. Terminate when only one merge is performed, which
// means we're completely sorted.
for (int blocksize= 1; old2; blocksize <<= 1) {
CBlob *new1=NULL, *new2=NULL, **newptr1= &new1, **newptr2= &new2;
while (old1 || old2) {
DBG_BLOB(printf("BSort: o1 0x%x, o2 0x%x, bs=%d\n",
old1, old2, blocksize));
DBG_BLOB(printf(" n1 0x%x, n2 0x%x\n",
new1, new2));
MergeLists(old1, old2, newptr1, blocksize);
MergeLists(old1, old2, newptr2, blocksize);
}
*newptr1= *newptr2= NULL; // Terminate lists
old1= new1;
old2= new2;
}
finishedBlobs= old1;
DBG_BLOB(AssertFinishedSorted());
DBG_BLOB(int final_len= ListLength(finishedBlobs));
DBG_BLOB(printf("BSort: DONE 0x%x, len=%d\n", finishedBlobs,
ListLength(finishedBlobs)));
DBG_BLOB(if (final_len != initial_len) len_error());
}
// Assert that finishedBlobs is in fact sorted. For testing only.
void CBlobAssembler::AssertFinishedSorted() {
if (!finishedBlobs) return;
CBlob *i= finishedBlobs;
CBlob *j= i->next;
while (j) {
assert(i->moments.area >= j->moments.area);
i= j;
j= i->next;
}
}
void CBlobAssembler::Reset() {
assert(!activeBlobs);
currentBlob= NULL;
currentRow=-1;
m_blobCount=0;
while (finishedBlobs) {
CBlob *tmp= finishedBlobs->next;
delete finishedBlobs;
finishedBlobs= tmp;
}
DBG_BLOB(printf("after CBlobAssember::Reset, leakcheck=%d\n", CBlob::leakcheck));
}
// Manage currentBlob
//
// We always want to guarantee that both currentBlob
// and currentBlob->next have had NewRow() called, and have
// been validated to remain on the active list. We could just
// do this for all activeBlobs at the beginning of each row,
// but it's less work to only do it on demand as segments come in
// since it might allow us to skip blobs for a given row
// if there are no segments which might overlap.
// BlobNewRow:
//
// Tell blob there is a new row of data, and confirm that the
// blob should still be on the active list by seeing if too many
// rows have elapsed since the last segment was added.
//
// If blob should no longer be on the active list, remove it and
// place on the finished list, and skip to the next blob.
//
// Call this either zero or one time per blob per row, never more.
//
// Pass in the pointer to the "next" field pointing to the blob, so
// we can delete the blob from the linked list if it's not valid.
void
CBlobAssembler::BlobNewRow(CBlob **ptr)
{
short left, top, right, bottom;
while (*ptr) {
CBlob *blob= *ptr;
blob->NewRow();
if (currentRow - blob->lastBottom.row > maxRowDelta) {
// Too many rows have elapsed. Move it to the finished list
*ptr= blob->next; // cut out of current list
// check to see if it meets height and area constraints
blob->getBBox(left, top, right, bottom);
if (bottom-top>1) //&& blob->GetArea()>=MIN_COLOR_CODE_AREA)
{
// add to finished blobs
blob->next= finishedBlobs;
finishedBlobs= blob;
}
else
delete blob;
} else {
// Blob is valid
return;
}
}
}
void
CBlobAssembler::RewindCurrent()
{
BlobNewRow(&activeBlobs);
previousBlobPtr= &activeBlobs;
currentBlob= *previousBlobPtr;
if (currentBlob) BlobNewRow(&currentBlob->next);
}
void
CBlobAssembler::AdvanceCurrent()
{
previousBlobPtr= &(currentBlob->next);
currentBlob= *previousBlobPtr;
if (currentBlob) BlobNewRow(&currentBlob->next);
}