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;
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);
412 void faceImageCallback(
const sensor_msgs::Image::ConstPtr& image,
const opencv_apps::FaceArrayStamped::ConstPtr& faces)
415 boost::mutex::scoped_lock lock(mutex_);
418 if (label_mapper_->getMap().empty())
430 opencv_apps::FaceArrayStamped ret_msg = *faces;
431 for (
size_t i = 0; i < faces->faces.size(); ++i)
433 cv::Mat face_img, resized_image;
434 extractImage(cv_img, faces->faces[i], face_img, face_padding_);
437 double confidence = 0.0;
438 predict(face_img, label_id, confidence);
441 ret_msg.faces[i].label = label_mapper_->lookup(label_id);
442 ret_msg.faces[i].confidence = confidence;
445 if (publish_debug_image)
446 drawFace(cv_img, ret_msg.faces[i]);
449 if (publish_debug_image)
452 debug_img_pub_.
publish(debug_img);
456 catch (cv::Exception& e)
458 NODELET_ERROR_STREAM(
"error at image processing: " << e.err <<
" " << e.func <<
" " << e.file <<
" " << e.line);
462 bool trainCallback(opencv_apps::FaceRecognitionTrain::Request& req, opencv_apps::FaceRecognitionTrain::Response& res)
464 boost::mutex::scoped_lock lock(mutex_);
467 std::vector<cv::Mat> images(req.images.size());
468 bool use_roi = !req.rects.empty();
470 if (use_roi && req.images.size() != req.rects.size())
472 throw std::length_error(
"req.images.size() != req.rects.size()");
475 for (
size_t i = 0; i < req.images.size(); ++i)
477 sensor_msgs::Image img = req.images[i];
482 extractImage(images[i], req.rects[i], face_img);
483 images[i] = face_img;
486 std::vector<std::string> labels(req.labels.begin(), req.labels.end());
487 train(images, labels);
491 catch (cv::Exception& e)
493 std::stringstream ss;
494 ss <<
"error at training: " << e.err <<
" " << e.func <<
" " << e.file <<
" " << e.line;
496 res.error = ss.str();
503 boost::mutex::scoped_lock lock(mutex_);
505 bool need_recreate_model =
false;
506 bool need_retrain =
false;
508 use_saved_data_ = config.use_saved_data;
509 save_train_data_ = config.save_train_data;
510 face_padding_ = config.face_padding;
512 if (face_model_size_.width != config.face_model_width)
514 face_model_size_.width = config.face_model_width;
517 if (face_model_size_.height != config.face_model_height)
519 face_model_size_.height = config.face_model_height;
523 if (data_dir_ != config.data_dir)
525 data_dir_ = config.data_dir;
528 storage_.reset(
new Storage(fs::path(data_dir_)));
531 if (config_.model_method != config.model_method)
533 need_recreate_model =
true;
536 if (config_.model_num_components != config.model_num_components)
538 need_recreate_model =
true;
541 if (config.model_method ==
"LBPH" &&
542 (config_.lbph_radius != config.lbph_radius || config_.lbph_neighbors != config.lbph_neighbors ||
543 config_.lbph_grid_x != config.lbph_grid_x || config_.lbph_grid_y != config.lbph_grid_y))
545 need_recreate_model =
true;
548 if (need_recreate_model)
552 if (config.model_method ==
"eigen")
555 #if (CV_MAJOR_VERSION >= 4) || (CV_MAJOR_VERSION >= 3 && CV_MINOR_VERSION >= 3) 556 model_ = face::EigenFaceRecognizer::create(config.model_num_components, config.model_threshold);
558 model_ = face::createEigenFaceRecognizer(config.model_num_components, config.model_threshold);
561 else if (config.model_method ==
"fisher")
563 #if (CV_MAJOR_VERSION >= 4) || (CV_MAJOR_VERSION >= 3 && CV_MINOR_VERSION >= 3) 564 model_ = face::FisherFaceRecognizer::create(config.model_num_components, config.model_threshold);
566 model_ = face::createFisherFaceRecognizer(config.model_num_components, config.model_threshold);
569 else if (config.model_method ==
"LBPH")
571 #if (CV_MAJOR_VERSION >= 4) || (CV_MAJOR_VERSION >= 3 && CV_MINOR_VERSION >= 3) 572 model_ = face::LBPHFaceRecognizer::create(config.lbph_radius, config.lbph_neighbors, config.lbph_grid_x,
575 model_ = face::createLBPHFaceRecognizer(config.lbph_radius, config.lbph_neighbors, config.lbph_grid_x,
581 catch (cv::Exception& e)
593 catch (cv::Exception& e)
599 if (config_.model_threshold != config.model_threshold)
603 #if CV_MAJOR_VERSION >= 3 604 if (face::BasicFaceRecognizer* model = dynamic_cast<face::BasicFaceRecognizer*>(model_.get()))
606 model->setThreshold(config.model_threshold);
608 else if (face::LBPHFaceRecognizer* model = dynamic_cast<face::LBPHFaceRecognizer*>(model_.get()))
610 model->setThreshold(config.model_threshold);
613 model_->set(
"threshold", config.model_threshold);
616 catch (cv::Exception& e)
631 async_ = boost::make_shared<message_filters::Synchronizer<ApproximateSyncPolicy> >(queue_size_);
632 async_->connectInput(img_sub_, face_sub_);
633 async_->registerCallback(boost::bind(&FaceRecognitionNodelet::faceImageCallback,
this,
boost::placeholders::_1,
638 sync_ = boost::make_shared<message_filters::Synchronizer<SyncPolicy> >(queue_size_);
639 sync_->connectInput(img_sub_, face_sub_);
664 face_model_size_ = cv::Size(190, 90);
667 cfg_srv_ = boost::make_shared<Server>(*pnh_);
668 Server::CallbackType
f =
670 cfg_srv_->setCallback(f);
673 pnh_->param(
"approximate_sync", use_async_,
false);
674 pnh_->param(
"queue_size", queue_size_, 100);
677 debug_img_pub_ = advertise<sensor_msgs::Image>(*pnh_,
"debug_image", 1);
678 face_pub_ = advertise<opencv_apps::FaceArrayStamped>(*pnh_,
"output", 1);
679 train_srv_ = pnh_->advertiseService(
"train", &FaceRecognitionNodelet::trainCallback,
this);
694 ROS_WARN(
"DeprecationWarning: Nodelet face_recognition/face_recognition is deprecated, " 695 "and renamed to opencv_apps/face_recognition.");
701 #ifdef USE_PLUGINLIB_CLASS_LIST_MACROS_H
~FaceRecognitionNodelet()
CvImageConstPtr toCvShare(const sensor_msgs::ImageConstPtr &source, const std::string &encoding=std::string())
#define NODELET_INFO_STREAM(...)
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
boost::shared_ptr< Server > cfg_srv_
Nodelet to automatically subscribe/unsubscribe topics according to subscription of advertised topics...
void init(const M_string &remappings)
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)
Demo code for equalizeHist function.
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_
sensor_msgs::ImagePtr toImageMsg() const
PLUGINLIB_EXPORT_CLASS(opencv_apps::FaceRecognitionNodelet, nodelet::Nodelet)
void publish(const boost::shared_ptr< M > &message) const
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
const std::map< std::string, int > & getMap() const
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())
#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_
uint32_t getNumSubscribers() const
dynamic_reconfigure::Server< Config > Server
ros::ServiceServer train_srv_
void predict(const cv::Mat &img, int &label, double &confidence)
#define NODELET_DEBUG(...)
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)