pm_mcc_node.cpp
Go to the documentation of this file.
00001 /********************************************
00002  * Shape dissimilarity measurement based on the Multi-scale
00003  * Convexity Concavity (MCC) representation.
00004  *
00005  * Based on article 
00006  * @ARTICLE{Adamek2004,
00007  *    author={Adamek, T. and O''Connor, N.E.},
00008  *    journal={Circuits and Systems for Video Technology, IEEE Transactions on}, 
00009  *    title={A multiscale representation method for nonrigid shapes with a single closed contour},
00010  *    year={2004},
00011  *    volume={14},
00012  *    number={5},
00013  *    pages={742-753},
00014  *    doi={10.1109/TCSVT.2004.826776},
00015  *    ISSN={1051-8215},
00016  *    }
00017  *************************************/
00018 
00019 #include <algorithm>
00020 #include <iostream>
00021 #include <fstream>
00022 #include <limits>
00023 #include <cmath>
00024 #include <cassert>
00025 
00026 #include <boost/numeric/ublas/matrix.hpp>
00027 #include <boost/numeric/ublas/io.hpp>
00028 
00029 #include <ros/ros.h>
00030 #include <std_msgs/String.h>
00031 #include <lama_common/point.h>
00032 #include <lama_common/polygon.h>
00033 #include <polygon_matcher/PolygonDissimilarity.h>
00034 
00035 // Number of points to keep for the mcc computation.
00036 unsigned int g_num_samples;
00037 
00038 // Number of scales to compute.
00039 unsigned int g_max_sigma;
00040 
00041 bool g_rotation_invariance;
00042 
00043 using std::sqrt;
00044 using std::abs;
00045 
00046 typedef boost::numeric::ublas::matrix<double> matrix;
00047 typedef std::vector<geometry_msgs::Polygon> polygon_list;
00048 
00049 int inpoly(const geometry_msgs::Polygon& p, const geometry_msgs::Point32& point)
00050 {
00051   float x = point.x;
00052   float y = point.y;
00053   unsigned int i, j, c = 0;
00054   for (i = 0, j = p.points.size() - 1; i < p.points.size(); j = i++)
00055   {
00056     if ((((p.points[i].y <= y) && (y < p.points[j].y)) ||
00057           ((p.points[j].y <= y) && (y < p.points[i].y))) &&
00058         (x < (p.points[j].x - p.points[i].x) * (y - p.points[i].y) / (p.points[j].y - p.points[i].y) + p.points[i].x))
00059       c = !c;
00060   }
00061   return c;
00062 }
00063 
00064 bool evolve(geometry_msgs::Polygon& input, polygon_list& output, unsigned int maxSigma)
00065 {
00066   double distance;
00067   double value;
00068   output.push_back(input);
00069   for (unsigned int s = 1; s < maxSigma; s++)
00070   {
00071     geometry_msgs::Polygon pSigma;
00072     for (unsigned int i = 0; i < input.points.size(); i++)
00073     {
00074       double sumX = 0;
00075       double sumY = 0;
00076       for (unsigned int j = 0; j < input.points.size(); j++)
00077       {
00078         // Apply a Gaussian kernel.
00079         // TODO: Apply the Gaussian kernel only where relevant.
00080         distance = std::min(abs(j - i), abs(input.points.size() - abs(j - i)));
00081         value = 1.0 / (s * sqrt(2.0 * M_PI)) * exp(-(distance * distance) / (2.0 * s * s));
00082         sumX += input.points[j].x * value;
00083         sumY += input.points[j].y * value;
00084       }
00085       geometry_msgs::Point32 p;
00086       p.x = sumX;
00087       p.y = sumY;
00088       p.z = 0;
00089       pSigma.points.push_back(p);
00090     }
00091     output.push_back(pSigma);
00092   }
00093   return true;
00094 }
00095 
00096 /* Compute the convexity of scaled polygons.
00097  *
00098  * Compute the convexity of scaled polygons by looking at the signed distance
00099  * between a point at scale s and the same point at scale (s - 1).
00100  * This approximates the curvature.
00101  *
00102  * Return the estimated shape complexity as the average of the differences
00103  * between max and min convexity/concavity measures over all scale levels.
00104  *
00105  * polygons[in] list of scaled polygons.
00106  * m[out] multi-scale convexity concavity representation.
00107  */
00108 double compute_convexity(const polygon_list& polygons, matrix& m)
00109 {
00110   double complexity = 0;
00111 
00112   for (unsigned int s = 1; s < g_max_sigma; s++)
00113   {
00114     double cMax = 0;
00115     double cMin = std::numeric_limits<double>::max();      
00116 
00117     for (unsigned int u = 0; u < g_num_samples; u++)
00118     {
00119       // k = 1 if point inside polygon; -1 otherwise.
00120       // k = 1 for convex part of the polygon contour.
00121       int k = 2 * inpoly(polygons[s], polygons[s-1].points[u]) - 1;
00122       // TODO: talk with Karel why not m(u,s) as stated in the article.
00123       m(u,s-1) = k * sqrt(
00124           (polygons[s].points[u].x - polygons[s-1].points[u].x) *
00125           (polygons[s].points[u].x - polygons[s-1].points[u].x) + 
00126           (polygons[s].points[u].y - polygons[s-1].points[u].y) *
00127           (polygons[s].points[u].y - polygons[s-1].points[u].y));
00128 
00129       if (m(u,s-1) < cMin)
00130       {
00131         cMin = m(u,s-1);
00132       }
00133       if (m(u,s-1) > cMax)
00134       {
00135         cMax = m(u,s-1);
00136       }
00137     }
00138     complexity += (cMax - cMin);
00139   }
00140   complexity /= (g_max_sigma - 1);
00141   return complexity;
00142 }
00143 
00144 /* Compute the distance between two MCC representations
00145  *
00146  * Compute the distance between two MCC representations, i.e. for each contour
00147  * point of a and b (rows) sum the absolute difference on all scale levels
00148  * (columns).
00149  * In order to achieve the optional rotational invariance, a circular shift is
00150  * applied on the rows of the second matrix.
00151  */
00152 matrix compare(matrix& a, matrix& b)
00153 {
00154   assert(a.size2() == b.size2());
00155 
00156   matrix ret(a.size1(), b.size1());
00157 
00158   size_t last_row_shift = 1;
00159   if (g_rotation_invariance)
00160   {
00161     last_row_shift = b.size1();
00162   }
00163 
00164   for (size_t i = 0; i < a.size1(); i++)
00165   {
00166     for (size_t j = 0; j < last_row_shift; j++)
00167     {
00168       double sum = 0;
00169       // TODO: Discuss with Karel why not s from 0 to a.size2()
00170       // First and last scale levels are excluded.
00171       for (size_t s = 1; s < a.size2() - 1; s++)
00172       {
00173         sum += std::fabs(a(i,s) - b(j,s));
00174       }
00175       // TODO: Discuss with Karel if there shouldn't be a normalization 
00176       // with last_row_shift here.
00177       ret(i,j) = (1.0 / a.size2()) * sum;
00178     }
00179   }
00180   return ret;
00181 }       
00182 
00183 /* Return the polygon which has (0,0) as barycenter
00184  */
00185 geometry_msgs::Polygon center(geometry_msgs::Polygon& p)
00186 {
00187   float sumx = 0;
00188   float sumy = 0;
00189   geometry_msgs::Polygon centeredPolygon;
00190   centeredPolygon.points.reserve(p.points.size());
00191   for (size_t i = 0; i < p.points.size(); ++i)
00192   {
00193     sumx += p.points[i].x;
00194     sumy += p.points[i].y;
00195   }
00196   for (size_t i = 0; i < p.points.size(); ++i)
00197   {
00198     geometry_msgs::Point32 outpoint;
00199     outpoint.x = p.points[i].x - sumx;
00200     outpoint.y = p.points[i].y - sumy;
00201     centeredPolygon.points.push_back(outpoint);
00202   }
00203   return centeredPolygon;
00204 }
00205 
00206 // TODO: Get explanation how minDistance works.
00207 // TODO: Change the name because there is no minimization in minDistance.
00208 double minDistance(const matrix& compared, const int start)
00209 {
00210   size_t n = compared.size1();
00211   size_t m = compared.size2();
00212   matrix D(n, m);
00213   D(0,0) = compared(0,start);
00214   
00215   //fill start column
00216   for (size_t i = 1; i < n; i++)
00217   {
00218     D(i,0) = compared(i,start) + D(i-1,0);
00219   }
00220   //fill first row
00221   for (size_t j = 1; j < m; j++)
00222   {
00223     D(0,j) = compared(0,(j+start) % m) + D(0,j-1);
00224   }
00225   // test 
00226   /*for (unsigned int  i = 1; i < n; i++) {
00227     D(i,i) = D(i-1,i-1)+compared(i,(i+start)%n);
00228     ROS_INFO("D %f %f",compared(i,i), D(i,i)); 
00229     }
00230     */
00231   for (size_t i = 1 ; i < n; i++)
00232   {
00233     for (size_t j = 1; j < m; j++)
00234     {
00235       D(i,j) = compared(i,(j+start) % m) + std::min(D(i-1,j), std::min(D(i,(j-1)), D(i-1, (j-1))));
00236     }
00237   }
00238   return D(n - 1, m - 1); 
00239 } 
00240 
00241 // TODO: write a class for dissimilarity in order to avoid global variables.
00242 
00243 /* PolygonDissimilarity service callback
00244  */
00245 bool dissimilarity(polygon_matcher::PolygonDissimilarity::Request& req,
00246     polygon_matcher::PolygonDissimilarity::Response& res)
00247 {
00248 
00249   ROS_DEBUG("Request: size_1=%zu, size_2=%zu", req.polygon1.points.size(), req.polygon2.points.size());
00250   ros::Time start = ros::Time::now();
00251 
00252   // TODO:  talk with Karel why centeredPolygon1 not used.
00253   // geometry_msgs::Polygon centeredPolygon1 = center(req.polygon1);
00254   // geometry_msgs::Polygon centeredPolygon2 = center(req.polygon2);
00255 
00256   geometry_msgs::Polygon polygon1res;
00257   geometry_msgs::Polygon polygon2res; 
00258   double delta;
00259   polygon1res.points = lama_common::resamplePolygon(req.polygon1.points, g_num_samples, delta);
00260   polygon2res.points = lama_common::resamplePolygon(req.polygon2.points, g_num_samples, delta);
00261   polygon_list polygon1evo;
00262   polygon_list polygon2evo;
00263   //create multi-polygon representation (increasing sigma)
00264   evolve(polygon1res, polygon1evo, g_max_sigma);
00265   evolve(polygon2res, polygon2evo, g_max_sigma);
00266   //create multiscale representation 
00267 
00268   res.processing_time = ros::Time::now() - start;
00269   ROS_DEBUG("Multi-polygon representation created after %.1f s", res.processing_time.toSec());
00270   matrix mcc1(g_num_samples, g_max_sigma);
00271   matrix mcc2(g_num_samples, g_max_sigma);
00272 
00273   // TODO: discuss with Karel the interest of the complexity normalization.
00274   double C1 = compute_convexity(polygon1evo, mcc1);
00275   double C2 = compute_convexity(polygon2evo, mcc2);
00276   ROS_DEBUG("Complexity normalization of polygon 1 = %f", C1);
00277   ROS_DEBUG("Complexity normalization of polygon 2 = %f", C2);
00278 
00279   res.processing_time = ros::Time::now() - start;
00280   ROS_DEBUG("Multi-scale representation created after %.1f s", res.processing_time.toSec());
00281 
00282   matrix comp = compare(mcc1, mcc2);
00283 
00284   res.processing_time = ros::Time::now() - start;
00285   ROS_DEBUG("Comparison created after %.1f s", res.processing_time.toSec());
00286 
00287   std::vector<double> result;
00288   result.reserve(comp.size2());
00289   for (size_t i = 0; i < comp.size2(); i++)
00290   {
00291     result.push_back(minDistance(comp, i));
00292   }
00293   res.raw_dissimilarity = (*std::min_element(result.begin(), result.end())) * 2.0 / ((C1 + C2) * (g_num_samples));
00294 
00295   res.processing_time = ros::Time::now() - start;
00296   ROS_DEBUG("Sending back response: %f  (in %.1f s)", res.raw_dissimilarity, res.processing_time.toSec());
00297 
00298   return true;
00299 }
00300 
00301 int main(int argc, char **argv)
00302 {
00303   int max_thread;
00304   ros::init(argc, argv, "mcc_polygon_dissimilarity_server");
00305   ros::NodeHandle nh("~");
00306 
00307   nh.param<int>("max_thread", max_thread, 1);
00308 
00309   // Number of points to keep for the mcc computation.
00310   int sample_count;
00311   nh.param<int>("sample_count", sample_count, 100);
00312   g_num_samples = sample_count;
00313 
00314   // Number of scales to compute.
00315   // TODO: talk with Karel: according to article, 10 should be sufficient.
00316   int scale_count;
00317   nh.param<int>("scale_count", scale_count, 20);
00318   g_max_sigma = scale_count;
00319 
00320   // With rotation_invariance = true, no cyclic optimisation will be done in compare.
00321   bool rotation_invariance;
00322   nh.param<bool>("rotation_invariance", rotation_invariance, true);
00323   g_rotation_invariance = rotation_invariance;
00324 
00325   ros::ServiceServer service = nh.advertiseService("compute_dissimilarity", dissimilarity);
00326   /* ros::Publisher pub = nh.advertise<std_msgs::String>("node_register", 10, true); */
00327 
00328   ROS_INFO("Ready to work (with %i threads)", max_thread);
00329   /* std_msgs::String msg; */
00330   /* msg.data = ros::this_node::getName(); */
00331   /* pub.publish(msg); */
00332 
00333   ros::MultiThreadedSpinner spinner(max_thread);
00334   spinner.spin();
00335 
00336   return 0;
00337 }


pm_mcc
Author(s): Gaël Ecorchard , Karel Košnar
autogenerated on Mon Mar 2 2015 19:33:20