discoverpixy
blob.h
1 //
2 // begin license header
3 //
4 // This file is part of Pixy CMUcam5 or "Pixy" for short
5 //
6 // All Pixy source code is provided under the terms of the
7 // GNU General Public License v2 (http://www.gnu.org/licenses/gpl-2.0.html).
8 // Those wishing to use Pixy source code, software and/or
9 // technologies under different licensing terms should contact us at
10 // cmucam@cs.cmu.edu. Such licensing terms are available for
11 // all portions of the Pixy codebase presented here.
12 //
13 // end license header
14 //
15 #ifndef _BLOB_H
16 #define _BLOB_H
17 
18 // TODO
19 //
20 // *** Priority 1
21 //
22 // *** Priority 2:
23 //
24 // *** Priority 3:
25 //
26 // *** Priority 4:
27 //
28 // Think about heap management of CBlobs
29 // Think about heap management of SLinkedSegments
30 //
31 // *** Priority 5 (maybe never do):
32 //
33 // Try small and large SMoments structure (small for segment)
34 // Try more efficient SSegment structure for lastBottom, nextBottom
35 //
36 // *** DONE
37 //
38 // DONE Compute elongation, major/minor axes (SMoments::GetStats)
39 // DONE Make XRC LUT
40 // DONE Use XRC LUT
41 // DONE Optimize blob assy
42 // DONE Start compiling
43 // DONE Conditionally record segments
44 // DONE Ask rich about FP, trig
45 // Take segmented image in (DONE in imageserver.cc, ARW 10/7/04)
46 // Produce colored segmented image out (DONE in imageserver.cc, ARW 10/7/04)
47 // Draw blob stats in image out (DONE for centroid, bounding box
48 // in imageserver.cc, ARW 10/7/04)
49 // Delete segments when deleting blob (DONE, ARW 10/7/04)
50 // Check to see if we attach to multiple blobs (DONE, ARW 10/7/04)
51 // Sort blobs according to area (DONE, ARW 10/7/04)
52 // DONE Sort blobs according to area
53 // DONE Clean up code
54 
55 #include <stdlib.h>
56 #include <assert.h>
57 //#include <memory.h>
58 #include <math.h>
59 
60 //#define INCLUDE_STATS
61 
62 // Uncomment this for verbose output for testing
63 //#include <iostream.h>
64 
65 struct SMomentStats {
66  int area;
67  // X is 0 on the left side of the image and increases to the right
68  // Y is 0 on the top of the image and increases to the bottom
69  float centroidX, centroidY;
70  // angle is 0 to PI, in radians.
71  // 0 points to the right (positive X)
72  // PI/2 points downward (positive Y)
73  float angle;
74  float majorDiameter;
75  float minorDiameter;
76 };
77 
78 // Image size is 352x278
79 // Full-screen blob area is 97856
80 // Full-screen centroid is 176,139
81 // sumX, sumY is then 17222656, 13601984; well within 32 bits
82 struct SMoments {
83  // Skip major/minor axis computation when this is false
84  static bool computeAxes;
85 
86  int area; // number of pixels
87  void Reset() {
88  area = 0;
89 #ifdef INCLUDE_STATS
90  sumX= sumY= sumXX= sumYY= sumXY= 0;
91 #endif
92  }
93 #ifdef INCLUDE_STATS
94  int sumX; // sum of pixel x coords
95  int sumY; // sum of pixel y coords
96  // XX, XY, YY used for major/minor axis calculation
97  long long sumXX; // sum of x^2 for each pixel
98  long long sumYY; // sum of y^2 for each pixel
99  long long sumXY; // sum of x*y for each pixel
100 #endif
101  void Add(const SMoments &moments) {
102  area += moments.area;
103 #ifdef INCLUDE_STATS
104  sumX += moments.sumX;
105  sumY += moments.sumY;
106  if (computeAxes) {
107  sumXX += moments.sumXX;
108  sumYY += moments.sumYY;
109  sumXY += moments.sumXY;
110  }
111 #endif
112  }
113 #ifdef INCLUDE_STATS
114  void GetStats(SMomentStats &stats) const;
115  bool operator==(const SMoments &rhs) const {
116  if (area != rhs.area) return 0;
117  if (sumX != rhs.sumX) return 0;
118  if (sumY != rhs.sumY) return 0;
119  if (computeAxes) {
120  if (sumXX != rhs.sumXX) return 0;
121  if (sumYY != rhs.sumYY) return 0;
122  if (sumXY != rhs.sumXY) return 0;
123  }
124  return 1;
125  }
126 #endif
127 };
128 
129 struct SSegment {
130  unsigned char model : 3 ; // which color channel
131  unsigned short row : 9 ;
132  unsigned short startCol : 10; // inclusive
133  unsigned short endCol : 10; // inclusive
134 
135  const static short invalid_row= 0x1ff;
136 
137  // Sum 0^2 + 1^2 + 2^2 + ... + n^2 is (2n^3 + 3n^2 + n) / 6
138  // Sum (a+1)^2 + (a+2)^2 ... b^2 is (2(b^3-a^3) + 3(b^2-a^2) + (b-a)) / 6
139  //
140  // Sum 0+1+2+3+...+n is (n^2 + n)/2
141  // Sum (a+1) + (a+2) ... b is (b^2-a^2 + b-a)/2
142 
143  void GetMoments(SMoments &moments) const {
144  int s= startCol - 1;
145  int e= endCol;
146 
147  moments.area = (e-s);
148 #ifdef INCLUDE_STATS
149  int e2= e*e;
150  int y= row;
151  int s2= s*s;
152  moments.sumX = ( (e2-s2) + (e-s) ) / 2;
153  moments.sumY = (e-s) * y;
154 
155  if (SMoments::computeAxes) {
156  int e3= e2*e;
157  int s3= s2*s;
158  moments.sumXY= moments.sumX*y;
159  moments.sumXX= (2*(e3-s3) + 3*(e2-s2) + (e-s)) / 6;
160  moments.sumYY= moments.sumY*y;
161  }
162 #endif
163  }
164 #ifdef INCLUDE_STATS
165  void GetMomentsTest(SMoments &moments) const;
166 #endif
167 };
168 
170  SSegment segment;
171  SLinkedSegment *next;
172  SLinkedSegment(const SSegment &segmentInit) :
173  segment(segmentInit), next(NULL) {}
174 };
175 
176 class CBlob {
177  // These are at the beginning for fast inclusion checking
178 public:
179  static int leakcheck;
180  CBlob *next; // next ptr for linked list
181 
182  // Bottom of blob, which is the surface we'll attach more segments to
183  // If bottom of blob contains multiple segments, this is the smallest
184  // segment containing the multiple segments
185  SSegment lastBottom;
186 
187  // Next bottom of blob, currently under construction
188  SSegment nextBottom;
189 
190  // Bounding box, inclusive. nextBottom.row contains the "bottom"
191  short left, top, right;
192 
193  void getBBox(short &leftRet, short &topRet,
194  short &rightRet, short &bottomRet) {
195  leftRet= left;
196  topRet= top;
197  rightRet= right;
198  bottomRet= lastBottom.row;
199  }
200 
201  // Segments which compose the blob
202  // Only recorded if CBlob::recordSegments is true
203  // firstSegment points to first segment in linked list
204  SLinkedSegment *firstSegment;
205  // lastSegmentPtr points to the next pointer field _inside_ the
206  // last element of the linked list. This is the field you would
207  // modify in order to append to the end of the list. Therefore
208  // **lastSegmentPtr should always equal to NULL.
209  // When the list is empty, lastSegmentPtr actually doesn't point inside
210  // a SLinkedSegment structure at all but instead at the firstSegment
211  // field above, which in turn is NULL.
212  SLinkedSegment **lastSegmentPtr;
213 
214  SMoments moments;
215 
216  static bool recordSegments;
217  // Set to true for testing code only. Very slow!
218  static bool testMoments;
219 
220  CBlob();
221  ~CBlob();
222 
223  int GetArea() const {
224  return(moments.area);
225  }
226 
227  // Clear blob data and free segments, if any
228  void Reset();
229 
230  void NewRow();
231 
232  void Add(const SSegment &segment);
233 
234  // This takes futileResister and assimilates it into this blob
235  //
236  // Takes advantage of the fact that we are always assembling top to
237  // bottom, left to right.
238  //
239  // Be sure to call like so:
240  // leftblob.Assimilate(rightblob);
241  //
242  // This lets us assume two things:
243  // 1) The assimilated blob contains no segments on the current row
244  // 2) The assimilated blob lastBottom surface is to the right
245  // of this blob's lastBottom surface
246  void Assimilate(CBlob &futileResister);
247 
248  // Only updates left, top, and right. bottom is updated
249  // by UpdateAttachmentSurface below
250  void UpdateBoundingBox(int newLeft, int newTop, int newRight);
251 };
252 
253 // Strategy for using CBlobAssembler:
254 //
255 // Make one CBlobAssembler for each color channel.
256 // CBlobAssembler ignores the model index, so you need to be sure to
257 // only pass the correct segments to each CBlobAssembler.
258 //
259 // At the beginning of a frame, call Reset() on each assembler
260 // As segments appear, call Add(segment)
261 // At the end of a frame, call EndFrame() on each assembler
262 // Get blobs from finishedBlobs. Blobs will remain valid until
263 // the next call to Reset(), at which point they will be deleted.
264 //
265 // To get statistics for a blob, do the following:
266 // SMomentStats stats;
267 // blob->moments.GetStats(stats);
268 // (See imageserver.cc: draw_blob() for an example)
269 
271  short currentRow;
272 
273  // Active blobs, in left to right order
274  // (Active means we are still potentially adding segments)
275  CBlob *activeBlobs;
276 
277  // Current candidate for adding a segment to. This is a member
278  // of activeBlobs, and scans left to right as we search the active blobs.
279  CBlob *currentBlob;
280 
281  // Pointer to pointer to current candidate, which is actually the pointer
282  // to the "next" field inside the previous candidate, or a pointer to
283  // the activeBlobs field of this object if the current candidate is the
284  // first element of the activeBlobs list. Used for inserting and
285  // deleting blobs.
286  CBlob **previousBlobPtr;
287 
288 public:
289  // Blobs we're no longer adding to
290  CBlob *finishedBlobs;
291  short maxRowDelta;
292  static bool keepFinishedSorted;
293 
294 public:
295  CBlobAssembler();
296  ~CBlobAssembler();
297 
298  // Call prior to starting a frame
299  // Deletes any previously created blobs
300  void Reset();
301 
302 
303  // Call once for each segment in the color channel
304  int Add(const SSegment &segment);
305 
306  // Call at end of frame
307  // Moves all active blobs to finished list
308  void EndFrame();
309 
310  int ListLength(const CBlob *b);
311 
312  // Split a list of blobs into two halves
313  void SplitList(CBlob *all, CBlob *&firstHalf, CBlob *&secondHalf);
314 
315  // Merge maxelts elements from old1 and old2 into newptr
316  void MergeLists(CBlob *&old1, CBlob *&old2, CBlob **&newptr, int maxelts);
317 
318  // Sorts finishedBlobs in order of descending area using an in-place
319  // merge sort (time n log n)
320  void SortFinished();
321 
322  // Assert that finishedBlobs is in fact sorted. For testing only.
323  void AssertFinishedSorted();
324 
325 protected:
326  // Manage currentBlob
327  //
328  // We always want to guarantee that both currentBlob
329  // and currentBlob->next have had NewRow() called, and have
330  // been validated to remain on the active list. We could just
331  // do this for all activeBlobs at the beginning of each row,
332  // but it's less work to only do it on demand as segments come in
333  // since it might allow us to skip blobs for a given row
334  // if there are no segments which might overlap.
335 
336  // BlobNewRow:
337  //
338  // Tell blob there is a new row of data, and confirm that the
339  // blob should still be on the active list by seeing if too many
340  // rows have elapsed since the last segment was added.
341  //
342  // If blob should no longer be on the active list, remove it and
343  // place on the finished list, and skip to the next blob.
344  //
345  // Call this either zero or one time per blob per row, never more.
346  //
347  // Pass in the pointer to the "next" field pointing to the blob, so
348  // we can delete the blob from the linked list if it's not valid.
349 
350  void BlobNewRow(CBlob **ptr);
351  void RewindCurrent();
352  void AdvanceCurrent();
353 
354  int m_blobCount;
355 };
356 
357 #endif // _BLOB_H
Definition: blob.h:270
Definition: blob.h:176
Definition: blob.h:82
Definition: blob.h:65
Definition: blob.h:129
Definition: blob.h:169