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 <classifiers/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 <models/scanlines/scanlinemodel.h>
00031 #include <models/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 <classifiers/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  */
00057 SimpleColorClassifier::SimpleColorClassifier(ScanlineModel *scanline_model,
00058                                              ColorModel *color_model,
00059                                              unsigned int min_num_points,
00060                                              unsigned int box_extent,
00061                                              bool upward,
00062                                              unsigned int neighbourhood_min_match,
00063                                              unsigned int grow_by)
00064 : Classifier("SimpleColorClassifier")
00065 {
00066   if (!scanline_model)
00067     throw fawkes::NullPointerException("SimpleColorClassifier: scanline_model may not be NULL");
00068   if (!color_model)
00069     throw fawkes::NullPointerException("SimpleColorClassifier: color_model may not be NULL");
00070 
00071   modified = false;
00072   this->scanline_model = scanline_model;
00073   this->color_model = color_model;
00074   this->min_num_points = min_num_points;
00075   this->box_extent = box_extent;
00076   this->upward = upward;
00077   this->grow_by = grow_by;
00078   this->neighbourhood_min_match = neighbourhood_min_match;
00079   set_hint(H_BALL);
00080 }
00081 
00082 
00083 /** Sets the object of interest (hint_t)
00084  * This function clears the current list of objects of interests
00085  * @param hint Object of interest
00086  */
00087 void
00088 SimpleColorClassifier::set_hint (hint_t hint)
00089 {
00090   colors_of_interest.clear();
00091   colors_of_interest.push_back(ColorObjectMap::get_instance().get(hint));
00092 }
00093 
00094 /** Adds another object of interest (hint_t)
00095  * @param hint Object of interest
00096  */
00097 void
00098 SimpleColorClassifier::add_hint (hint_t hint)
00099 {
00100   colors_of_interest.push_back(ColorObjectMap::get_instance().get(hint));
00101   colors_of_interest.unique();
00102 }
00103 
00104 
00105 unsigned int
00106 SimpleColorClassifier::consider_neighbourhood( unsigned int x, unsigned int y , color_t what)
00107 {
00108   color_t c;
00109   unsigned int num_what = 0;
00110 
00111   unsigned char yp = 0, up = 0, vp = 0;
00112   int start_x = -2, start_y = -2;
00113   int end_x = 2, end_y = 2;
00114 
00115   if (x < (unsigned int)abs(start_x)) {
00116     start_x = 0;
00117   }
00118   if (y < (unsigned int)abs(start_y)) {
00119     start_y = 0;
00120   }
00121 
00122   if (x > _width - end_x) {
00123     end_x = 0;
00124   }
00125   if (y == _height - end_y) {
00126     end_y = 0;
00127   }
00128 
00129   for (int dx = start_x; dx <= end_x ; dx += 2) {
00130     for (int dy = start_y; dy <= end_y; ++dy) {
00131       if ((dx == 0) && (dy == 0)) {
00132         continue;
00133       }
00134 
00135       //      cout << "x=" << x << "  dx=" << dx << "  +=" << x+dx
00136       //   << "  y=" << y << "  dy=" << dy << "  +2=" << y+dy << endl;
00137 
00138       YUV422_PLANAR_YUV(_src, _width, _height, x+dx, y+dy, yp, up, vp);
00139       c = color_model->determine(yp, up, vp);
00140 
00141       if (c == what) {
00142         ++num_what;
00143       }
00144     }
00145   }
00146 
00147   return num_what;
00148 }
00149 
00150 std::list< ROI > *
00151 SimpleColorClassifier::classify()
00152 {
00153 
00154   if (_src == NULL) {
00155     //cout << "SimpleColorClassifier: ERROR, src buffer not set. NOT classifying." << endl;
00156     return new std::list< ROI >;
00157   }
00158 
00159 
00160   std::list< ROI > *rv;
00161   std::list< ROI >::iterator roi_it, roi_it2;
00162   color_t c;
00163   std::map<color_t, std::list< ROI > > to_be_considered;
00164   std::map<color_t, std::list< ROI > >::iterator tbc_it;
00165 
00166   for (std::list<color_t>::const_iterator it = colors_of_interest.begin(); it != colors_of_interest.end(); ++it) {
00167     to_be_considered[*it];
00168   }
00169 
00170   unsigned int  x = 0, y = 0;
00171   unsigned char yp = 0, up = 0, vp = 0;
00172   unsigned int num_what = 0;
00173 
00174   ROI r;
00175 
00176   scanline_model->reset();
00177   while (! scanline_model->finished()) {
00178 
00179     x = (*scanline_model)->x;
00180     y = (*scanline_model)->y;
00181 
00182     YUV422_PLANAR_YUV(_src, _width, _height, x, y, yp, up, vp);
00183 
00184     /*
00185      cout << "SimpleColorClassifier: Checking at ("
00186                                                   << x
00187                                                   << ","
00188                                                   << y
00189                                                   << ") "
00190                                                     << " with color Y=" << (int)macro_pixel[y_pos]
00191                                                     << "  U=" << (int)macro_pixel[0]
00192                                                     << "  V=" << (int)macro_pixel[2]
00193                                                     << endl;
00194                                                     */
00195 
00196     c = color_model->determine(yp,up, vp);
00197 
00198     if ((tbc_it = to_be_considered.find(c)) != to_be_considered.end()) {
00199       rv = &tbc_it->second;
00200       // Yeah, found a ball, make it big and name it a ROI
00201       // Note that this may throw out a couple of ROIs for just one ball,
00202       // as the name suggests this one is really ABSOLUTELY simple and not
00203       // useful for anything else than quick testing
00204 
00205       if (neighbourhood_min_match) {
00206         num_what = consider_neighbourhood((*scanline_model)->x, (*scanline_model)->y, c);
00207       }
00208       if (num_what >= neighbourhood_min_match) {
00209 
00210         bool ok = false;
00211 
00212         for (roi_it = rv->begin(); roi_it != rv->end(); ++roi_it) {
00213           if ( (*roi_it).contains(x, y) ) {
00214             // everything is fine, this point is already in another ROI
00215             ok = true;
00216             break;
00217           }
00218         }
00219         if (! ok) {
00220           for (roi_it = rv->begin(); roi_it != rv->end(); ++roi_it) {
00221             if ( (*roi_it).neighbours(x, y, scanline_model->get_margin()) ) {
00222               // ROI is neighbour of this point, extend region
00223               (*roi_it).extend(x, y);
00224               ok = true;
00225               break;
00226             }
00227           }
00228         }
00229 
00230         if (! ok) {
00231           if ( upward ) {
00232             if ( x < box_extent ) {
00233               x = 0;
00234             } else {
00235               x -= box_extent;
00236             }
00237             if ( y < box_extent ) {
00238               y = 0;
00239             } else {
00240               y -= box_extent;
00241             }
00242           }
00243           r.start.x = x;
00244           r.start.y = y;
00245 
00246           unsigned int to_x = (*scanline_model)->x + box_extent;
00247           unsigned int to_y = (*scanline_model)->y + box_extent;
00248           if (to_x > _width)  to_x = _width;
00249           if (to_y > _height) to_y = _height;
00250           r.width = to_x - r.start.x;
00251           r.height = to_y - r.start.y;
00252           r.hint = ColorObjectMap::get_instance().get(c);
00253 
00254           r.line_step = _width;
00255           r.pixel_step = 1;
00256 
00257           r.image_width  = _width;
00258           r.image_height = _height;
00259 
00260           if ( (r.start.x + r.width) > _width ) {
00261             r.width -= (r.start.x + r.width) - _width;
00262           }
00263           if ( (r.start.y + r.height) > _height ) {
00264             r.height -= (r.start.y + r.height) - _height;
00265           }
00266 
00267           rv->push_back( r );
00268         }
00269       } // End if enough neighbours
00270     } // end if is orange
00271 
00272     ++(*scanline_model);
00273   }
00274 
00275   // Grow regions
00276   if (grow_by > 0) {
00277     for (tbc_it = to_be_considered.begin(); tbc_it != to_be_considered.end(); ++tbc_it) {
00278       rv = &tbc_it->second;
00279 
00280       for (roi_it = rv->begin(); roi_it != rv->end(); ++roi_it) {
00281         (*roi_it).grow( grow_by );
00282       }
00283     }
00284   }
00285 
00286   //bool restart = false;
00287   // Merge neighbouring regions
00288   for (tbc_it = to_be_considered.begin(); tbc_it != to_be_considered.end(); ++tbc_it) {
00289     rv = &tbc_it->second;
00290 
00291     for (roi_it = rv->begin(); roi_it != rv->end(); ++roi_it) {
00292       roi_it2 = roi_it;//rv->begin();
00293       ++roi_it2;
00294 
00295       while ( roi_it2 != rv->end() ) {
00296         if ((roi_it != roi_it2) && roi_it->neighbours(&(*roi_it2), scanline_model->get_margin())) {
00297           *roi_it += *roi_it2;
00298           rv->erase(roi_it2);
00299           roi_it2 = rv->begin(); //restart
00300         } else {
00301           ++roi_it2;
00302         }
00303       }
00304     }
00305   }
00306 
00307   std::list< ROI > *result = new std::list< ROI >;
00308   for (tbc_it = to_be_considered.begin(); tbc_it != to_be_considered.end(); ++tbc_it) {
00309     rv = &tbc_it->second;
00310 
00311     // Throw away all ROIs that have not enough classified points
00312     for (roi_it = rv->begin(); roi_it != rv->end(); ++roi_it) {
00313       while ( (roi_it != rv->end()) &&
00314              ((*roi_it).num_hint_points < min_num_points )) {
00315                roi_it = rv->erase( roi_it );
00316              }
00317     }
00318 
00319     // sort ROIs by number of hint points, descending (and thus call reverse)
00320     rv->sort();
00321     rv->reverse();
00322     result->merge(*rv);
00323   }
00324 
00325   return result;
00326 }
00327 
00328 
00329 /** Get mass point of ball.
00330  * @param roi ROI to consider
00331  * @param massPoint contains mass point upon return
00332  */
00333 void
00334 SimpleColorClassifier::get_mass_point_of_ball( ROI *roi, fawkes::point_t *massPoint )
00335 {
00336   unsigned int nrOfOrangePixels;
00337   nrOfOrangePixels = 0;
00338   massPoint->x     = 0;
00339   massPoint->y     = 0;
00340 
00341   // for accessing ROI pixels
00342   register unsigned int h = 0;
00343   register unsigned int w = 0;
00344   // planes
00345   register unsigned char *yp   = _src + (roi->start.y * roi->line_step) + (roi->start.x * roi->pixel_step);
00346   register unsigned char *up   = YUV422_PLANAR_U_PLANE(_src, roi->image_width, roi->image_height)
00347     + ((roi->start.y * roi->line_step) / 2 + (roi->start.x * roi->pixel_step) / 2) ;
00348   register unsigned char *vp   = YUV422_PLANAR_V_PLANE(_src, roi->image_width, roi->image_height)
00349     + ((roi->start.y * roi->line_step) / 2 + (roi->start.x * roi->pixel_step) / 2);
00350   // line starts
00351   unsigned char *lyp  = yp;
00352   unsigned char *lup  = up;
00353   unsigned char *lvp  = vp;
00354 
00355   color_t color;
00356 
00357   // consider each ROI pixel
00358   for (h = 0; h < roi->height; ++h) {
00359     for (w = 0; w < roi->width; w += 2) {
00360       // classify its color
00361       color = color_model->determine(*yp++, *up++, *vp++);
00362       yp++;
00363       // ball pixel?
00364       if (color == ColorObjectMap::get_instance().get(roi->hint)) {
00365         // take into account its coordinates
00366         massPoint->x += w;
00367         massPoint->y += h;
00368         nrOfOrangePixels++;
00369       }
00370     }
00371     // next line
00372     lyp  += roi->line_step;
00373     lup  += roi->line_step / 2;
00374     lvp  += roi->line_step / 2;
00375     yp    = lyp;
00376     up    = lup;
00377     vp    = lvp;
00378   }
00379 
00380   // to obtain mass point, divide by number of pixels that were added up
00381   massPoint->x = (unsigned int) (float(massPoint->x) / float(nrOfOrangePixels));
00382   massPoint->y = (unsigned int) (float(massPoint->y) / float(nrOfOrangePixels));
00383 
00384   /* shift mass point
00385    such that it is relative to image
00386    (not relative to ROI) */
00387   massPoint->x += roi->start.x;
00388   massPoint->y += roi->start.y;
00389 }
00390 
00391 } // end namespace firevision