icub-basic-demos
pf3dBottomup.cpp
1 /*
2  * A bottom-up approach for generating particles for the "pf3dTracker" module
3  *
4  * Copyright (C) 2010 RobotCub Consortium
5  *
6  * Author: Martim Brandao
7  * Note: Should you use or reference my work on your own research, please let me know (mbrandao _AT_ isr.ist.utl.pt)
8  *
9  * Image sequence as input, N particles (3D position of balls) as output.
10  *
11  * CopyPolicy: Released under the terms of the GNU GPL v2.0.
12  *
13  */
14 
15 #include <yarp/cv/Cv.h>
16 #include <iCub/pf3dBottomup.hpp>
17 
18 #include <iostream>
19 #include <iomanip>
20 #include <fstream>
21 #include <sstream>
22 #include <ctime>
23 #include <utility>
24 
25 //member that is repeatedly called by YARP, to give this object the chance to do something.
26 //should this function return "false", the object would be terminated.
27 //I already have one image, when I get here (I either acquire it in the initialization method or in the end of this same method).
28 bool pf3dBottomup::updateModule()
29 {
30  if(_doneInitializing)
31  {
32  CvMemStorage* storage = cvCreateMemStorage(0);
33  CvSeq* contours = 0;
34  double raio=0.03;
35  int num_detected_objects;
36 
37  if(_blur>0) cvSmooth(image,image, CV_GAUSSIAN, 0, 0, _blur, 0);
38  cvCvtColor(image, hsv, CV_BGR2HSV );
39 
40  cvInRangeS(hsv, cvScalar(0,_maskSmin,MIN(_maskVmin,_maskVmax),0),
41  cvScalar(181,256,MAX(_maskVmin,_maskVmax),0), mask); //update mask
42  cvSplit(hsv, hue, sat, val, 0 );
43 
44  // Histogram Backprojection
45  cvCalcBackProject(&hue, backproject, _object_model.hist);
46  cvAnd(backproject, mask, backproject, 0);
47 
48  // Bottom-up detection algorithm...
49 
50  //cvThreshold(backproject, backproject, 40, 0, CV_THRESH_TOZERO );
51 
52  // Normalize backprojection to global max
53  normalize_to_global_max(backproject);
54 
55  // Build scale-space
56  cvConvert(backproject, infloat);
57  ss.BuildAll((float*)infloat->imageData);
58 
59  // Segmentation (localmaxima + floodfill)
60  scale_space_segmentation(backproject, &ss, backprojectmask2);
61 
62  // 3D Localization
63  cvSetImageROI(backprojectmask2, cvRect(1,1,image->width,image->height));
64  num_detected_objects = object_localization_simple(backprojectmask2, &_object_model, &_camera);
65  //object_localization(backprojectmask2, _object_model, _camera);
66  cvResetImageROI(backprojectmask2);
67 
68  // Output particles
69  if(num_detected_objects>0){
70  Bottle& particleOutput=_outputParticlePort.prepare(); int count;
71  particleOutput.clear();
72  particleOutput.addInt32(_nParticles);
73  for(count=0;count<_nParticles;count++)
74  {
75  particleOutput.addFloat64((double)cvmGet(_object_model.particles,0,count));
76  particleOutput.addFloat64((double)cvmGet(_object_model.particles,1,count));
77  particleOutput.addFloat64((double)cvmGet(_object_model.particles,2,count));
78  }
79  //particleOutput.addInt32(num_detected_objects);
80  _outputParticlePort.write();
81  }
82 
83  // acquire a new image
84  _yarpImage = _inputVideoPort.read(); //read one image from the buffer.
85  //temporary cheating (resize to 640x480)
86  cv::Mat imageMat=cv::cvarrToMat(image);
87  cv::resize(yarp::cv::toCvMat(*_yarpImage),imageMat,imageMat.size());
88  //--end
89  cvCvtColor(image, image, CV_RGB2BGR);
90  }
91 
92  return true; //continue. //in this case it means everything is fine.
93 }
94 
95 
96 //------------------------------------------------------------------------------------------------------------
97 
98 
99 //member function that set the object up.
100 bool pf3dBottomup::configure(ResourceFinder &rf)
101 {
102  _doneInitializing=false;
103 
104  string trackedObjectColorTemplate;
105 
106  //***********************************
107  //Read options from the command line.
108  //***********************************
109  _inputVideoPortName = rf.check("inputVideoPort",
110  Value("/pf3dBottomup/video:i"),
111  "Input video port (string)").asString();
112  _outputParticlePortName = rf.check("outputParticlePort",
113  Value("/pf3dBottomup/particles:o"),
114  "Output particle port (string)").asString();
115 
116  _inputVideoPort.open(_inputVideoPortName);
117  _outputParticlePort.open(_outputParticlePortName);
118 
119  _nParticles = rf.check("nParticles",
120  Value("100"),
121  "Number of particles used in the tracker (int)").asInt32();
122  _calibrationImageWidth = rf.check("w",
123  Value(640),
124  "Image width (int)").asInt32();
125  _calibrationImageHeight = rf.check("h",
126  Value(480),
127  "Image height (int)").asInt32();
128  _camera.fx = rf.check("perspectiveFx",
129  Value(1),
130  "Focal distance * kx (double)").asFloat64();
131  _camera.fy = rf.check("perspectiveFy",
132  Value(1),
133  "Focal distance * ky (double)").asFloat64();
134  _camera.cx = rf.check("perspectiveCx",
135  Value(1),
136  "X position of the projection center, in pixels (double)").asFloat64();
137  _camera.cy = rf.check("perspectiveCy",
138  Value(1),
139  "Y position of the projection center, in pixels (double)").asFloat64();
140  _scaleSpaceLevels = rf.check("scaleSpaceLevels",
141  Value(3),
142  "Number of levels on the scale space (int)").asInt32();
143  _maskVmin = rf.check("maskVmin",
144  Value(15),
145  "Minimum acceptable image value (int)").asInt32();
146  _maskVmax = rf.check("maskVmax",
147  Value(255),
148  "Maximum acceptable image value (int)").asInt32();
149  _maskSmin = rf.check("maskSmin",
150  Value(70),
151  "Minimum acceptable image saturation (int)").asInt32();
152  _blur = rf.check("Blur",
153  Value(0),
154  "Blur variance applied to the input image (int)").asInt32();
155  _object_model.raio_esfera = rf.check("sphereRadius",
156  Value(0.03),
157  "Radius of the sphere in case that is our object (double)").asFloat64();
158 
159  trackedObjectColorTemplate = rf.findFile("trackedObjectColorTemplate");
160  if(trackedObjectColorTemplate==""){
161  cout<<"Couldnt find color model specified in pf3dBottomup.ini\n";
162  return false;
163  }
164 
165  // object model
166  calc_hist_from_model_2D(trackedObjectColorTemplate, &_object_model.hist, _maskVmin, _maskVmax);
167  _object_model.particles = cvCreateMat(3,_nParticles,CV_32FC1);
168 
169  // camera model
170  _camera.fov = 2*atan(_calibrationImageHeight/(2*_camera.fy))*180/PI; //field of view in degrees
171  _camera.aspect = _calibrationImageWidth/(double)_calibrationImageHeight * (_camera.fy/_camera.fx); //aspect ratio
172  _camera.znear = 0.01; //Z near
173  _camera.zfar = 1000; //Z far
174 
175  // scale space
176  _scaleSpaceLevels=3;
177  _scaleSpaceScales[0]=16.0 * _calibrationImageWidth/640;
178  _scaleSpaceScales[1]= 8.0 * _calibrationImageWidth/640;
179  _scaleSpaceScales[2]= 4.0 * _calibrationImageWidth/640;
180  ss.AllocateResources(_calibrationImageHeight, _calibrationImageWidth, _scaleSpaceLevels, _scaleSpaceScales);
181 
182  // read one image from the stream
183  _yarpImage = _inputVideoPort.read();
184  image = cvCreateImage(cvSize(_calibrationImageWidth, _calibrationImageHeight), 8, 3);
185  cv::cvtColor(yarp::cv::toCvMat(*_yarpImage),cv::cvarrToMat(image),CV_RGB2BGR);
186 
187  // allocate all images
188  infloat = cvCreateImage( cvGetSize(image), IPL_DEPTH_32F, 1);
189  hsv = cvCreateImage( cvGetSize(image), 8, 3 );
190  hue = cvCreateImage( cvGetSize(image), 8, 1 );
191  sat = cvCreateImage( cvGetSize(image), 8, 1 );
192  val = cvCreateImage( cvGetSize(image), 8, 1 );
193  mask = cvCreateImage( cvGetSize(image), 8, 1 );
194  backproject = cvCreateImage( cvGetSize(image), 8, 1 );
195  backprojectmask2 = cvCreateImage( cvSize(image->width+2,image->height+2), 8, 1 );
196 
197  // done
198  _doneInitializing=true;
199 
200  return true;
201 }
202 
203 
204 //member that closes the object.
205 bool pf3dBottomup::close()
206 {
207  // ports
208  _inputVideoPort.close();
209  _outputParticlePort.close();
210 
211  //resources
212  ss.FreeResources();
213  cvReleaseMat(&_object_model.particles);
214  cvReleaseImage(&image);
215  cvReleaseImage(&infloat);
216  cvReleaseImage(&hsv);
217  cvReleaseImage(&hue);
218  cvReleaseImage(&sat);
219  cvReleaseImage(&val);
220  cvReleaseImage(&mask);
221  cvReleaseImage(&backproject);
222  cvReleaseImage(&backprojectmask2);
223 
224  return true;
225 }
226 
227 
228 //member that closes the object.
229 bool pf3dBottomup::interruptModule()
230 {
231  //ports
232  _inputVideoPort.interrupt();
233  _outputParticlePort.interrupt();
234 
235  return true;
236 }
237 
238 
239 //constructor
240 pf3dBottomup::pf3dBottomup()
241 {
242 }
243 
244 
245 //destructor
246 pf3dBottomup::~pf3dBottomup()
247 {
248  cout<<"oh my god! they killed kenny! you bastards!\n";
249 }
250 
251 
252 
253 //------------------------------------------------------------------------------------------------------------
254 //
255 // Segmentation and localization functions
256 //
257 //------------------------------------------------------------------------------------------------------------
258 
259 
260 
261 //compute histogram from color template
262 void pf3dBottomup::calc_hist_from_model_2D(string file, CvHistogram **objhist, int _vmin, int _vmax)
263 {
264  float max_val = 0.f;
265  float bins[35][10], *curbin;
266  int i, j;
267  float kernFactor=0.1F;
268  IplImage *histmodel = 0, *histmask = 0, *histhue = 0, *histsat = 0, *histval = 0;
269  CvHistogram *hist;
270 
271  int dims[] = {35, 10};
272  float cbRanges[] = {0, 180};
273  float crRanges[] = {0, 255};
274  float* ranges[] = {cbRanges, crRanges};
275  IplImage* imgs[] = {0, 0};
276 
277  auto histMat = cv::imread(file);
278  histmodel = cvCreateImage(cvSize(histMat.cols,histMat.rows),histMat.depth(),histMat.channels());
279  IplImage ipltemp;
280  cvInitImageHeader(&ipltemp, cvSize(histMat.cols, histMat.rows), cvIplDepth(histMat.flags), histMat.channels());
281  cvSetData(&ipltemp, histMat.data, (int)histMat.step[0]);
282  histmodel = &ipltemp;
283  cvCvtColor(histmodel, histmodel, CV_BGR2HSV);
284  histmask = cvCreateImage(cvGetSize(histmodel), 8, 1);
285  histhue = cvCreateImage(cvGetSize(histmodel), 8, 1);
286  histsat = cvCreateImage(cvGetSize(histmodel), 8, 1);
287  histval = cvCreateImage(cvGetSize(histmodel), 8, 1);
288  cvInRangeS(histmodel, cvScalar(0,70,MIN(_vmin,_vmax),0),cvScalar(181,256,MAX(_vmin,_vmax),0), histmask);
289  cvSplit(histmodel, histhue, histsat, histval, 0);
290 
291  imgs[0]=histhue; imgs[1]=histsat;
292  hist = cvCreateHist(2, dims, CV_HIST_ARRAY, ranges, CV_HIST_UNIFORM);
293 
294  cvCalcHist((IplImage **)imgs, hist, 0, histmask);
295 
296  //martim: add some variance to the histogram
297  for(i=0;i<dims[0];i++)
298  for(j=0;j<dims[1];j++)
299  bins[i][j]=0;
300  for(i=0;i<dims[0];i++)
301  for(j=0;j<dims[1];j++){
302  bins[i][j]+=(float)((1+kernFactor)*cvGetReal2D(hist->bins,i,j));
303  //hue e' circular
304  if(i>0) bins[i-1][j]+=(float)(kernFactor*cvGetReal2D(hist->bins,i,j));
305  else bins[dims[0]-1][j]+=(float)(kernFactor*cvGetReal2D(hist->bins,i,j));
306  if(i<dims[0]-1) bins[i+1][j]+=(float)(kernFactor*cvGetReal2D(hist->bins,i,j));
307  else bins[0][j]+=(float)(kernFactor*cvGetReal2D(hist->bins,i,j));
308  //saturacao nao e' circular
309  if(j>0) bins[i][j-1]+=(float)(kernFactor*cvGetReal2D(hist->bins,i,j));
310  if(j<dims[1]-1) bins[i][j+1]+=(float)(kernFactor*cvGetReal2D(hist->bins,i,j));
311  if(j>1) bins[i][j-2]+=(float)(kernFactor*cvGetReal2D(hist->bins,i,j));
312  if(j<dims[1]-2) bins[i][j+2]+=(float)(kernFactor*cvGetReal2D(hist->bins,i,j));
313  }
314  for(i=0;i<dims[0];i++)
315  for(j=0;j<dims[1];j++){
316  curbin = (float*)cvPtr2D(hist->bins, i, j);
317  (*curbin) = bins[i][j];
318  }
319  //--end
320 
321  cvGetMinMaxHistValue( hist, 0, &max_val, 0, 0 );
322  //cvNormalizeHist(hist, 2500); //tolerancia. 1000 durante o dia (pouca variabilidade); 10000 durante a noite
323  cvConvertScale( hist->bins, hist->bins, max_val ? 255. / max_val : 0., 0 );
324 
325  *objhist = hist;
326  cvReleaseImage(&histmask);
327  cvReleaseImage(&histhue);
328 }
329 
330 //normalize grayscale image so that its global max becomes = 255
331 void pf3dBottomup::normalize_to_global_max(IplImage *img)
332 {
333  int i,j;
334  int step;
335  uchar *data;
336  int pmax=0, max=0;
337  int M=255;
338  int px1=0,numpx1=0,px2=0,numpx2=0;
339 
340  step = img->widthStep/sizeof(uchar);
341  data = (uchar *)img->imageData;
342 
343  //find global maximum
344  for(i=0 ; i<img->height ; i++)
345  for(j=0 ; j<img->width ; j++)
346  if(data[i*step+j] > max){ pmax=i*step+j; max=data[pmax]; }
347  //max must be big enough
348  if(max > 50){
349  //normalize
350  for(i=0 ; i<img->height ; i++)
351  for(j=0 ; j<img->width ; j++)
352  data[i*step+j] = ((data[i*step+j]*M)/max);
353  }
354 }
355 
356 //segmentation algorithm (local maxima followed by kind of floodfill) on all scale-space levels, joined in one resulting grayscale img
357 void pf3dBottomup::scale_space_segmentation(IplImage *img, ScaleSpace *ss, IplImage *result)
358 {
359  int pmax=0;
360  int l,i,j,si,sj,aux;
361  int r=1;
362  IplImage *outgray, *outfloat, *floodmask;
363  float *data;
364  int step;
365  uchar *maxdata;
366  int maxstep;
367 
368  step = img->widthStep/sizeof(uchar);
369 
370  outgray = cvCreateImage(cvGetSize(img), 8, 1);
371  outfloat = cvCreateImage(cvGetSize(img), IPL_DEPTH_32F, 1);
372  floodmask = cvCreateImage(cvGetSize(result), 8, 1 );
373 
374  cvZero(result);
375 
376  // For each level... do segmentation
377  for(l=0 ; l < ss->GetLevels() ; l++){
378  int thres = 128; //maximos so acima deste valor...
379  int max = 0;
380  cvZero(floodmask);
381 
382  outfloat->imageData = (char*)(ss->GetLevel(l));
383 
384  //aux
385  cvConvert(outfloat, outgray);
386  data = (float *)outfloat->imageData;
387  step = outfloat->widthStep/sizeof(float);
388  maxdata = (uchar *)outgray->imageData;
389  maxstep = outgray->widthStep/sizeof(uchar);
390 
391  //localmaxima v2
392  for(i=r;i<img->height-r;i++){
393  for(j=r;j<img->width-r;j++){
394  //maximos so a partir de threshold. todo: T depende da variancia!
395  if(data[i*step+j] < thres){ maxdata[i*maxstep+j]=0; continue; }
396 
397  //3x3 window
398  for(si=i-r,aux=0 ; si<=i+r && !aux ; si++)
399  for(sj=j-r ; sj<=j+r ; sj++)
400  if(!(si==i && sj==j) && data[i*step+j] <= data[si*step+sj]){
401  maxdata[i*maxstep+j]=0; aux=1; break;
402  }
403 
404  //em cada maximo: FloodFill
405  if(!aux){
406  float T=80; //data[i*step+j]*0.8;
407  cvFloodFill2(outfloat, cvPoint(j,i), cvScalar(255),
408  cvScalar(T), cvScalar(T), NULL,
409  4+(255<<8)+CV_FLOODFILL_MASK_ONLY+CV_FLOODFILL_FIXED_RANGE, floodmask);
410  }
411  }
412  }
413  cvOr(floodmask, result, result, 0);
414  //--end
415  }
416 
417  cvReleaseImage(&floodmask);
418  cvReleaseImage(&outfloat);
419  cvReleaseImage(&outgray);
420 }
421 
422 //guess 3D position of object from segmentation (assuming it is a ball). returns number of objects
423 int pf3dBottomup::object_localization_simple(IplImage *segm, ObjectModel *model, CameraModel *camera)
424 {
425  CvMemStorage* storage = cvCreateMemStorage(0);
426  CvSeq* contours = 0;
427  CvMoments moments;
428  CvPoint center;
429  double raio=0.03;
430  double m00,m01,m10,area;
431  double fx,fy,cx,cy;
432  int i,j,count=0;
433 
434  fx=camera->fx; fy=camera->fy; cx=camera->cx; cy=camera->cy;
435  raio = model->raio_esfera;
436 
437  //careful! "segm" image will change...
438  cvFindContours( segm, storage, &contours, sizeof(CvContour), CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0) );
439 
440  while(contours)
441  {
442  area = fabs(cvContourArea( contours, CV_WHOLE_SEQ ));
443  if(area>300.0 * segm->width/640){ //threshold area ---> depends on image size....
444  double uu,vv,raiopx,xx,yy,zz;
445 
446  cvMoments(contours,&moments,0);
447  m00 = cvGetSpatialMoment( &moments, 0, 0);
448  m10 = cvGetSpatialMoment( &moments, 1, 0);
449  m01 = cvGetSpatialMoment( &moments, 0, 1);
450  uu = m10/m00;
451  vv = m01/m00;
452  center = cvPoint((int)uu, (int)vv);
453  raiopx = sqrt(1.0*area/PI);
454  zz = raio/( ((uu+raiopx-cx)/fx)-((uu-cx)/fx) ); //usar eixo vv tb? media
455  xx = zz*(uu-cx)/fx;
456  yy = zz*(vv-cy)/fy;
457 
458  cvmSet(model->particles,0,count, xx*1000);
459  cvmSet(model->particles,1,count, yy*1000);
460  cvmSet(model->particles,2,count, zz*1000);
461 
462  count++;
463  }
464  contours = contours->h_next;
465  }
466 
467  // generate particles (fill the rest of particle vector with scattered versions of the measured ones)
468  for(i=0 , j=count ; i<count ; i++)
469  for( ; j<count+(i+1)*(_nParticles-count)/count ; j++){
470  cvmSet(model->particles,0,j, cvmGet(model->particles,0,i)+SCATTER(30));
471  cvmSet(model->particles,1,j, cvmGet(model->particles,1,i)+SCATTER(30));
472  cvmSet(model->particles,2,j, cvmGet(model->particles,2,i)+SCATTER(50));
473  }
474 
475  cvReleaseMemStorage( &storage );
476 
477  return count;
478 }
479 
480 
481