Move the sources to trunk
[opencv] / filters / Tracker3dFilter / trackers / BlobTracker / BlobTracker.cpp
1 /*M///////////////////////////////////////////////////////////////////////////////////////
2 //
3 //  IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
4 //
5 //  By downloading, copying, installing or using the software you agree to this license.
6 //  If you do not agree to this license, do not download, install,
7 //  copy or use the software.
8 //
9 //
10 //                        Intel License Agreement
11 //                For Open Source Computer Vision Library
12 //
13 // Copyright (C) 2002, Intel Corporation, all rights reserved.
14 // Third party copyrights are property of their respective owners.
15 //
16 // Redistribution and use in source and binary forms, with or without modification,
17 // are permitted provided that the following conditions are met:
18 //
19 //   * Redistributions of source code must retain the above copyright notice,
20 //     this list of conditions and the following disclaimer.
21 //
22 //   * Redistributions in binary form must reproduce the above copyright notice,
23 //     this list of conditions and the following disclaimer in the documentation
24 //     and/or other materials provided with the distribution.
25 //
26 //   * The name of Intel Corporation may not be used to endorse or promote products
27 //     derived from this software without specific prior written permission.
28 //
29 // This software is provided by the copyright holders and contributors "as is" and
30 // any express or implied warranties, including, but not limited to, the implied
31 // warranties of merchantability and fitness for a particular purpose are disclaimed.
32 // In no event shall the Intel Corporation or contributors be liable for any direct,
33 // indirect, incidental, special, exemplary, or consequential damages
34 // (including, but not limited to, procurement of substitute goods or services;
35 // loss of use, data, or profits; or business interruption) however caused
36 // and on any theory of liability, whether in contract, strict liability,
37 // or tort (including negligence or otherwise) arising in any way out of
38 // the use of this software, even if advised of the possibility of such damage.
39 //
40 //M*/
41
42
43 // ////////////////////////////////////////////////////////////////////////////
44 //    BlobTracker.cpp
45 //
46 //    This file contains the bulk of the implementation of the 
47 //    BlobTracker class.
48 //
49 // ////////////////////////////////////////////////////////////////////////////
50
51 #include <stack>
52 #include <algorithm>
53 #include <comcat.h>
54 #include "cvstreams.h"
55 #include "BlobTracker.h"
56 #include "BlobTrkObject.h"
57 #include "BlobTrackerPropertyPage.h"
58
59 static void FloodFill(unsigned char *image, int width, int height, int start_x, int start_y, int new_color, CvRect &rect, int &area);
60
61
62 //
63 // CreateInstance
64 //
65 // Used by the DirectShow base classes to create instances
66 //
67 CUnknown *BlobTracker::CreateInstance(IUnknown *outer, HRESULT *phr)
68 {
69     CUnknown *punk = new BlobTracker(outer, phr);
70     if (punk == NULL) {
71         *phr = E_OUTOFMEMORY;
72     }
73     return punk;
74
75 }
76
77
78 // ////////////////////////////////////////////////////////////////////////////
79 // BlobTracker::BlobTracker()
80 //
81 // Constructor.
82 //
83 // ////////////////////////////////////////////////////////////////////////////
84 BlobTracker::BlobTracker(IUnknown *outer, HRESULT *phr)
85     : CUnknown(NAME("Blob Tracker"), outer)
86 {
87     // Set member variables to defaults
88     m_output_options = IBlobTracker::OUTPUT_CROSSHAIRS;
89     m_num_objects = 0;
90     m_pixel_threshold = 99; 
91     m_size_threshold = 3;
92     m_distance_threshold = 80;
93     m_image = NULL;
94     m_tmp_image = NULL;
95     m_objects = new BlobTrackerObjectList;
96 }
97
98 // ////////////////////////////////////////////////////////////////////////////
99 // BlobTracker::~BlobTracker()
100 //
101 // Destructor.
102 //
103 // ////////////////////////////////////////////////////////////////////////////
104 BlobTracker::~BlobTracker()
105 {
106     cvReleaseImage( &m_image );
107     cvReleaseImage( &m_tmp_image );
108
109     delete m_objects;
110 }
111
112
113 HRESULT BlobTracker::NonDelegatingQueryInterface(REFIID iid, void **ppv)
114 {
115     if (iid == IID_IBlobTracker)
116         return GetInterface((IUnknown *)(void *)static_cast<IBlobTracker *>(this), ppv);
117
118     if (iid == IID_ITracker)
119         return GetInterface((IUnknown *)(void *)static_cast<ITracker *>(this), ppv);
120
121     return CUnknown::NonDelegatingQueryInterface(iid, ppv);
122 }
123
124
125 // ITracker methods
126
127 STDMETHODIMP BlobTracker::CheckFormat(IplImage *image_header)
128 {
129     if (image_header->depth != IPL_DEPTH_8U
130         || image_header->width % 4 != 0)
131     {
132         return E_FAIL;
133     }
134
135     if (image_header->nChannels == 1
136         && strncmp(image_header->colorModel, "GRAY", 4) == 0)
137     {
138         return NOERROR;
139     }
140
141     if (image_header->nChannels == 3
142         && strncmp(image_header->colorModel, "RGB", 3) == 0)
143     {
144         return NOERROR;
145     }
146
147     return E_FAIL;
148 }
149
150 STDMETHODIMP BlobTracker::SetFormat(IplImage *image_header)
151 {
152     HRESULT hr = CheckFormat(image_header);
153     if (FAILED(hr))
154         return E_FAIL;
155
156     m_image_format = *image_header;
157
158     m_image = cvCreateImage( cvSize(image_header->width,image_header->height), IPL_DEPTH_8U, 1 );
159     m_image->origin = image_header->origin;
160
161     if (image_header->nChannels != 1)
162     {
163         m_tmp_image = cvCreateImage( cvSize(image_header->width,image_header->height), IPL_DEPTH_8U, 1 );
164         m_image->origin = image_header->origin;
165     }
166
167     return NOERROR;
168 }
169
170
171 STDMETHODIMP BlobTracker::SetNumberOfObjects(int num_objects)
172 {
173     if (num_objects < 1)
174         return E_FAIL;
175
176     m_num_objects = num_objects;
177
178     return NOERROR;
179 }
180
181 STDMETHODIMP BlobTracker::GetPropertyPage(GUID *page)
182 {
183     *page = CLSID_BlobTrackerPropertyPage;
184     return NOERROR;
185 }
186
187 // ////////////////////////////////////////////////////////////////////////////
188 // BlobTracker::Process()
189 //
190 // Performs the processing of one frame.
191 //
192 // Inputs: 
193 //      Input frame data (src)
194 //
195 // ////////////////////////////////////////////////////////////////////////////
196
197 static inline bool MatchFormat(IplImage *image, IplImage *format)
198 {
199     return (image->nChannels == format->nChannels
200             && image->width == format->width
201             && image->height == format->height
202             && image->depth == format->depth
203             && image->dataOrder == format->dataOrder
204             && image->origin == format->origin
205             && strncmp(image->channelSeq, format->channelSeq, 4) == 0);
206
207 }
208
209 // Used for sorting in descending order by size
210 static inline bool size_cmp(const BlobTrackerObject &o1, const BlobTrackerObject &o2)
211 {
212     return o1.GetSize() > o2.GetSize();
213 }
214
215 // compute the square of the distance between two points.
216 static inline int distance(const CvPoint &p1, const CvPoint &p2)
217 {
218     int dx = p1.x - p2.x;
219     int dy = p1.y - p2.y;
220     return dx*dx + dy*dy;
221 }
222
223 static inline bool InRect(const CvRect &rect, const CvPoint &p)
224 {
225     return (p.x >= rect.x && p.x < rect.x + rect.width
226             && p.y >= rect.y && p.y < rect.y + rect.height);
227 }
228
229 STDMETHODIMP BlobTracker::Process(IplImage *image)
230 {
231     // Make sure we've gone through our initialization stage
232     if (m_image == NULL || m_num_objects == 0)
233         return E_FAIL;
234
235     // Make sure the image format matches what is expected.
236     if (!MatchFormat(image, &m_image_format))
237         return E_FAIL;
238
239     // Convert the image to gray if required.
240     IplImage *gray_image = image;
241     if (image->nChannels != 1)
242     {
243         cvCvtColor(image, m_tmp_image, CV_BGR2GRAY );
244         gray_image = m_tmp_image;
245     }
246
247     // Dilation or blurring of the image enlarges small points to make them more visible
248     // and connects any points with gaps so they become a single point.
249     // Blurring has the advantage that it helps filter out noise, whereas dilation
250     // exacerbates noise. However, blurring reduces the intensity of the bright spots,
251     // so the threshold must be set lower.
252     // Since background noise isn't a big problem with the cameras we use, on the
253     // whole we determined that dilation improves tracking.
254     cvDilate(gray_image, m_image, 0, 2);
255     //iplBlur(gray_image, m_image, 3, 3, 1, 1);
256
257
258     // Copy the modified image back into the input image, so the
259     // image seen in the video window is the one that is actually processed.
260     // This is fairly expensive, especially if color conversion is required
261     // (i.e, if the input image is RGB); this step may be eliminated with no
262     // loss of tracking functionality, in which case the video window will
263     // show the original image rather than the processed image. (Crosshairs
264     // are drawn on the input image in any case.)
265     if (image->nChannels == 1)
266         cvCopy(m_image, image);
267     else
268         cvCvtColor(m_image, image, CV_GRAY2BGR );
269
270
271     // Search the frame for bright spots.
272     BlobTrackerObjectList new_objects;
273     FindObjects(new_objects);
274
275
276     // Match up the objects just found with the ones found previously,
277     // to try to achieve continuity of object ids across frames.
278     BlobTrackerObjectList old_objects = *m_objects;
279     m_objects->clear();
280     unsigned long matched = 0;
281     for (BlobTrackerObjectList::iterator old = old_objects.begin(); old != old_objects.end(); )
282     {
283         int min_d = m_distance_threshold;
284         BlobTrackerObjectList::iterator min_o = new_objects.end();
285         for (BlobTrackerObjectList::iterator o = new_objects.begin(); o != new_objects.end(); o++)
286         {
287             if (InRect(old->GetRect(), o->GetCenter()))
288             {
289                 min_o = o;
290                 break;
291             }
292
293             int d = distance(o->GetCenter(), old->GetCenter());
294             if (d < min_d)
295             {
296                 min_d = d;
297                 min_o = o;
298             }
299         }
300
301         if (min_o != new_objects.end())
302         {
303             int id = old->GetId();
304
305             min_o->SetId(id);
306             matched |= 1 << id;
307             m_objects->push_back(*min_o);
308             new_objects.erase(min_o);
309             old_objects.erase(old);
310         }
311         else
312         {
313             old++;
314         }
315     }
316
317
318     // For any objects that weren't matched up, assign ids in
319     // order of decreasing size.
320     if (m_objects->size() < m_num_objects && new_objects.size() > 0)
321     {
322         std::sort(new_objects.begin(), new_objects.end(), size_cmp);
323
324         int id = 0;
325         for (BlobTrackerObjectList::iterator o = new_objects.begin();
326              o != new_objects.end() && m_objects->size() < m_num_objects;
327              o++)
328         {
329             while (matched & (1 << id))
330                 id++;
331             o->SetId(id);
332             id++;
333             m_objects->push_back(*o);
334         }
335     }
336
337     // Generate output frame
338     FillDestinationData(image);
339
340     return NOERROR;
341 }
342
343
344 // (These functions are not very efficient, but they are called rarely enough
345 // that it is not a big impact on performance.)
346 static inline int GetPixel(const unsigned char *image, int width, int x, int y)
347 {
348     return image[y*width + x];
349 }
350
351 static inline void SetPixel(unsigned char *image, int width, int x, int y, int color)
352 {
353     image[y*width + x] = color;
354 }
355
356
357 // Search the image for spots that meet the brightness and size thresholds.
358 void BlobTracker::FindObjects(BlobTrackerObjectList &objects)
359 {
360     for (int y = 0; y < m_image_format.height; y++)
361     {
362         for (int x = 0; x < m_image_format.width; x++)
363         {
364             // Check each pixel to see if it meets the brightness threshold.
365             // (This call to GetPixel is the only one that is executed with enough frequency to have
366             // any impact on performance, but optimizing it gave minimal performance improvement.)
367             if (GetPixel((unsigned char *)m_image->imageData, m_image_format.width, x, y) >= m_pixel_threshold)
368             {
369                 // Find all connected bright pixels. Set them all to black while keeping track
370                 // of the bounding rectangle and the number of pixels found.
371                 CvRect rect;
372                 int area;
373                 FloodFill((unsigned char *)m_image->imageData, m_image_format.width, m_image_format.height, x, y, m_pixel_threshold, rect, area);
374
375                 // Ignore any spots that don't meet the size threshold.
376                 if (area >= m_size_threshold)
377                     objects.push_back(BlobTrackerObject(rect, area));
378             }
379         }
380     }
381 }
382
383
384 // Filled horizontal segment of scanline y for xl$<=$x$<=$xr.
385 // Parent segment was on line y-dy. dy=l or -1
386 struct Segment 
387 {
388     int y, xl, xr, dy;
389     Segment(int a, int b, int c, int d) : y(a), xl(b), xr(c), dy(d) { };
390 };
391
392 static inline void push(std::stack<Segment> &stack, int height, int y, int xl, int xr, int dy)
393 {
394     y += dy;
395     if (y >= 0 && y < height)
396         stack.push(Segment(y, xl, xr, dy));
397 }
398
399
400 static inline void SetPixel(unsigned char *image, int width, int x, int y, int color, CvRect &rect, int &area)
401 {
402     if (x < rect.x)
403         rect.x = x;
404     else if (x >= rect.x+rect.width)
405         rect.width = x+1-rect.x;
406     if (y < rect.y)
407         rect.y = y;
408     else if (y >= rect.y+rect.height)
409         rect.height = y+1-rect.y;
410     SetPixel(image, width, x, y, color);
411     area++;
412 }
413
414 #define GET_PIXEL(x, y) GetPixel(image, width, x, y)
415 #define SET_PIXEL(x, y) SetPixel(image, width, x, y, 0, rect, area)
416 #define PUSH(y, xl, xr, dy) push(stack, height, y, xl, xr, dy)
417
418
419 // FloodFill: set the pixel at (x,y) and all of its 4-connected neighbors
420 // that meet the brightness threshold to zero while keeping track
421 // of the bounding rectangle and the number of pixels found.
422 // A 4-connected neighbor is a pixel above, below, left, or right of
423 // a pixel.
424 // Algorithm adapted from one by Paul Heckbert 13 Sept 1982, 28 Jan 1987.
425
426 static void FloodFill(unsigned char *image, int width, int height, int start_x, int start_y, int threshold, CvRect &rect, int &area)
427 {
428     CvRect init_rect = { start_x, start_y, 1, 1 };
429     rect = init_rect;
430     area = 0;
431
432     std::stack<Segment> stack;
433     PUSH(start_y, start_x, start_x, 1); /* needed in some cases */
434     PUSH(start_y+1, start_x, start_x, -1); /* seed segment (popped 1st) */
435
436     while (!stack.empty())
437     {
438         /* pop segment off stack and fill a neighboring scan line */
439         const Segment seg = stack.top();
440         stack.pop();
441
442         // segment of scan line y-dy for xl<=x<=x2 was
443         // previously filled,
444         // now explore adjacent pixels in scan line y
445
446         int l;
447         int x;
448         for (x = seg.xl; x >= 0 && GET_PIXEL(x, seg.y) >= threshold; x--)
449             SET_PIXEL(x, seg.y);
450
451         if (x >= seg.xl)
452             goto skip;
453         l = x+1;
454         if (l < seg.xl)
455             PUSH(seg.y, l, seg.xl-1, -seg.dy); /* leak on left? */
456         x = seg.xl+1;
457         do {
458             for (; x < width && GET_PIXEL(x, seg.y) >= threshold; x++)
459                 SET_PIXEL(x, seg.y);
460             PUSH(seg.y, l, x-1, seg.dy);
461             if (x > seg.xr+1)
462                 PUSH(seg.y, seg.xr+1, x-1, -seg.dy); /* leak on right? */
463 skip:
464             for (x++; x <= seg.xr && GET_PIXEL(x, seg.y) < threshold; x++)
465                 ;
466             l = x;
467         } while (x <= seg.xr);
468     }
469 }
470
471
472 // Registration information
473
474 // List of class IDs and creator functions for the class factory. This
475 // provides the link between the COM entry point in the DLL and an object
476 // being created. The class factory will call the static CreateInstance.
477
478 CFactoryTemplate g_Templates[] = {
479     { L"Blob Tracker", &CLSID_BlobTracker, BlobTracker::CreateInstance },
480     { L"Blob Tracker Property Page", &CLSID_BlobTrackerPropertyPage, BlobTrackerPropertyPage::CreateInstance }
481 };
482 int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);
483
484
485 //
486 // DllRegisterServer
487 //
488 // Register the COM objects (the tracker and the property page).
489 // Also add the tracker to the "Video Trackers" component category.
490 STDAPI DllRegisterServer()
491 {
492     HRESULT hr = AMovieDllRegisterServer2( TRUE );
493     if (FAILED(hr))
494         return hr;
495
496     ICatRegister *reg;
497     hr = CoCreateInstance(CLSID_StdComponentCategoriesMgr, NULL, CLSCTX_ALL, IID_ICatRegister, (void **)&reg);
498     if (FAILED(hr))
499         return hr;
500
501     CATEGORYINFO catinfo;
502     catinfo.catid = CATID_Trackers;
503     catinfo.lcid = 0x409;
504     wcscpy(catinfo.szDescription, L"Video Trackers");
505     reg->RegisterCategories(1, &catinfo);
506     reg->RegisterClassImplCategories(CLSID_BlobTracker, 1, const_cast<GUID *>(&CATID_Trackers));
507
508     reg->Release();
509
510     return NOERROR;
511 } // DllRegisterServer
512
513
514 //
515 // DllUnregisterServer
516 //
517 // Unregister the COM objects (the tracker and the property page).
518 // Also remove the tracker from the "Video Trackers" component category.
519 STDAPI DllUnregisterServer()
520 {
521     HRESULT hr = AMovieDllRegisterServer2( FALSE );
522     if (FAILED(hr))
523         return hr;
524
525     ICatRegister *reg;
526     hr = CoCreateInstance(CLSID_StdComponentCategoriesMgr, NULL, CLSCTX_ALL, IID_ICatRegister, (void **)&reg);
527     if (FAILED(hr))
528         return hr;
529
530     reg->UnRegisterClassImplCategories(CLSID_BlobTracker, 1, const_cast<GUID *>(&CATID_Trackers));
531
532     reg->Release();
533
534     return NOERROR;
535 } // DllUnregisterServer