Fawkes API  Fawkes Development Version
simple.cpp
00001 
00002 /***************************************************************************
00003  *  simple.cpp - Implementation of a SimpleColorClassifier
00004  *
00005  *  Created: Thu May 16 00:00:00 2005
00006  *  Copyright  2005-2007  Tim Niemueller [www.niemueller.de]
00007  *
00008  ****************************************************************************/
00009 
00010 /*  This program is free software; you can redistribute it and/or modify
00011  *  it under the terms of the GNU General Public License as published by
00012  *  the Free Software Foundation; either version 2 of the License, or
00013  *  (at your option) any later version. A runtime exception applies to
00014  *  this software (see LICENSE.GPL_WRE file mentioned below for details).
00015  *
00016  *  This program is distributed in the hope that it will be useful,
00017  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00018  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00019  *  GNU Library General Public License for more details.
00020  *
00021  *  Read the full text in the LICENSE.GPL_WRE file in the doc directory.
00022  */
00023 
00024 #include <fvclassifiers/simple.h>
00025 
00026 #include <fvutils/color/colorspaces.h>
00027 #include <fvutils/color/yuv.h>
00028 #include <fvutils/color/color_object_map.h>
00029 
00030 #include <fvmodels/scanlines/scanlinemodel.h>
00031 #include <fvmodels/color/colormodel.h>
00032 
00033 #include <core/exceptions/software.h>
00034 
00035 #include <cstdlib>
00036 
00037 namespace firevision {
00038 #if 0 /* just to make Emacs auto-indent happy */
00039 }
00040 #endif
00041 
00042 /** @class SimpleColorClassifier <fvclassifiers/simple.h>
00043  * Simple classifier.
00044  */
00045 
00046 /** Constructor.
00047  * @param scanline_model scanline model
00048  * @param color_model color model
00049  * @param min_num_points minimum number of points in ROI to be considered
00050  * @param box_extent basic extent of a new ROI
00051  * @param upward set to true if you have an upward scanline model, this means that
00052  * points are traversed from the bottom to the top. In this case the ROIs are initially
00053  * extended towards the top instead of the bottom.
00054  * @param neighbourhood_min_match minimum number of object pixels to grow neighbourhood
00055  * @param grow_by grow region by that many pixels
00056  * @param color color to look for
00057  */
00058 SimpleColorClassifier::SimpleColorClassifier(ScanlineModel *scanline_model,
00059                                              ColorModel *color_model,
00060                                              unsigned int min_num_points,
00061                                              unsigned int box_extent,
00062                                              bool upward,
00063                                              unsigned int neighbourhood_min_match,
00064                                              unsigned int grow_by,
00065                                              color_t color)
00066   : Classifier("SimpleColorClassifier"),
00067     color(color)
00068 {
00069   if (scanline_model == NULL) {
00070     throw fawkes::NullPointerException("SimpleColorClassifier: scanline_model "
00071                                        "may not be NULL");
00072   }
00073   if (color_model == NULL) {
00074     throw fawkes::NullPointerException("SimpleColorClassifier: color_model "
00075                                        "may not be NULL");
00076   }
00077 
00078   modified = false;
00079   this->scanline_model = scanline_model;
00080   this->color_model = color_model;
00081   this->min_num_points = min_num_points;
00082   this->box_extent = box_extent;
00083   this->upward = upward;
00084   this->grow_by = grow_by;
00085   this->neighbourhood_min_match = neighbourhood_min_match;
00086 }
00087 
00088 
00089 unsigned int
00090 SimpleColorClassifier::consider_neighbourhood( unsigned int x, unsigned int y , color_t what)
00091 {
00092   color_t c;
00093   unsigned int num_what = 0;
00094 
00095   unsigned char yp = 0, up = 0, vp = 0;
00096   int start_x = -2, start_y = -2;
00097   int end_x = 2, end_y = 2;
00098 
00099   if (x < (unsigned int)abs(start_x)) {
00100     start_x = 0;
00101   }
00102   if (y < (unsigned int)abs(start_y)) {
00103     start_y = 0;
00104   }
00105 
00106   if (x > _width - end_x) {
00107     end_x = 0;
00108   }
00109   if (y == _height - end_y) {
00110     end_y = 0;
00111   }
00112 
00113   for (int dx = start_x; dx <= end_x ; dx += 2) {
00114     for (int dy = start_y; dy <= end_y; ++dy) {
00115       if ((dx == 0) && (dy == 0)) {
00116         continue;
00117       }
00118 
00119       //      cout << "x=" << x << "  dx=" << dx << "  +=" << x+dx
00120       //   << "  y=" << y << "  dy=" << dy << "  +2=" << y+dy << endl;
00121 
00122       YUV422_PLANAR_YUV(_src, _width, _height, x+dx, y+dy, yp, up, vp);
00123       c = color_model->determine(yp, up, vp);
00124 
00125       if (c == what) {
00126         ++num_what;
00127       }
00128     }
00129   }
00130 
00131   return num_what;
00132 }
00133 
00134 std::list< ROI > *
00135 SimpleColorClassifier::classify()
00136 {
00137 
00138   if (_src == NULL) {
00139     //cout << "SimpleColorClassifier: ERROR, src buffer not set. NOT classifying." << endl;
00140     return new std::list< ROI >;
00141   }
00142 
00143 
00144   std::list< ROI > *rv = new std::list< ROI >();
00145   std::list< ROI >::iterator roi_it, roi_it2;
00146   color_t c;
00147 
00148   unsigned int  x = 0, y = 0;
00149   unsigned char yp = 0, up = 0, vp = 0;
00150   unsigned int num_what = 0;
00151 
00152   ROI r;
00153 
00154   scanline_model->reset();
00155   while (! scanline_model->finished()) {
00156 
00157     x = (*scanline_model)->x;
00158     y = (*scanline_model)->y;
00159 
00160     YUV422_PLANAR_YUV(_src, _width, _height, x, y, yp, up, vp);
00161 
00162     c = color_model->determine(yp,up, vp);
00163 
00164     if (color == c) {
00165       // Yeah, found a ball, make it big and name it a ROI
00166       // Note that this may throw out a couple of ROIs for just one ball,
00167       // as the name suggests this one is really ABSOLUTELY simple and not
00168       // useful for anything else than quick testing
00169 
00170       if (neighbourhood_min_match) {
00171         num_what =
00172           consider_neighbourhood((*scanline_model)->x, (*scanline_model)->y, c);
00173       }
00174       if (num_what >= neighbourhood_min_match) {
00175         bool ok = false;
00176 
00177         for (roi_it = rv->begin(); roi_it != rv->end(); ++roi_it) {
00178           if ( (*roi_it).contains(x, y) ) {
00179             // everything is fine, this point is already in another ROI
00180             ok = true;
00181             break;
00182           }
00183         }
00184         if (! ok) {
00185           for (roi_it = rv->begin(); roi_it != rv->end(); ++roi_it) {
00186             if ( (*roi_it).neighbours(x, y, scanline_model->get_margin()) ) {
00187               // ROI is neighbour of this point, extend region
00188               (*roi_it).extend(x, y);
00189               ok = true;
00190               break;
00191             }
00192           }
00193         }
00194 
00195         if (! ok) {
00196           if ( upward ) {
00197             if ( x < box_extent ) {
00198               x = 0;
00199             } else {
00200               x -= box_extent;
00201             }
00202             if ( y < box_extent ) {
00203               y = 0;
00204             } else {
00205               y -= box_extent;
00206             }
00207           }
00208           r.start.x = x;
00209           r.start.y = y;
00210 
00211           unsigned int to_x = (*scanline_model)->x + box_extent;
00212           unsigned int to_y = (*scanline_model)->y + box_extent;
00213           if (to_x > _width)  to_x = _width;
00214           if (to_y > _height) to_y = _height;
00215           r.width = to_x - r.start.x;
00216           r.height = to_y - r.start.y;
00217           r.hint = c;
00218           r.color = c;
00219 
00220           r.line_step = _width;
00221           r.pixel_step = 1;
00222 
00223           r.image_width  = _width;
00224           r.image_height = _height;
00225 
00226           if ( (r.start.x + r.width) > _width ) {
00227             r.width -= (r.start.x + r.width) - _width;
00228           }
00229           if ( (r.start.y + r.height) > _height ) {
00230             r.height -= (r.start.y + r.height) - _height;
00231           }
00232 
00233           rv->push_back( r );
00234         }
00235       } // End if enough neighbours
00236     } // end if is orange
00237 
00238     ++(*scanline_model);
00239   }
00240 
00241   // Grow regions
00242   if (grow_by > 0) {
00243     for (roi_it = rv->begin(); roi_it != rv->end(); ++roi_it) {
00244       (*roi_it).grow( grow_by );
00245     }
00246   }
00247 
00248   // Merge neighbouring regions
00249   for (roi_it = rv->begin(); roi_it != rv->end(); ++roi_it) {
00250     roi_it2 = roi_it;
00251     ++roi_it2;
00252 
00253     while ( roi_it2 != rv->end() ) {
00254       if ((roi_it != roi_it2) &&
00255           roi_it->neighbours(&(*roi_it2), scanline_model->get_margin()))
00256       {
00257         *roi_it += *roi_it2;
00258         rv->erase(roi_it2);
00259         roi_it2 = rv->begin(); //restart
00260       } else {
00261         ++roi_it2;
00262       }
00263     }
00264   }
00265 
00266   // Throw away all ROIs that have not enough classified points
00267   for (roi_it = rv->begin(); roi_it != rv->end(); ++roi_it) {
00268     while ( (roi_it != rv->end()) &&
00269             ((*roi_it).num_hint_points < min_num_points ))
00270     {
00271       roi_it = rv->erase( roi_it );
00272     }
00273   }
00274 
00275   // sort ROIs by number of hint points, descending (and thus call reverse)
00276   rv->sort();
00277   rv->reverse();
00278 
00279   return rv;
00280 }
00281 
00282 
00283 /** Get mass point of primary color.
00284  * @param roi ROI to consider
00285  * @param massPoint contains mass point upon return
00286  */
00287 void
00288 SimpleColorClassifier::get_mass_point_of_color( ROI *roi,
00289                                                 fawkes::point_t *massPoint )
00290 {
00291   unsigned int nrOfOrangePixels;
00292   nrOfOrangePixels = 0;
00293   massPoint->x     = 0;
00294   massPoint->y     = 0;
00295 
00296   // for accessing ROI pixels
00297   register unsigned int h = 0;
00298   register unsigned int w = 0;
00299   // planes
00300   register unsigned char *yp   = _src + (roi->start.y * roi->line_step) + (roi->start.x * roi->pixel_step);
00301   register unsigned char *up   = YUV422_PLANAR_U_PLANE(_src, roi->image_width, roi->image_height)
00302     + ((roi->start.y * roi->line_step) / 2 + (roi->start.x * roi->pixel_step) / 2) ;
00303   register unsigned char *vp   = YUV422_PLANAR_V_PLANE(_src, roi->image_width, roi->image_height)
00304     + ((roi->start.y * roi->line_step) / 2 + (roi->start.x * roi->pixel_step) / 2);
00305   // line starts
00306   unsigned char *lyp  = yp;
00307   unsigned char *lup  = up;
00308   unsigned char *lvp  = vp;
00309 
00310   color_t dcolor;
00311 
00312   // consider each ROI pixel
00313   for (h = 0; h < roi->height; ++h) {
00314     for (w = 0; w < roi->width; w += 2) {
00315       // classify its color
00316       dcolor = color_model->determine(*yp++, *up++, *vp++);
00317       yp++;
00318       // ball pixel?
00319       if (color == dcolor) {
00320         // take into account its coordinates
00321         massPoint->x += w;
00322         massPoint->y += h;
00323         nrOfOrangePixels++;
00324       }
00325     }
00326     // next line
00327     lyp  += roi->line_step;
00328     lup  += roi->line_step / 2;
00329     lvp  += roi->line_step / 2;
00330     yp    = lyp;
00331     up    = lup;
00332     vp    = lvp;
00333   }
00334 
00335   // to obtain mass point, divide by number of pixels that were added up
00336   massPoint->x = (unsigned int) (float(massPoint->x) / float(nrOfOrangePixels));
00337   massPoint->y = (unsigned int) (float(massPoint->y) / float(nrOfOrangePixels));
00338 
00339   /* shift mass point
00340    such that it is relative to image
00341    (not relative to ROI) */
00342   massPoint->x += roi->start.x;
00343   massPoint->y += roi->start.y;
00344 }
00345 
00346 } // end namespace firevision