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