VisionRectangle.C

00001 #include "Robots/SeaBeeIII/VisionRectangle.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 VISIONRECTANGLE_C
00017 #define VISIONRECTANGLE_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 VisionRectangle::VisionRectangle
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 VisionRectangle::~VisionRectangle()
00046 {
00047 }
00048 
00049 // ######################################################################
00050 void VisionRectangle::registerTopics()
00051 {
00052   LINFO("Registering VisionRectangle Message");
00053   this->registerPublisher("VisionRectangleMessageTopic");
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 VisionRectangle::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* VisionRectangle::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 VisionRectangle::updateFrame(Image<PixRGB<byte> > img, std::string cameraId)
00274 {
00275         bool isFwdCamera = false;
00276         if(cameraId == "FwdCamera")
00277                 isFwdCamera = true;
00278 
00279   LINFO("Image Received: %d", itsFrameCount);
00280 
00281   if(img.initialized())
00282     {
00283 
00284       CvMemStorage* storage = cvCreateMemStorage(0);
00285       IplImage* img0 = img2ipl(img);
00286 
00287       CvSeq *cvsquares = findSquares4( img0, storage );
00288 
00289       cvReleaseImage( &img0 );
00290 
00291       RobotSimEvents::QuadrilateralIceVector quadVect;
00292       ImageIceMod::QuadrilateralIce quad;
00293       Point2D<int> avgCenter(0,0);
00294       std::multimap<int,Point2D<int> > tempPoints;
00295 
00296       // iterate over all the quadrilateral points found
00297       for(int i=0; i < cvsquares->total; i++) {
00298 
00299         // get an individual point
00300         Point2D<int> quadPoint;
00301         quadPoint.i = ((CvPoint*)cvGetSeqElem(cvsquares, i))->x;
00302         quadPoint.j = ((CvPoint*)cvGetSeqElem(cvsquares, i))->y;
00303 
00304         // add current point's position to running average of point positions
00305         avgCenter += Point2D<int>(quadPoint.i,quadPoint.j);
00306 
00307         // add the point to map, sorted by point Y-axis position
00308         tempPoints.insert(make_pair(quadPoint.j,quadPoint));
00309 
00310         //LINFO("tempPoints size: %d\n",tempPoints.size());
00311 
00312         // if we have added the 4th point on the quadrilateral
00313         if (tempPoints.size() == 4)
00314           {
00315             std::vector<Point2D<int> > tempVec;
00316 
00317             for(std::map<int,Point2D<int > >::const_iterator it = tempPoints.begin();
00318                 it != tempPoints.end(); ++it)
00319               {
00320                 tempVec.push_back(it->second);
00321               }
00322 
00323 
00324             // compare first two points to determine which is top left
00325             // and which is top right
00326             if(tempVec[0].i < tempVec[1].i)
00327               {
00328                 quad.tl.i = tempVec[0].i;
00329                 quad.tr.i = tempVec[1].i;
00330 
00331                 quad.tl.j = tempVec[0].j;
00332                 quad.tr.j = tempVec[1].j;
00333               }
00334             else
00335               {
00336                 quad.tr.i = tempVec[0].i;
00337                 quad.tl.i = tempVec[1].i;
00338 
00339                 quad.tr.j = tempVec[0].j;
00340                 quad.tl.j = tempVec[1].j;
00341               }
00342 
00343             // compare second two points to determine bottom left and
00344             // bottom right
00345             if(tempVec[2].i < tempVec[3].i)
00346               {
00347                 quad.bl.i = tempVec[2].i;
00348                 quad.br.i = tempVec[3].i;
00349 
00350                 quad.bl.j = tempVec[2].j;
00351                 quad.br.j = tempVec[3].j;
00352               }
00353             else
00354               {
00355                 quad.br.i = tempVec[2].i;
00356                 quad.bl.i = tempVec[3].i;
00357 
00358                 quad.br.j = tempVec[2].j;
00359                 quad.bl.j = tempVec[3].j;
00360               }
00361 
00362 
00363             // divide by total number of averaged points
00364             // to get current quad's center position
00365             avgCenter /= Point2D<int>(4,4);
00366             quad.center.i = avgCenter.i;
00367             quad.center.j = avgCenter.j;
00368 
00369 
00370             bool isDupe = false;
00371 
00372             // make sure the quad's center is not too close
00373             // to a prev. quad's center in order to avoid duplicates
00374             for(uint j = 0; j < quadVect.size(); j++)
00375               {
00376                 if(avgCenter.distance(Point2D<int>(quadVect[j].center.i,quadVect[j].center.j))
00377                    < MIN_CENTER_DIST)
00378                   {
00379                     isDupe = true;
00380                   }
00381               }
00382 
00383             // not dupe so add it to vector
00384             if(!isDupe)
00385               {
00386                 LineSegment2D vertLine = LineSegment2D((Point2D<int>(quad.tr.i,quad.tr.j) +
00387                                                         Point2D<int>(quad.tl.i,quad.tl.j))/2,
00388                                                        (Point2D<int>(quad.br.i,quad.br.j) +
00389                                                         Point2D<int>(quad.bl.i,quad.bl.j))/2);
00390 
00391                 LineSegment2D horizLine = LineSegment2D((Point2D<int>(quad.tl.i,quad.tl.j) +
00392                                                          Point2D<int>(quad.bl.i,quad.bl.j))/2,
00393                                                         (Point2D<int>(quad.tr.i,quad.tr.j) +
00394                                                          Point2D<int>(quad.br.i,quad.br.j))/2);
00395                 float ratio = 0.0;
00396                 float angle = 0.0;
00397                 if(vertLine.length() > horizLine.length())
00398                   {
00399                     if(horizLine.length() > 0)
00400                       ratio = vertLine.length() / horizLine.length();
00401 
00402                     angle = vertLine.angle();
00403                   }
00404                 else
00405                   {
00406                     if(vertLine.length() > 0)
00407                       ratio = horizLine.length() / vertLine.length();
00408 
00409                     angle = horizLine.angle();
00410                   }
00411 
00412 
00413                 // change angle to degrees
00414                  angle = angle * (180/M_PI);
00415 
00416                 // normalize angle so that zero degrees is facing forawrd
00417                 // turning to the right is [0 -> 90]
00418                 // turning to the left is [0 -> -90]
00419                 if(angle < 0)
00420                   angle += 90;
00421                 else
00422                   angle += -90;
00423 
00424                 quad.ratio = ratio;
00425                 quad.angle = angle;
00426                 quadVect.push_back(quad);
00427 
00428                 // draw the quad on the image
00429                 drawLine(img,Point2D<int>(quad.tr.i,quad.tr.j),
00430                          Point2D<int>(quad.br.i,quad.br.j),
00431                          PixRGB<byte>(0,255,0),2);
00432                 drawLine(img,Point2D<int>(quad.br.i,quad.br.j),
00433                          Point2D<int>(quad.bl.i,quad.bl.j),
00434                          PixRGB<byte>(0,255,0),2);
00435                 drawLine(img,Point2D<int>(quad.bl.i,quad.bl.j),
00436                          Point2D<int>(quad.tl.i,quad.tl.j),
00437                          PixRGB<byte>(0,255,0),2);
00438                 drawLine(img,Point2D<int>(quad.tl.i,quad.tl.j),
00439                          Point2D<int>(quad.tr.i,quad.tr.j),
00440                          PixRGB<byte>(0,255,0),2);
00441 
00442                 char* str = new char[20];
00443                 sprintf(str,"%1.2f, %2.1f",ratio,angle);
00444                  writeText(img,Point2D<int>(quad.center.i,quad.center.j),str);
00445                 delete [] str;
00446 
00447               }
00448 
00449             // re-initialize for next quad
00450             quad = ImageIceMod::QuadrilateralIce();
00451             avgCenter = Point2D<int>(0,0);
00452             tempPoints.clear();
00453 
00454           }
00455       }
00456 
00457       itsOfs->writeRGB(img, "Vision Rectangle Image",
00458                        FrameInfo("Vision Rectangle Image", SRC_POS));
00459 
00460       itsOfs->updateNext();
00461 
00462       if (quadVect.size() > 0) {
00463         RobotSimEvents::VisionRectangleMessagePtr msg = new RobotSimEvents::VisionRectangleMessage;
00464         msg->quads = quadVect;
00465         msg->isFwdCamera = isFwdCamera;
00466         this->publish("VisionRectangleMessageTopic", msg);
00467       }
00468 
00469       cvReleaseMemStorage( &storage );
00470     }
00471 }
00472 
00473 
00474 bool VisionRectangle::inCorner(CvSeq* result)
00475 {
00476 
00477   for(int pti=0; pti < result->total; pti++)
00478     {
00479       Point2D<uint> pt;
00480       pt.i = ((CvPoint*)cvGetSeqElem(result, pti))->x;
00481       pt.j = ((CvPoint*)cvGetSeqElem(result, pti))->y;
00482 
00483       //edge contours are inset by 1
00484       uint right_edge = itsWidth - 2;
00485       uint bottom_edge = itsHeight - 2;
00486 
00487       if((pt.i <= CORNER_TOLERANCE && pt.j <= CORNER_TOLERANCE) ||
00488          (pt.i >= right_edge-CORNER_TOLERANCE && pt.j <= CORNER_TOLERANCE) ||
00489          (pt.i <= CORNER_TOLERANCE && pt.j >= bottom_edge-CORNER_TOLERANCE) ||
00490          (pt.i >= right_edge-CORNER_TOLERANCE && pt.j >= bottom_edge-CORNER_TOLERANCE)
00491          )
00492         return true;
00493     }
00494 
00495   return false;
00496 }
00497 
00498 #endif
Generated on Sun May 8 08:42:07 2011 for iLab Neuromorphic Vision Toolkit by  doxygen 1.6.3