├── .gitignore ├── CMakeLists.txt ├── README.md ├── launch └── edge_leg_detector.launch ├── package.xml └── src └── edge_leg_detector └── detector.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | 6 | # Compiled Dynamic libraries 7 | *.so 8 | *.dylib 9 | 10 | # Compiled Static libraries 11 | *.lai 12 | *.la 13 | *.a 14 | 15 | *~ 16 | server.log 17 | *.user 18 | *.swp 19 | *.swo 20 | 21 | *.pyc 22 | 23 | # Folder 24 | .ropeproject/ 25 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.3) 2 | project(edge_leg_detector) 3 | 4 | find_package(catkin REQUIRED COMPONENTS 5 | geometry_msgs 6 | roscpp 7 | rospy 8 | sensor_msgs 9 | ) 10 | 11 | find_package(Boost REQUIRED COMPONENTS thread) 12 | 13 | ################################### 14 | ## catkin specific configuration ## 15 | ################################### 16 | 17 | catkin_package( 18 | CATKIN_DEPENDS roscpp rospy sensor_msgs geometry_msgs 19 | ) 20 | 21 | ########### 22 | ## Build ## 23 | ########### 24 | 25 | include_directories( 26 | ${catkin_INCLUDE_DIRS} 27 | ${Boost_INCLUDE_DIRS} 28 | ) 29 | 30 | add_executable(detector src/edge_leg_detector/detector.cpp) 31 | 32 | target_link_libraries(detector 33 | ${catkin_LIBRARIES} 34 | ${Boost_LIBRARIES} 35 | ) 36 | 37 | ############# 38 | ## Install ## 39 | ############# 40 | 41 | install(TARGETS detector 42 | ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} 43 | LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} 44 | RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} 45 | ) 46 | 47 | install(DIRECTORY launch 48 | DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} 49 | ) 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Leg Detector 2 | This is a ROS package that implements a leg detector from a LaserScan message, and publish the position of humans in a PoseArray message. 3 | 4 | This code is based on the work described in: 5 | 6 | Bellotto, N.; Huosheng Hu, "Multisensor-Based Human Detection and Tracking for Mobile Service Robots," Systems, Man, and Cybernetics, Part B: Cybernetics, IEEE Transactions on , vol.39, no.1, pp.167,181, Feb. 2009 7 | doi: 10.1109/TSMCB.2008.2004050 8 | 9 | Run this package by typing 10 | 11 | ``` 12 | roslaunch edge_leg_detector edge_leg_detector.launch laser_scan:=[LASER_SCAN_TOPIC] 13 | ``` 14 | 15 | Where ```[LASER_SCAN_TOPIC]``` is the name of the topic with the LaserScan sensor data. For example, if the name of the topic is ```/scan``` then, the leg detector can be initialized as: 16 | 17 | ``` 18 | roslaunch edge_leg_detector edge_leg_detector.launch laser_scan:=/scan 19 | ``` 20 | -------------------------------------------------------------------------------- /launch/edge_leg_detector.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | edge_leg_detector 4 | 0.0.1 5 | A leg detector that identifies three different leg patterns based on the paper of Nicholas Belloto 6 | 7 | Marco Antonio Becerra Pedraza 8 | Ferdian Jovan 9 | 10 | MIT 11 | 12 | catkin 13 | 14 | boost 15 | roscpp 16 | rospy 17 | sensor_msgs 18 | geometry_msgs 19 | 20 | boost 21 | roscpp 22 | rospy 23 | sensor_msgs 24 | geometry_msgs 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/edge_leg_detector/detector.cpp: -------------------------------------------------------------------------------- 1 | // FILE: "Leg_detector_node" 2 | // AUTHOR: Marco Antonio Becerra Pedraza (http://www.marcobecerrap.com) 3 | // MODIFIED BY: Ferdian Jovan (http://github.com/ferdianjovan) 4 | // SUMMARY: This program receives the LaserScan msgs and executes a leg detector algorithm 5 | // > to search for persons. At the end publishes a a vector with all the persons found 6 | // > and their position relative to the sensor. 7 | // 8 | // NOTES: This leg detector is based on the work described in: 9 | // Bellotto, N. & Hu, H. 10 | // Multisensor-Based Human Detection and Tracking for Mobile Service Robots 11 | // IEEE Trans. on Systems, Man, and Cybernetics -- Part B, 2009, 39, 167-181 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #define PI 3.1416 24 | #define FILTER_SIZE 2 25 | #define FLANK_THRESHOLD 0.1 26 | 27 | #define FLANK_U 1 28 | #define FLANK_D -1 29 | 30 | //Antropometric parameters 31 | #define ANTRO_a0 0.1 //| 32 | #define ANTRO_a1 0.2 //|-> Leg width (min-max) 33 | #define ANTRO_b0 0 // | 34 | #define ANTRO_b1 0.4 // |-> Free space between two legs (min-max) 35 | #define ANTRO_c0 0.1 // | 36 | #define ANTRO_c1 0.4 // |-> Two legs together width (min-max) 37 | 38 | // Pattern Type 39 | #define TYPE_LA 1 // Legs separated 40 | #define TYPE_FS 2 // Legs dephased 41 | #define TYPE_SL 3 // Legs together 42 | 43 | 44 | using namespace std; 45 | 46 | bool sensor_on = false; 47 | 48 | int g_counter = 0; 49 | 50 | vector < double > rec_x; 51 | vector < double > rec_y; 52 | string sensor_frame_id; 53 | sensor_msgs::LaserScan SensorMsg; 54 | boost::mutex mutex; 55 | 56 | void LaserFilter_Mean( vector *vector_r, unsigned size ); 57 | void LaserCallback (const sensor_msgs::LaserScan::ConstPtr& msg); 58 | void FindPattern( string str, string pattern, list *element_found ); 59 | void ValidatePattern( list *Pattern_list, int TYPE, vector flank_id0, vector flank_id1, vector laser_x, vector laser_y); 60 | double Dist2D( double x0, double y0, double x1, double y1 ); 61 | void HumanPose( vector *r_x, vector *r_y, list Pattern_list, int TYPE, vector flank_id0, vector flank_id1, vector laser_x, vector laser_y ); 62 | 63 | // added by Ferdian Jovan 64 | // function to restrict a possibility of persons standing next to each other 65 | void ValidateDistance(); 66 | 67 | 68 | int main(int argc, char **argv){ 69 | 70 | ros::init(argc, argv, "edge_leg_detector"); 71 | ros::NodeHandle n; 72 | ros::Publisher node_pub = n.advertise ("edge_leg_detector", 2); // Humans in the environment 73 | 74 | // get param from launch file 75 | string laser_scan = "/scan"; 76 | ros::param::get("~laser_scan", laser_scan); 77 | ros::Subscriber node_sub = n.subscribe(laser_scan, 2, LaserCallback); 78 | geometry_msgs::PoseArray msgx; 79 | ros::Rate loop_rate(15); 80 | 81 | 82 | int seq_counter = 0; 83 | 84 | while( ros::ok() ){ 85 | if( sensor_on == true ){ 86 | 87 | // delete persons who are too near to each other 88 | void ValidateDistance(); 89 | 90 | //------------------------------------------ 91 | // Copying to proper PoseArray data structure 92 | vector < geometry_msgs::Pose > HumanPoseVector; 93 | for( int K = 0; K < rec_x.size(); K++ ){ 94 | geometry_msgs::Point HumanPoint; 95 | geometry_msgs::Quaternion HumanQuaternion; 96 | 97 | HumanPoint.x = rec_x[ K ]; 98 | HumanPoint.y = rec_y[ K ]; 99 | HumanPoint.z = 0; 100 | 101 | HumanQuaternion.x = 0;//|-> Orientation is ignored 102 | HumanQuaternion.y = 0;//| 103 | HumanQuaternion.z = 0;//| 104 | HumanQuaternion.w = 1;//| 105 | 106 | geometry_msgs::Pose HumanPose; 107 | HumanPose.position = HumanPoint; 108 | HumanPose.orientation= HumanQuaternion; 109 | HumanPoseVector.push_back( HumanPose ); 110 | } 111 | 112 | // Header config 113 | msgx.header.stamp = ros::Time::now(); 114 | msgx.header.frame_id = SensorMsg.header.frame_id; 115 | msgx.header.seq = seq_counter; 116 | msgx.poses = HumanPoseVector; 117 | //------------------------------------------ 118 | 119 | node_pub.publish( msgx ); 120 | } 121 | ros::spinOnce(); 122 | loop_rate.sleep(); 123 | seq_counter++; 124 | } 125 | 126 | return 0; 127 | } 128 | 129 | 130 | void LaserCallback (const sensor_msgs::LaserScan::ConstPtr& msg){ 131 | 132 | // To get header data from sensor msg 133 | SensorMsg = *msg; 134 | 135 | // Vectors... 136 | rec_x.clear(); 137 | rec_y.clear(); 138 | 139 | sensor_on = true; 140 | 141 | double px, py, pr, pt; 142 | vector < double > laser_x; 143 | vector < double > laser_y; 144 | vector < double > laser_r; 145 | vector < double > laser_t; 146 | for( unsigned i = 0; i < msg->ranges.size(); i++ ){ 147 | pr = msg->ranges[ i ]; 148 | pt = msg->angle_min + ( i * msg->angle_increment); 149 | laser_r.push_back( pr ); 150 | laser_t.push_back( pt ); 151 | } 152 | 153 | // Filtering laser scan 154 | LaserFilter_Mean( &laser_r, FILTER_SIZE ); 155 | for( unsigned i = 0; i < msg->ranges.size(); i++ ){ 156 | px = laser_r[ i ] * cos( laser_t[ i ] ); 157 | py = laser_r[ i ] * sin( laser_t[ i ] ); 158 | laser_x.push_back( px ); 159 | laser_y.push_back( py ); 160 | } 161 | 162 | string str_aux = ""; 163 | // Finding flanks in the laser scan... 164 | vector < int > laser_flank; 165 | laser_flank.assign(laser_r.size(), 0); 166 | for( unsigned i = 1; i < laser_flank.size(); i++ ){ 167 | if( fabs( laser_r[ i ] - laser_r[ i - 1 ] ) > FLANK_THRESHOLD ) 168 | laser_flank[ i ] = ( ( laser_r[ i ] - laser_r[ i - 1 ] ) > 0 ) ? FLANK_U : FLANK_D; 169 | } 170 | 171 | vector < int > flank_id0; 172 | vector < int > flank_id1; 173 | string flank_string = ""; 174 | int past_value = 0; 175 | int idx = 0; 176 | for( unsigned i = 1; i < laser_flank.size(); i++ ){ 177 | if( laser_flank[ i ] != 0 ){ 178 | if( past_value != laser_flank[ i ] ){ 179 | flank_id0.push_back( i - 1 ); 180 | flank_id1.push_back( i ); 181 | flank_string += ( laser_flank[ i ] > 0 ) ? "S" : "B"; 182 | idx++; 183 | } 184 | else 185 | flank_id1[ idx - 1 ] = i; 186 | } 187 | past_value = laser_flank[ i ]; 188 | } 189 | 190 | // PATTERN RECOGNITION 191 | string LEGS_LA = "BSBS"; 192 | string LEGS_FS1 = "BBS"; 193 | string LEGS_FS2 = "BSS"; 194 | string LEGS_SL = "BS"; 195 | 196 | list Pattern_LA; 197 | list Pattern_FS1; 198 | list Pattern_FS2; 199 | list Pattern_SL; 200 | 201 | FindPattern( flank_string, LEGS_LA, &Pattern_LA ); 202 | FindPattern( flank_string, LEGS_FS1, &Pattern_FS1 ); 203 | FindPattern( flank_string, LEGS_FS2, &Pattern_FS2 ); 204 | FindPattern( flank_string, LEGS_SL, &Pattern_SL ); 205 | 206 | // ANTROPOMETRIC VALIDATION (the non antropometric patterns are erased from the list) 207 | ValidatePattern( &Pattern_LA, TYPE_LA, flank_id0, flank_id1, laser_x, laser_y); 208 | ValidatePattern( &Pattern_FS1, TYPE_FS, flank_id0, flank_id1, laser_x, laser_y); 209 | ValidatePattern( &Pattern_FS2, TYPE_FS, flank_id0, flank_id1, laser_x, laser_y); 210 | ValidatePattern( &Pattern_SL, TYPE_SL, flank_id0, flank_id1, laser_x, laser_y); 211 | 212 | // ERASE REDUNDANT PATTERNS FROM ACCEPTED ONES (If a LA or FS pattern is accepted, we erase the SL on it) 213 | // a) Erase SL from LA 214 | list::iterator it_K; 215 | for( it_K = Pattern_LA.begin(); it_K != Pattern_LA.end(); it_K++ ){ 216 | list::iterator it_M; 217 | // Erase first leg 218 | for( it_M = Pattern_SL.begin(); it_M != Pattern_SL.end(); it_M++ ) 219 | if( flank_id0[ *it_K ] == flank_id0[ *it_M ] ){ 220 | Pattern_SL.erase( it_M ); 221 | break; 222 | } 223 | // Erase second leg 224 | for( it_M = Pattern_SL.begin(); it_M != Pattern_SL.end(); it_M++ ) 225 | if( flank_id0[ *it_K + 2 ] == flank_id0[ *it_M ] ){ 226 | Pattern_SL.erase( it_M ); 227 | break; 228 | } 229 | 230 | } 231 | // b) Erase SL from FS1 "BBS" 232 | for( it_K = Pattern_FS1.begin(); it_K != Pattern_FS1.end(); it_K++ ){ 233 | list::iterator it_M; 234 | for( it_M = Pattern_SL.begin(); it_M != Pattern_SL.end(); it_M++ ) 235 | if( flank_id0[ *it_K + 1 ] == flank_id0[ *it_M ] ){ 236 | Pattern_SL.erase( it_M ); 237 | break; 238 | } 239 | } 240 | // c) Erase SL from FS2 "BSS" 241 | for( it_K = Pattern_FS1.begin(); it_K != Pattern_FS1.end(); it_K++ ){ 242 | list::iterator it_M; 243 | for( it_M = Pattern_SL.begin(); it_M != Pattern_SL.end(); it_M++ ) 244 | if( flank_id0[ *it_K ] == flank_id0[ *it_M ] ){ 245 | Pattern_SL.erase( it_M ); 246 | break; 247 | } 248 | } 249 | 250 | 251 | boost::mutex::scoped_lock lock(mutex); 252 | //CENTROID PATTERN COMPUTATION & UNCERTAINTY 253 | rec_x.clear(); 254 | rec_y.clear(); 255 | 256 | HumanPose( &rec_x, &rec_y, Pattern_LA, TYPE_LA, flank_id0, flank_id1, laser_x, laser_y); 257 | HumanPose( &rec_x, &rec_y, Pattern_FS1, TYPE_FS, flank_id0, flank_id1, laser_x, laser_y); 258 | HumanPose( &rec_x, &rec_y, Pattern_FS2, TYPE_FS, flank_id0, flank_id1, laser_x, laser_y); 259 | HumanPose( &rec_x, &rec_y, Pattern_SL, TYPE_SL, flank_id0, flank_id1, laser_x, laser_y); 260 | } 261 | 262 | 263 | // Mean value of the 'size' adjacent values 264 | void LaserFilter_Mean( vector *vector_r, unsigned size ){ 265 | for( unsigned i = 0; i < ( (*vector_r).size() - size ); i++ ){ 266 | double mean = 0; 267 | for( unsigned k = 0; k < size; k++ ){ 268 | mean += (*vector_r)[ i + k ]; 269 | } 270 | (*vector_r)[ i ] = mean / size; 271 | } 272 | } 273 | 274 | 275 | // Reports a found string pattern in a list 276 | void FindPattern( string str, string pattern, list *element_found ){ 277 | size_t found = 0; 278 | 279 | while( string::npos != ( found = str.find( pattern, found ) ) ){ 280 | (*element_found).push_back( found ); 281 | found++; 282 | } 283 | 284 | } 285 | 286 | 287 | // Performs the antropometric validation of the leg patterns 288 | void ValidatePattern( list *Pattern_list, int TYPE, vector flank_id0, vector flank_id1, vector laser_x, vector laser_y){ 289 | 290 | double ANTRO_a_1, ANTRO_a_2, ANTRO_b, ANTRO_c; // Antropometric values from patterns to compare with constants. 291 | bool SavePattern = true; 292 | bool cond_a = true, cond_b = true, cond_c = true; 293 | list::iterator it; 294 | 295 | for( it = (*Pattern_list).begin(); it != (*Pattern_list).end(); it++ ){ 296 | 297 | // Obtain antropometric values 298 | switch( TYPE ){ 299 | case TYPE_LA: //BSBS 300 | ANTRO_a_1 = Dist2D( laser_x[ flank_id1[ *it ] ], laser_y[ flank_id1[ *it ] ], laser_x[ flank_id0[ *it + 1 ] ], laser_y[ flank_id0[ *it + 1 ] ]); 301 | ANTRO_a_2 = Dist2D( laser_x[ flank_id1[ *it + 2 ] ], laser_y[ flank_id1[ *it + 2 ] ], laser_x[ flank_id0[ *it + 3 ] ], laser_y[ flank_id0[ *it + 3 ] ]); 302 | ANTRO_b = Dist2D( laser_x[ flank_id0[ *it + 1 ] ], laser_y[ flank_id0[ *it + 1 ] ], laser_x[ flank_id1[ *it + 2 ] ], laser_y[ flank_id1[ *it + 2 ] ] ); 303 | ANTRO_c = 0; 304 | cond_a = ( ( ANTRO_a_1 >= ANTRO_a0 ) && ( ANTRO_a_1 <= ANTRO_a1 ) ) && ( ( ANTRO_a_2 >= ANTRO_a0 ) && ( ANTRO_a_2 <= ANTRO_a1 ) ); 305 | cond_b = ( ( ANTRO_b >= ANTRO_b0 ) && ( ANTRO_b <= ANTRO_b1 ) ); 306 | cond_c = true; 307 | break; 308 | case TYPE_FS: // BBS & BSS 309 | ANTRO_a_1 = Dist2D( laser_x[ flank_id1[ *it ] ], laser_y[ flank_id1[ *it ] ], laser_x[ flank_id0[ *it + 1 ] ], laser_y[ flank_id0[ *it + 1 ] ]); 310 | ANTRO_a_2 = Dist2D( laser_x[ flank_id1[ *it + 1 ] ], laser_y[ flank_id1[ *it + 1 ] ], laser_x[ flank_id0[ *it + 2 ] ], laser_y[ flank_id0[ *it + 2 ] ]); 311 | ANTRO_b = Dist2D( laser_x[ flank_id0[ *it + 1 ] ], laser_y[ flank_id0[ *it + 1 ] ], laser_x[ flank_id1[ *it + 1 ] ], laser_y[ flank_id1[ *it + 1 ] ] ); 312 | ANTRO_c = 0; 313 | cond_a = ( ( ANTRO_a_1 >= ANTRO_a0 ) && ( ANTRO_a_1 <= ANTRO_a1 ) ) && ( ( ANTRO_a_2 >= ANTRO_a0 ) && ( ANTRO_a_2 <= ANTRO_a1 ) ); 314 | cond_b = ( ( ANTRO_b >= ANTRO_b0 ) && ( ANTRO_b <= ANTRO_b1 ) ); 315 | cond_c = true; 316 | break; 317 | case TYPE_SL: // BS 318 | ANTRO_a_1 = 0; 319 | ANTRO_a_2 = 0; 320 | ANTRO_b = 0; 321 | ANTRO_c = Dist2D( laser_x[ flank_id1[ *it ] ], laser_y[ flank_id1[ *it ] ], laser_x[ flank_id0[ *it + 1 ] ], laser_y[ flank_id0[ *it + 1 ] ]); 322 | cond_a = true; 323 | cond_b = true; 324 | cond_c = ( ( ANTRO_c >= ANTRO_c0 ) && ( ANTRO_c <= ANTRO_c1 ) ); 325 | break; 326 | } 327 | 328 | SavePattern = cond_a && cond_b && cond_c; 329 | 330 | if( !SavePattern ){ 331 | it = (*Pattern_list).erase( it ); 332 | it--; 333 | } 334 | } 335 | } 336 | 337 | 338 | // Euclidean distance between two coordinate points 339 | double Dist2D( double x0, double y0, double x1, double y1 ){ 340 | return sqrt( pow( x0 - x1, 2 ) + pow( y0 - y1, 2 ) ); 341 | } 342 | 343 | 344 | void HumanPose( vector *r_x, vector *r_y, list Pattern_list, int TYPE, vector flank_id0, vector flank_id1, vector laser_x, vector laser_y ){ 345 | 346 | double c_x, c_y; 347 | int l1, l2, l3, l4; 348 | int count; 349 | list::iterator it; 350 | 351 | for( it = Pattern_list.begin(); it != Pattern_list.end(); it++ ){ 352 | c_x = 0; 353 | c_y = 0; 354 | count = 0; 355 | 356 | l1 = flank_id1[ *it ]; 357 | l2 = flank_id0[ *it + 1 ]; 358 | 359 | switch( TYPE ){ 360 | case TYPE_LA: 361 | l3 = flank_id1[ *it + 2 ]; 362 | l4 = flank_id0[ *it + 3 ]; 363 | break; 364 | case TYPE_FS: 365 | l3 = flank_id1[ *it + 1 ]; 366 | l4 = flank_id0[ *it + 2 ]; 367 | break; 368 | case TYPE_SL: 369 | l3 = 1; 370 | l4 = 0; 371 | break; 372 | } 373 | 374 | for( int i = l1; i <= l2; i++ ){ 375 | c_x += laser_x[ i ]; 376 | c_y += laser_y[ i ]; 377 | count++; 378 | } 379 | for( int i = l3; i <= l4; i++ ){ 380 | c_x += laser_x[ i ]; 381 | c_y += laser_y[ i ]; 382 | count++; 383 | } 384 | 385 | c_x /= (double) count; 386 | c_y /= (double) count; 387 | 388 | (*r_x).push_back( c_x ); 389 | (*r_y).push_back( c_y ); 390 | } 391 | } 392 | 393 | 394 | // Validate distance between persons 395 | void ValidateDistance(){ 396 | boost::mutex::scoped_lock lock(mutex); 397 | int j = 0; 398 | while(j < (rec_x.size() - 1)) 399 | { 400 | // if the Euclidean distance between two persons are smaller than 401 | // the maximum width of a leg then the second person must be eliminated 402 | if (ANTRO_b1 > Dist2D(rec_x[j], rec_y[j], rec_x[j+1], rec_y[j+1])) 403 | { 404 | rec_x.erase(rec_x.begin() + (j + 1)); 405 | rec_y.erase(rec_y.begin() + (j + 1)); 406 | } 407 | else 408 | { 409 | j++; 410 | } 411 | } 412 | } 413 | --------------------------------------------------------------------------------