25 #include <opencv2/opencv.hpp>
26 #include <opencv2/highgui/highgui.hpp>
28 #include <yarp/os/all.h>
29 #include <yarp/sig/all.h>
30 #include <yarp/math/Math.h>
31 #include <yarp/cv/Cv.h>
34 using namespace yarp::os;
35 using namespace yarp::sig;
36 using namespace yarp::math;
37 using namespace yarp::cv;
41 class Seg2cloudModule :
public RFModule,
public PortReader
44 vector<cv::Point> contour;
45 vector<cv::Point> floodPoints;
49 string homeContextPath;
55 double spatial_distance;
58 bool polygon,flood3d,flood,seg;
62 BufferedPort<ImageOf<PixelMono> > portDispIn;
63 BufferedPort<ImageOf<PixelRgb> > portDispOut;
64 BufferedPort<ImageOf<PixelRgb> > portImgIn;
65 BufferedPort<Bottle> portPointsOut;
66 BufferedPort<Bottle> portPoints2DOut;
77 bool read(ConnectionReader &connection)
79 Bottle data; data.read(connection);
80 cout <<
" Read: " << data.toString() << endl;
83 lock_guard<mutex> lg(mtx);
84 cv::Point point(data.get(0).asInt32(),data.get(1).asInt32());
85 contour.push_back(point);
86 if (contour.size()>12){
97 bool configure(ResourceFinder &rf)
99 name = rf.check(
"name", Value(
"seg2cloud"),
"Getting module name").asString();
101 homeContextPath=rf.getHomeContextPath();
102 savename = rf.check(
"savename", Value(
"cloud3D"),
"Default file savename").asString();
103 saving = rf.check(
"savingClouds", Value(
false),
"Toggle save clouds as file").asBool();
104 fileFormat = rf.check(
"format", Value(
"off"),
"Default file format").asString();
105 seedAuto = rf.check(
"seedAuto", Value(
true),
"Automatic seed computation").asBool();
108 cout <<
"Files will be saved in "<< homeContextPath <<
" folder, as " << savename <<
"N." << fileFormat <<
", with increasing numeration N" << endl;
111 downsampling=std::max(1,rf.check(
"downsampling",Value(1)).asInt32());
112 spatial_distance=rf.check(
"spatial_distance",Value(0.005)).asFloat64();
113 color_distance=rf.check(
"color_distance",Value(6)).asInt32();
116 printf(
"Opening ports\n" );
118 ret = ret && portDispIn.open(
"/"+name+
"/disp:i");
119 ret = ret && portImgIn.open(
"/"+name+
"/img:i");
120 ret = ret && portSeedIn.open(
"/"+name+
"/seed:i");
122 ret = ret && portPointsOut.open(
"/"+name+
"/pnt:o");
123 ret = ret && portPoints2DOut.open(
"/"+name+
"/pnt2D:o");
124 ret = ret && portDispOut.open(
"/"+name+
"/disp:o");
126 ret = ret && portSFM.open(
"/"+name+
"/SFM:rpc");
127 ret = ret && portSeg.open(
"/"+name+
"/seg:rpc");
128 ret = ret && portRpc.open(
"/"+name+
"/rpc:i");
131 printf(
"Problems opening ports\n");
134 printf(
"Ports opened\n");
136 portSeedIn.setReader(*
this);
139 polygon=flood3d=flood=seg=
false;
141 seed.x = -1; seed.y = -1;
143 rect = cv::Rect(1, 1, 0,0);
149 bool interruptModule()
151 portDispIn.interrupt();
152 portDispOut.interrupt();
153 portImgIn.interrupt();
154 portPointsOut.interrupt();
155 portPoints2DOut.interrupt();
168 portPointsOut.close();
169 portPoints2DOut.close();
185 ImageOf<PixelMono> *imgDispIn=portDispIn.read();
189 ImageOf<PixelRgb> *imgIn=portImgIn.read();
193 lock_guard<mutex> lg(mtx);
195 ImageOf<PixelRgb> &imgDispOut=portDispOut.prepare();
196 imgDispOut.resize(imgDispIn->width(),imgDispIn->height());
198 cv::Mat imgInMat=toCvMat(*imgIn);
199 cv::Mat imgDispInMat=toCvMat(*imgDispIn);
200 cv::Mat imgDispOutMat=toCvMat(imgDispOut);
201 cv::cvtColor(imgDispInMat,imgDispOutMat,CV_GRAY2RGB);
203 vector<vector<cv::Point> > contours;
204 contours.push_back(contour);
205 cv::drawContours(imgDispOutMat,contours,0,cv::Scalar(255,255,0));
206 cv::rectangle(imgDispOutMat,rect,cv::Scalar(255,50,0));
208 if ((seed.x>0) && (seed.y>0)){
210 cv::circle(imgDispOutMat,seed, 3,cv::Scalar(0,0,255), 2);
214 if (polygon||flood3d||flood||seg){
219 Bottle &bpoints=portPointsOut.prepare();
220 vector<Vector> points;
223 if (contour.size()>3)
225 cout <<
"3D points from selected contour "<<endl;
227 pointsFromContour(imgIn, contour, rect, points, bpoints);
229 cout <<
"Retrieved " << points.size() <<
" 3D points" <<endl;
234 cout <<
"Please select a polygon on the image before calling 'polygon' " << endl;
240 cout <<
"Getting seed "<<endl;
244 if(!getDepthSeed(imgDispInMat,seed)){
245 portPointsOut.unprepare();
251 if ((seed.x<0) && (seed.y<0))
253 cout <<
" click for a seed" << endl;
254 portPointsOut.unprepare();
260 cout <<
"3D points flood3D "<<endl;
263 Bottle cmdSFM,replySFM;
264 cmdSFM.addString(
"Flood3D");
265 cmdSFM.addInt32(seed.x);
266 cmdSFM.addInt32(seed.y);
267 cmdSFM.addFloat64(spatial_distance);
268 bool ok = portSFM.write(cmdSFM,replySFM);
271 for (
int i=0; i<replySFM.size(); i+=5)
273 int x=replySFM.get(i+0).asInt32();
274 int y=replySFM.get(i+1).asInt32();
275 PixelRgb px=imgIn->pixel(x,y);
278 point[0]=replySFM.get(i+2).asFloat64();
279 point[1]=replySFM.get(i+3).asFloat64();
280 point[2]=replySFM.get(i+4).asFloat64();
285 points.push_back(point);
286 floodPoints.push_back(cv::Point(x,y));
288 Bottle &bpoint = bpoints.addList();
289 bpoint.addFloat64(point[0]);
290 bpoint.addFloat64(point[1]);
291 bpoint.addFloat64(point[2]);
293 cout <<
"Retrieved " << points.size() <<
" 3D points" <<endl;
295 cout <<
" SFM didn't reply" << endl;
304 if ((seed.x<0) && (seed.y<0))
306 cout <<
" click for a seed" << endl;
307 portPointsOut.unprepare();
312 cout <<
"Extracting 3D points from 2D color flood blob"<<endl;
315 cv::Scalar delta(color_distance);
318 cv::Mat mask = cv::Mat::zeros(imgInMat.rows + 2, imgInMat.cols + 2, CV_8U);
319 cv::floodFill(imgDispInMat, mask, seed, cv::Scalar(255), NULL, delta, delta, 4 + (255 << 8) | cv::FLOODFILL_FIXED_RANGE| cv::FLOODFILL_MASK_ONLY);
322 vector<vector<cv::Point> > contoursFlood;
323 vector<cv::Vec4i> hierarchy;
324 cv::findContours(mask, contoursFlood, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
325 vector<cv::Point> contourFlood = contoursFlood[0];
328 pointsFromContour(imgIn, contourFlood, rect, points, bpoints);
330 cout <<
"Retrieved " << points.size() <<
" 3D points" <<endl;
336 if ((seed.x<0) && (seed.y<0))
338 cout <<
" click for a seed" << endl;
339 portPointsOut.unprepare();
345 cout <<
"Extracting 3D points from segmented blob "<<endl;
348 Bottle cmdSeg, replySeg;
349 cmdSeg.addString(
"get_component_around");
350 cmdSeg.addInt32(seed.x);
351 cmdSeg.addInt32(seed.y);
352 if (portSeg.write(cmdSeg,replySeg))
354 Bottle* pixelList=replySeg.get(0).asList();
356 if (pixelList->size()==0)
358 cout <<
"Empty bottle received" <<endl;
362 portPointsOut.write();
366 cout <<
"Read " << pixelList->size() <<
" points from segmentation algorithm" <<endl;
367 cv::Mat binImg = cv::Mat(imgDispInMat.rows, imgDispInMat.cols, CV_8U, 0.0);
368 for (
int i=0; i<pixelList->size(); i++)
370 Bottle* point=pixelList->get(i).asList();
371 int x = point->get(0).asFloat64();
372 int y = point->get(1).asFloat64();
373 binImg.at<uchar>(y,x) = 255;
377 vector<vector<cv::Point> > contoursSeg;
378 vector<cv::Vec4i> hierarchy;
379 cv::findContours(binImg, contoursSeg, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
380 vector<cv::Point> contourSeg = contoursSeg[0];
384 pointsFromContour(imgIn, contourSeg, rect, points, bpoints);
386 cout <<
"Retrieved " << points.size() <<
" 3D points" <<endl;
389 cout <<
"Segmentation module did not provide reply" << endl;
401 for (
int t = 0; t < 10; t++){
402 portPointsOut.write();
403 cout <<
" 3D points sent time" << t << endl;
408 portPointsOut.unprepare();
411 seed.x = -1; seed.y = -1;
412 polygon=flood=flood3d=seg=
false;
417 if (floodPoints.size()>0){
418 PixelRgb color(130,255,0);
419 Bottle &bpoints2D = portPoints2DOut.prepare();
421 for (
size_t i=0; i<floodPoints.size(); i++){
422 imgDispOut.pixel(floodPoints[i].x,floodPoints[i].y)=color;
423 Bottle &bpoint2D = bpoints2D.addList();
425 bpoint2D.addInt32(floodPoints[i].x);
426 bpoint2D.addInt32(floodPoints[i].y);
430 for (
int t = 0; t < 10; t++){
432 portPoints2DOut.write();
433 cout <<
" 2D points sent time" << t << endl;
439 portPoints2DOut.unprepare();
449 bool saveCloud(
const vector<Vector> &points)
452 stringstream fileName;
453 string fileNameFormat;
455 fileName << homeContextPath +
"/" + savename << fileCount;
457 if (fileFormat ==
"ply")
459 fileNameFormat = fileName.str()+
".ply";
460 cout <<
"Saving as " << fileNameFormat << endl;
461 fout.open(fileNameFormat.c_str());
465 fout <<
"format ascii 1.0\n";
466 fout <<
"element vertex " << points.size() <<
"\n";
467 fout <<
"property float x\n";
468 fout <<
"property float y\n";
469 fout <<
"property float z\n";
470 fout <<
"property uchar diffuse_red\n";
471 fout <<
"property uchar diffuse_green\n";
472 fout <<
"property uchar diffuse_blue\n";
473 fout <<
"end_header\n";
475 for (
unsigned int i=0; i<points.size(); i++){
476 fout << points[i][0] <<
" " << points[i][1] <<
" " << points[i][2] <<
" " << (int)points[i][3] <<
" " << (
int)points[i][4] <<
" " << (int)points[i][5] <<
"\n";
481 cout <<
"Points saved as " << fileNameFormat << endl;
485 }
else if (fileFormat ==
"off"){
486 fileNameFormat = fileName.str()+
".off";
487 cout <<
"Saving as " << fileNameFormat << endl;
488 fout.open(fileNameFormat.c_str());
493 fout<<points.size()<<
" 0 0"<<endl;
495 for (
size_t i=0; i<points.size(); i++)
497 fout<<points[i].subVector(0,2).toString(3,4)<<
" "<<
498 points[i].subVector(3,5).toString(0,3)<<endl;
504 cout <<
"Points saved as " << fileNameFormat << endl;
507 }
else if (fileFormat ==
"none"){
508 cout <<
"Points not saved" << endl;
516 bool pointsFromContour(
const ImageOf<PixelRgb> *imgIn,
const vector<cv::Point> contourIn, cv::Rect &boundBox, vector<Vector> &pointsInContour, Bottle &bpoints)
518 boundBox = cv::boundingRect(contourIn);
521 cmd.addString(
"Rect");
522 cmd.addInt32(boundBox.x); cmd.addInt32(boundBox.y);
523 cmd.addInt32(boundBox.width); cmd.addInt32(boundBox.height);
524 cmd.addInt32(downsampling);
525 if (portSFM.write(cmd,reply))
528 for (
int x=boundBox.x; x<boundBox.x+boundBox.width; x+=downsampling)
530 for (
int y=boundBox.y; y<boundBox.y+boundBox.height; y+=downsampling)
532 if (cv::pointPolygonTest(contourIn,cv::Point2f((
float)x,(
float)y),
false)>0.0)
534 floodPoints.push_back(cv::Point(x,y));
537 point[0]=reply.get(idx+0).asFloat64();
538 point[1]=reply.get(idx+1).asFloat64();
539 point[2]=reply.get(idx+2).asFloat64();
541 Bottle &bpoint = bpoints.addList();
542 bpoint.addFloat64(point[0]);
543 bpoint.addFloat64(point[1]);
544 bpoint.addFloat64(point[2]);
548 PixelRgb px=imgIn->pixel(x,y);
553 bpoint.addFloat64(point[3]);
554 bpoint.addFloat64(point[4]);
555 bpoint.addFloat64(point[5]);
557 pointsInContour.push_back(point);
571 bool getDepthSeed(cv::Point2i &seedPoint)
573 ImageOf<PixelMono> *imgDispIn=portDispIn.read();
577 cv::Mat imgDispInMat=toCvMat(*imgDispIn);
579 return getDepthSeed(imgDispInMat,seedPoint);
583 bool getDepthSeed(
const cv::Mat &disparity,cv::Point2i &seedPoint)
585 cout <<
"Finding seed automatically" << endl;
586 cv::Mat depth = disparity.clone();
590 double sigmaX1 = 1.5;
591 double sigmaY1 = 1.5;
592 cv::GaussianBlur(depth, depth, cv::Size(gaussSize,gaussSize), sigmaX1, sigmaY1);
594 cv::threshold(depth, depth, 30, -1, CV_THRESH_TOZERO);
596 int dilate_niter = 4;
601 cv::dilate(depth, depth, cv::Mat(), cv::Point(-1,-1), dilate_niter, cv::BORDER_CONSTANT, cv::morphologyDefaultBorderValue());
603 cv::GaussianBlur(depth, depth, cv::Size(gaussSize,gaussSize), sigmaX2, sigmaY2, cv::BORDER_DEFAULT);
605 cv::erode(depth, depth, cv::Mat(), cv::Point(-1,-1), erode_niter, cv::BORDER_CONSTANT, cv::morphologyDefaultBorderValue());
609 double minVal, maxVal;
610 cv::Point minLoc, maxLoc;
612 int fillFlags = 8 | ( 255 << 8 ) | cv::FLOODFILL_FIXED_RANGE;
614 cv::Mat aux = depth.clone();
616 int minBlobSize = 300;
618 int imageThreshRatioLow = 10;
619 int imageThreshRatioHigh = 20;
620 while (fillSize < minBlobSize){
622 cv::minMaxLoc( aux, &minVal, &maxVal, &minLoc, &maxLoc );
625 fillSize = floodFill(aux, maxLoc, 0, 0, cv::Scalar(maxVal/imageThreshRatioLow), cv::Scalar(maxVal/imageThreshRatioHigh), fillFlags);
629 cv::Mat fillMask(depth.rows+2, depth.cols+2, CV_8U);
631 cv::floodFill(depth, fillMask, maxLoc, 255, 0, cv::Scalar(maxVal/imageThreshRatioLow), cv::Scalar(maxVal/imageThreshRatioHigh), cv::FLOODFILL_MASK_ONLY + fillFlags);
634 std::vector<std::vector<cv::Point > > contours;
635 std::vector<cv::Vec4i> hierarchy;
638 aux = fillMask(cv::Range(1,depth.rows+1), cv::Range(1,depth.cols+1)).clone();
639 cv::findContours( aux, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, cv::Point(0, 0) );
644 double blobSizeOld = -1, blobSize = -1;
645 for(
int c = 0; c < contours.size(); c++ ){
648 blobSize = cv::contourArea(contours[c]);
651 if( blobSize > minBlobSize && blobSize > blobSizeOld)
654 blobSizeOld = blobSize;
662 cv::Rect blobBox = cv::boundingRect(contours[blobI]);
664 double seedValid = -1;
666 seedAux.x = blobBox.tl().x;
667 seedAux.y = blobBox.tl().y;
668 while (seedValid < 0){
669 seedAux.x = seedAux.x + 4;
670 seedAux.y = seedAux.y + 1;
673 cv::circle(depth, seedAux, 1, cv::Scalar(0,0,0),2);
675 if ((seedAux.x > blobBox.br().x ) ||(seedAux.y > blobBox.br().y )){
676 cout <<
"Seed could not be found"<< endl;
680 seedValid = pointPolygonTest(contours[blobI], seedAux,
false );
683 seedPoint.x = seedAux.x+2;
684 seedPoint.y = seedAux.y+2;
686 cout <<
"Seed found at " << seedPoint.x <<
" ," << seedPoint.y << endl;
690 cout <<
"Seed could not be determined " << endl;
701 rect = cv::Rect(1,1,0,0);
708 bool respond(
const Bottle &command, Bottle &reply)
710 string cmd=command.get(0).asString();
711 int ack=Vocab32::encode(
"ack");
712 int nack=Vocab32::encode(
"nack");
717 reply.addVocab32(ack);
721 else if (cmd==
"seedAuto"){
722 seedAuto = command.get(1).asBool();
723 cout <<
"Automatic seeding is " << seedAuto << endl;
724 reply.addVocab32(ack);
728 else if (cmd==
"saving"){
729 saving = command.get(1).asBool();
730 cout <<
"Saving clouds as files is " << saving << endl;
731 reply.addVocab32(ack);
735 else if (cmd==
"setFormat")
737 if (command.size()>=2){
738 string format = command.get(1).asString();
739 if ((format==
"ply")||(format==
"off")||(format==
"none")){
741 reply.addVocab32(ack);
742 reply.addString(
"Format set correctly");
744 reply.addVocab32(nack);
745 reply.addString(
"No valid format chosen. Choose ply/off/none");
750 else if (cmd==
"setFileName")
752 if (command.size()>=2){
753 savename = command.get(1).asString();
754 reply.addVocab32(ack);
755 reply.addString(
"File Name set correctly");
759 else if (cmd==
"help")
761 reply.addVocab32(
"many");
762 reply.addString(
"Available commands are:");
763 reply.addString(
"help - produces this help");
764 reply.addString(
"clear - Clears displays and saved points");
765 reply.addString(
"setFormat string(fileformat)- Sets the format in which the points will be saved. 'fileformat' can be 'ply', 'off' or 'none'.");
766 reply.addString(
"setFileName string(filename)- Sets the base name given to the files where the 3D points will be saved. ");
767 reply.addString(
"saving (bool) - Activates/deactivated saving each reconstructed cloud in file.");
768 reply.addString(
"---------- Extraction Methods -----------");
769 reply.addString(
"polygon - Gets pointcloud from the selected polygon on the disp image");
770 reply.addString(
"flood int(color_distance) int int (coords(opt))- Gets pointcloud from 2D color flood using the parameter color_distance. Optionally coords can be given by command.");
771 reply.addString(
"flood3d double(spatial_distance) int int (coords(opt))- gets pointcloud from 3D color flood (based on depth with parameter spatial_distance). Optionally coords can be given by command.");
772 reply.addString(
"seg int int (coords(opt))- gets pointcloud from an externally segmented blob. Optionally coords can be given by command.");
773 reply.addString(
"seedAuto (bool) - Toggles from manual (click) seed to 'automatic' one for flood3d");
777 else if ((cmd==
"polygon") || (cmd==
"flood3d")|| (cmd==
"flood")|| (cmd==
"seg"))
779 if (portSFM.getOutputCount()==0)
780 reply.addVocab32(nack);
783 lock_guard<mutex> lg(mtx);
786 if (contour.size()>2)
789 reply.addVocab32(ack);
792 reply.addVocab32(nack);
794 else if (cmd==
"flood3d")
799 if (command.size()>=2){
800 spatial_distance=command.get(1).asFloat64();
806 if(!getDepthSeed(seed))
808 cout <<
"couldnt retrieve autoseed "<< endl;
811 }
else if (command.size()>=4){
812 seed.x=command.get(2).asInt32();
813 seed.y=command.get(3).asInt32();
815 }
else if ((seed.x <0) && (seed.y<0)){
816 cout <<
"seed needs to be clicked" << endl;
819 reply.addVocab32(ack);
821 reply.addInt32(seed.x);
822 reply.addInt32(seed.y);
823 reply.addFloat64(spatial_distance);
827 else if (cmd==
"flood")
829 reply.addVocab32(ack);
832 if (command.size()>=2){
833 color_distance=command.get(1).asInt32();
834 reply.addInt32(color_distance);
837 if (command.size()>=4){
838 seed.x=command.get(2).asInt32();
839 seed.y=command.get(3).asInt32();
840 reply.addInt32(seed.x);
841 reply.addInt32(seed.y);
849 reply.addVocab32(ack);
850 if (command.size()>=3){
851 seed.x=command.get(1).asInt32();
852 seed.y=command.get(2).asInt32();
853 reply.addInt32(seed.x);
854 reply.addInt32(seed.y);
864 RFModule::respond(command,reply);
872 int main(
int argc,
char *argv[])
875 if (!yarp.checkNetwork())
877 yError(
"unable to find YARP server!");
883 rf.setDefaultContext(
"seg2cloud");
884 rf.configure(argc,argv);
885 return mod.runModule(rf);