36 #include <boost/filesystem.hpp> 37 #include <boost/shared_ptr.hpp> 38 #include <boost/thread.hpp> 39 #include <boost/version.hpp> 44 #include <opencv2/opencv.hpp> 49 #include <dynamic_reconfigure/server.h> 59 #include <opencv_apps/FaceArrayStamped.h> 60 #include <opencv_apps/FaceRecognitionTrain.h> 61 #include <opencv_apps/FaceRecognitionConfig.h> 64 #if BOOST_VERSION < 105000 67 namespace fs = boost::filesystem;
70 #if CV_MAJOR_VERSION >= 3 71 #include <opencv2/face.hpp> 72 namespace face = cv::face;
77 #if CV_MAJOR_VERSION >= 4 78 #include <opencv2/imgcodecs/legacy/constants_c.h> 79 #include <opencv2/imgproc/imgproc_c.h> 85 #if BOOST_VERSION < 105000 93 path& path::append<typename path::iterator>(
typename path::iterator lhs,
typename path::iterator rhs,
94 const codecvt_type& cvt)
96 for (; lhs != rhs; ++lhs)
102 path::const_iterator it(p.begin());
103 std::string user_dir = (*it).string();
104 if (user_dir.length() == 0 || user_dir[0] !=
'~')
108 if (user_dir.length() == 1)
110 homedir = getenv(
"HOME");
111 if (homedir ==
nullptr)
113 homedir = getpwuid(getuid())->pw_dir;
118 std::string uname = user_dir.substr(1, user_dir.length());
119 passwd* pw = getpwnam(uname.c_str());
122 homedir = pw->pw_dir;
124 ret = path(std::string(homedir));
125 return ret.append(++it, p.end(), path::codecvt());
134 std::map<std::string, int>
m_;
137 void add(std::vector<std::string>& l)
140 for (std::map<std::string, int>::const_iterator it = m_.begin(); it != m_.end(); ++it)
145 for (
const std::string& i : l)
147 if (m_.find(i) == m_.end())
154 std::vector<int>
get(std::vector<std::string>& v)
156 std::vector<int> ret(v.size());
157 for (
size_t i = 0; i < v.size(); ++i)
165 for (std::map<std::string, int>::const_iterator it = m_.begin(); it != m_.end(); ++it)
167 if (it->second ==
id)
172 const std::map<std::string, int>&
getMap()
const 180 for (std::map<std::string, int>::const_iterator it = m_.begin(); it != m_.end(); ++it)
196 if (!fs::exists(base_dir_))
200 if (!fs::is_directory(base_dir_))
202 std::stringstream ss;
203 ss << base_dir_ <<
" is not a directory";
204 throw std::runtime_error(ss.str());
209 if (!fs::create_directories(base_dir_))
211 std::stringstream ss;
212 ss <<
"failed to initialize directory: " << base_dir_;
213 throw std::runtime_error(ss.str());
217 void load(std::vector<cv::Mat>& images, std::vector<std::string>& labels,
bool append =
true)
224 fs::directory_iterator end;
225 for (fs::directory_iterator it(base_dir_); it != end; ++it)
227 if (fs::is_directory(*it))
229 std::string label = it->path().stem().string();
230 for (fs::directory_iterator cit(it->path()); cit != end; ++cit)
232 if (fs::is_directory(*cit))
234 fs::path file_path = cit->path();
237 cv::Mat img = cv::imread(file_path.string(), CV_LOAD_IMAGE_COLOR);
238 labels.push_back(label);
239 images.push_back(img);
241 catch (cv::Exception& e)
243 ROS_ERROR_STREAM(
"Error: load image from " << file_path <<
": " << e.what());
250 void save(
const std::vector<cv::Mat>& images,
const std::vector<std::string>& labels)
252 if (images.size() != labels.size())
254 throw std::length_error(
"images.size() != labels.size()");
256 for (
size_t i = 0; i < images.size(); ++i)
258 save(images[i], labels[i]);
262 void save(
const cv::Mat& image,
const std::string& label)
264 fs::path img_dir = base_dir_ / fs::path(label);
265 if (!fs::exists(img_dir) && !fs::create_directories(img_dir))
267 std::stringstream ss;
268 ss <<
"failed to initialize directory: " << img_dir;
269 throw std::runtime_error(ss.str());
271 fs::directory_iterator end;
273 for (fs::directory_iterator it(img_dir); it != end; ++it)
275 if (!fs::is_directory(*it))
279 std::stringstream ss;
281 ss << label <<
"_" << std::setw(6) << std::setfill(
'0') << file_count <<
".jpg";
282 fs::path file_path = img_dir / ss.str();
286 cv::imwrite(file_path.string(), image);
288 catch (cv::Exception& e)
297 typedef opencv_apps::FaceRecognitionConfig
Config;
298 typedef dynamic_reconfigure::Server<Config>
Server;
327 void drawFace(cv::Mat& img,
const opencv_apps::Face& face)
329 cv::Rect r(
int(face.face.x - face.face.width / 2.0 - face.face.width * face_padding_ / 2.0),
330 int(face.face.y - face.face.height / 2.0 - face.face.height * face_padding_ / 2.0),
331 int(face.face.width + face.face.width * face_padding_),
332 int(face.face.height + face.face.height * face_padding_));
333 cv::Scalar color(0.0, 0.0, 255.0);
335 cv::rectangle(img, r.tl(), r.br(), color, boldness, CV_AA);
337 double font_scale = 1.5;
338 int text_height = 20;
340 if (r.br().y + text_height > img.rows)
341 text_bl = r.tl() + cv::Point(0, -text_height);
343 text_bl = r.br() + cv::Point(-r.width, text_height);
344 std::stringstream ss;
345 ss << face.label <<
" (" << std::fixed << std::setprecision(2) << face.confidence <<
")";
346 cv::putText(img, ss.str(), text_bl, cv::FONT_HERSHEY_PLAIN, font_scale, color, boldness, CV_AA);
349 void extractImage(
const cv::Mat& img,
const opencv_apps::Rect& rect, cv::Mat& ret,
double padding = 0.0)
351 int x = std::max(0,
int(rect.x - rect.width / 2.0 - rect.width * padding / 2.0));
352 int y = std::max(0,
int(rect.y - rect.height / 2.0 - rect.height * padding / 2.0));
353 cv::Rect r(x, y, std::min(img.cols - x,
int(rect.width + rect.width * padding)),
354 std::min(img.rows - y,
int(rect.height + rect.height * padding)));
358 void extractImage(
const cv::Mat& img,
const opencv_apps::Face& face, cv::Mat& ret,
double padding = 0.0)
360 extractImage(img, face.face, ret, padding);
366 std::vector<cv::Mat> images;
367 std::vector<std::string> labels;
368 train(images, labels);
371 void train(std::vector<cv::Mat>& images, std::vector<std::string>& labels)
373 size_t new_image_size = images.size();
377 storage_->load(images, labels);
383 std::vector<cv::Mat> resized_images(images.size());
384 for (
int i = 0; i < images.size(); ++i)
386 cv::resize(images[i], resized_images[i], face_model_size_, 0, 0, cv::INTER_CUBIC);
387 cv::cvtColor(resized_images[i], resized_images[i], CV_BGR2GRAY);
390 label_mapper_->add(labels);
391 std::vector<int> ids = label_mapper_->get(labels);
394 model_->train(resized_images, ids);
396 if (save_train_data_ && new_image_size > 0)
398 std::vector<cv::Mat> new_images(images.begin(), images.begin() + new_image_size);
399 std::vector<std::string> new_labels(labels.begin(), labels.begin() + new_image_size);
400 storage_->save(new_images, new_labels);
404 void predict(
const cv::Mat& img,
int& label,
double& confidence)
407 cv::resize(img, resized_img, face_model_size_, 0, 0, cv::INTER_CUBIC);
408 cv::cvtColor(resized_img, resized_img, CV_BGR2GRAY);
409 model_->predict(resized_img, label, confidence);
413 const opencv_apps::FaceArrayStamped::ConstPtr& faces)
416 boost::mutex::scoped_lock lock(mutex_);
419 if (label_mapper_->getMap().empty())
431 opencv_apps::FaceArrayStamped ret_msg = *faces;
432 for (
size_t i = 0; i < faces->faces.size(); ++i)
434 cv::Mat face_img, resized_image;
435 extractImage(cv_img, faces->faces[i], face_img, face_padding_);
438 double confidence = 0.0;
439 predict(face_img, label_id, confidence);
442 ret_msg.faces[i].label = label_mapper_->lookup(label_id);
443 ret_msg.faces[i].confidence = confidence;
446 if (publish_debug_image)
447 drawFace(cv_img, ret_msg.faces[i]);
450 if (publish_debug_image)
453 debug_img_pub_.
publish(debug_img);
457 catch (cv::Exception& e)
459 NODELET_ERROR_STREAM(
"error at image processing: " << e.err <<
" " << e.func <<
" " << e.file <<
" " << e.line);
463 bool trainCallback(opencv_apps::FaceRecognitionTrain::Request& req, opencv_apps::FaceRecognitionTrain::Response& res)
465 boost::mutex::scoped_lock lock(mutex_);
468 std::vector<cv::Mat> images(req.images.size());
469 bool use_roi = !req.rects.empty();
471 if (use_roi && req.images.size() != req.rects.size())
473 throw std::length_error(
"req.images.size() != req.rects.size()");
476 for (
size_t i = 0; i < req.images.size(); ++i)
478 sensor_msgs::Image img = req.images[i];
483 extractImage(images[i], req.rects[i], face_img);
484 images[i] = face_img;
487 std::vector<std::string> labels(req.labels.begin(), req.labels.end());
488 train(images, labels);
492 catch (cv::Exception& e)
494 std::stringstream ss;
495 ss <<
"error at training: " << e.err <<
" " << e.func <<
" " << e.file <<
" " << e.line;
497 res.error = ss.str();
504 boost::mutex::scoped_lock lock(mutex_);
506 bool need_recreate_model =
false;
507 bool need_retrain =
false;
509 use_saved_data_ = config.use_saved_data;
510 save_train_data_ = config.save_train_data;
511 face_padding_ = config.face_padding;
513 if (face_model_size_.width != config.face_model_width)
515 face_model_size_.width = config.face_model_width;
518 if (face_model_size_.height != config.face_model_height)
520 face_model_size_.height = config.face_model_height;
524 if (data_dir_ != config.data_dir)
526 data_dir_ = config.data_dir;
529 storage_.reset(
new Storage(fs::path(data_dir_)));
532 if (config_.model_method != config.model_method)
534 need_recreate_model =
true;
537 if (config_.model_num_components != config.model_num_components)
539 need_recreate_model =
true;
542 if (config.model_method ==
"LBPH" &&
543 (config_.lbph_radius != config.lbph_radius || config_.lbph_neighbors != config.lbph_neighbors ||
544 config_.lbph_grid_x != config.lbph_grid_x || config_.lbph_grid_y != config.lbph_grid_y))
546 need_recreate_model =
true;
549 if (need_recreate_model)
553 if (config.model_method ==
"eigen")
556 #if (CV_MAJOR_VERSION >= 4) || (CV_MAJOR_VERSION >= 3 && CV_MINOR_VERSION >= 3) 557 model_ = face::EigenFaceRecognizer::create(config.model_num_components, config.model_threshold);
559 model_ = face::createEigenFaceRecognizer(config.model_num_components, config.model_threshold);
562 else if (config.model_method ==
"fisher")
564 #if (CV_MAJOR_VERSION >= 4) || (CV_MAJOR_VERSION >= 3 && CV_MINOR_VERSION >= 3) 565 model_ = face::FisherFaceRecognizer::create(config.model_num_components, config.model_threshold);
567 model_ = face::createFisherFaceRecognizer(config.model_num_components, config.model_threshold);
570 else if (config.model_method ==
"LBPH")
572 #if (CV_MAJOR_VERSION >= 4) || (CV_MAJOR_VERSION >= 3 && CV_MINOR_VERSION >= 3) 573 model_ = face::LBPHFaceRecognizer::create(config.lbph_radius, config.lbph_neighbors, config.lbph_grid_x,
576 model_ = face::createLBPHFaceRecognizer(config.lbph_radius, config.lbph_neighbors, config.lbph_grid_x,
582 catch (cv::Exception& e)
594 catch (cv::Exception& e)
600 if (config_.model_threshold != config.model_threshold)
604 #if CV_MAJOR_VERSION >= 3 605 if (face::BasicFaceRecognizer* model = dynamic_cast<face::BasicFaceRecognizer*>(model_.get()))
607 model->setThreshold(config.model_threshold);
609 else if (face::LBPHFaceRecognizer* model = dynamic_cast<face::LBPHFaceRecognizer*>(model_.get()))
611 model->setThreshold(config.model_threshold);
614 model_->set(
"threshold", config.model_threshold);
617 catch (cv::Exception& e)
632 async_ = boost::make_shared<message_filters::Synchronizer<ApproximateSyncPolicy> >(queue_size_);
633 async_->connectInput(img_sub_, face_sub_);
634 async_->registerCallback(boost::bind(&FaceRecognitionNodelet::faceImageCallback,
this, _1, _2));
638 sync_ = boost::make_shared<message_filters::Synchronizer<SyncPolicy> >(queue_size_);
639 sync_->connectInput(img_sub_, face_sub_);
640 sync_->registerCallback(boost::bind(&FaceRecognitionNodelet::faceImageCallback,
this, _1, _2));
657 face_model_size_ = cv::Size(190, 90);
660 cfg_srv_ = boost::make_shared<Server>(*pnh_);
661 Server::CallbackType
f = boost::bind(&FaceRecognitionNodelet::configCallback,
this, _1, _2);
662 cfg_srv_->setCallback(f);
665 pnh_->param(
"approximate_sync", use_async_,
false);
666 pnh_->param(
"queue_size", queue_size_, 100);
669 debug_img_pub_ = advertise<sensor_msgs::Image>(*pnh_,
"debug_image", 1);
670 face_pub_ = advertise<opencv_apps::FaceArrayStamped>(*pnh_,
"output", 1);
671 train_srv_ = pnh_->advertiseService(
"train", &FaceRecognitionNodelet::trainCallback,
this);
686 ROS_WARN(
"DeprecationWarning: Nodelet face_recognition/face_recognition is deprecated, " 687 "and renamed to opencv_apps/face_recognition.");
CvImageConstPtr toCvShare(const sensor_msgs::ImageConstPtr &source, const std::string &encoding=std::string())
#define NODELET_INFO_STREAM(...)
const std::map< std::string, int > & getMap() const
virtual void onInit()
Initialize nodehandles nh_ and pnh_. Subclass should call this method in its onInit method...
void configCallback(Config &config, uint32_t level)
opencv_apps::FaceRecognitionConfig Config
void publish(const boost::shared_ptr< M > &message) const
boost::shared_ptr< Server > cfg_srv_
Nodelet to automatically subscribe/unsubscribe topics according to subscription of advertised topics...
boost::shared_ptr< LabelMapper > label_mapper_
Demo code to calculate moments.
std::string lookup(int id)
void add(std::vector< std::string > &l)
void train(std::vector< cv::Mat > &images, std::vector< std::string > &labels)
bool trainCallback(opencv_apps::FaceRecognitionTrain::Request &req, opencv_apps::FaceRecognitionTrain::Response &res)
void drawFace(cv::Mat &img, const opencv_apps::Face &face)
void load(std::vector< cv::Mat > &images, std::vector< std::string > &labels, bool append=true)
message_filters::sync_policies::ExactTime< sensor_msgs::Image, opencv_apps::FaceArrayStamped > SyncPolicy
void faceImageCallback(const sensor_msgs::Image::ConstPtr &image, const opencv_apps::FaceArrayStamped::ConstPtr &faces)
Storage(const fs::path &base_dir)
#define NODELET_ERROR_STREAM(...)
ros::Publisher debug_img_pub_
PLUGINLIB_EXPORT_CLASS(opencv_apps::FaceRecognitionNodelet, nodelet::Nodelet)
std::map< std::string, int > m_
cv::Ptr< face::FaceRecognizer > model_
void subscribe()
This method is called when publisher is subscribed by other nodes. Set up subscribers in this method...
void save(const cv::Mat &image, const std::string &label)
boost::shared_ptr< Storage > storage_
#define NODELET_WARN_THROTTLE(rate,...)
image_transport::SubscriberFilter img_sub_
path user_expanded_path(const path &p)
void unsubscribe()
This method is called when publisher is unsubscribed by other nodes. Shut down subscribers in this me...
message_filters::Subscriber< opencv_apps::FaceArrayStamped > face_sub_
boost::shared_ptr< message_filters::Synchronizer< SyncPolicy > > sync_
void extractImage(const cv::Mat &img, const opencv_apps::Face &face, cv::Mat &ret, double padding=0.0)
message_filters::sync_policies::ApproximateTime< sensor_msgs::Image, opencv_apps::FaceArrayStamped > ApproximateSyncPolicy
CvImagePtr toCvCopy(const sensor_msgs::ImageConstPtr &source, const std::string &encoding=std::string())
virtual void onInit()
Initialize nodehandles nh_ and pnh_. Subclass should call this method in its onInit method...
boost::shared_ptr< message_filters::Synchronizer< ApproximateSyncPolicy > > async_
#define ROS_WARN_STREAM(args)
void subscribe(ImageTransport &it, const std::string &base_topic, uint32_t queue_size, const TransportHints &transport_hints=TransportHints())
uint32_t getNumSubscribers() const
#define ROS_INFO_STREAM(args)
void subscribe(ros::NodeHandle &nh, const std::string &topic, uint32_t queue_size, const ros::TransportHints &transport_hints=ros::TransportHints(), ros::CallbackQueueInterface *callback_queue=0)
ROSCPP_DECL std::string append(const std::string &left, const std::string &right)
cv::Size face_model_size_
#define ROS_ERROR_STREAM(args)
boost::shared_ptr< image_transport::ImageTransport > it_
dynamic_reconfigure::Server< Config > Server
ros::ServiceServer train_srv_
void predict(const cv::Mat &img, int &label, double &confidence)
#define NODELET_DEBUG(...)
sensor_msgs::ImagePtr toImageMsg() const
void extractImage(const cv::Mat &img, const opencv_apps::Rect &rect, cv::Mat &ret, double padding=0.0)
void save(const std::vector< cv::Mat > &images, const std::vector< std::string > &labels)