BinFinder.C

00001 #include "Robots/SeaBeeIII/BinFinder.H"
00002 
00003 #include "Component/ModelParam.H"
00004 #include "Component/ModelOptionDef.H"
00005 #include "Image/OpenCVUtil.H"
00006 #include "Raster/Raster.H"
00007 #include "MBARI/Geometry2D.H"
00008 #include "Image/Pixels.H"
00009 
00010 #include "Image/ColorOps.H"
00011 
00012 
00013 #include "Media/MediaOpts.H"
00014 
00015 
00016 #ifndef BINFINDER_C
00017 #define BINFINDER_C
00018 
00019 using namespace std;
00020 
00021 #define MIN_CENTER_DIST  15
00022 #define MIN_AREA         150
00023 #define CORNER_TOLERANCE 4
00024 
00025 int thresh = 42;
00026 double angle_thresh = 0.3;
00027 
00028 
00029 // ######################################################################
00030 BinFinder::BinFinder
00031 ( OptionManager& mgr,
00032   const std::string& descrName,
00033   const std::string& tagName) :
00034   VisionBrainComponentI(mgr, descrName, tagName)
00035 {
00036 
00037   itsWidth  = 320;
00038   itsHeight = 240;
00039   //  itsMwin.reset
00040   //    (new XWinManaged(Dims(2*itsWidth,2*itsHeight), 0, 0, "Master window"));
00041   itsDisp.resize(2*itsWidth, 2*itsHeight);
00042 }
00043 
00044 // ######################################################################
00045 BinFinder::~BinFinder()
00046 {
00047 }
00048 
00049 // ######################################################################
00050 void BinFinder::registerTopics()
00051 {
00052   LINFO("Registering BinFinder Message");
00053   this->registerPublisher("BinFinderMessageTopic");
00054   registerVisionTopics();
00055 }
00056 
00057 
00058 // ######################################################################
00059 // helper function:
00060 // finds a cosine of angle between vectors
00061 // from pt0->pt1 and from pt0->pt2
00062 double BinFinder::angle( CvPoint* pt1, CvPoint* pt2, CvPoint* pt0 ) {
00063   double dx1 = pt1->x - pt0->x;
00064   double dy1 = pt1->y - pt0->y;
00065   double dx2 = pt2->x - pt0->x;
00066   double dy2 = pt2->y - pt0->y;
00067   return (dx1*dx2 + dy1*dy2)/sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10);
00068 }
00069 
00070 
00071 // ######################################################################
00072 // returns sequence of squares detected on the image.
00073 // the sequence is stored in the specified memory storage
00074 CvSeq* BinFinder::findSquares4( IplImage* img, CvMemStorage* storage ) {
00075   CvSeq* contours;
00076   int i, c, l, N = 11;
00077 
00078   CvSize sz = cvSize( img->width & -2, img->height & -2 );
00079 
00080   IplImage* timg = cvCloneImage( img ); // make a copy of input image
00081   IplImage* gray = cvCreateImage( sz, 8, 1 );
00082   IplImage* pyr = cvCreateImage( cvSize(sz.width/2, sz.height/2), 8, 3 );
00083   IplImage* tgray;
00084 
00085   CvSeq* result;
00086 
00087   double s, t;
00088   // create empty sequence that will contain points -
00089   // 4 points per square (the square's vertices)
00090   CvSeq* squares = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvPoint), storage );
00091 
00092   double img_area = sz.width * sz.height;
00093   double max_area = img_area * 0.5;
00094 
00095   // select the maximum ROI in the image
00096   // with the width and height divisible by 2
00097   //cvSetImageROI( timg, cvRect( max_roi_x1, max_roi_y1, max_roi_w, max_roi_h ));
00098   cvSetImageROI( timg, cvRect( 0, 0, sz.width, sz.height ));
00099 
00100   // down-scale and upscale the image to filter out the noise
00101   cvPyrDown( timg, pyr, 7 );
00102   cvPyrUp( pyr, timg, 7 );
00103   tgray = cvCreateImage( sz, 8, 1 );
00104 
00105   // TAKE OUT LATER
00106   itsDisp.clear();
00107   inplacePaste(itsDisp, ipl2rgb(img), Point2D<int>(0, 0));
00108 
00109   // find squares in every color plane of the image
00110   for( c = 0; c < 3; c++ ) {
00111     // extract the c-th color plane
00112     cvSetImageCOI( timg, c+1 );
00113     cvCopy( timg, tgray, 0 );
00114 
00115     // try several threshold levels
00116     for( l = 0; l < N; l++ ) {
00117       // hack: use Canny instead of zero threshold level.
00118       // Canny helps to catch squares with gradient shading
00119       if( l == 0 ) {
00120         // apply Canny. Take the upper threshold from slider
00121         // and set the lower to 0 (which forces edges merging)
00122         cvCanny( tgray, gray, 0, thresh, 5 );
00123 
00124 
00125 
00126 //         inplacePaste(itsDisp, Image<PixRGB<byte> >(toRGB(ipl2gray(gray))), Point2D<int>(itsWidth, 0));
00127 //         inplacePaste(itsDisp, Image<PixRGB<byte> >(toRGB(ipl2gray(tgray))), Point2D<int>(0, itsHeight));
00128 //         LINFO("cvC [color: %d, level: %d]", c, l);
00129 //         itsMwin->drawImage(itsDisp,0,0);
00130 //         Raster::waitForKey();
00131 
00132 
00133 
00134         // dilate canny output to remove potential
00135         // holes between edge segments
00136         cvDilate( gray, gray, 0, 1 );
00137 
00138 
00139 //         inplacePaste(itsDisp, Image<PixRGB<byte> >(toRGB(ipl2gray(gray))), Point2D<int>(itsWidth, 0));
00140 //         inplacePaste(itsDisp, Image<PixRGB<byte> >(toRGB(ipl2gray(tgray))), Point2D<int>(0, itsHeight));
00141 //         LINFO("cvD [color: %d, level: %d]", c, l);
00142 //         itsMwin->drawImage(itsDisp,0,0);
00143 //         Raster::waitForKey();
00144 
00145 
00146       } else {
00147         // apply threshold if l!=0:
00148         // tgray(x,y) = gray(x,y) < (l+1)*255/N ? 255 : 0
00149         cvThreshold( tgray, gray, (l+1)*255/N, 255, CV_THRESH_BINARY );
00150 
00151 //         inplacePaste(itsDisp, Image<PixRGB<byte> >(toRGB(ipl2gray(gray))), Point2D<int>(itsWidth, 0));
00152 //         inplacePaste(itsDisp, Image<PixRGB<byte> >(toRGB(ipl2gray(tgray))), Point2D<int>(0, itsHeight));
00153 //         LINFO("cvT [color: %d, level: %d]", c, l);
00154 //         itsMwin->drawImage(itsDisp,0,0);
00155 //         Raster::waitForKey();
00156       }
00157 
00158       // find contours and store them all as a list
00159       cvFindContours( gray, storage, &contours, sizeof(CvContour),
00160                       CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0) );
00161 
00162       // test each contour
00163       uint count =0;
00164       while( contours ) {
00165 
00166         // approximate contour with accuracy proportional
00167         // to the contour perimeter
00168         result = cvApproxPoly( contours, sizeof(CvContour), storage,
00169                                CV_POLY_APPROX_DP, cvContourPerimeter(contours)*0.02, 0 );
00170 
00171         // square contours should have 4 vertices after approximation
00172         // relatively large area (to filter out noisy contours)
00173         // and be convex.
00174         // Note: absolute value of an area is used because
00175         // area may be positive or negative - in accordance with the
00176         // contour orientation
00177 
00178         //also going to check that the area is smaller than max_area
00179         double rect_area = fabs(cvContourArea(result,CV_WHOLE_SEQ));
00180 
00181 //         if (
00182 //             result->total == 4
00183 //             &&
00184 //             rect_area > 100
00185 //             && rect_area < max_area
00186 //             //            && cvCheckContourConvexity(result)
00187 //             )
00188 //           {
00189 
00190 //             Image<PixRGB<byte> > conImg = ipl2rgb(img);
00191 //             Point2D<int> pt1;             Point2D<int> pt2;
00192 //             for(int pti=0; pti < result->total-1; pti++)
00193 //               {
00194 //                 pt1.i = ((CvPoint*)cvGetSeqElem(result, pti))->x;
00195 //                 pt1.j = ((CvPoint*)cvGetSeqElem(result, pti))->y;
00196 //                 LINFO("[%d,%d]", pt1.i, pt1.j);
00197 
00198 //                 pt2.i = ((CvPoint*)cvGetSeqElem(result, pti+1))->x;
00199 //                 pt2.j = ((CvPoint*)cvGetSeqElem(result, pti+1))->y;
00200 
00201 //                 // draw the quad on the image
00202 //                 drawLine(conImg, pt1, pt2, PixRGB<byte>(255,0,0),1);
00203 //               }
00204 //             LINFO("[%d,%d]", pt2.i, pt2.j);
00205 //             pt1.i = ((CvPoint*)cvGetSeqElem(result, 0))->x;
00206 //             pt1.j = ((CvPoint*)cvGetSeqElem(result, 0))->y;
00207 
00208 //             // draw the quad on the image
00209 //             drawLine(conImg,pt2, pt1, PixRGB<byte>(255,0,0),1);
00210 
00211 //             inplacePaste(itsDisp, conImg, Point2D<int>(itsWidth, itsHeight));
00212 //             LINFO("con [%d] [color: %d, level: %d]", count, c, l);
00213 //             LINFO("[total: %d: area: %f  conv: %d inCorner: %d",
00214 //                   result->total, rect_area, cvCheckContourConvexity(result), inCorner(result));
00215 //             itsMwin->drawImage(itsDisp,0,0);
00216 //             Raster::waitForKey();
00217 //           }
00218 
00219         if (
00220             result->total == 4
00221             && rect_area > MIN_AREA
00222             && rect_area < max_area
00223             && cvCheckContourConvexity(result)
00224             && !inCorner(result)
00225             )
00226           {
00227             s = 0;
00228 
00229             for( i = 0; i < 5; i++ ) {
00230               // find minimum angle between joint
00231               // edges (maximum of cosine)
00232               if( i >= 2 ) {
00233                 t = fabs(angle(
00234                                (CvPoint*)cvGetSeqElem( result, i ),
00235                                (CvPoint*)cvGetSeqElem( result, i-2 ),
00236                              (CvPoint*)cvGetSeqElem( result, i-1 )));
00237                 s = s > t ? s : t;
00238               }
00239             }
00240 
00241             // if cosines of all angles are small
00242             // (all angles are ~90 degree) then write quandrange
00243             // vertices to resultant sequence
00244             if( s < angle_thresh)
00245               {
00246                 //LINFO("RECTANGLE BABY");
00247                 for( i = 0; i < 4; i++ )
00248                   cvSeqPush( squares, (CvPoint*)cvGetSeqElem( result, i ));
00249               }
00250           }
00251 
00252         // take the next contour
00253         contours = contours->h_next;
00254         count++;
00255       }
00256 //       LINFO("Bot[#contours: %d] [color: %d, level: %d]", count, c, l);
00257 //       itsMwin->drawImage(itsDisp,0,0);
00258 //       Raster::waitForKey();
00259     }
00260   }
00261 
00262 
00263   // release all the temporary images
00264   cvReleaseImage( &gray );
00265   cvReleaseImage( &pyr );
00266   cvReleaseImage( &tgray );
00267   cvReleaseImage( &timg );
00268 
00269   return squares;
00270 }
00271 
00272 // ######################################################################
00273 void BinFinder::updateFrame(Image<PixRGB<byte> > img, std::string cameraId)
00274 {
00275   LINFO("Image Received: %d", itsFrameCount);
00276         bool isFwdCamera = false;
00277         if(cameraId == "FwdCamera")
00278                 isFwdCamera = true;
00279 
00280   if(img.initialized())
00281     {
00282 
00283       CvMemStorage* storage = cvCreateMemStorage(0);
00284       IplImage* img0 = img2ipl(img);
00285 
00286       CvSeq *cvsquares = findSquares4( img0, storage );
00287 
00288       cvReleaseImage( &img0 );
00289 
00290       RobotSimEvents::QuadrilateralIceVector quadVect;
00291       ImageIceMod::QuadrilateralIce quad;
00292       Point2D<int> avgCenter(0,0);
00293       std::multimap<int,Point2D<int> > tempPoints;
00294 
00295       // iterate over all the quadrilateral points found
00296       for(int i=0; i < cvsquares->total; i++) {
00297 
00298         // get an individual point
00299         Point2D<int> quadPoint;
00300         quadPoint.i = ((CvPoint*)cvGetSeqElem(cvsquares, i))->x;
00301         quadPoint.j = ((CvPoint*)cvGetSeqElem(cvsquares, i))->y;
00302 
00303         // add current point's position to running average of point positions
00304         avgCenter += Point2D<int>(quadPoint.i,quadPoint.j);
00305 
00306         // add the point to map, sorted by point Y-axis position
00307         tempPoints.insert(make_pair(quadPoint.j,quadPoint));
00308 
00309         //LINFO("tempPoints size: %d\n",tempPoints.size());
00310 
00311         // if we have added the 4th point on the quadrilateral
00312         if (tempPoints.size() == 4)
00313           {
00314             std::vector<Point2D<int> > tempVec;
00315 
00316             for(std::map<int,Point2D<int > >::const_iterator it = tempPoints.begin();
00317                 it != tempPoints.end(); ++it)
00318               {
00319                 tempVec.push_back(it->second);
00320               }
00321 
00322 
00323             // compare first two points to determine which is top left
00324             // and which is top right
00325             if(tempVec[0].i < tempVec[1].i)
00326               {
00327                 quad.tl.i = tempVec[0].i;
00328                 quad.tr.i = tempVec[1].i;
00329 
00330                 quad.tl.j = tempVec[0].j;
00331                 quad.tr.j = tempVec[1].j;
00332               }
00333             else
00334               {
00335                 quad.tr.i = tempVec[0].i;
00336                 quad.tl.i = tempVec[1].i;
00337 
00338                 quad.tr.j = tempVec[0].j;
00339                 quad.tl.j = tempVec[1].j;
00340               }
00341 
00342             // compare second two points to determine bottom left and
00343             // bottom right
00344             if(tempVec[2].i < tempVec[3].i)
00345               {
00346                 quad.bl.i = tempVec[2].i;
00347                 quad.br.i = tempVec[3].i;
00348 
00349                 quad.bl.j = tempVec[2].j;
00350                 quad.br.j = tempVec[3].j;
00351               }
00352             else
00353               {
00354                 quad.br.i = tempVec[2].i;
00355                 quad.bl.i = tempVec[3].i;
00356 
00357                 quad.br.j = tempVec[2].j;
00358                 quad.bl.j = tempVec[3].j;
00359               }
00360 
00361 
00362             // divide by total number of averaged points
00363             // to get current quad's center position
00364             avgCenter /= Point2D<int>(4,4);
00365             quad.center.i = avgCenter.i;
00366             quad.center.j = avgCenter.j;
00367 
00368 
00369             bool isDupe = false;
00370 
00371             // make sure the quad's center is not too close
00372             // to a prev. quad's center in order to avoid duplicates
00373             for(uint j = 0; j < quadVect.size(); j++)
00374               {
00375                 if(avgCenter.distance(Point2D<int>(quadVect[j].center.i,quadVect[j].center.j))
00376                    < MIN_CENTER_DIST)
00377                   {
00378                     isDupe = true;
00379                   }
00380               }
00381 
00382             // not dupe so add it to vector
00383             if(!isDupe)
00384               {
00385                 LineSegment2D vertLine = LineSegment2D((Point2D<int>(quad.tr.i,quad.tr.j) +
00386                                                         Point2D<int>(quad.tl.i,quad.tl.j))/2,
00387                                                        (Point2D<int>(quad.br.i,quad.br.j) +
00388                                                         Point2D<int>(quad.bl.i,quad.bl.j))/2);
00389 
00390                 LineSegment2D horizLine = LineSegment2D((Point2D<int>(quad.tl.i,quad.tl.j) +
00391                                                          Point2D<int>(quad.bl.i,quad.bl.j))/2,
00392                                                         (Point2D<int>(quad.tr.i,quad.tr.j) +
00393                                                          Point2D<int>(quad.br.i,quad.br.j))/2);
00394                 float ratio = 0.0;
00395                 float angle = 0.0;
00396                 if(vertLine.length() > horizLine.length())
00397                   {
00398                     if(horizLine.length() > 0)
00399                       ratio = vertLine.length() / horizLine.length();
00400 
00401                     angle = vertLine.angle();
00402                   }
00403                 else
00404                   {
00405                     if(vertLine.length() > 0)
00406                       ratio = horizLine.length() / vertLine.length();
00407 
00408                     angle = horizLine.angle();
00409                   }
00410 
00411 
00412                 // change angle to degrees
00413                  angle = angle * (180/M_PI);
00414 
00415                 // normalize angle so that zero degrees is facing forawrd
00416                 // turning to the right is [0 -> 90]
00417                 // turning to the left is [0 -> -90]
00418                 if(angle < 0)
00419                   angle += 90;
00420                 else
00421                   angle += -90;
00422 
00423                 quad.ratio = ratio;
00424                 quad.angle = angle;
00425                 quadVect.push_back(quad);
00426 
00427                 // draw the quad on the image
00428                 drawLine(img,Point2D<int>(quad.tr.i,quad.tr.j),
00429                          Point2D<int>(quad.br.i,quad.br.j),
00430                          PixRGB<byte>(0,255,0),2);
00431                 drawLine(img,Point2D<int>(quad.br.i,quad.br.j),
00432                          Point2D<int>(quad.bl.i,quad.bl.j),
00433                          PixRGB<byte>(0,255,0),2);
00434                 drawLine(img,Point2D<int>(quad.bl.i,quad.bl.j),
00435                          Point2D<int>(quad.tl.i,quad.tl.j),
00436                          PixRGB<byte>(0,255,0),2);
00437                 drawLine(img,Point2D<int>(quad.tl.i,quad.tl.j),
00438                          Point2D<int>(quad.tr.i,quad.tr.j),
00439                          PixRGB<byte>(0,255,0),2);
00440 
00441                 char* str = new char[20];
00442                 sprintf(str,"%1.2f, %2.1f",ratio,angle);
00443                  writeText(img,Point2D<int>(quad.center.i,quad.center.j),str);
00444                 delete [] str;
00445 
00446               }
00447 
00448             // re-initialize for next quad
00449             quad = ImageIceMod::QuadrilateralIce();
00450             avgCenter = Point2D<int>(0,0);
00451             tempPoints.clear();
00452 
00453           }
00454       }
00455 
00456       itsOfs->writeRGB(img, "Vision Rectangle Image",
00457                        FrameInfo("Vision Rectangle Image", SRC_POS));
00458 
00459       itsOfs->updateNext();
00460 
00461       if (quadVect.size() > 0) {
00462         RobotSimEvents::VisionRectangleMessagePtr msg = new RobotSimEvents::VisionRectangleMessage;
00463         msg->quads = quadVect;
00464         msg->isFwdCamera = isFwdCamera;
00465         this->publish("VisionRectangleMessageTopic", msg);
00466       }
00467 
00468       cvReleaseMemStorage( &storage );
00469     }
00470 }
00471 
00472 
00473 bool BinFinder::inCorner(CvSeq* result)
00474 {
00475 
00476   for(int pti=0; pti < result->total; pti++)
00477     {
00478       Point2D<uint> pt;
00479       pt.i = ((CvPoint*)cvGetSeqElem(result, pti))->x;
00480       pt.j = ((CvPoint*)cvGetSeqElem(result, pti))->y;
00481 
00482       //edge contours are inset by 1
00483       uint right_edge = itsWidth - 2;
00484       uint bottom_edge = itsHeight - 2;
00485 
00486       if((pt.i <= CORNER_TOLERANCE && pt.j <= CORNER_TOLERANCE) ||
00487          (pt.i >= right_edge-CORNER_TOLERANCE && pt.j <= CORNER_TOLERANCE) ||
00488          (pt.i <= CORNER_TOLERANCE && pt.j >= bottom_edge-CORNER_TOLERANCE) ||
00489          (pt.i >= right_edge-CORNER_TOLERANCE && pt.j >= bottom_edge-CORNER_TOLERANCE)
00490          )
00491         return true;
00492     }
00493 
00494   return false;
00495 }
00496 
00497 #endif
Generated on Sun May 8 08:05:57 2011 for iLab Neuromorphic Vision Toolkit by  doxygen 1.6.3