segmentation
All Data Structures Namespaces Files Functions Variables Modules Pages
main.cpp
1 /*
2  * Copyright (C) 2011 Department of Robotics Brain and Cognitive Sciences - Istituto Italiano di Tecnologia
3  * Author: Vadim Tikhanoff, Carlo Ciliberto
4  * email: vadim.tikhanoff@iit.it
5  * Permission is granted to copy, distribute, and/or modify this program
6  * under the terms of the GNU General Public License, version 2 or any
7  * later version published by the Free Software Foundation.
8  *
9  * A copy of the license can be found at
10  * http://www.robotcub.org/icub/license/gpl.txt
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
15  * Public License for more details
16  */
17 
104 #include <yarp/os/Network.h>
105 #include <yarp/os/BufferedPort.h>
106 #include <yarp/os/RFModule.h>
107 #include <yarp/os/PeriodicThread.h>
108 #include <yarp/os/Stamp.h>
109 
110 #include <yarp/sig/Image.h>
111 #include <yarp/sig/Vector.h>
112 
113 #include <yarp/cv/Cv.h>
114 
115 #include <opencv2/core.hpp>
116 #include <opencv2/opencv.hpp>
117 
118 #include <string>
119 #include <vector>
120 #include <iostream>
121 #include <fstream>
122 #include <cmath>
123 #include <mutex>
124 
125 using namespace std;
126 using namespace yarp::os;
127 using namespace yarp::sig;
128 using namespace yarp::cv;
129 
130 class BlobDetectorThread: public PeriodicThread
131 {
132 private:
133  ResourceFinder &rf;
134 
135  BufferedPort<ImageOf<PixelMono>> port_i_img;
136  BufferedPort<Image> port_i_propImg; // extra port just to propagate image
137  Port port_o_img;
138  Port port_o_propImg;
139  Port port_o_blobs;
140  Port port_o_clean;
141  cv::Mat imgProp;
142 
143  int gaussian_winsize;
144 
145  double window_ratio;
146  double thresh;
147  int erode_itr;
148  int dilate_itr;
149  int maxHeight;
150  int maxWidth;
151 
152  mutex mtx;
153  mutex contours;
154  Bottle blobs;
155  Bottle non_blobs;
156 
157  int offset;
158 
159  Vector area, orientation, axe1, axe2;
160  int numBlobs;
161 
162  cv::Point tmpCenter[500], pt1, pt2;
163  int numObj;
164  double thetatmp;
165 
166 public:
167  BlobDetectorThread(ResourceFinder &_rf)
168  :PeriodicThread(0.005),rf(_rf) { }
169 
170  string details;
171 
172  virtual bool threadInit()
173  {
174  string name=rf.find("name").asString();
175 
176  port_i_img.open("/"+name+"/img:i");
177  port_i_propImg.open("/"+name+"/propImg:i");
178  port_o_img.open("/"+name+"/img:o");
179  port_o_propImg.open("/"+name+"/propImg:o");
180 
181  port_o_blobs.open("/"+name+"/blobs:o");
182  port_o_clean.open("/"+name+"/binary:o");
183 
184  gaussian_winsize=rf.check("gaussian_winsize",Value(9)).asInt32();
185 
186  thresh=rf.check("thresh",Value(10.0)).asFloat64();
187  erode_itr=rf.check("erode_itr",Value(8)).asInt32();
188  dilate_itr=rf.check("dilate_itr",Value(3)).asInt32();
189  window_ratio=rf.check("window_ratio",Value(0.6)).asFloat64();
190 
191  maxHeight = rf.check("maxHeight",Value(150)).asInt32();
192  maxWidth = rf.check("maxWidth",Value(150)).asInt32();
193 
194  details=rf.check("details",Value("off")).asString();
195 
196  offset=rf.check("offset",Value(0)).asInt32();
197  //details = false;
198  numBlobs = 0;
199  orientation.clear();
200  axe1.clear();
201  axe2.clear();
202  area.clear();
203  area.resize(500);
204  orientation.resize(500);
205  axe1.resize(500);
206  axe2.resize(500);
207 
208  return true;
209  }
210 
211  virtual void setThreshold(double newThreshold)
212  {
213  lock_guard<mutex> lg(mtx);
214  thresh = newThreshold;
215  }
216 
217  virtual void run()
218  {
219  if (ImageOf<PixelMono> *img=port_i_img.read(false))
220  {
221  Stamp ts;
222  port_i_img.getEnvelope(ts);
223 
224  cv::Mat gray=toCvMat(*img);
225  cv::Mat imgProp=gray.clone();
226  imgProp.setTo(cv::Scalar::all(0));
227 
228  cv::GaussianBlur(gray,gray,cv::Size(gaussian_winsize,gaussian_winsize),0.0);
229  cv::threshold(gray,gray,thresh,255.0,cv::THRESH_BINARY);
230  cv::equalizeHist(gray,gray); //normalize brightness and increase contrast
231  cv::erode(gray,gray,cv::Mat(),cv::Point(-1,-1),erode_itr);
232  cv::dilate(gray,gray,cv::Mat(),cv::Point(-1,-1),dilate_itr);
233 
234  lock_guard<mutex> lg(mtx);
235  blobs.clear();
236  non_blobs.clear();
237 
238  int w_offset=cvRound(0.5*gray.size().width*(1.0-window_ratio));
239  int h_offset=cvRound(0.5*gray.size().height*(1.0-window_ratio));
240  int itr = 0;
241  for (int row=h_offset; row<gray.size().height-h_offset; row++)
242  {
243  for (int col=w_offset; col<gray.size().width-w_offset; col++)
244  {
245  if (gray.at<uchar>(row,col)==255)
246  {
247  cv::Rect comp;
248  cv::floodFill(gray,cv::Point(col,row),cv::Scalar(255-(blobs.size()+non_blobs.size()+1)),&comp);
249 
250  if (5<comp.width && comp.width<maxWidth && 5<comp.height && comp.height<maxHeight)
251  {
252  Bottle &b=blobs.addList();
253  b.addFloat64(comp.x-offset);
254  b.addFloat64(comp.y-offset);
255  b.addFloat64(comp.x+comp.width+offset);
256  b.addFloat64(comp.y+comp.height+offset);
257  if (details=="on")
258  {
259  if (orientation.size() > 0 )
260  {
261  b.addFloat64(orientation[itr+1]);
262  b.addInt32((int)axe2[itr+1]);
263  b.addInt32((int)axe1[itr+1]);
264  }
265  }
266  itr++;
267  }
268  else
269  {
270  Bottle &n=non_blobs.addList();
271  n.addFloat64(comp.x-offset);
272  n.addFloat64(comp.y-offset);
273  n.addFloat64(comp.x+comp.width+offset);
274  n.addFloat64(comp.y+comp.height+offset);
275  if (details=="on")
276  {
277  if (orientation.size() > 0 )
278  {
279  n.addFloat64(orientation[itr+1]);
280  n.addInt32((int)axe2[itr+1]);
281  n.addInt32((int)axe1[itr+1]);
282  }
283  }
284  itr++;
285  }
286  }
287  }
288  }
289 
290  cleanBlobs(gray);
291 
292  if (details=="on")
293  {
294  lock_guard<mutex> lg(contours);
295  processImg(gray);
296  }
297 
298  port_o_img.setEnvelope(ts);
299  port_o_img.write(*img);
300 
301  port_o_blobs.setEnvelope(ts);
302  port_o_blobs.write(blobs);
303  Image *prop= port_i_propImg.read(false);
304 
305  if (prop!=NULL)
306  {
307  port_o_propImg.setEnvelope(ts);
308  port_o_propImg.write(*prop);
309  }
310 
311  if (!imgProp.empty())
312  {
313  ImageOf<PixelMono> sendImg;
314  sendImg.resize(imgProp.size().width,imgProp.size().height);
315  imgProp.copyTo(toCvMat(sendImg));
316  port_o_clean.setEnvelope(ts);
317  port_o_clean.write(sendImg);
318  }
319  }
320  }
321 
322  virtual void threadRelease()
323  {
324  port_i_img.close();
325  port_o_img.close();
326  port_o_blobs.close();
327  port_i_propImg.close();
328  port_o_clean.close();
329  port_o_propImg.close();
330  }
331 
332  bool execReq(const Bottle &command, Bottle &reply)
333  {
334  if(command.get(0).asVocab32()==Vocab32::encode("thresh"))
335  {
336  lock_guard<mutex> lg(mtx);
337  thresh = command.get(1).asFloat64();
338  reply.addVocab32("ok");
339  return true;
340  }
341  if(command.get(0).asVocab32()==Vocab32::encode("erode"))
342  {
343  lock_guard<mutex> lg(mtx);
344  erode_itr = command.get(1).asInt32();
345  reply.addVocab32("ok");
346  return true;
347  }
348  if(command.get(0).asVocab32()==Vocab32::encode("dilate"))
349  {
350  lock_guard<mutex> lg(mtx);
351  dilate_itr = command.get(1).asInt32();
352  reply.addVocab32("ok");
353  return true;
354  }
355 
356  return false;
357  }
358 
359  void cleanBlobs(cv::Mat &image)
360  {
361  for (int i=0; i<blobs.size(); i++)
362  {
363  cv::Point cog(-1,-1);
364  if ((i>=0) && (i<blobs.size()))
365  {
366  cv::Point tl,br;
367  Bottle *item=blobs.get(i).asList();
368  if (item==NULL)
369  cout << "ITEM IS NULL" << cog.x << cog.y <<endl;
370 
371  tl.x=(int)item->get(0).asFloat64() - 2;
372  tl.y=(int)item->get(1).asFloat64() - 2;
373  br.x=(int)item->get(2).asFloat64() + 2;
374  br.y=(int)item->get(3).asFloat64() + 2;
375 
376  image(cv::Rect(tl.x,tl.y,br.x-tl.x,br.y-tl.y)).copyTo(imgProp(cv::Rect(tl.x,tl.y,br.x-tl.x,br.y-tl.y)));
377  }
378  }
379  }
380 
381  void processImg(cv::Mat &image)
382  {
383  for (int i=0; i<blobs.size(); i++)
384  {
385  cv::Point cog(-1,-1);
386  if ((i>=0) && (i<blobs.size()))
387  {
388  cv::Point tl,br;
389  Bottle *item=blobs.get(i).asList();
390  if (item==NULL)
391  cout << "ITEM IS NULL" << cog.x << cog.y <<endl;
392 
393  tl.x=(int)item->get(0).asFloat64() - 10;
394  tl.y=(int)item->get(1).asFloat64() - 10;
395  br.x=(int)item->get(2).asFloat64() + 10;
396  br.y=(int)item->get(3).asFloat64() + 10;
397 
398  cog.x=(tl.x + br.x)>>1;
399  cog.y=(tl.y + br.y)>>1;
400 
401  cv::Mat imageRoi=image(cv::Rect(tl.x,tl.y,br.x-tl.x,br.y-tl.y));
402  getOrientations(imageRoi);
403  }
404  }
405  numBlobs = 0;
406  numObj = 0;
407  }
408 
409  void getOrientations(cv::Mat &image)
410  {
411  vector<vector<cv::Point>> cont;
412  cv::findContours(image,cont,CV_RETR_LIST,CV_CHAIN_APPROX_NONE);
413 
414  //go first through all contours in order to find if there are some duplications
415  for (auto &c:cont)
416  {
417  numObj++;
418  cv::RotatedRect boxtmp = cv::minAreaRect(cv::Mat(c));
419  tmpCenter[numObj].x = cvRound(boxtmp.center.x);
420  tmpCenter[numObj].y = cvRound(boxtmp.center.y);
421  area[numObj] = cv::contourArea(c);
422  }
423 
424  //check for duplicate center points
425  int inc = 0;
426  Vector index(numObj);
427  for (int x=1; x<numObj; x++)
428  {
429  if (abs( tmpCenter[x].x -tmpCenter[x+1].x ) < 10 )
430  {
431  if (abs( tmpCenter[x].y -tmpCenter[x+1].y) < 10)
432  {
433  if ( area[x] < area[x+1] )
434  {
435  index[inc] = x;
436  inc++;
437  }
438  else
439  {
440  index[inc] = x+1;
441  inc++;
442  }
443  }
444  }
445  }
446 
447  for (auto &c:cont)
448  {
449  numBlobs++;
450  for (int i= 0; i<inc; i++)
451  if (numBlobs == index[i])
452  continue;
453 
454  if (c.size()<6)
455  continue;
456 
457  cv::RotatedRect box = cv::fitEllipse(cv::Mat(c));
458  cv::Point center = box.center;
459  cv::Size size(cvRound(box.size.width*0.5),cvRound(box.size.height*0.5));
460 
461  if ((box.size.width > 0) && (box.size.width < 300) && (box.size.height > 0) && (box.size.height < 300))
462  {
463  axe1[numBlobs] = size.width;
464  axe2[numBlobs] = size.height;
465 
466  vector<float> line(4);
467  cv::fitLine(c,line,CV_DIST_L2,0,0.01,0.01);
468  float t = (box.size.width + box.size.height)/2;
469  pt1.x = cvRound(line[2] - line[0] *t );
470  pt1.y = cvRound(line[3] - line[1] *t );
471  pt2.x = cvRound(line[2] + line[0] *t );
472  pt2.y = cvRound(line[3] + line[1] *t );
473  cv::line(image,pt1,pt2,cv::Scalar(255,255,255),2,cv::LINE_AA);
474  thetatmp = 0.0;
475  thetatmp = (180.0/M_PI) * atan2( (double)(pt2.y - pt1.y) , (double)(pt2.x - pt1.x) );
476 
477  thetatmp=(thetatmp<0.0?-thetatmp:180.0-thetatmp);
478  orientation[numBlobs]=thetatmp;
479  }
480  }
481  }
482 };
483 
484 
485 class BlobDetectorModule: public RFModule
486 {
487 private:
488  BlobDetectorThread *bdThr;
489  Port rpcPort;
490 
491 public:
492  BlobDetectorModule() { }
493 
494  virtual bool configure(ResourceFinder &rf)
495  {
496  bdThr=new BlobDetectorThread(rf);
497 
498  if(!bdThr->start())
499  {
500  delete bdThr;
501  return false;
502  }
503 
504  string name=rf.find("name").asString();
505  rpcPort.open("/"+name+"/rpc");
506  attach(rpcPort);
507 
508  return true;
509  }
510 
511  virtual bool interruptModule()
512  {
513  rpcPort.interrupt();
514 
515  return true;
516  }
517 
518  virtual bool close()
519  {
520  bdThr->stop();
521  delete bdThr;
522 
523  rpcPort.close();
524 
525  return true;
526  }
527 
528  virtual double getPeriod()
529  {
530  return 0.1;
531  }
532 
533  virtual bool updateModule()
534  {
535  return true;
536  }
537 
538  virtual bool respond(const Bottle &command, Bottle &reply)
539  {
540  reply.clear();
541  if (command.get(0).asString()=="details")
542  {
543  if (command.get(1).asString()=="on")
544  {
545  bdThr->details = "on";
546  reply.addString("setting details to ON");
547  return true;
548  }else
549  {
550  bdThr->details = "off";
551  reply.addString("setting details to OFF");
552  return true;
553  }
554  return false;
555  } else if (command.get(0).asString()=="thresh")
556  {
557  double newThresh = command.get(1).asFloat64();
558  if (newThresh<0 || newThresh>255.0)
559  {
560  reply.addString("Invalid threshold (expecting a value between 0 and 255)");
561  return false;
562  }
563  reply.addString("Setting threshold");
564  bdThr->setThreshold(newThresh);
565  return true;
566  }
567  if(bdThr->execReq(command,reply))
568  return true;
569  else
570  return RFModule::respond(command,reply);
571  }
572 };
573 
574 
575 int main(int argc, char *argv[])
576 {
577  Network yarp;
578 
579  if (!yarp.checkNetwork())
580  return 1;
581 
582  ResourceFinder rf;
583  rf.setDefaultContext("blobExtractor");
584  rf.setDefaultConfigFile("config.ini");
585  rf.setDefault("name","blobExtractor");
586  rf.configure(argc,argv);
587 
588  BlobDetectorModule mod;
589 
590  return mod.runModule(rf);
591 }
592 
593