358 lines
11 KiB
C++
358 lines
11 KiB
C++
//
|
|
// 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
|
|
//
|
|
#ifndef _BLOB_H
|
|
#define _BLOB_H
|
|
|
|
// TODO
|
|
//
|
|
// *** Priority 1
|
|
//
|
|
// *** Priority 2:
|
|
//
|
|
// *** Priority 3:
|
|
//
|
|
// *** Priority 4:
|
|
//
|
|
// Think about heap management of CBlobs
|
|
// Think about heap management of SLinkedSegments
|
|
//
|
|
// *** Priority 5 (maybe never do):
|
|
//
|
|
// Try small and large SMoments structure (small for segment)
|
|
// Try more efficient SSegment structure for lastBottom, nextBottom
|
|
//
|
|
// *** DONE
|
|
//
|
|
// DONE Compute elongation, major/minor axes (SMoments::GetStats)
|
|
// DONE Make XRC LUT
|
|
// DONE Use XRC LUT
|
|
// DONE Optimize blob assy
|
|
// DONE Start compiling
|
|
// DONE Conditionally record segments
|
|
// DONE Ask rich about FP, trig
|
|
// Take segmented image in (DONE in imageserver.cc, ARW 10/7/04)
|
|
// Produce colored segmented image out (DONE in imageserver.cc, ARW 10/7/04)
|
|
// Draw blob stats in image out (DONE for centroid, bounding box
|
|
// in imageserver.cc, ARW 10/7/04)
|
|
// Delete segments when deleting blob (DONE, ARW 10/7/04)
|
|
// Check to see if we attach to multiple blobs (DONE, ARW 10/7/04)
|
|
// Sort blobs according to area (DONE, ARW 10/7/04)
|
|
// DONE Sort blobs according to area
|
|
// DONE Clean up code
|
|
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
//#include <memory.h>
|
|
#include <math.h>
|
|
|
|
//#define INCLUDE_STATS
|
|
|
|
// Uncomment this for verbose output for testing
|
|
//#include <iostream.h>
|
|
|
|
struct SMomentStats {
|
|
int area;
|
|
// X is 0 on the left side of the image and increases to the right
|
|
// Y is 0 on the top of the image and increases to the bottom
|
|
float centroidX, centroidY;
|
|
// angle is 0 to PI, in radians.
|
|
// 0 points to the right (positive X)
|
|
// PI/2 points downward (positive Y)
|
|
float angle;
|
|
float majorDiameter;
|
|
float minorDiameter;
|
|
};
|
|
|
|
// Image size is 352x278
|
|
// Full-screen blob area is 97856
|
|
// Full-screen centroid is 176,139
|
|
// sumX, sumY is then 17222656, 13601984; well within 32 bits
|
|
struct SMoments {
|
|
// Skip major/minor axis computation when this is false
|
|
static bool computeAxes;
|
|
|
|
int area; // number of pixels
|
|
void Reset() {
|
|
area = 0;
|
|
#ifdef INCLUDE_STATS
|
|
sumX= sumY= sumXX= sumYY= sumXY= 0;
|
|
#endif
|
|
}
|
|
#ifdef INCLUDE_STATS
|
|
int sumX; // sum of pixel x coords
|
|
int sumY; // sum of pixel y coords
|
|
// XX, XY, YY used for major/minor axis calculation
|
|
long long sumXX; // sum of x^2 for each pixel
|
|
long long sumYY; // sum of y^2 for each pixel
|
|
long long sumXY; // sum of x*y for each pixel
|
|
#endif
|
|
void Add(const SMoments &moments) {
|
|
area += moments.area;
|
|
#ifdef INCLUDE_STATS
|
|
sumX += moments.sumX;
|
|
sumY += moments.sumY;
|
|
if (computeAxes) {
|
|
sumXX += moments.sumXX;
|
|
sumYY += moments.sumYY;
|
|
sumXY += moments.sumXY;
|
|
}
|
|
#endif
|
|
}
|
|
#ifdef INCLUDE_STATS
|
|
void GetStats(SMomentStats &stats) const;
|
|
bool operator==(const SMoments &rhs) const {
|
|
if (area != rhs.area) return 0;
|
|
if (sumX != rhs.sumX) return 0;
|
|
if (sumY != rhs.sumY) return 0;
|
|
if (computeAxes) {
|
|
if (sumXX != rhs.sumXX) return 0;
|
|
if (sumYY != rhs.sumYY) return 0;
|
|
if (sumXY != rhs.sumXY) return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
#endif
|
|
};
|
|
|
|
struct SSegment {
|
|
unsigned char model : 3 ; // which color channel
|
|
unsigned short row : 9 ;
|
|
unsigned short startCol : 10; // inclusive
|
|
unsigned short endCol : 10; // inclusive
|
|
|
|
const static short invalid_row= 0x1ff;
|
|
|
|
// Sum 0^2 + 1^2 + 2^2 + ... + n^2 is (2n^3 + 3n^2 + n) / 6
|
|
// Sum (a+1)^2 + (a+2)^2 ... b^2 is (2(b^3-a^3) + 3(b^2-a^2) + (b-a)) / 6
|
|
//
|
|
// Sum 0+1+2+3+...+n is (n^2 + n)/2
|
|
// Sum (a+1) + (a+2) ... b is (b^2-a^2 + b-a)/2
|
|
|
|
void GetMoments(SMoments &moments) const {
|
|
int s= startCol - 1;
|
|
int e= endCol;
|
|
|
|
moments.area = (e-s);
|
|
#ifdef INCLUDE_STATS
|
|
int e2= e*e;
|
|
int y= row;
|
|
int s2= s*s;
|
|
moments.sumX = ( (e2-s2) + (e-s) ) / 2;
|
|
moments.sumY = (e-s) * y;
|
|
|
|
if (SMoments::computeAxes) {
|
|
int e3= e2*e;
|
|
int s3= s2*s;
|
|
moments.sumXY= moments.sumX*y;
|
|
moments.sumXX= (2*(e3-s3) + 3*(e2-s2) + (e-s)) / 6;
|
|
moments.sumYY= moments.sumY*y;
|
|
}
|
|
#endif
|
|
}
|
|
#ifdef INCLUDE_STATS
|
|
void GetMomentsTest(SMoments &moments) const;
|
|
#endif
|
|
};
|
|
|
|
struct SLinkedSegment {
|
|
SSegment segment;
|
|
SLinkedSegment *next;
|
|
SLinkedSegment(const SSegment &segmentInit) :
|
|
segment(segmentInit), next(NULL) {}
|
|
};
|
|
|
|
class CBlob {
|
|
// These are at the beginning for fast inclusion checking
|
|
public:
|
|
static int leakcheck;
|
|
CBlob *next; // next ptr for linked list
|
|
|
|
// Bottom of blob, which is the surface we'll attach more segments to
|
|
// If bottom of blob contains multiple segments, this is the smallest
|
|
// segment containing the multiple segments
|
|
SSegment lastBottom;
|
|
|
|
// Next bottom of blob, currently under construction
|
|
SSegment nextBottom;
|
|
|
|
// Bounding box, inclusive. nextBottom.row contains the "bottom"
|
|
short left, top, right;
|
|
|
|
void getBBox(short &leftRet, short &topRet,
|
|
short &rightRet, short &bottomRet) {
|
|
leftRet= left;
|
|
topRet= top;
|
|
rightRet= right;
|
|
bottomRet= lastBottom.row;
|
|
}
|
|
|
|
// Segments which compose the blob
|
|
// Only recorded if CBlob::recordSegments is true
|
|
// firstSegment points to first segment in linked list
|
|
SLinkedSegment *firstSegment;
|
|
// lastSegmentPtr points to the next pointer field _inside_ the
|
|
// last element of the linked list. This is the field you would
|
|
// modify in order to append to the end of the list. Therefore
|
|
// **lastSegmentPtr should always equal to NULL.
|
|
// When the list is empty, lastSegmentPtr actually doesn't point inside
|
|
// a SLinkedSegment structure at all but instead at the firstSegment
|
|
// field above, which in turn is NULL.
|
|
SLinkedSegment **lastSegmentPtr;
|
|
|
|
SMoments moments;
|
|
|
|
static bool recordSegments;
|
|
// Set to true for testing code only. Very slow!
|
|
static bool testMoments;
|
|
|
|
CBlob();
|
|
~CBlob();
|
|
|
|
int GetArea() const {
|
|
return(moments.area);
|
|
}
|
|
|
|
// Clear blob data and free segments, if any
|
|
void Reset();
|
|
|
|
void NewRow();
|
|
|
|
void Add(const SSegment &segment);
|
|
|
|
// 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 Assimilate(CBlob &futileResister);
|
|
|
|
// Only updates left, top, and right. bottom is updated
|
|
// by UpdateAttachmentSurface below
|
|
void UpdateBoundingBox(int newLeft, int newTop, int newRight);
|
|
};
|
|
|
|
// Strategy for using CBlobAssembler:
|
|
//
|
|
// Make one CBlobAssembler for each color channel.
|
|
// CBlobAssembler ignores the model index, so you need to be sure to
|
|
// only pass the correct segments to each CBlobAssembler.
|
|
//
|
|
// At the beginning of a frame, call Reset() on each assembler
|
|
// As segments appear, call Add(segment)
|
|
// At the end of a frame, call EndFrame() on each assembler
|
|
// Get blobs from finishedBlobs. Blobs will remain valid until
|
|
// the next call to Reset(), at which point they will be deleted.
|
|
//
|
|
// To get statistics for a blob, do the following:
|
|
// SMomentStats stats;
|
|
// blob->moments.GetStats(stats);
|
|
// (See imageserver.cc: draw_blob() for an example)
|
|
|
|
class CBlobAssembler {
|
|
short currentRow;
|
|
|
|
// Active blobs, in left to right order
|
|
// (Active means we are still potentially adding segments)
|
|
CBlob *activeBlobs;
|
|
|
|
// Current candidate for adding a segment to. This is a member
|
|
// of activeBlobs, and scans left to right as we search the active blobs.
|
|
CBlob *currentBlob;
|
|
|
|
// Pointer to pointer to current candidate, which is actually the pointer
|
|
// to the "next" field inside the previous candidate, or a pointer to
|
|
// the activeBlobs field of this object if the current candidate is the
|
|
// first element of the activeBlobs list. Used for inserting and
|
|
// deleting blobs.
|
|
CBlob **previousBlobPtr;
|
|
|
|
public:
|
|
// Blobs we're no longer adding to
|
|
CBlob *finishedBlobs;
|
|
short maxRowDelta;
|
|
static bool keepFinishedSorted;
|
|
|
|
public:
|
|
CBlobAssembler();
|
|
~CBlobAssembler();
|
|
|
|
// Call prior to starting a frame
|
|
// Deletes any previously created blobs
|
|
void Reset();
|
|
|
|
|
|
// Call once for each segment in the color channel
|
|
int Add(const SSegment &segment);
|
|
|
|
// Call at end of frame
|
|
// Moves all active blobs to finished list
|
|
void EndFrame();
|
|
|
|
int ListLength(const CBlob *b);
|
|
|
|
// Split a list of blobs into two halves
|
|
void SplitList(CBlob *all, CBlob *&firstHalf, CBlob *&secondHalf);
|
|
|
|
// Merge maxelts elements from old1 and old2 into newptr
|
|
void MergeLists(CBlob *&old1, CBlob *&old2, CBlob **&newptr, int maxelts);
|
|
|
|
// Sorts finishedBlobs in order of descending area using an in-place
|
|
// merge sort (time n log n)
|
|
void SortFinished();
|
|
|
|
// Assert that finishedBlobs is in fact sorted. For testing only.
|
|
void AssertFinishedSorted();
|
|
|
|
protected:
|
|
// 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 BlobNewRow(CBlob **ptr);
|
|
void RewindCurrent();
|
|
void AdvanceCurrent();
|
|
|
|
int m_blobCount;
|
|
};
|
|
|
|
#endif // _BLOB_H
|