faces.cpp
Go to the documentation of this file.
1 /*********************************************************************
2  * Faces-specific computer vision algorithms.
3  *
4  **********************************************************************
5  *
6  * Software License Agreement (BSD License)
7  *
8  * Copyright (c) 2008, Willow Garage, Inc.
9  * All rights reserved.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  *
15  * * Redistributions of source code must retain the above copyright
16  * notice, this list of conditions and the following disclaimer.
17  * * Redistributions in binary form must reproduce the above
18  * copyright notice, this list of conditions and the following
19  * disclaimer in the documentation and/or other materials provided
20  * with the distribution.
21  * * Neither the name of the Willow Garage nor the names of its
22  * contributors may be used to endorse or promote products derived
23  * from this software without specific prior written permission.
24  *
25  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
28  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
29  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
30  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
31  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
32  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
33  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
35  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36  * POSSIBILITY OF SUCH DAMAGE.
37  *********************************************************************/
38 
39 /* Author: Caroline Pantofaru */
40 
41 #include <face_detector/faces.h>
42 #include <cfloat>
43 #include <algorithm>
44 #include <iostream>
45 
46 #include <ros/time.h>
47 #include <ros/console.h>
48 
49 #include <string>
50 #include <vector>
51 
52 namespace people
53 {
54 
56  list_(),
57  cam_model_(NULL)
58 {
59 }
60 
62 {
63  // Kill all the threads.
64  threads_.interrupt_all();
65  threads_.join_all();
66  delete face_go_mutex_;
67  face_go_mutex_ = NULL;
68 
69  for (int i = list_.size(); i > 0; i--)
70  {
71  list_.pop_back();
72  }
73 
74  cam_model_ = 0;
75 }
76 
77 /* Note: The multi-threading in this file is left over from a previous incarnation that allowed multiple
78  * cascades to be run at once. It hasn't been removed in case we want to return to that model, and since
79  * there isn't much overhead. Right now, however, only one classifier is being run per instantiation
80  * of the face_detector node.
81  */
82 
83 void Faces::initFaceDetectionDisparity(uint num_cascades, std::string haar_classifier_filename, double face_size_min_m,
84  double face_size_max_m, double max_face_z_m, double face_sep_dist_m)
85 {
86  images_ready_ = 0;
87 
88  face_size_min_m_ = face_size_min_m;
89  face_size_max_m_ = face_size_max_m;
90  max_face_z_m_ = max_face_z_m;
91  face_sep_dist_m_ = face_sep_dist_m;
92 
93  face_go_mutex_ = new boost::mutex();
94  bool cascade_ok = cascade_.load(haar_classifier_filename);
95 
96  if (!cascade_ok)
97  {
98  ROS_ERROR_STREAM("Cascade file " << haar_classifier_filename << " doesn't exist.");
99  return;
100  }
101  threads_.create_thread(boost::bind(&Faces::faceDetectionThreadDisparity, this, 0));
102 }
103 
105 std::vector<Box2D3D> Faces::detectAllFacesDisparity(const cv::Mat &image, double threshold,
106  const cv::Mat &disparity_image,
108 {
109  faces_.clear();
110 
111  // Convert the image to grayscale, if necessary.
112  if (image.channels() == 1)
113  {
114  cv_image_gray_.create(image.size(), CV_8UC1);
115  image.copyTo(cv_image_gray_);
116  }
117  else if (image.channels() == 3)
118  {
119  cv_image_gray_.create(image.size(), CV_8UC1);
120  cv::cvtColor(image, cv_image_gray_, CV_BGR2GRAY);
121  }
122  else
123  {
124  std::cerr << "Unknown image type" << std::endl;
125  return faces_;
126  }
127 
128  disparity_image_ = &disparity_image;
129  cam_model_ = cam_model;
130 
131  // Tell the face detection threads that they can run once.
133  boost::mutex::scoped_lock fgmlock(*(face_go_mutex_));
134  images_ready_++;
135  fgmlock.unlock();
136 
137  face_detection_ready_cond_.notify_all();
138 
139  boost::mutex::scoped_lock fdmlock(face_done_mutex_);
140  while (num_threads_to_wait_for_ > 0)
141  {
142  face_detection_done_cond_.wait(fdmlock);
143  }
144 
145  return faces_;
146 }
147 
150 {
151  while (true)
152  {
153  boost::mutex::scoped_lock fgmlock(*(face_go_mutex_));
154  boost::mutex::scoped_lock tlock(t_mutex_, boost::defer_lock);
155  while (true)
156  {
157  tlock.lock();
158  if (images_ready_)
159  {
160  --images_ready_;
161  tlock.unlock();
162  break;
163  }
164  tlock.unlock();
165  face_detection_ready_cond_.wait(fgmlock);
166  }
167 
168  // Find the faces using OpenCV's haar cascade object detector.
169  cv::Point3d p3_1(0, 0, max_face_z_m_);
170  cv::Point3d p3_2(face_size_min_m_, 0, max_face_z_m_);
171  cv::Point2d p2_1 = (cam_model_->left()).project3dToPixel(p3_1);
172  cv::Point2d p2_2 = (cam_model_->left()).project3dToPixel(p3_2);
173  int this_min_face_size = static_cast<int>(floor(fabs(p2_2.x - p2_1.x)));
174 
175  std::vector<cv::Rect> faces_vec;
176  cascade_.detectMultiScale(cv_image_gray_, faces_vec, 1.2, 2, CV_HAAR_DO_CANNY_PRUNING,
177  cv::Size(this_min_face_size, this_min_face_size));
178 
179  // Filter the faces using depth information, if available.
180  // Currently checks that the actual face size is within the given limits.
181  cv::Scalar color(0, 255, 0);
182  Box2D3D one_face;
183  double avg_disp = 0.0;
184  cv::Mat uvd(1, 3, CV_32FC1);
185  cv::Mat xyz(1, 3, CV_32FC1);
186  // For each face...
187  for (uint iface = 0; iface < faces_vec.size(); iface++) // face_seq->total; iface++) {
188  {
189  one_face.status = "good";
190 
191  one_face.box2d = faces_vec[iface];
192  one_face.id = i; // The cascade that computed this face.
193 
194  // Get the median disparity in the middle half of the bounding box.
195  cv::Mat disp_roi_shallow(*disparity_image_,
196  cv::Rect(floor(one_face.box2d.x + 0.25 * one_face.box2d.width),
197  floor(one_face.box2d.y + 0.25 * one_face.box2d.height),
198  floor(one_face.box2d.x + 0.75 * one_face.box2d.width) -
199  floor(one_face.box2d.x + 0.25 * one_face.box2d.width) + 1,
200  floor(one_face.box2d.y + 0.75 * one_face.box2d.height) -
201  floor(one_face.box2d.y + 0.25 * one_face.box2d.height) + 1));
202  cv::Mat disp_roi = disp_roi_shallow.clone();
203  cv::Mat tmat = disp_roi.reshape(1, disp_roi.rows * disp_roi.cols);
204  cv::Mat tmat_sorted;
205  cv::sort(tmat, tmat_sorted, CV_SORT_EVERY_COLUMN + CV_SORT_DESCENDING);
206 
207  // Get the middle valid disparity (-1 disparities are invalid)
208  avg_disp = tmat_sorted.at<float>(floor(cv::countNonZero(tmat_sorted >= 0.0) / 2.0));
209 
210  // Fill in the rest of the face data structure.
211  one_face.center2d = cv::Point2d(one_face.box2d.x + one_face.box2d.width / 2.0,
212  one_face.box2d.y + one_face.box2d.height / 2.0);
213  one_face.width2d = one_face.box2d.width;
214 
215  // If the median disparity was valid and the face is a reasonable size, the face status is "good".
216  // If the median disparity was valid but the face isn't a reasonable size, the face status is "bad".
217  // Otherwise, the face status is "unknown".
218  if (avg_disp > 0)
219  {
220  cam_model_->projectDisparityTo3d(cv::Point2d(0.0, 0.0), avg_disp, p3_1);
221  cam_model_->projectDisparityTo3d(cv::Point2d(one_face.box2d.width, 0.0), avg_disp, p3_2);
222  one_face.width3d = fabs(p3_2.x - p3_1.x);
223  cam_model_->projectDisparityTo3d(one_face.center2d, avg_disp, one_face.center3d);
224  if (one_face.center3d.z > max_face_z_m_ ||
225  one_face.width3d < face_size_min_m_ ||
226  one_face.width3d > face_size_max_m_)
227  {
228  one_face.status = "bad";
229  }
230  }
231  else
232  {
233  one_face.width3d = 0.0;
234  one_face.center3d = cv::Point3d(0.0, 0.0, 0.0);
235  one_face.status = "unknown";
236  }
237 
238  // Add faces to the output .
239  // lock faces
240  boost::mutex::scoped_lock lock(face_mutex_);
241  faces_.push_back(one_face);
242  lock.unlock();
243  }
244 
245  boost::mutex::scoped_lock fdmlock(face_done_mutex_);
247  fdmlock.unlock();
248  face_detection_done_cond_.notify_one();
249  }
250 }
251 
253 void Faces::initFaceDetectionDepth(uint num_cascades, std::string haar_classifier_filename, double face_size_min_m,
254  double face_size_max_m, double max_face_z_m, double face_sep_dist_m)
255 {
256  images_ready_ = 0;
257 
258  face_size_min_m_ = face_size_min_m;
259  face_size_max_m_ = face_size_max_m;
260  max_face_z_m_ = max_face_z_m;
261  face_sep_dist_m_ = face_sep_dist_m;
262 
263  face_go_mutex_ = new boost::mutex();
264  bool cascade_ok = cascade_.load(haar_classifier_filename);
265 
266  if (!cascade_ok)
267  {
268  ROS_ERROR_STREAM("Cascade file " << haar_classifier_filename << " doesn't exist.");
269  return;
270  }
271  threads_.create_thread(boost::bind(&Faces::faceDetectionThreadDepth, this, 0));
272 }
273 
275 std::vector<Box2D3D> Faces::detectAllFacesDepth(const cv::Mat &image, double threshold, const cv::Mat &dimage,
277 {
278  faces_.clear();
279 
280  // Convert the image to grayscale, if necessary.
281 
282  if (image.channels() == 1)
283  {
284  cv_image_gray_.create(image.size(), CV_8UC1);
285  image.copyTo(cv_image_gray_);
286  }
287  else if (image.channels() == 3)
288  {
289  cv_image_gray_.create(image.size(), CV_8UC1);
290  cv::cvtColor(image, cv_image_gray_, CV_BGR2GRAY);
291  }
292  else
293  {
294  std::cerr << "Unknown image type" << std::endl;
295  return faces_;
296  }
297 
298  depth_image_ = &dimage;
299  cam_model_ = cam_model;
300 
301  // Tell the face detection threads that they can run once.
303  boost::mutex::scoped_lock fgmlock(*(face_go_mutex_));
304  images_ready_++;
305  fgmlock.unlock();
306 
307  face_detection_ready_cond_.notify_all();
308 
309  boost::mutex::scoped_lock fdmlock(face_done_mutex_);
310  while (num_threads_to_wait_for_ > 0)
311  {
312  face_detection_done_cond_.wait(fdmlock);
313  }
314 
315  return faces_;
316 }
317 
320 {
321  while (true)
322  {
323  boost::mutex::scoped_lock fgmlock(*(face_go_mutex_));
324  boost::mutex::scoped_lock tlock(t_mutex_, boost::defer_lock);
325  while (true)
326  {
327  tlock.lock();
328  if (images_ready_)
329  {
330  --images_ready_;
331  tlock.unlock();
332  break;
333  }
334  tlock.unlock();
335  face_detection_ready_cond_.wait(fgmlock);
336  }
337 
338  // Find the faces using OpenCV's haar cascade object detector.
339  cv::Point3d p3_1(0, 0, max_face_z_m_);
340  cv::Point3d p3_2(face_size_min_m_, 0, max_face_z_m_);
341  cv::Point2d p2_1 = (cam_model_->left()).project3dToPixel(p3_1);
342  cv::Point2d p2_2 = (cam_model_->left()).project3dToPixel(p3_2);
343  int this_min_face_size = static_cast<int>(floor(fabs(p2_2.x - p2_1.x)));
344 
345  std::vector<cv::Rect> faces_vec;
346  cascade_.detectMultiScale(cv_image_gray_, faces_vec, 1.2, 2, CV_HAAR_DO_CANNY_PRUNING,
347  cv::Size(this_min_face_size, this_min_face_size));
348 
349  // Filter the faces using depth information, if available.
350  // Currently checks that the actual face size is within the given limits.
351  cv::Scalar color(0, 255, 0);
352  Box2D3D one_face;
353  double avg_d = 0.0;
354  cv::Mat uvd(1, 3, CV_32FC1);
355  cv::Mat xyz(1, 3, CV_32FC1);
356  // For each face...
357  for (uint iface = 0; iface < faces_vec.size(); iface++)
358  {
359  one_face.status = "good";
360 
361  one_face.box2d = faces_vec[iface];
362  one_face.id = i; // The cascade that computed this face.
363 
364  // Get the median depth in the middle half of the bounding box.
365  cv::Mat depth_roi_shallow(*depth_image_,
366  cv::Rect(floor(one_face.box2d.x + 0.25 * one_face.box2d.width),
367  floor(one_face.box2d.y + 0.25 * one_face.box2d.height),
368  floor(one_face.box2d.x + 0.75 * one_face.box2d.width) -
369  floor(one_face.box2d.x + 0.25 * one_face.box2d.width) + 1,
370  floor(one_face.box2d.y + 0.75 * one_face.box2d.height) -
371  floor(one_face.box2d.y + 0.25 * one_face.box2d.height) + 1));
372 
373  // Fill in the rest of the face data structure.
374  one_face.center2d = cv::Point2d(one_face.box2d.x + one_face.box2d.width / 2.0,
375  one_face.box2d.y + one_face.box2d.height / 2.0);
376  one_face.width2d = one_face.box2d.width;
377 
378  // If the median depth was valid and the face is a reasonable size, the face status is "good".
379  // If the median depth was valid but the face isn't a reasonable size, the face status is "bad".
380  // Otherwise, the face status is "unknown".
381 
382  std::vector<float> depths;
383  // Copy the depths removing the nans.
384  for (int i = 0; i < depth_roi_shallow.rows; i++)
385  {
386  const float* dptr = depth_roi_shallow.ptr<float>(i);
387  for (int j = 0; j < depth_roi_shallow.cols; j++)
388  {
389  if (dptr[j] == dptr[j])
390  {
391  depths.push_back(dptr[j]);
392  }
393  }
394  }
395 
396  std::vector<float>::iterator dbegin = depths.begin();
397  std::vector<float>::iterator dend = depths.end();
398 
399  if (depths.size() > 0)
400  {
401  std::sort(dbegin, dend);
402  avg_d = depths[floor(depths.size() / 2.0)];
403 
404  one_face.width3d = fabs((cam_model_->left()).getDeltaX(one_face.box2d.width, avg_d));
405  one_face.center3d = (cam_model_->left()).projectPixelTo3dRay(one_face.center2d);
406 
407  one_face.center3d = (avg_d / one_face.center3d.z) * one_face.center3d;
408  if (one_face.center3d.z > max_face_z_m_ ||
409  one_face.width3d < face_size_min_m_ ||
410  one_face.width3d > face_size_max_m_)
411  {
412  one_face.status = "bad";
413  }
414  }
415  else
416  {
417  one_face.width3d = 0.0;
418  one_face.center3d = cv::Point3d(0.0, 0.0, 0.0);
419  one_face.status = "unknown";
420  }
421 
422  // Add faces to the output vector.
423  // lock faces
424  boost::mutex::scoped_lock lock(face_mutex_);
425  faces_.push_back(one_face);
426  lock.unlock();
427  }
428 
429  boost::mutex::scoped_lock fdmlock(face_done_mutex_);
431  fdmlock.unlock();
432  face_detection_done_cond_.notify_one();
433  }
434 }
435 }; // namespace people
void initFaceDetectionDepth(uint num_cascades, std::string haar_classifier_filename, double face_size_min_m, double face_size_max_m, double max_face_z_m, double face_sep_dist_m)
Initialize the face detector with images and depth.
Definition: faces.cpp:253
std::vector< Face > list_
Definition: faces.h:165
boost::condition face_detection_ready_cond_
Definition: faces.h:176
A structure for holding information about boxes in 2d and 3d.
Definition: faces.h:69
std::vector< Box2D3D > detectAllFacesDepth(const cv::Mat &image, double threshold, const cv::Mat &depth_image, image_geometry::StereoCameraModel *cam_model)
Detect all faces in an image and depth image.
Definition: faces.cpp:275
int num_threads_to_wait_for_
Definition: faces.h:177
std::string status
Definition: faces.h:76
std::vector< Box2D3D > faces_
Definition: faces.h:166
const PinholeCameraModel & left() const
double width3d
Definition: faces.h:74
cv::Mat const * disparity_image_
Definition: faces.h:169
cv::Mat const * depth_image_
Definition: faces.h:170
Definition: faces.h:63
cv::Point3d center3d
Definition: faces.h:72
image_geometry::StereoCameraModel * cam_model_
Definition: faces.h:171
double face_size_min_m_
Definition: faces.h:99
void projectDisparityTo3d(const cv::Point2d &left_uv_rect, float disparity, cv::Point3d &xyz) const
double width2d
Definition: faces.h:73
boost::thread_group threads_
Definition: faces.h:175
double face_size_max_m_
Definition: faces.h:100
void faceDetectionThreadDepth(uint i)
Definition: faces.cpp:319
double max_face_z_m_
Definition: faces.h:101
cv::Rect box2d
Definition: faces.h:75
boost::condition face_detection_done_cond_
Definition: faces.h:176
double face_sep_dist_m_
Definition: faces.h:103
cv::Mat cv_image_gray_
Definition: faces.h:168
boost::mutex t_mutex_
Definition: faces.h:173
void faceDetectionThreadDisparity(uint i)
Definition: faces.cpp:149
int images_ready_
Definition: faces.h:178
cv::Point2d center2d
Definition: faces.h:71
boost::mutex * face_go_mutex_
Definition: faces.h:174
void initFaceDetectionDisparity(uint num_cascades, std::string haar_classifier_filename, double face_size_min_m, double face_size_max_m, double max_face_z_m, double face_sep_dist_m)
Initialize the face detector with images and disparities.
Definition: faces.cpp:83
boost::mutex face_mutex_
Definition: faces.h:173
boost::mutex face_done_mutex_
Definition: faces.h:173
#define ROS_ERROR_STREAM(args)
cv::CascadeClassifier cascade_
Definition: faces.h:181
std::vector< Box2D3D > detectAllFacesDisparity(const cv::Mat &image, double threshold, const cv::Mat &disparity_image, image_geometry::StereoCameraModel *cam_model)
Detect all faces in an image and disparity image.
Definition: faces.cpp:105


face_detector
Author(s): Caroline Pantofaru
autogenerated on Sun Feb 21 2021 03:56:45