Main Page | Namespace List | Class Hierarchy | Alphabetical List | Class List | File List | Namespace Members | Class Members | File Members | Related Pages

Modules/ImageProcessor/GT2005ImageProcessor/GT2005GoalRecognizer.cpp

Go to the documentation of this file.
00001 /**
00002  * @file GT2005GoalRecognizer.cpp
00003  *
00004  * Implementation of class GT2005GoalRecognizer
00005  *
00006  * @author <a href="mailto:oberlies@sim.tu-darmstadt.de">Tobias Oberlies</a>
00007  */
00008 
00009 //#include "Tools/Player.h"
00010 //#include "Tools/FieldDimensions.h"
00011 //#include "Tools/RangeArray.h"
00012 //#include "Tools/Debugging/View.h"
00013 //#include "Representations/Perception/Image.h"
00014 //#include "Representations/Perception/ColorTable.h"
00015 //#include "Representations/Perception/ObstaclesPercept.h"
00016 //#include "Representations/Perception/LandmarksPercept.h"
00017 //#include "Representations/Perception/CameraMatrix.h"
00018 //#include "Modules/ImageProcessor/ImageProcessorTools/ColorTableReferenceColor.h"
00019 #include "Tools/ColorClasses.h"
00020 #include "Tools/Math/Geometry.h"
00021 #include "Tools/Debugging/DebugImages.h"
00022 #include "Tools/Debugging/DebugDrawings.h"
00023 #include "Platform/GTAssert.h"
00024 #include "Representations/Perception/LandmarksPercept.h"
00025 #include "GT2005ImageProcessorTools.h"
00026 #include "GT2005GoalRecognizer.h"
00027 
00028 
00029 
00030 /** Parameters */
00031 enum parameters
00032 {
00033   /** The maximal length of a scanline towards the edge. If no edge is detected within 
00034       this range, it is assumed, that the current focus is nowhere near the goalpost
00035       and goalpost detection will be cancelled. */
00036   maxScanLengthToGoalpostEdge = 14,
00037 
00038   /** Goal hypotheses flatter than this value are not considered to be goals. */
00039   minGoalpostHeight = 7
00040 };
00041 
00042 
00043 
00044 /**
00045  * Macro to draw a rectangle that is given in horizon-aligned coordinates. 
00046  * @param horizoninfo The current ImageInfo instance.
00047  * @param x1,y1,x2,y2 The coordinates of 2 opposite corners in horizon-aligned coordinates.
00048  * @param penwidth The line width.
00049  * @param penstyle The line style, e.g. ls_solid.
00050  * @param pencolor The drawing colour.
00051  */
00052 #ifdef NDEBUG
00053 #define HORIZON_ALIGNED_RECTANGLE(horizoninfo, id, x1, y1, x2, y2, penwidth, penstyle, pencolor, cross) ;
00054 
00055 #else
00056 #define HORIZON_ALIGNED_RECTANGLE(horizoninfo, id, x1, y1, x2, y2, penwidth, penstyle, pencolor, cross) \
00057   { \
00058     Vector2<int> _topleft = horizoninfo.toImageCoordinates(x1, y1); \
00059     Vector2<int> _bottomleft = horizonInfo.toImageCoordinates(x1, y2); \
00060     Vector2<int> _bottomright = horizoninfo.toImageCoordinates(x2, y2); \
00061     Vector2<int> _topright = horizoninfo.toImageCoordinates(x2, y1); \
00062     QUADRANGLE(imageProcessor_flagsAndGoals, _topleft.x, _topleft.y, _bottomleft.x, _bottomleft.y, _bottomright.x, _bottomright.y, _topright.x, _topright.y, penwidth, penstyle, pencolor); \
00063     NQUADRANGLE(id, _topleft.x, _topleft.y, _bottomleft.x, _bottomleft.y, _bottomright.x, _bottomright.y, _topright.x, _topright.y, penwidth, penstyle, pencolor); \
00064     if (cross) \
00065     { \
00066       NLINE(id, _topleft.x, _topleft.y, _bottomright.x, _bottomright.y, penwidth, penstyle, pencolor); \
00067       NLINE(id, _bottomleft.x, _bottomleft.y, _topright.x, _topright.y, penwidth, penstyle, pencolor); \
00068     } \
00069   }
00070 
00071 #endif
00072 
00073 
00074 GT2005GoalRecognizer::GT2005GoalRecognizer(colorClass color, const ImageProcessorInterfaces& interfaces, const ColorCorrector& colorCorrector, const ImageInfo& horizonInfo) 
00075   : goalColor(color),
00076     interfaces(interfaces),
00077     colorCorrector(colorCorrector),
00078     horizonInfo(horizonInfo)
00079 {
00080 }
00081 
00082 void GT2005GoalRecognizer::notifyAboutNewImage()
00083 {
00084   // Reset detection counter
00085   detectionCounter = 0;
00086 
00087   // Clear locked areas and goal hypothesises
00088   lockAreaCount = 0;
00089   flagLockCount = 0;
00090   hypothesisCount = 0;
00091 
00092   // Store frame number
00093   frameNumberImage = interfaces.image.frameNumber;
00094 
00095 
00096   // Copy current image into the debug image
00097   if (yellow == goalColor)
00098   {
00099     INIT_DEBUG_IMAGE(imageProcessorGoal1, interfaces.image);
00100     N_INIT_DEBUG_GRAY_SCALE_IMAGE(goalRecognizerYellow, interfaces.image);
00101   }
00102   else
00103   {
00104     INIT_DEBUG_IMAGE(imageProcessorGoal2, interfaces.image);
00105     N_INIT_DEBUG_GRAY_SCALE_IMAGE(goalRecognizerBlue, interfaces.image);
00106   }
00107 
00108   NDECLARE_DEBUGDRAWING("GoalRecognizer:goal fragments:all",
00109                         "drawingOnImage",
00110                         "Draws the bounding box of all goal fragment candidates in the image. Crossed out fragments were discarded half right after the analysis.");
00111   NDECLARE_DEBUGDRAWING("GoalRecognizer:goal fragments:merged",
00112                         "drawingOnImage",
00113                         "Draws the bounding box of goal fragments that are merged.");
00114   NDECLARE_DEBUGDRAWING("GoalRecognizer:goal fragments:result",
00115                         "drawingOnImage",
00116                         "Draws the bounding box of the found goal.");
00117 }
00118 
00119 void GT2005GoalRecognizer::notifyAboutFlags()
00120 {
00121   // Skip scanlines near the flags
00122   flagLockCount = min(interfaces.landmarksPercept.numberOfFlags, static_cast<int>(maxFlagLocks));
00123   for (int i = 0; i<flagLockCount; i++)
00124   {
00125     // Calculate X coordinate in horizon-aligned coordinates
00126     flagLock[i].min = horizonInfo.horizonAlignedXOf(interfaces.landmarksPercept.flags[i].bottomLeft);
00127     flagLock[i].max = horizonInfo.horizonAlignedXOf(interfaces.landmarksPercept.flags[i].bottomRight);
00128 
00129     // Increase range by two flag widths
00130     double width = flagLock[i].max - flagLock[i].min;
00131     flagLock[i].min -= (width + 3.0);
00132     flagLock[i].max += (width + 3.0);
00133   }
00134 
00135   // Store frame number (because this method might not be called on every image)
00136   frameNumberFlags = interfaces.landmarksPercept.frameNumber;
00137 }
00138 
00139 
00140 void GT2005GoalRecognizer::notifyAboutFinish()
00141 {
00142   // Edge detector state machines
00143   GT2005GoalRecognizer::EdgeDetector detector[2] = 
00144   {
00145     GT2005GoalRecognizer::EdgeDetector(goalColor),
00146     GT2005GoalRecognizer::EdgeDetector(goalColor)
00147   };
00148 
00149   // Merge goal fragments
00150   bool deletedHypothesis[maxHypothesises];
00151   mergeFragments(deletedHypothesis);
00152 
00153   // Interpret goal hypothesises
00154   interpretResults(detector, deletedHypothesis);
00155 
00156 
00157   if (yellow == goalColor)
00158   {
00159     // Send debug image
00160     SEND_DEBUG_IMAGE(imageProcessorGoal1);
00161     N_SEND_DEBUG_GRAY_SCALE_IMAGE(goalRecognizerYellow);
00162   }
00163   else
00164   {
00165     // This is the second GoalRecognizer, so send drawing stuff now
00166     DEBUG_DRAWING_FINISHED(imageProcessor_flagsAndGoals);
00167     SEND_DEBUG_IMAGE(imageProcessorGoal2);
00168     N_SEND_DEBUG_GRAY_SCALE_IMAGE(goalRecognizerBlue);
00169   }
00170 }
00171 
00172 bool GT2005GoalRecognizer::inspectNeighbourhood(const Vector2<int>& point)
00173 {
00174   // Skip if on the image border
00175   if (nearImageBorder(point, 0))
00176     return false;
00177 
00178   // Look at 9-connected neighbourhood and count the number of pixels in target colour
00179   int targetCount = 0;
00180   for (int i = -1; i<=1; ++i)
00181     for (int j = -1; j<=1; ++j)
00182       if (goalColor == CORRECTED_COLOR_CLASS(point.x+i, point.y+j, interfaces.image.image[point.y+j][0][point.x+i], interfaces.image.image[point.y+j][1][point.x+i], interfaces.image.image[point.y+j][2][point.x+i], interfaces.colorTable, colorCorrector))
00183       {
00184         // This pixel has the goal colour
00185         ++targetCount;
00186       }
00187 
00188   // Positive result if the majority of the pixels are in goal colour
00189   return targetCount>4;
00190 }
00191 
00192 void GT2005GoalRecognizer::analyzeGoal(Vector2<int> focus)
00193 {
00194   // Edge detector state machines
00195   GT2005GoalRecognizer::EdgeDetector detector[2] = 
00196   {
00197     GT2005GoalRecognizer::EdgeDetector(goalColor), 
00198     GT2005GoalRecognizer::EdgeDetector(goalColor)
00199   };
00200 
00201   // Flags storing on which image border scanning has been done, to prevent repeated scans 
00202   // on the image borders within one analysis step or in different steps. The array is 
00203   // indexed by the enum imageBorderType.
00204   bool imageBorderScanned[4] = {false, false, false, false};
00205 
00206   // Object to store detection result
00207   GT2005GoalRecognizer::GoalHypothesis result;
00208 
00209 
00210   //-- Analyze the left goalpost
00211   analyzeGoalpost(detector, -horizonInfo.horizon.direction, true, focus, result.goalpost[0], imageBorderScanned);
00212 
00213 
00214   //-- Scan along the cross bar
00215   scanCrossBar(detector, focus, result, imageBorderScanned);
00216 
00217   // Filter obvious false hypothesis
00218   result.width = static_cast<int>(result.crossBarEndpointH.x-result.goalpost[0].edgePointH.x);
00219   if (result.goalpost[0].height+result.width < 15 || result.width <= 5)
00220   {
00221     // Prevent this blob to be analysed again
00222     lockArea(result.crossBarEndpointH.x + 10, result.goalpost[0].bottomPointH.y + 10, result.goalpost[0].onGreen);
00223 
00224     HORIZON_ALIGNED_RECTANGLE(horizonInfo, "GoalRecognizer:goal fragments:all",
00225       result.goalpost[0].edgePointH.x, min(result.goalpost[0].topPointH.y, result.crossBarEndpointH.y),
00226       result.crossBarEndpointH.x, result.goalpost[0].bottomPointH.y,
00227       1, Drawings::ps_solid, Drawings::red, true);
00228     return;
00229   }
00230 
00231 
00232   //-- Analyze the right goalpost
00233   analyzeGoalpost(detector, horizonInfo.horizon.direction, false, focus, result.goalpost[1], imageBorderScanned);
00234   
00235   // Possibly recalculate the height of the left goalpost (necessary if scanCrossBar 
00236   // scanned along the image border downwards)
00237   if (result.crossBarEndpointH.y < result.goalpost[1].topPointH.y)
00238   {
00239     result.goalpost[1].topPoint = result.crossBarEndpoint;
00240     result.goalpost[1].topPointH = result.crossBarEndpointH;
00241     result.goalpost[1].height = static_cast<int>(result.goalpost[1].bottomPointH.y - result.goalpost[1].topPointH.y);
00242   }
00243 
00244   // Calculate the width and height
00245   result.width = static_cast<int>(result.goalpost[1].edgePointH.x - result.goalpost[0].edgePointH.x);
00246   result.height = max(result.goalpost[0].height, result.goalpost[1].height);
00247 
00248   // Goal hypothesis is on the green field?
00249   result.onGreen = result.goalpost[0].onGreen || result.goalpost[1].onGreen;
00250 
00251   HORIZON_ALIGNED_RECTANGLE(horizonInfo, "GoalRecognizer:goal fragments:all",
00252     result.goalpost[0].edgePointH.x, min(min(result.goalpost[0].topPointH.y, result.goalpost[1].topPointH.y), result.crossBarEndpointH.y),
00253     result.goalpost[1].edgePointH.x, max(result.goalpost[0].bottomPointH.y, result.goalpost[1].bottomPointH.y),
00254     1, Drawings::ps_solid, Drawings::red, false);
00255 
00256   //-- Interpretation
00257   // Prevent that this goal is discovered again
00258   lockArea(result.goalpost[1].edgePoint.x + 5, max(result.goalpost[0].bottomPointH.y, result.goalpost[1].bottomPointH.y) + 5, result.goalpost[0].onGreen);
00259 
00260   // Add goal to hypothsises
00261   if (hypothesisCount < maxHypothesises)
00262     hypothesis[hypothesisCount++] = result;   // TODO: Use pointer
00263 }
00264 
00265 void GT2005GoalRecognizer::analyzeGoalpost(GT2005GoalRecognizer::EdgeDetector* detector, 
00266                                            const Vector2<double>& directionOfEdge, 
00267                                            bool leftGoalpost,
00268                                            Vector2<int>& focus, 
00269                                            GT2005GoalRecognizer::Goalpost& result,
00270                                            bool* imageBorderScanned)
00271 {
00272   // Container to hold the detected points on the edge
00273   EdgePointList edgePoints;
00274 
00275   // Scan along the image border, because the normal scanning would violate the borders
00276   bool borderScan = false;
00277 
00278   // While scanning towards the edge, a huge target-coloured area was discovered. This 
00279   // usually happens, if a goal was only partially detected in a preceding goal analysis 
00280   // and hence not entirely locked and detected again. As a result, the same goal might be 
00281   // scanned again, which is a waste of processing time. Therefore I cancel the detection
00282   // in these cases
00283   bool detectionCanceled = false;
00284 
00285   // Bresenham scanlines along and perpendicular to the goalpost
00286   // The vertical scanline is slightly rotated inwards (i.e. away from the edge) to account 
00287   // for motion skew or a non-perfect horizon.
00288   const double verticalScanSlope = 0.2;
00289   BresenhamLineScan horizontalScan(directionOfEdge);
00290   BresenhamLineScan verticalScan(horizonInfo.vertLine.direction - directionOfEdge*verticalScanSlope);
00291 
00292   // Offset between the two parallel scanlines (horizontal and vertical scans are usually 
00293   // done on two scanlines for more noise tolerance)
00294   Vector2<int> horizontalScanOffset(static_cast<int>(floor(-horizonInfo.vertLine.direction.x*2+0.5)),
00295                                     static_cast<int>(floor(-horizonInfo.vertLine.direction.y*2+0.5)));
00296   Vector2<int> verticalScanOffset(static_cast<int>(floor(-directionOfEdge.x+0.5)), 
00297                                   static_cast<int>(floor(-directionOfEdge.y+0.5)));
00298 
00299   //-- Scan towards the edge
00300   detector[0].clear(focus, false);
00301   detector[1].clear(focus, true);
00302   switch (detectEdgeTwice(detector, focus, horizontalScan, horizontalScanOffset, maxScanLengthToGoalpostEdge, edgePoints))
00303   {
00304   case imageBorder:
00305     // Check whether the focus is on the image border (might not be there, because 
00306     // the last point in the target class is returned)
00307     borderScan = nearImageBorder(focus, 1);
00308 
00309     // Otherwise assume that there is an edge
00310     // Even if the focus is not on the image border, it is still possible that there is 
00311     // no edge. The edge detector would have needed to investigates some more pixels (not 
00312     // available due to the image border) to decide this. But since assuming there might 
00313     // a (weak) edge is not harmful if its wrong, so we do this. 
00314     // Note that the point is not stored as weak edge (but as on image border) but only a 
00315     // scanning strategy is like after edges is pursued.
00316     break;
00317 
00318   case none:
00319     // Scanning towards the edge did find nothing but goal-coloured pixels. In this case
00320     // we cancel the scanning, because it is likely, that we are running into an area,
00321     // that has been scanned during a preceding goal analysis
00322     detectionCanceled = true;
00323   }
00324   
00325   // Save this point for upwards scanning
00326   Vector2<int> firstEdgePoint = focus;
00327 
00328   // Variable to store if the downwards scan started with a scan on the image border
00329   GT2005GoalRecognizer::ImageBorderSide firstEdgeImageBorder = noBorder;
00330 
00331 
00332   //-- Sense along the edge downwards
00333   if (borderScan)
00334   {
00335     // Scan along the image border and store on which image border the scan has been done.
00336     // The scanned side will be scanned again in the other direction at the beginning of
00337     // the upwards sensing step
00338     firstEdgeImageBorder =
00339       scanOnImageBorder(detector, focus, horizonInfo.vertLine.direction, 3, imageBorderScanned);
00340     borderScan = false;
00341 
00342     if (firstEdgeImageBorder != noBorder)
00343     {
00344       // Store image border point
00345       edgePoints.add(focus, imageBorder);
00346 
00347       // Set slightly inwards for the following scans
00348       focus += verticalScanOffset*2;
00349       horizonInfo.clipToImageBoundary(focus);
00350     }
00351   }
00352 
00353   for (int i = 0; !detectionCanceled && i<25; ++i)
00354   {
00355     DEBUG_RESPONSE ("gt05-ip:GoalRecognizer:assert loop termination",
00356       ASSERT(i<23); // This loop should be not be infinite without the limit i<25
00357     );
00358 
00359     if (borderScan)
00360     {
00361       // Slide along the image border to the bottom
00362       if (noBorder == scanOnImageBorder(detector, focus, horizonInfo.vertLine.direction, 3, imageBorderScanned))
00363       {
00364         // The border scan failed without finding any pixels in the target class, so
00365         // assume the bottom point has been outrun or scanning along the image border
00366         // into which the focus has run doesn't make sense
00367         break;
00368       }
00369       borderScan = false;
00370 
00371       // Store image border point
00372       edgePoints.add(focus, imageBorder);
00373 
00374       // Set slightly inwards
00375       focus += verticalScanOffset*2;
00376       horizonInfo.clipToImageBoundary(focus);
00377     }
00378     else
00379     {
00380       // Scan downwards for 20 pixels (at maximum)
00381       detector[0].clear(focus, true);
00382       detector[1].clear(focus, true);
00383       int scanned;
00384       if (scanAlongLine(detector, focus, verticalScan, verticalScanOffset, 20, scanned) == imageBorder)
00385       {
00386         // Assuming that we either hit the bottom border, or the image border and the 
00387         // edge converge, continue scanning on the edge (not true if there is only a 
00388         // very thin strip of the goal in the image, but don't worry too much about 
00389         // this case)
00390         borderScan = true;
00391 
00392         // Store image border point
00393         edgePoints.add(focus, imageBorder);
00394         continue;
00395       }
00396 
00397       // Only scan horizontal again if vertical scan made it at least some pixels
00398       if (scanned>4)
00399       {
00400         // Scan towards the edge
00401         detector[0].clear(focus, true);
00402         detector[1].clear(focus, true);
00403         switch(detectEdgeTwice(detector, focus, horizontalScan, horizontalScanOffset, maxScanLengthToGoalpostEdge, edgePoints))
00404         {
00405         case imageBorder:
00406           // Check whether the focus is on the image border
00407           borderScan = nearImageBorder(focus, 1);
00408           break;
00409 
00410         case none:
00411           // There is no edge, so cancel goalpost detection
00412           detectionCanceled = true;
00413         }
00414       }
00415 
00416       // Leave the loop if vertical scan didn't get far
00417       if (scanned<8)
00418         break;
00419     }
00420   } // Downwards sensing loop
00421 
00422   // Store bottom point
00423   result.bottomPoint = focus;
00424   result.bottomPointH = horizonInfo.toHorizonAligned(focus);
00425   //int bottomPointIndex = edgePoints.size() - 1;
00426 
00427   // Store reference colour of the edge detector used on the inner scanline. It is needed
00428   // for the detection of the free part of the goal (is done in the analysis at the end)
00429   detector[1].getReferenceColor(result.color);
00430 
00431   //-- Sense along edge upwards
00432   // This is only necessary on the left goalpost. The crossbar analysis ends at the top 
00433   // point of the right goalpost.
00434   focus = firstEdgePoint;
00435 
00436   if (leftGoalpost && !detectionCanceled)
00437   {
00438     verticalScan = BresenhamLineScan(-horizonInfo.vertLine.direction - directionOfEdge*verticalScanSlope);
00439     horizontalScanOffset = -horizontalScanOffset;
00440     borderScan = false;
00441 
00442     if (firstEdgeImageBorder != noBorder)
00443     {
00444       // The scan towards the edge ran into the image border and as a consequence, the 
00445       // sensing towards the bottom began with a scan along the border. It might be 
00446       // necessary to begin with a scan along the image border again, but this would 
00447       // usually be prohibited (no image side may be scanned twice). So we need to 
00448       // explicitly allow the scan on the same side.
00449       imageBorderScanned[firstEdgeImageBorder] = false;
00450 
00451       // Now scan along the image border
00452       if (noBorder != scanOnImageBorder(detector, focus, -horizonInfo.vertLine.direction, 3, imageBorderScanned))
00453       {
00454         // Store image border point
00455         edgePoints.add(focus, imageBorder);
00456 
00457         // Set slightly inwards for the following scans
00458         focus += verticalScanOffset*2;
00459         horizonInfo.clipToImageBoundary(focus);
00460       }
00461 
00462       // Disallow the scanned image border side again (the call to scanOnImageBorder 
00463       // might not have scanned along any side or along a different side, both would be 
00464       // okay)
00465       imageBorderScanned[firstEdgeImageBorder] = true;
00466     }
00467 
00468     for (int i = 0; !detectionCanceled && i<12; ++i)
00469     {
00470       DEBUG_RESPONSE ("gt05-ip:GoalRecognizer:assert loop termination",
00471         ASSERT(i<10);   // This loop should be not be infinite without the limit i<12
00472       );
00473 
00474       if (borderScan)
00475       {
00476         // Slide alon the image border to the top
00477         if (noBorder == scanOnImageBorder(detector, focus, horizonInfo.vertLine.direction, 3, imageBorderScanned))
00478         {
00479           // The border scan failed without finding any pixels in the target class, 
00480           // so assume the bottom point has been outrun or that the focus ran into
00481           // an image border not worth to be followed (with resepect to our current
00482           // desired scanning direction)
00483           break;
00484         }
00485         borderScan = false;
00486 
00487         // Store image border point
00488         edgePoints.add(focus, imageBorder);
00489 
00490         // Set slightly inwards for the following scanning
00491         focus += verticalScanOffset*2;
00492         horizonInfo.clipToImageBoundary(focus);
00493       }
00494       else
00495       {
00496         // Scan upwards for 20 pixels (at maximum)
00497         detector[0].clear(focus, true);
00498         detector[1].clear(focus, true);
00499         int scanned;
00500         if (scanAlongLine(detector, focus, verticalScan, verticalScanOffset, 20, scanned) == imageBorder)
00501         {
00502           // Store image border point
00503           edgePoints.add(focus, imageBorder);
00504 
00505           // Scan along the image border
00506           borderScan = true;
00507           continue;
00508         }
00509       
00510         // Only scan horizontal again if vertical scan made it at least some pixels
00511         if (scanned>8)
00512         {
00513           // Scan towards the edge
00514           detector[0].clear(focus, true);
00515           detector[1].clear(focus, true);
00516           switch(detectEdgeTwice(detector, focus, horizontalScan, horizontalScanOffset, maxScanLengthToGoalpostEdge, edgePoints))
00517           {
00518           case imageBorder:
00519             // Scan along the border
00520             borderScan = true;
00521             break;
00522 
00523           case none:
00524             // Assume there is actually no goalpost
00525             detectionCanceled = true;
00526           }
00527         }
00528         else
00529           break;
00530       }
00531 
00532     } // Upwards sensing loop
00533   }
00534   
00535   // If the top point of the goalpost still has to be found (for the following cross bar 
00536   // analysis), but there was no goapost, just scan straight upwards
00537   if (detectionCanceled && leftGoalpost)
00538   {
00539     verticalScan = BresenhamLineScan(-horizonInfo.vertLine.direction);
00540     
00541     // Scan straight upwards (unlike before, where the scanline is slightly slanted away
00542     // from the edge)
00543     detector[0].clear(focus, true);
00544     detector[1].clear(focus, true);
00545     
00546     int dummy;
00547     if (scanAlongLine(detector, focus, verticalScan, verticalScanOffset, 1024, dummy) == imageBorder)
00548     {
00549       // Trouble with the image border again
00550       detector[0].clear(focus, false);
00551       detector[1].clear(focus, false);
00552       scanOnImageBorder(detector, focus, horizonInfo.vertLine.direction, 3, imageBorderScanned);
00553     }
00554   }
00555 
00556 
00557   // Store top point
00558   result.topPoint = focus;
00559   result.topPointH = horizonInfo.toHorizonAligned(focus);
00560 
00561   //-- Analysis
00562   result.nonExistant = detectionCanceled;
00563   
00564   if (detectionCanceled)
00565   {
00566     // Set default values
00567     result.height = 0;
00568     result.visibleHeight = 0;
00569     result.edgePoint = result.topPoint;
00570     result.edgePointH = result.topPointH;
00571     result.onGreen = false;
00572   }
00573   else
00574   {
00575     // Calculate the height of the goalpost
00576     result.height = static_cast<int>(result.bottomPointH.y - result.topPointH.y);
00577     //OUTPUT(idText,text,result.height[index]);
00578 
00579     // Check if there is green below the goalpost
00580     if (result.height > minGoalpostHeight)
00581       result.onGreen = detectGreenBelowGoalpost(result.bottomPoint, directionOfEdge, result.height);
00582     else
00583       result.onGreen = false;
00584 
00585 
00586     // TODO replace by proper one
00587     result.visibleHeight = edgePoints.getCount(edge)+edgePoints.getCount(deviationWithoutEdge)>0 ? 1 : 0;
00588 
00589     if (result.visibleHeight > 0)
00590     {
00591       // Do additional scans
00592       // Check straightness
00593       // Store slope
00594 
00595       // Average all edge point as the goalpost edge
00596       for (int i = 0; i<edgePoints.size(); ++i)
00597       {
00598         switch (edgePoints.getType(i))
00599         {
00600         case edge:
00601         case deviationWithoutEdge:
00602           result.edgePoint += edgePoints[i];
00603         }
00604       }
00605       result.edgePoint /= edgePoints.getCount(edge)+edgePoints.getCount(deviationWithoutEdge);
00606       result.edgePointH = horizonInfo.invRotation * Vector2<double>(static_cast<double>(result.edgePoint.x),
00607                                     static_cast<double>(result.edgePoint.y));
00608 
00609       // Store the number of weak and strong edges
00610       result.strongEdgeCount = edgePoints.getCount(edge);
00611       result.weakEdgeCount = edgePoints.getCount(deviationWithoutEdge);
00612     }
00613     else // visibleHeight == 0
00614     {
00615       ASSERT(edgePoints.getCount(imageBorder)>0);
00616 
00617       // Use the leftmost/rightmost 
00618       double extremeHX = leftGoalpost ? 1024.0f : -1024.0f;
00619 
00620       for (int i = 0; i<edgePoints.size(); ++i)
00621       {
00622         if (edgePoints.getType(i) == imageBorder)
00623         {
00624           // Transform into the horizon-aligned coordinate system
00625           double HX = horizonInfo.invRotation.c[0].x * edgePoints[i].x
00626               + horizonInfo.invRotation.c[1].x * edgePoints[i].y;
00627           if ((HX < extremeHX) == leftGoalpost)
00628           {
00629             // Use this point as extreme point. 
00630             // NB: with goalpostVisibleHeight==0 it is obvious that albeit the name,
00631             // the value is not an edge point
00632             result.edgePoint = edgePoints[i];
00633             result.edgePointH.x = HX;
00634             extremeHX = HX;
00635           }
00636         }
00637       }
00638 
00639       // Store edge point as well in horizon-aligned coordinates
00640       result.edgePointH.y = horizonInfo.invRotation.c[0].y * result.edgePoint.x
00641                 + horizonInfo.invRotation.c[1].y * result.edgePoint.y;
00642     }
00643   }
00644 
00645 
00646   // Possible improvements
00647   // - straightness check with added scans to the edge -> need to store more edge point so
00648   //   that scanlines with weak edges aren't scanned again & no scans towards the image 
00649   //   boundary
00650 }
00651 
00652 GT2005GoalRecognizer::EdgeType GT2005GoalRecognizer::detectEdgeTwice(GT2005GoalRecognizer::EdgeDetector* detector,
00653                                                                      Vector2<int>& focus,
00654                                                                      BresenhamLineScan& scanLine,
00655                                                                      const Vector2<int>& scanLineOffset,
00656                                                                      int maxScanLength,
00657                                                                      EdgePointList& edgePoints)
00658 {
00659   // Start point for the first scanline (scanning from focus is done second)
00660   Vector2<int> focus0 = focus + scanLineOffset;
00661 
00662   int distance[2];
00663   bool onImageBorder[2];// = {false, false};
00664 
00665   // Scan towards the edge
00666   GT2005GoalRecognizer::EdgeType type =
00667     detectEdge(detector[0], focus0, scanLine, maxScanLength, distance[0]);
00668   if (type == none)
00669   {
00670     // Signalize that there is an large goal-coloured area where the edge was expected
00671     return none;
00672   }
00673   else
00674   {
00675     // Store the detected edge
00676     edgePoints.add(focus0, type);
00677     onImageBorder[0] = type == imageBorder;
00678   }
00679   
00680   // Scan towards the edge again
00681   scanLine.init();
00682   Vector2<int> focus1 = focus;
00683   type = detectEdge(detector[1], focus1, scanLine, maxScanLength, distance[1]);
00684   if (type == none)
00685   {
00686     // Signalize that there is an large goal-coloured area where the edge was expected
00687     return none;
00688   }
00689   else
00690   {
00691     // Store the detected edge
00692     edgePoints.add(focus1, type);
00693     onImageBorder[1] = type == imageBorder;
00694   }
00695 
00696   //-- Use the point closer the start point as new focus
00697   if (onImageBorder[0])
00698   {
00699     if (onImageBorder[1])
00700     {
00701       // Return the lastPointInClass nearer to the start point as new focus
00702       if (distance[0]<distance[1])
00703         focus = focus0;
00704       else
00705         focus = focus1;
00706 
00707       // Both edges on the image border
00708       return imageBorder;
00709     }
00710     else
00711     {
00712       // Return edge point in focus
00713       focus = focus1;
00714       return edge;
00715     }
00716   }
00717   else
00718   {
00719     if (onImageBorder[1])
00720     {
00721       // Return edge point in focus
00722       focus = focus0;
00723       return edge;
00724     }
00725     else
00726     {
00727       // Use the edge point nearer to the start point as new focus
00728       if (distance[0]<distance[1])
00729         focus = focus0;
00730       else
00731         focus = focus1;
00732       
00733       // Edge detected
00734       return edge;
00735     }
00736   }
00737 }
00738 
00739 GT2005GoalRecognizer::ImageBorderSide GT2005GoalRecognizer::scanOnImageBorder(
00740                                            GT2005GoalRecognizer::EdgeDetector* detector,
00741                                            Vector2<int>& focus,
00742                                            const Vector2<double>& targetDirection,
00743                                            int maxBorderDistance,
00744                                            bool* scannedSides)
00745 {
00746   // Exclude the possibility, that the focus is considered near to more than two adjacent
00747   // borders
00748   ASSERT(maxBorderDistance < horizonInfo.maxImageCoordinates.x/2 &&
00749          maxBorderDistance < horizonInfo.maxImageCoordinates.y/2);
00750 
00751 
00752   // Check to which borders the focus is close
00753   // If the focus is close to two borders, i.e. in a corner of the image, the following 
00754   // code also computes two vectors pointing away from that corner. These can be used to 
00755   // decide, whether a border scan makes any sense.
00756   bool nearBorder[4] = {false, false, false, false};
00757   int nearCount = 0;
00758   bool scannedBefore[2];
00759   Vector2<double> scanDirection[2];
00760   double awayFromEdgeFactor = 1.0;
00761   
00762   if (focus.y <= maxBorderDistance)
00763   {
00764     // Top
00765     nearBorder[topBorder] = true;
00766     scannedBefore[nearCount] = scannedSides[topBorder];
00767     scanDirection[nearCount++].x = awayFromEdgeFactor;
00768     awayFromEdgeFactor *= -1.0;
00769 
00770   }
00771   if (focus.x <= maxBorderDistance)
00772   {
00773     // Left
00774     nearBorder[leftBorder] = true;
00775     scannedBefore[nearCount] = scannedSides[leftBorder];
00776     scanDirection[nearCount++].y = -awayFromEdgeFactor;
00777     awayFromEdgeFactor *= -1.0;
00778   }
00779   if (horizonInfo.maxImageCoordinates.y-focus.y <= maxBorderDistance)
00780   {
00781     // Bottom
00782     nearBorder[bottomBorder] = true;
00783     scannedBefore[nearCount] = scannedSides[bottomBorder];
00784     scanDirection[nearCount++].x = -awayFromEdgeFactor;
00785     awayFromEdgeFactor *= -1.0;
00786   }
00787   if (horizonInfo.maxImageCoordinates.x-focus.x <= maxBorderDistance)
00788   {
00789     // Right
00790     nearBorder[rightBorder] = true;
00791     scannedBefore[nearCount] = scannedSides[rightBorder];
00792     scanDirection[nearCount++].y = awayFromEdgeFactor;
00793     //awayFromEdgeFactor *= -1.0;
00794   }
00795 
00796   // If the focus is near the top right corner, the vectors are pointing towards the edge,
00797   // so reverse them
00798   if (nearBorder[topBorder] && nearBorder[rightBorder])
00799   {
00800     scanDirection[0] = -scanDirection[0];
00801     scanDirection[1] = -scanDirection[1];
00802   }
00803 
00804   // Compute the scan direction
00805   switch (nearCount)
00806   {
00807   case 0:
00808     // Focus is not close to any border
00809     return noBorder;
00810   
00811   case 1:
00812   {
00813     // Focus is close to one border, so allow scanning along this border in both 
00814     // directions, if the angle with the target direction is less than 60 degrees and if
00815     // it hasn't been scanned before
00816     double dotProduct = targetDirection*scanDirection[0];
00817     if (scannedBefore[0] || fabs(dotProduct) < 0.5)
00818       return noBorder;
00819 
00820     // Reverse the scan direction if necessary
00821     scanDirection[0] *= sgn(dotProduct);
00822     break;
00823   }
00824   case 2:
00825   default:
00826     // Check if the target direction has an angle less than 60 degrees with one of the 
00827     // scan directions (that haven't been scanned before). Here the directions may not be 
00828     // reversed, because then scanning would run right into the corner
00829     if (!scannedBefore[0] && targetDirection*scanDirection[0] > 0.5)
00830     {
00831       // Use this direction (scanDirection[0] will be used below, so nothing to do)
00832     }
00833     else if (!scannedBefore[1] && targetDirection*scanDirection[1] > 0.5)
00834     {
00835       scanDirection[0] = scanDirection[1];
00836     }
00837     else
00838     {
00839       // None of the potential directions meet the requirements
00840       return noBorder;
00841     }
00842   }
00843   ASSERT(scanDirection[0].x*scanDirection[0].y==0.0);
00844 
00845   // Compute the start points for scanning
00846   Vector2<int> newFocus = focus;
00847   Vector2<int> scanOffset(0, 0);
00848   GT2005GoalRecognizer::ImageBorderSide scanSide;
00849 
00850   if (nearBorder[topBorder] && scanDirection[0].y==0.0)
00851   {
00852     // Top
00853     newFocus.y = 0;
00854     scanOffset.y = 1;
00855     scanSide = topBorder;
00856   }
00857   else if (nearBorder[leftBorder] && scanDirection[0].x==0.0)
00858   {
00859     // Left
00860     newFocus.x = 0;
00861     scanOffset.x = 1;
00862     scanSide = leftBorder;
00863   }
00864   else if (nearBorder[bottomBorder] && scanDirection[0].y==0.0)
00865   {
00866     // Bottom
00867     newFocus.y = horizonInfo.maxImageCoordinates.y;
00868     scanOffset.y = -1;
00869     scanSide = bottomBorder;
00870   }
00871   else
00872   {
00873     // Right
00874     newFocus.x = horizonInfo.maxImageCoordinates.x;
00875     scanOffset.x = -1;
00876     scanSide = rightBorder;
00877   }
00878 
00879   /*DOT(imageProcessor_flagsAndGoals, newFocus.x, newFocus.y, Drawings::Color::orange, Drawings::Color::orange);
00880   ARROW(imageProcessor_flagsAndGoals, newFocus.x, newFocus.y, newFocus.x+scanDirection[0].x*10, newFocus.y+scanDirection[0].y*10,   1, Drawings::PenStyle::ps_solid, Drawings::Color::orange);
00881   */
00882 
00883   // Scan along edge
00884   BresenhamLineScan scanLine(scanDirection[0]);
00885   int scannedCount;
00886   scanAlongLine(detector, newFocus, scanLine, scanOffset, 1024, scannedCount);
00887   
00888   // Check if any scanning was done at all
00889   if (scannedCount < 0)
00890   {
00891     // Nothing scanned
00892     return noBorder;
00893   }
00894   else
00895   {
00896     // Don't scan this side again
00897     scannedSides[scanSide] = true;
00898 
00899     // Return the new focus and on which side we did the scanning
00900     LINE(imageProcessor_flagsAndGoals, focus.x, focus.y, newFocus.x, newFocus.y, 1, Drawings::ps_solid, Drawings::orange);
00901     DOT(imageProcessor_flagsAndGoals, focus.x, focus.y, Drawings::white, Drawings::white);
00902     DOT(imageProcessor_flagsAndGoals, newFocus.x, newFocus.y, Drawings::orange, Drawings::orange);
00903     focus = newFocus;
00904         return scanSide;
00905   }
00906 }
00907 
00908 
00909 
00910 void GT2005GoalRecognizer::scanCrossBar(GT2005GoalRecognizer::EdgeDetector* detector,
00911                                         Vector2<int>& focus,
00912                                         GT2005GoalRecognizer::GoalHypothesis& result,
00913                                         bool* imageBorderScanned)
00914 {
00915   DOT(imageProcessor_flagsAndGoals, focus.x, focus.y, Drawings::orange, Drawings::orange);  
00916   
00917   // Set to true if at least one scan towards the edge does not end on the image border
00918   result.crossBarInImage = false;
00919 
00920   // Check if start point is on the image border
00921   if (nearImageBorder(focus, 3))
00922   {
00923     // Scan along the edge
00924     scanOnImageBorder(detector, focus, horizonInfo.horizon.direction, 3, imageBorderScanned);
00925   }
00926   bool borderScan = false;
00927   bool nonBorderScanDone = false;
00928   
00929   DOT(imageProcessor_flagsAndGoals, focus.x, focus.y, pink, pink);
00930 
00931 
00932   // Bresenham lines for scans towards the edge
00933   BresenhamLineScan upwardsScan(-horizonInfo.vertLine.direction);
00934 
00935   //
00936   bool calculateScanLine = false;
00937   
00938   // Last edge point (for extrapolating the optimal slope)
00939   Vector2<int> lastEdgePoint = focus;
00940 
00941 
00942   //-- Start with scanning exactly horizontally 
00943   Vector2<double> scanDirection = horizonInfo.horizon.direction;
00944   BresenhamLineScan horizontalScan(scanDirection);
00945   Vector2<int> horizontalScanOffset(static_cast<int>(floor(horizonInfo.vertLine.direction.x+0.5)),
00946                                     static_cast<int>(floor(horizonInfo.vertLine.direction.y+0.5)));
00947   focus += horizontalScanOffset;
00948   detector[0].clear(focus, true);
00949   detector[1].clear(focus, true);
00950   int scanned;
00951 
00952   switch (scanAlongLine(detector, focus, horizontalScan, horizontalScanOffset, 12, scanned))
00953   {
00954   case GT2005GoalRecognizer::imageBorder:
00955     // Scan along the border
00956     borderScan = true;
00957 
00958   case GT2005GoalRecognizer::edge:
00959   case GT2005GoalRecognizer::deviationWithoutEdge:
00960     if (scanned < 6)
00961     {
00962       // Scan again with a lower starting point
00963       focus = lastEdgePoint + Vector2<int>(static_cast<int>(floor(horizonInfo.vertLine.direction.x*5 + horizonInfo.horizon.direction.x*2 + 0.5)),
00964                                            static_cast<int>(floor(horizonInfo.vertLine.direction.y*5 + horizonInfo.horizon.direction.y*2 + 0.5)));
00965       if (scanAlongLine(detector, focus, horizontalScan, horizontalScanOffset, 12, scanned) == imageBorder)
00966       {
00967         // Image border reached
00968         borderScan = true;
00969       }
00970     }
00971     
00972     // The horiziontal scanline was not ok, so recalculate it
00973     calculateScanLine = true;
00974   }
00975 
00976 
00977   //-- Sense along the crossbar
00978   for (int i = 0; !borderScan && i<25; ++i)
00979   {
00980     DEBUG_RESPONSE ("gt05-ip:GoalRecognizer:assert loop termination",
00981       ASSERT(i<23);   // This loop should not be infinite without the limit i<25
00982     );
00983 
00984     // The horizontal scan did not reach the border, hence there is at least some of the 
00985     // crossbar visible
00986     nonBorderScanDone = true;
00987 
00988     // Get in touch with the edge again
00989     detector[1].clear();
00990     int scanTillEdgeLength;
00991     if (detectEdge(detector[1], focus, upwardsScan, 10, scanTillEdgeLength) == imageBorder)
00992     {
00993       // Check, wether the focus is near the edge
00994       if (borderScan = nearImageBorder(focus, 1)) // Single = is correct!
00995         break;
00996 
00997       // If the focus is not near the image border, we assume that there was no edge
00998       // even if there might have been one. But if there actually is one, the subsequent
00999       // step will run into it anyway.
01000     }
01001     else
01002     {
01003       // We found an edge at the top of the goal, so at least parts of the crossbar is visible
01004       result.crossBarInImage = true;
01005     }
01006 
01007     // Recalculate scanning direction (if necessary)
01008     if (calculateScanLine || scanTillEdgeLength>2)
01009     {
01010       // If the goal is seen from nearby, the crossbar may have different slopes and 
01011       // may appear curved to the left (in sanning direction from the left to the right 
01012       // goalpost). Therefore the following calculates the slope from the first two 
01013       // edge points (first iteration) or recalculates it, if the slope changed. 
01014       horizontalScan = BresenhamLineScan(lastEdgePoint, focus);
01015       scanDirection.x = static_cast<double>(focus.x-lastEdgePoint.x);
01016       scanDirection.y = static_cast<double>(focus.y-lastEdgePoint.y);
01017       scanDirection.normalize();
01018       Vector2<int> horizontalScanOffset(static_cast<int>(floor(-scanDirection.y+0.5)),
01019                                         static_cast<int>(floor( scanDirection.x+0.5)));
01020 
01021       // Don't allow scanDirections with angles greater than 60 degrees to the horizon,
01022       // because they are unlikely and may lead to infinite loops (since the direction
01023       // for scans towards the edge is not adapted)
01024       if (horizonInfo.horizon.direction*scanDirection < 0.5)
01025         break;
01026     }
01027 
01028     // Scan to the right
01029     focus += horizontalScanOffset;
01030     detector[0].clear();
01031     detector[1].clear(); // WErte??
01032     switch (scanAlongLine(detector, focus, horizontalScan, horizontalScanOffset, 12, scanned))
01033     {
01034     case GT2005GoalRecognizer::imageBorder:
01035       // Cross bar is outside the image, so quit the loop
01036       borderScan = true;
01037       break;
01038 
01039     case GT2005GoalRecognizer::edge:
01040     case GT2005GoalRecognizer::deviationWithoutEdge:
01041       // The cross bar should appear straight or like a convex curve (with the sides of
01042       // the goal; often if the dog is close to the goal). With the intitial scan 
01043       // direction calculation and the recalculations, the scan should not run into the
01044       // top edge at this point. Hence assume the right goalpost.
01045 
01046       // ScanAlongLine is keen to get through and returns the furthest point it could
01047       // consider to be inside the goal. However here this might just have outrun the 
01048       // right goalpost, so we set the focus a bit back. The folowing goalpost analysis
01049       // begins with a scan towards the edge anyway.
01050       focus = focus - Vector2<int>(static_cast<int>(scanDirection.x*4),
01051                                    static_cast<int>(scanDirection.y*4));
01052       result.crossBarEndpoint = focus;
01053       result.crossBarEndpointH = horizonInfo.toHorizonAligned(focus);
01054       return;
01055     }
01056   }
01057 
01058   // The current point is potentially useful to determine the height of the right goalpost
01059   // (if its top endpoint is outside the image). Additionally it is used to filter false
01060   // goal hypothesises
01061   result.crossBarEndpoint = focus;
01062   result.crossBarEndpointH = horizonInfo.toHorizonAligned(focus);
01063 
01064   // Only scan along the edge again if there was any of the cross bar visible (and hence we
01065   // haven't just done that)
01066   if (nonBorderScanDone && borderScan)
01067     scanOnImageBorder(detector, focus, horizonInfo.horizon.direction, 3, imageBorderScanned);
01068 }
01069 
01070 GT2005GoalRecognizer::EdgeType GT2005GoalRecognizer::detectEdge(GT2005GoalRecognizer::EdgeDetector& detector, 
01071                                                                 Vector2<int>& focus, 
01072                                                                 BresenhamLineScan& scanLine,
01073                                                                 int maxScanLength,
01074                                                                 int& pixelsUntilEdge)
01075 {
01076   // Reset Bresenham scanline
01077   scanLine.init();
01078 
01079   // Feed detector with points on the Bresenham scanLine
01080   int i;
01081   for (i = 0; i<maxScanLength && focus.x>=0 && focus.y>=0 && focus.x<=horizonInfo.maxImageCoordinates.x && focus.y<=horizonInfo.maxImageCoordinates.y; ++i)
01082   {
01083     // Colour and colour class of focused pixel
01084     unsigned char color[3] = 
01085     {
01086       colorCorrector.correct(focus.x, focus.y, 0, interfaces.image.image[focus.y][0][focus.x]),
01087       colorCorrector.correct(focus.x, focus.y, 1, interfaces.image.image[focus.y][1][focus.x]),
01088       colorCorrector.correct(focus.x, focus.y, 2, interfaces.image.image[focus.y][2][focus.x])
01089     };
01090     colorClass colorCls = COLOR_CLASS(color[0], color[1], color[2], interfaces.colorTable);
01091 
01092     switch (detector.inspectPixel(focus, i, colorCls, Vector3<int>(static_cast<int>(color[0]), static_cast<int>(color[1]), static_cast<int>(color[2]))))
01093     {
01094     case edge:
01095       DOT(imageProcessor_flagsAndGoals, detector.getLastPointInClass().x, detector.getLastPointInClass().y, Drawings::black, Drawings::green);
01096       STUPID_DEBUG_IMAGE_SET_PIXEL(goalColor, focus.x, focus.y, GREEN);
01097       N_STUPID_DEBUG_IMAGE_SET_PIXEL(goalColor, focus.x, focus.y, green);
01098 
01099       // Return edge via focus argument
01100       detector.getLastPointInClass(focus, pixelsUntilEdge);
01101       return edge;
01102       break;
01103 
01104     case deviationWithoutEdge:
01105       DOT(imageProcessor_flagsAndGoals, detector.getLastPointInClass().x, detector.getLastPointInClass().y, Drawings::black, Drawings::white);
01106       STUPID_DEBUG_IMAGE_SET_PIXEL(goalColor, focus.x, focus.y, RED);
01107       N_STUPID_DEBUG_IMAGE_SET_PIXEL(goalColor, focus.x, focus.y, yellowOrange);
01108 
01109       // Return last point in class via focus argument
01110       detector.getLastPointInClass(focus, pixelsUntilEdge);
01111       return deviationWithoutEdge;
01112       break;
01113 
01114     default:
01115       STUPID_DEBUG_IMAGE_SET_PIXEL(goalColor, focus.x, focus.y, BLACK);
01116       N_STUPID_DEBUG_IMAGE_SET_PIXEL(goalColor, focus.x, focus.y, colorCls==goalColor? goalColor : ( goalColor==skyblue?blue:orange ));
01117     }
01118 
01119     // Get the next point on the line
01120     scanLine.getNext(focus);
01121   }
01122   
01123   if (i==maxScanLength)
01124   {
01125     // Signalize no edge found withing scan length limit
01126     DOT(imageProcessor_flagsAndGoals, detector.getLastPointInClass().x, detector.getLastPointInClass().y, Drawings::black, Drawings::red);
01127     return none;
01128   }
01129   else
01130   {
01131     // Return last point in class via focus argument
01132     detector.getLastPointInClass(focus, pixelsUntilEdge);
01133     return imageBorder;
01134   }
01135 }
01136 
01137 GT2005GoalRecognizer::EdgeType GT2005GoalRecognizer::scanAlongLine(
01138                                     GT2005GoalRecognizer::EdgeDetector* detector,
01139                                     Vector2<int>& focus,
01140                                     BresenhamLineScan& scanLine,
01141                                     const Vector2<int>& scanLineOffset,
01142                                     int maximumScan,
01143                                     int& pixelsScanned)
01144 {
01145   // Scanning is done on two scanlines at once and only if both EdgeDetectors report 
01146   // edges/deviations stop scanning
01147   bool edge[2];
01148   bool withinBounds[2];
01149 
01150   // Temporary variables
01151   unsigned char color[3];
01152   colorClass colorCls;
01153   Vector2<int> focus2;
01154 
01155   // Feed the detectors with points on the Bresenham scanLine
01156   for (int i = 0; i <= maximumScan; ++i)
01157   {
01158     // First scanline
01159     if (withinBounds[0] = focus.x>=0 && focus.y>=0 && focus.x<=horizonInfo.maxImageCoordinates.x && focus.y<=horizonInfo.maxImageCoordinates.y)
01160     {
01161       // Colour and colour class of focused pixel
01162       color[0] = colorCorrector.correct(focus.x, focus.y, 0, interfaces.image.image[focus.y][0][focus.x]);
01163       color[1] = colorCorrector.correct(focus.x, focus.y, 1, interfaces.image.image[focus.y][1][focus.x]);
01164       color[2] = colorCorrector.correct(focus.x, focus.y, 2, interfaces.image.image[focus.y][2][focus.x]);
01165       colorCls = COLOR_CLASS(color[0], color[1], color[2], interfaces.colorTable);
01166 
01167       // Feed EdgeDetector
01168       edge[0] = none != detector[0].inspectPixel(focus, i, colorCls, Vector3<int>(static_cast<int>(color[0]), static_cast<int>(color[1]), static_cast<int>(color[2])));
01169 
01170       if (edge[0])
01171       {
01172         STUPID_DEBUG_IMAGE_SET_PIXEL(goalColor, focus.x, focus.y, WHITE);
01173         N_STUPID_DEBUG_IMAGE_SET_PIXEL(goalColor, focus.x, focus.y, black);
01174       }
01175       else
01176       {
01177         STUPID_DEBUG_IMAGE_SET_PIXEL(goalColor, focus.x, focus.y, BLUE);
01178         N_STUPID_DEBUG_IMAGE_SET_PIXEL(goalColor, focus.x, focus.y, colorCls==goalColor? goalColor : ( goalColor==skyblue?blue:orange ));
01179       }
01180     }
01181     
01182     // Second scanline
01183     focus2 = focus + scanLineOffset;
01184     if (withinBounds[1] = focus2.x>=0 && focus2.y>=0 && focus2.x<=horizonInfo.maxImageCoordinates.x && focus2.y<=horizonInfo.maxImageCoordinates.y)
01185     {
01186       // Colour and colour class of focused pixel
01187       color[0] = colorCorrector.correct(focus2.x, focus2.y, 0, interfaces.image.image[focus2.y][0][focus2.x]);
01188       color[1] = colorCorrector.correct(focus2.x, focus2.y, 1, interfaces.image.image[focus2.y][1][focus2.x]);
01189       color[2] = colorCorrector.correct(focus2.x, focus2.y, 2, interfaces.image.image[focus2.y][2][focus2.x]);
01190       colorCls = COLOR_CLASS(color[0], color[1], color[2], interfaces.colorTable);
01191 
01192       // Feed EdgeDetector
01193       edge[1] = none != detector[1].inspectPixel(focus2, i, colorCls, Vector3<int>(static_cast<int>(color[0]), static_cast<int>(color[1]), static_cast<int>(color[2])));
01194 
01195       if (edge[1])
01196       {
01197         STUPID_DEBUG_IMAGE_SET_PIXEL(goalColor, focus2.x, focus2.y, WHITE);
01198         N_STUPID_DEBUG_IMAGE_SET_PIXEL(goalColor, focus2.x, focus2.y, black);
01199       }
01200       else
01201       {
01202         STUPID_DEBUG_IMAGE_SET_PIXEL(goalColor, focus2.x, focus2.y, BLUE);
01203         N_STUPID_DEBUG_IMAGE_SET_PIXEL(goalColor, focus2.x, focus2.y, colorCls==goalColor? goalColor : ( goalColor==skyblue?blue:orange ));
01204       }
01205     }
01206 
01207     // Merge results of both scanlines
01208     if (withinBounds[0] && withinBounds[1])
01209     {
01210       if (edge[0] && edge[1])
01211       {
01212         // Return point before edge/deviation via focus argument
01213         int scannedFurther = detector[0].getLastIndexInClass()>detector[1].getLastIndexInClass() ? 0 : 1;
01214         detector[scannedFurther].getLastPointInClass(focus, pixelsScanned);
01215 
01216         // Signalize edge/deviation
01217         // NB: This method does not distinguish between strong and weak edges!
01218         return GT2005GoalRecognizer::edge;
01219       }
01220     }
01221     else
01222     {
01223       // Return the valid point via focus argument and signalize image border
01224       int scannedFurther = detector[0].getLastIndexInClass()>detector[1].getLastIndexInClass() ? 0 : 1;
01225       detector[scannedFurther].getLastPointInClass(focus, pixelsScanned);
01226       return GT2005GoalRecognizer::imageBorder;
01227     }
01228 
01229     // Get the next point on the line
01230     scanLine.getNext(focus);
01231   }
01232 
01233   // Maximum scanning length reached. In this case there might actually be an edge at the 
01234   // focus which has not yet been detected, hence set the focus to the last point in the 
01235   // target class on the scanline that got further (same as all other return cases).
01236   int scannedFurther = detector[0].getLastIndexInClass()>detector[1].getLastIndexInClass() ? 0 : 1;
01237   detector[scannedFurther].getLastPointInClass(focus, pixelsScanned);
01238   return GT2005GoalRecognizer::none;
01239 }
01240 
01241 bool GT2005GoalRecognizer::detectGreenBelowGoalpost(Vector2<int> focus, const Vector2<double>& directionOfEdge, int goalpostHeight)
01242 {
01243   // Calculate the number of horizontal lines to be scanned
01244   int scanLineCount = 1 + max(2, goalpostHeight/16);
01245   
01246   // Bresenham line for the scans
01247   // Scanning is done against the directionOfEdge, because this is likely to be the side
01248   // of the image border. This way we can skip pixels until the focus is back inside the
01249   // image.
01250   BresenhamLineScan scanLine(-directionOfEdge);
01251   //focus.x += static_cast<int>(directionOfEdge.x * 5); // no need to round, doesn't need to be exact
01252   focus.y += static_cast<int>(directionOfEdge.y * 5);
01253 
01254   // Vertical between the line segments
01255   Vector2<int> verticalOffset(static_cast<int>(floor(horizonInfo.vertLine.direction.x*2+0.5)), 
01256                               static_cast<int>(floor(horizonInfo.vertLine.direction.y*2+0.5)));
01257 
01258   // Check short horizontal line segments for green pixels
01259   int greenCount = 0, pixelCount = 0;
01260   colorClass color;
01261   for (int i = 0; i < scanLineCount; ++i)
01262   {
01263     // Move focus downwards
01264     focus += verticalOffset;
01265 
01266     // Scan horizontally and count green pixels
01267     greenCount = pixelCount = 0;
01268     scanLine.init();
01269     Vector2<int> focus2 = focus;
01270     for (int j = 0; pixelCount < 5; ++j)
01271     {
01272       // Next scanline pixel
01273       scanLine.getNext(focus2);
01274 
01275       // Check if inside the image
01276       if (focus2.x >= 0 && focus2.y >= 0 && focus2.x <= horizonInfo.maxImageCoordinates.x && focus2.y <= horizonInfo.maxImageCoordinates.y)
01277       {
01278         ++pixelCount;
01279 
01280         // Check colour class
01281         color = CORRECTED_COLOR_CLASS(focus2.x, focus2.y, interfaces.image.image[focus2.y][0][focus2.x], interfaces.image.image[focus2.y][1][focus2.x], interfaces.image.image[focus2.y][2][focus2.x], interfaces.colorTable, colorCorrector);
01282         if (color == green)
01283         {
01284           ++greenCount;
01285           STUPID_DEBUG_IMAGE_SET_PIXEL(goalColor, focus2.x, focus2.y, YELLOW);
01286           N_STUPID_DEBUG_IMAGE_SET_PIXEL(goalColor, focus2.x, focus2.y, green);
01287         }
01288         else
01289         {
01290           STUPID_DEBUG_IMAGE_SET_PIXEL(goalColor, focus2.x, focus2.y, GRAY);
01291           N_STUPID_DEBUG_IMAGE_SET_PIXEL(goalColor, focus2.x, focus2.y, white);
01292         }
01293       }
01294       else
01295       {
01296         // Skip this scanline if the image boundary wasn't reached within 8 pixels
01297         if (j>=8)
01298           break;
01299       }
01300     }
01301 
01302     // Check at least 3 (out of the scanned 5) pixels where green
01303     if (greenCount >= 3)
01304       return true;
01305   }
01306 
01307   // None of the scanline where sufficiently green so return false
01308   return false;
01309 }
01310 
01311 void GT2005GoalRecognizer::lockArea(double x, double y, bool onGreen)
01312 {
01313   // If the goal hypothesis is stading on the green floor, don't scan below it at all
01314   if (onGreen)
01315     y = 1024.0;
01316 
01317   // Pop locks that are well included in the new one. This way, a goal that is detected 
01318   // twice including the right goalpost (happens if the striker is close to the goal and 
01319   // the right goalpost scan fails to get to the bottom) results in two locks and hence
01320   // a lock to the bottom (2nd lock is always to the bottom)
01321   while (lockAreaCount>0)
01322   {
01323     if (x >= lockAreaStack[lockAreaCount-1].x && y >= lockAreaStack[lockAreaCount-1].y+8)
01324       // Pop lock
01325       --lockAreaCount;
01326     else
01327       break;
01328   }
01329 
01330   // Add lock to the stack
01331   if (lockAreaCount == lockAreaStackSize-1)
01332   {
01333     // Make the last lock go all the way to the bottom (and don't increase the counter)
01334     lockAreaStack[lockAreaCount].x = 1024.0;
01335     lockAreaStack[lockAreaCount].y = y;
01336   }
01337   else if (lockAreaCount < lockAreaStackSize-1)
01338   {
01339     // Add to the stack the usual way
01340     lockAreaStack[lockAreaCount].x = x;
01341     lockAreaStack[lockAreaCount].y = y;
01342     ++lockAreaCount;
01343   }
01344   //else doesn't happen
01345 }
01346 
01347 void GT2005GoalRecognizer::calculateLockedPixels(const Vector2<int>& scanLineStart)
01348 {
01349   // Convert to horizon-aligned coordinates
01350   Vector2<double> startH = horizonInfo.toHorizonAligned(scanLineStart);
01351 
01352   // Check if this scanline is near a flag
01353   if (frameNumberImage == frameNumberFlags)
01354     for (int i = 0; i < flagLockCount; i++)
01355     {
01356       if (flagLock[i].isInside(startH.x))
01357       {
01358         // Don't look for goals on this scanline at all
01359         lockedPixels = 1024;
01360         return;
01361       }
01362     }
01363 
01364   // Pop area locks that are no longer relevant (because there will be no more pixel 
01365   // inspection in these areas)
01366   while (lockAreaCount>0)
01367   {
01368     if (lockAreaStack[lockAreaCount-1].x < startH.x)
01369       // Pop lock
01370       --lockAreaCount;
01371     else
01372       break;
01373   }
01374 
01375   // Compute locked pixels
01376   lockedPixels = 0;
01377   if (lockAreaCount>0)
01378   {
01379     // Calculate the number of pixels that will be inspected until the locked area is 
01380     // left. This consists of the following steps (merged to be efficient)
01381     //  - calculate the vector from the scanline start point to the point where the 
01382     //    scanline leaves the locked area (in the horizon-aligned coordinate system);
01383     //    this vector has an x-value 0
01384     //  - if it points upwards, don't lock any pixels
01385     //  - transform it to image coordinates
01386     //  - calculate, how many pixels will be generated by BresenhamLineScan for this 
01387     //    line segment
01388     double segmentLength = lockAreaStack[lockAreaCount-1].y - startH.y;
01389     if (segmentLength <= 0)
01390       return;
01391     Vector2<int> segment = horizonInfo.toImageCoordinates(0.0, segmentLength);
01392     lockedPixels = max(abs(segment.x), abs(segment.y));
01393   }
01394 }
01395 
01396 void GT2005GoalRecognizer::recalculateLockedPixels(const Vector2<int>& currentPoint)
01397 {
01398   // Removing lock areas and check for flag locks not necessary, because we are still
01399   // on the same scanline
01400 
01401   // Compute locked pixels on the current scanline
01402   lockedPixels = 0;
01403   if (lockAreaCount>0)
01404   {
01405     double segmentLength = lockAreaStack[lockAreaCount-1].y - horizonInfo.horizonAlignedYOf(currentPoint);
01406     if (segmentLength <= 0)
01407       return;
01408     Vector2<int> segment = horizonInfo.toImageCoordinates(0.0, segmentLength);
01409     lockedPixels = max(abs(segment.x), abs(segment.y));
01410   }
01411 }
01412 
01413 void GT2005GoalRecognizer::mergeFragments(bool* deleted)
01414 {
01415   //TODO: make efficient (with pointers?)
01416   for (int i=0; i<maxHypothesises; ++i)
01417     deleted[i] = false;
01418 
01419   // Loop through all pairs of goals
01420   for (int i=0; i<hypothesisCount; ++i)
01421     if (!deleted[i])
01422     {
01423       for (int j=i+1; j<hypothesisCount; ++j)
01424         if (!deleted[j])
01425         {
01426           // Preconditions for a merge
01427           int volumeLeft  = hypothesis[i].height*hypothesis[i].width;
01428           int volumeRight = hypothesis[j].height*hypothesis[j].width;
01429           const int& heightLeft  = hypothesis[i].height;
01430           const int& heightRight = hypothesis[j].height;
01431           int verticalOffset = abs(static_cast<int>(hypothesis[j].goalpost[0].topPointH.y
01432                                                   - hypothesis[i].goalpost[1].topPointH.y));
01433           int horizontalGap = static_cast<int>(hypothesis[j].goalpost[0].edgePointH.x
01434                                              - hypothesis[i].goalpost[1].edgePointH.x);
01435 
01436           // Precondition: similar size
01437           bool sizeSimilar = volumeLeft*3>volumeRight && volumeRight*3>volumeLeft
01438                           || heightLeft*3>heightRight*2 && heightRight*3>heightLeft*2;
01439 
01440           if (sizeSimilar)
01441           {
01442             // Check the vertical offset between the corners
01443             bool verticalDifferenceAcceptable = verticalOffset < horizontalGap
01444                                              || verticalOffset < min(hypothesis[i].height, hypothesis[j].height);
01445 
01446             // Check that the horizontal gap is less than a third of the new
01447             // width. 
01448             int newWidth = static_cast<int>(hypothesis[j].goalpost[1].edgePointH.x
01449                                           - hypothesis[i].goalpost[0].edgePointH.x);
01450             
01451             // Reduce the gap if the the inner goalposts were not found at all.
01452             if (hypothesis[j].goalpost[0].nonExistant)
01453               horizontalGap -= 8;
01454             if (hypothesis[i].goalpost[1].nonExistant)
01455               horizontalGap -= 8;
01456   
01457             if (verticalDifferenceAcceptable
01458               && horizontalGap*3<newWidth)
01459             {
01460               // Draw the fragments
01461               HORIZON_ALIGNED_RECTANGLE(horizonInfo, "GoalRecognizer:goal fragments:merged",
01462                 hypothesis[i].goalpost[0].edgePointH.x, min(min(hypothesis[i].goalpost[0].topPointH.y, hypothesis[i].goalpost[1].topPointH.y), hypothesis[i].crossBarEndpointH.y),
01463                 hypothesis[i].goalpost[1].edgePointH.x, max(hypothesis[i].goalpost[0].bottomPointH.y, hypothesis[i].goalpost[1].bottomPointH.y),
01464                 1, Drawings::ps_solid, Drawings::orange, false);
01465               HORIZON_ALIGNED_RECTANGLE(horizonInfo, "GoalRecognizer:goal fragments:merged",
01466                 hypothesis[j].goalpost[0].edgePointH.x, min(min(hypothesis[j].goalpost[0].topPointH.y, hypothesis[j].goalpost[1].topPointH.y), hypothesis[j].crossBarEndpointH.y),
01467                 hypothesis[j].goalpost[1].edgePointH.x, max(hypothesis[j].goalpost[0].bottomPointH.y, hypothesis[j].goalpost[1].bottomPointH.y),
01468                 1, Drawings::ps_solid, Drawings::orange, false);
01469 
01470               // Merge the two hypothesises
01471               hypothesis[i].width = newWidth;
01472               hypothesis[i].height = max(hypothesis[i].height, hypothesis[j].height);
01473               hypothesis[i].onGreen = hypothesis[i].onGreen || hypothesis[j].onGreen;
01474               hypothesis[i].goalpost[1] = hypothesis[j].goalpost[1];
01475               deleted[j] = true;
01476             }
01477           }
01478         }
01479     }
01480 }
01481 
01482 
01483 void GT2005GoalRecognizer::interpretResults(GT2005GoalRecognizer::EdgeDetector* detector, bool* deleted)
01484 {
01485   // Variable for selected hypothesis
01486   int selected = -1;
01487 
01488   // Variables for interpretation result
01489   bool visibleGoalpost[2];
01490   GT2005GoalRecognizer::FreeSide freeSideOfGoal = noFreeSide;
01491   Vector2<int> endpointOfFreePart;
01492   bool freePartOnImageBorder[2] = {false, false};
01493 
01494 
01495   for (int i = 0; i<hypothesisCount; i++)
01496   if (!deleted[i])
01497   {
01498     MODIFY("GoalRecognizer:Hypothesis", hypothesis[i]);
01499 
01500     // Interpret goalposts
01501     bool possibleGoalPart[2];
01502     bool strongGoalpost[2];
01503 
01504     for (int side=0; side<2; ++side)
01505     {
01506       possibleGoalPart[side] = hypothesis[i].goalpost[side].nonExistant == false
01507                             && hypothesis[i].goalpost[side].height > minGoalpostHeight
01508                             && hypothesis[i].goalpost[side].height*5 > hypothesis[i].width 
01509 //                            && (hypothesis[i].goalpost[side].height*5 > hypothesis[i].width || !hypothesis[i].crossBarInImage)  // Less strict alternative to above criterion  // TODO: use individual goalpost top visibility
01510                             && hypothesis[i].goalpost[side].bottomPointH.y - horizonInfo.distanceTopLeftCorner > -8
01511                             && hypothesis[i].goalpost[side].weakEdgeCount <= hypothesis[i].goalpost[side].strongEdgeCount/8 + 1;
01512       visibleGoalpost[side] = possibleGoalPart[side] 
01513                             && hypothesis[i].goalpost[side].visibleHeight > 0
01514                             && hypothesis[i].goalpost[side].height*2 > hypothesis[i].height;
01515                             // hypothesis[i].goalpost[side].visibleHeight*2 > height
01516                             // hypothesis[i].goalpost[side].curvature < ??;
01517       strongGoalpost[side] = visibleGoalpost[side]
01518                             && hypothesis[i].goalpost[side].onGreen;
01519     }
01520 
01521     // Interpret goal
01522     bool minimalWidth = hypothesis[i].width > 10;
01523     bool reasonableWidth = hypothesis[i].width > 20;
01524     bool largeWidth = hypothesis[i].width > 100;
01525     bool reasonableHeight = hypothesis[i].height > 30;
01526     bool largeHeight = hypothesis[i].height > 50;
01527     //OUTPUT(idText,text, "goal interpretation " << (strongGoalpost[0]?(largeHeight?"I":"|"):(hypothesis[i].goalpost[0].nonExistant?"x":"'")) << (minimalWidth?"-":"") << (reasonableWidth?"-":"") << (largeWidth?"-":"") << (strongGoalpost[1]?(largeHeight?"I":"|"):(hypothesis[i].goalpost[1].nonExistant?"x":"'")));
01528 
01529     // Minimal prerequisites for a goal
01530     if ((  (strongGoalpost[0] || strongGoalpost[1])
01531            && (reasonableWidth || (reasonableHeight && minimalWidth)))
01532         || (possibleGoalPart[0] || possibleGoalPart[1])
01533            && largeWidth && largeHeight)
01534     {
01535       // Select this blob
01536       selected = i;
01537 
01538       // Determine free part of goal
01539       int freeWidth;
01540       freeSideOfGoal = detectFreePartOfGoal(detector, hypothesis[selected], freeWidth, endpointOfFreePart, freePartOnImageBorder);
01541 
01542       DEBUG_RESPONSE("gt05-ip:GoalRecognizer:print if free part on image border",
01543         OUTPUT(idText, text, "Free side: " << (freeSideOfGoal==bothSides ? "both" : (freeSideOfGoal==leftSide ? "left" : "right"))
01544                              << ", on border: " << (freePartOnImageBorder[0] ? "left " : "") << (freePartOnImageBorder[1] ? "right " : ""));
01545       );
01546 
01547       // Check if free part of goal is reasonable
01548       if (visibleGoalpost[0] && visibleGoalpost[1])
01549       {
01550         // Free width must be at least a third of the goal width
01551         if (freeWidth*3 < hypothesis[i].width)
01552           freeSideOfGoal = noFreeSide;
01553       }
01554       else
01555       {
01556         // Free width must be at least half of the height
01557         if (freeWidth*2 < hypothesis[i].height)
01558           freeSideOfGoal = noFreeSide;
01559       }
01560 
01561       break;
01562     }
01563   }
01564 
01565   // Draw
01566   HORIZON_ALIGNED_RECTANGLE(horizonInfo, "GoalRecognizer:goal fragments:result",
01567     hypothesis[selected].goalpost[0].edgePointH.x, min(min(hypothesis[selected].goalpost[0].topPointH.y, hypothesis[selected].goalpost[1].topPointH.y), hypothesis[selected].crossBarEndpointH.y),
01568     hypothesis[selected].goalpost[1].edgePointH.x, max(hypothesis[selected].goalpost[0].bottomPointH.y, hypothesis[selected].goalpost[1].bottomPointH.y),
01569     1, Drawings::ps_solid, Drawings::green, false);
01570 
01571   // Publish goal percept
01572   if (-1 != selected)
01573     publishResults(hypothesis[selected], visibleGoalpost, freeSideOfGoal, endpointOfFreePart, freePartOnImageBorder);
01574 }
01575 
01576 
01577 void GT2005GoalRecognizer::publishResults(const GT2005GoalRecognizer::GoalHypothesis& h,
01578                                           const bool* visibleGoalpost,
01579                                           const GT2005GoalRecognizer::FreeSide freeSide,
01580                                           const Vector2<int>& endpointOfFreePart,
01581                                           const bool* freePartOnImageBorder)
01582 {
01583   // Calculate angles to the edges of the goal
01584   Vector2<double> top, bottom, left, right;
01585 
01586   Geometry::calculateAnglesForPoint((h.goalpost[0].topPoint+h.goalpost[1].topPoint)/2, interfaces.cameraMatrix, horizonInfo.previousCameraMatrix, interfaces.image.cameraInfo, top);
01587   if (h.goalpost[0].bottomPointH.y > h.goalpost[1].bottomPointH.y)
01588     Geometry::calculateAnglesForPoint(h.goalpost[0].bottomPoint, interfaces.cameraMatrix, horizonInfo.previousCameraMatrix, interfaces.image.cameraInfo, bottom);
01589   else
01590     Geometry::calculateAnglesForPoint(h.goalpost[1].bottomPoint, interfaces.cameraMatrix, horizonInfo.previousCameraMatrix, interfaces.image.cameraInfo, bottom);
01591   Geometry::calculateAnglesForPoint(h.goalpost[0].edgePoint, interfaces.cameraMatrix, horizonInfo.previousCameraMatrix, interfaces.image.cameraInfo, left);
01592   Geometry::calculateAnglesForPoint(h.goalpost[1].edgePoint, interfaces.cameraMatrix, horizonInfo.previousCameraMatrix, interfaces.image.cameraInfo, right);
01593 
01594   ConditionalBoundary boundary;
01595   boundary.addY(top.y, true);   // TODO: determine if top points of goal were found
01596   boundary.addY(bottom.y, true);
01597   boundary.addX(left.x, !visibleGoalpost[0]);
01598   boundary.addX(right.x, !visibleGoalpost[1]);
01599 
01600   // Calculate the corners of the goal in the image
01601   double topHY = (h.goalpost[0].topPointH.y + h.goalpost[1].topPointH.y)/2;
01602   double bottomHY = max(h.goalpost[0].bottomPointH.y, h.goalpost[1].bottomPointH.y);
01603   Vector2<int> topLeftCorner = horizonInfo.toImageCoordinates(h.goalpost[0].edgePointH.x, topHY);
01604   Vector2<int> bottomLeftCorner = horizonInfo.toImageCoordinates(h.goalpost[0].edgePointH.x, bottomHY);
01605   Vector2<int> bottomRightCorner = horizonInfo.toImageCoordinates(h.goalpost[1].edgePointH.x, bottomHY);
01606   Vector2<int> topRightCorner = horizonInfo.toImageCoordinates(h.goalpost[1].edgePointH.x, topHY);
01607 
01608   // Publish goal percept
01609   interfaces.landmarksPercept.addGoal(goalColor, boundary, topLeftCorner, topRightCorner, bottomLeftCorner, bottomRightCorner, interfaces.cameraMatrix); 
01610 
01611 
01612   // Free part of goal
01613   switch (freeSide)
01614   {
01615   case bothSides:
01616     // Entire goal has been seen to be free
01617     interfaces.obstaclesPercept.setFreePartOfGoal(goalColor, left.x, right.x, 
01618         !freePartOnImageBorder[0], !freePartOnImageBorder[1],
01619         horizonInfo.toImageCoordinates(h.goalpost[0].edgePointH.x, bottomHY),
01620         horizonInfo.toImageCoordinates(h.goalpost[1].edgePointH.x, bottomHY));
01621     break;
01622 
01623   case leftSide:
01624   case rightSide:
01625     {
01626       // Scans from the goalposts towards the midle found and obstacle (presumably 
01627       // the goaly) at the point endpointOfFreePart, hence the free part of the 
01628       // goal ranges from the left/right goalpost to this point.
01629 
01630       // Calculate the angle to the endpoint at the goalie
01631       Vector2<double> otherEndpointAngle;
01632       Geometry::calculateAnglesForPoint(endpointOfFreePart, interfaces.cameraMatrix, horizonInfo.previousCameraMatrix, interfaces.image.cameraInfo, otherEndpointAngle);
01633 
01634       // Calculate the HX coordinate of the second endpoint
01635       double otherEndpointHX = horizonInfo.horizonAlignedXOf(endpointOfFreePart);
01636 
01637       // Publish free part of goal
01638       if (freeSide == leftSide)
01639         interfaces.obstaclesPercept.setFreePartOfGoal(goalColor, left.x, otherEndpointAngle.x,
01640             !freePartOnImageBorder[0], !freePartOnImageBorder[1],
01641             horizonInfo.toImageCoordinates(h.goalpost[0].edgePointH.x, bottomHY),
01642             horizonInfo.toImageCoordinates(otherEndpointHX, bottomHY));
01643       else
01644         interfaces.obstaclesPercept.setFreePartOfGoal(goalColor, otherEndpointAngle.x, right.x,
01645             !freePartOnImageBorder[0], !freePartOnImageBorder[1],
01646             horizonInfo.toImageCoordinates(otherEndpointHX, bottomHY),
01647             horizonInfo.toImageCoordinates(h.goalpost[1].edgePointH.x, bottomHY));
01648     }
01649   }
01650 }
01651 
01652 
01653 GT2005GoalRecognizer::FreeSide GT2005GoalRecognizer::detectFreePartOfGoal(GT2005GoalRecognizer::EdgeDetector* detector, 
01654                                     const GT2005GoalRecognizer::GoalHypothesis& goal, 
01655                                     int& freeWidth, 
01656                                     Vector2<int>& otherSide,
01657                                     bool* onImageBorder)
01658 {
01659   // Height of the endpoints for the scans to detect the free part of the goal. 0 is at 
01660   // the bottom, 1 at the top of the goalpost. */
01661   const double freePartScanHeight = 0.5; //0.25;
01662 
01663   // Fraction of the height that the starting point of the free part of goal scan 
01664   // endpoints are set inwards
01665   const int freePartScanOffsetFraction = 16;
01666 
01667 
01668   // If one of the goalpost has a significantly smaller height than the other one, it is 
01669   // assumed to be above the goalie, so scanning is only done from one goalpost. Otherwise
01670   // scan from both goalposts
01671   // NB: the following if treats the 2nd case first
01672   if (goal.goalpost[0].height*3 >= goal.height*2 && goal.goalpost[1].height*2 >= goal.height)
01673   {
01674     // Scanline endpoints
01675     Vector2<int> focus[2];
01676 
01677     for (int i = 0; i<2; ++i)
01678     {
01679       //-- Calculate scanline
01680       //if (goalpost[i].topPointVisible ...
01681       //else
01682 
01683       // Use points at specified height as starting points
01684       focus[i] = horizonInfo.toImageCoordinates(goal.goalpost[i].edgePointH.x - goal.goalpost[i].height/freePartScanOffsetFraction * (2*i-1), 
01685                                                 goal.goalpost[i].bottomPointH.y - static_cast<double>(goal.goalpost[i].height)*freePartScanHeight);
01686 
01687       // Clip to image border
01688       horizonInfo.clipToImageBoundary(focus[i]);
01689     }
01690 
01691     // Offset between the scanlines (scanning is done twice like usual)
01692     Vector2<int> scanLineOffset(static_cast<int>(floor(horizonInfo.vertLine.direction.x*2.0+0.5)),
01693                                 static_cast<int>(floor(horizonInfo.vertLine.direction.y*2.0+0.5)));
01694 
01695     // Scan lines
01696     BresenhamLineScan scanLine0(focus[0], focus[1]);
01697     BresenhamLineScan scanLine1(focus[1], focus[0]);
01698 
01699 
01700     //-- Look for the free part
01701     // Points where the scanning detected the end of the goal-colour. Presumably this is 
01702     // where the goalie stands.
01703     int goalieEdgeIndex[2] = {0, 0};
01704     bool scanEndOnImageBorder[2] = {false, false};
01705 
01706     // Scan left to right
01707     detector[0].clear(focus[0], false);
01708     detector[1].clear(focus[0], false);
01709     detector[0].setReferenceColor(goal.goalpost[0].color, 2);
01710     detector[1].setReferenceColor(goal.goalpost[0].color, 2);
01711     scanEndOnImageBorder[0] = (imageBorder == scanAlongLine(detector, focus[0], scanLine0, scanLineOffset, scanLine0.numberOfPixels+10, goalieEdgeIndex[0]));
01712 
01713     // See whether its worth to scan in the other direction at all
01714     if (goalieEdgeIndex[0]*8 < scanLine0.numberOfPixels*7)
01715     {
01716       // Scan right to left
01717       detector[0].clear(focus[1], false);
01718       detector[1].clear(focus[1], false);
01719       detector[0].setReferenceColor(goal.goalpost[1].color, 2);
01720       detector[1].setReferenceColor(goal.goalpost[1].color, 2);
01721       scanEndOnImageBorder[1] = (imageBorder == scanAlongLine(detector, focus[1], scanLine1, scanLineOffset, scanLine1.numberOfPixels+10-goalieEdgeIndex[0], goalieEdgeIndex[1]));
01722     }
01723     if (goalieEdgeIndex[0]+goalieEdgeIndex[1] >= scanLine1.numberOfPixels)
01724     {
01725       // Free part endpoints are on the image border if goalposts are invisible
01726       onImageBorder[0] = goal.goalpost[0].visibleHeight==0;
01727       onImageBorder[1] = goal.goalpost[1].visibleHeight==0;
01728 
01729       // The entire visible goal is free
01730       freeWidth = goal.width;
01731       return bothSides;
01732     }
01733     else if (goalieEdgeIndex[0] > goalieEdgeIndex[1])
01734     {
01735       // Left endpoint on image border if left goalpost is, right endpoint on border if 
01736       // scan from the left ended on image border
01737       onImageBorder[0] = goal.goalpost[0].visibleHeight==0;
01738       onImageBorder[1] = scanEndOnImageBorder[0];
01739 
01740       // Calculate the width of the free part and return the edge point
01741       // Tricky calculation (TODO: explain)
01742       freeWidth = goalieEdgeIndex[0]*goal.width/scanLine0.numberOfPixels;
01743       otherSide = focus[0];
01744       return leftSide;
01745     }
01746     else
01747     {
01748       // The other way round
01749       onImageBorder[0] = scanEndOnImageBorder[1];
01750       onImageBorder[1] = goal.goalpost[1].visibleHeight==0;     
01751 
01752       // Calculate the width of the free part and return the edge point
01753       freeWidth = goalieEdgeIndex[1]*goal.width/scanLine1.numberOfPixels;
01754       otherSide = focus[1];
01755       return rightSide;
01756     }
01757   }
01758   else
01759   {
01760     //-- Compute scanline
01761     // Only use the longer goalpost
01762     int longerGoalpost = goal.goalpost[0].height > goal.goalpost[1].height ? 0 : 1;
01763 
01764     // Start from the longer goalpost at the specified height (relative to goalpost height)
01765     Vector2<int> focus = horizonInfo.toImageCoordinates(goal.goalpost[longerGoalpost].edgePointH.x - goal.goalpost[longerGoalpost].height/freePartScanOffsetFraction * (2*longerGoalpost-1),
01766                                                         goal.goalpost[longerGoalpost].bottomPointH.y - static_cast<double>(goal.goalpost[longerGoalpost].height)*freePartScanHeight);
01767     
01768     // Scan line parallel to the horizon
01769     BresenhamLineScan scanLine(longerGoalpost==0 ? horizonInfo.horizon.direction
01770                                                  : -horizonInfo.horizon.direction);
01771 
01772     // Offset between the scanlines (scanning is done twice like usual)
01773     Vector2<int> scanLineOffset(static_cast<int>(floor(horizonInfo.vertLine.direction.x*2.0+0.5)),
01774                                 static_cast<int>(floor(horizonInfo.vertLine.direction.y*2.0+0.5)));
01775 
01776     
01777     //-- Scan to find free part of goal
01778     // Point where the scanning detected the end of the goal-colour. 
01779     int goalieEdgeIndex = 0;
01780 
01781     // Scan from the longer goalpost
01782     detector[0].clear(focus, false);
01783     detector[1].clear(focus, false);
01784     detector[0].setReferenceColor(goal.goalpost[longerGoalpost].color, 2);
01785     detector[1].setReferenceColor(goal.goalpost[longerGoalpost].color, 2);
01786     bool scanEndOnImageBorder = (imageBorder == scanAlongLine(detector, focus, scanLine, scanLineOffset, scanLine.numberOfPixels+10, goalieEdgeIndex));
01787 
01788     // Store which side is on image border
01789     onImageBorder[longerGoalpost] = goal.goalpost[longerGoalpost].visibleHeight==0;
01790     onImageBorder[1-longerGoalpost] = scanEndOnImageBorder;
01791     
01792     // Calculate the width of the free part and return the edge point
01793     // WARNING: This uses the non-documented property that the direction vectors are
01794     // multiplied by 1024.0 for the conversion to int!!!
01795     freeWidth = goalieEdgeIndex*1024/scanLine.numberOfPixels;
01796     otherSide = focus;
01797     return longerGoalpost==0 ? leftSide : rightSide;
01798   }
01799 }
01800 
01801 
01802 
01803 /** Function for initialization of const member in EdgeDetector. */
01804 Matrix3x3<int> GT2005GoalRecognizer::EdgeDetector::inverseCovarianceMatrix(colorClass goalColor)
01805 {
01806   switch (goalColor)
01807   {
01808   case yellow:
01809     return Matrix3x3<int>(Vector3<int>(279,-89,240),
01810                               Vector3<int>(-89,1272,-82),
01811                               Vector3<int>(240,-82,586));
01812   case skyblue:
01813     return Matrix3x3<int>(Vector3<int>(186,19,-3)*2,
01814                               Vector3<int>(19,470,-29)*2,
01815                               Vector3<int>(-3,-29,257)*2);
01816   default:
01817     return Matrix3x3<int>();
01818   }
01819 }
01820 
01821 /** Constructor. */
01822 GT2005GoalRecognizer::EdgeDetector::EdgeDetector(colorClass target)
01823   : targetColorClass(target),
01824     ignoredClassification(target==skyblue ? blue : noColor),
01825     invCovar(inverseCovarianceMatrix(target)),
01826     referenceColor(),
01827     referenceColorBase(0),
01828     deviationCounter(0),
01829     edgeCounter(0),
01830     lastPointInClass(),
01831     lastPointInClassIndex(-1),
01832     connectedToClass(false)
01833 {
01834 }
01835 
01836 GT2005GoalRecognizer::EdgeDetector::EdgeDetector(colorClass target, Vector3<int> referenceColor, int referenceColorBase)
01837   : targetColorClass(target),
01838     ignoredClassification(target==skyblue ? blue : noColor),
01839     invCovar(inverseCovarianceMatrix(target)),
01840     referenceColor(referenceColor),
01841     referenceColorBase(referenceColorBase),
01842     deviationCounter(0),
01843     edgeCounter(0),
01844     lastPointInClass(),
01845     lastPointInClassIndex(-1),
01846     connectedToClass(false)
01847 {
01848 }
01849 
01850 /** Thresholds for distances to reference colour. */
01851 const float GT2005GoalRecognizer::EdgeDetector::classThreshold = 300000;
01852 const float GT2005GoalRecognizer::EdgeDetector::edgeThreshold = 600000;
01853 

Generated on Mon Mar 20 21:59:47 2006 for GT2005 by doxygen 1.3.6