gaze  0.1.0
Perform gaze tracking with common webcams.
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Groups Pages
eye_like.cpp
1 #include "gaze/pipeline_steps/eye_like.h"
2 
3 #include <cmath>
4 
5 #include "opencv2/opencv.hpp"
6 #include "yaml-cpp/yaml.h"
7 
8 #include "gaze/util/config.h"
9 #include "gaze/util/data.h"
10 #include "gaze/util/dlibcv.h"
11 #include "gaze/util/pipeline_utils.h"
12 
13 
14 namespace gaze {
15 
31 namespace eyelike {
32 
41 double compute_dynamic_threshold(const cv::Mat& mat, double std_dev_factor) {
42  cv::Scalar stdMagnGrad, meanMagnGrad;
43  cv::meanStdDev(mat, meanMagnGrad, stdMagnGrad);
44  double std_dev = stdMagnGrad[0];
45  return std_dev_factor * std_dev + meanMagnGrad[0];
46 }
47 
57 cv::Mat compute_x_gradient(const cv::Mat& image) {
58  cv::Mat out(image.rows, image.cols, CV_64F);
59 
60  for (auto y = decltype(image.rows){0}; y < image.rows; ++y) {
61  const uchar *row = image.ptr<uchar>(y);
62  double *out_row = out.ptr<double>(y);
63 
64  out_row[0] = row[1] - row[0];
65  for (auto x = decltype(image.cols){1}; x < image.cols - 1; ++x) {
66  out_row[x] = (row[x + 1] - row[x - 1]) / 2.0;
67  }
68  out_row[image.cols - 1] = row[image.cols - 1] - row[image.cols - 2];
69  }
70 
71  return out;
72 }
73 
81 cv::Mat matrix_magnitude(const cv::Mat& mat_x, const cv::Mat& mat_y) {
82  cv::Mat mags(mat_x.rows, mat_x.cols, CV_64F);
83  for (auto y = decltype(mat_x.rows){0}; y < mat_x.rows; ++y) {
84  const double *Xr = mat_x.ptr<double>(y), *Yr = mat_y.ptr<double>(y);
85  double *row = mags.ptr<double>(y);
86  for (auto x = decltype(mat_x.cols){0}; x < mat_x.cols; ++x) {
87  double gX = Xr[x], gY = Yr[x];
88  double magnitude = std::sqrt((gX * gX) + (gY * gY));
89  row[x] = magnitude;
90  }
91  }
92  return mags;
93 }
94 
111 void test_possible_centers_formula(
112  int x, int y,
113  const cv::Mat& weight,
114  double gx, double gy,
115  cv::Mat& out) { // NOLINT
116  for (auto cy = decltype(out.rows){0}; cy < out.rows; ++cy) {
117  double *out_row = out.ptr<double>(cy);
118  const unsigned char *Wr = weight.ptr<unsigned char>(cy);
119  for (auto cx = decltype(out.cols){0}; cx < out.cols; ++cx) {
120  if (x == cx && y == cy) {
121  continue;
122  }
123  double dx = x - cx;
124  double dy = y - cy;
125  // normalize d
126  double magnitude = std::sqrt(dx * dx + dy * dy);
127  dx = dx / magnitude;
128  dy = dy / magnitude;
129  double dotProduct = dx * gx + dy * gy;
130  dotProduct = std::max(0.0, dotProduct);
131  out_row[cx] += dotProduct * dotProduct * Wr[cx];
132  }
133  }
134 }
135 
145 cv::Point unscale_point(cv::Point point, cv::Rect orig_size,
146  double fast_eye_width) {
147  double ratio = fast_eye_width / orig_size.width;
148  int x = std::round(point.x / ratio);
149  int y = std::round(point.y / ratio);
150  return cv::Point(x, y);
151 }
152 
165 cv::Point find_eye_center(
166  const cv::Mat& face, const cv::Rect& eye,
167  double gradient_threshold, double fast_eye_width = 50) {
168  cv::Rect eye_roi_cut = eye;
169  eye_roi_cut.width = std::min(eye.width, face.cols - eye.x);
170  eye_roi_cut.height = std::min(eye.height, face.rows - eye.y);
171  eye_roi_cut.x = std::max(eye.x, 0);
172  eye_roi_cut.y = std::max(eye.y, 0);
173 
174  cv::Mat eye_roi_unscaled = face(eye_roi_cut);
175  cv::Mat eye_roi;
176  cv::resize(
177  eye_roi_unscaled,
178  eye_roi,
179  cv::Size(fast_eye_width,
180  fast_eye_width / eye_roi_unscaled.cols * eye_roi_unscaled.rows));
181 
182  cv::Mat gradientX = compute_x_gradient(eye_roi);
183  cv::Mat gradientY = compute_x_gradient(eye_roi.t()).t();
184  cv::Mat mags = matrix_magnitude(gradientX, gradientY);
185  double gradientThresh = compute_dynamic_threshold(mags,
186  gradient_threshold);
187 
188  for (auto y = decltype(eye_roi.rows){0}; y < eye_roi.rows; ++y) {
189  double *x_row = gradientX.ptr<double>(y);
190  double *y_row = gradientY.ptr<double>(y);
191  const double *M_ROW = mags.ptr<double>(y);
192  for (auto x = decltype(eye_roi.cols){0}; x < eye_roi.cols; ++x) {
193  if (M_ROW[x] > gradientThresh) {
194  x_row[x] /= M_ROW[x];
195  y_row[x] /= M_ROW[x];
196  } else {
197  x_row[x] = 0.0;
198  y_row[x] = 0.0;
199  }
200  }
201  }
202 
203  cv::Mat weight;
204  cv::GaussianBlur(eye_roi, weight, cv::Size(5, 5), 0, 0);
205  weight = 255 - weight;
206 
207  cv::Mat outSum = cv::Mat::zeros(eye_roi.rows, eye_roi.cols, CV_64F);
208  for (int y = 0; y < weight.rows; ++y) {
209  const double *X_ROW = gradientX.ptr<double>(y);
210  const double *Y_ROW = gradientY.ptr<double>(y);
211  for (int x = 0; x < weight.cols; ++x) {
212  if (X_ROW[x] == 0.0 && Y_ROW[x] == 0.0) {
213  continue;
214  }
215  test_possible_centers_formula(x, y, weight, X_ROW[x], Y_ROW[x], outSum);
216  }
217  }
218 
219  cv::Point maxP;
220  double maxVal;
221  cv::minMaxLoc(outSum, nullptr, &maxVal, nullptr, &maxP);
222 
223  return unscale_point(maxP, eye, fast_eye_width);
224 }
225 
226 } // namespace eyelike
227 
228 namespace util {
229 
240  dlib::full_object_detection object_detection) {
241  int index_ex;
242  int index_en;
243  if (object_detection.num_parts() == 5) {
244  if (eye == 0) {
245  index_ex = 0;
246  index_en = 1;
247  } else {
248  index_ex = 2;
249  index_en = 3;
250  }
251  } else {
252  if (eye == 0) {
253  index_ex = 45;
254  index_en = 42;
255  } else {
256  index_ex = 36;
257  index_en = 39;
258  }
259  }
260 
261  auto get_rectangle = [](dlib::point one, dlib::point two)
262  -> dlib::rectangle {
263  dlib::rectangle result(one, two);
264  double scale = (one - two).length() * 1.5;
265  return dlib::centered_rect(result, scale, scale);
266  };
267 
268  return get_rectangle(object_detection.part(index_ex),
269  object_detection.part(index_en));
270 }
271 
272 } // namespace util
273 
274 namespace pipeline {
275 
276 EyeLike::EyeLike()
277  : relative_threshold_factor(0.3),
278  sigma_factor(0.005) {
279  YAML::Node config = util::get_config(this->number);
280  this->name = config["name"] ?
281  config["name"].as<std::string>() : "EyeLike";
282 
283  if (config["relative_threshold"]) {
284  this->relative_threshold_factor =
285  config["relative_threshold"]
286  .as<decltype(this->relative_threshold_factor)>();
287  }
288 }
289 
290 void EyeLike::process(util::Data& data) {
291  if (data.landmarks.num_parts() < 5) {
292  return;
293  }
294 
295  dlib::rectangle face = data.landmarks.get_rect();
296 
297  // avoid OpenCV ROI errors since dlib's bounding box can be outside
298  face = face.intersect(
299  dlib::rectangle(0, 0,
300  data.source_image.cols - 1, data.source_image.rows - 1));
301 
302  cv::Mat face_mat = data.source_image(util::dlib_to_cv(face));
303  cv::cvtColor(face_mat, face_mat, cv::COLOR_RGB2GRAY);
304 
305  for (int i = 0; i < 2; ++i) {
306  dlib::rectangle eye = util::get_eye_region(i, data.landmarks);
307  eye.set_left(eye.left() - face.left());
308  eye.set_right(eye.right() - face.left());
309  eye.set_top(eye.top() - face.top());
310  eye.set_bottom(eye.bottom() - face.top());
311 
312  cv::Point pupil = eyelike::find_eye_center(face_mat,
313  util::dlib_to_cv(eye),
314  this->relative_threshold_factor, 50);
315 
316  data.centers[i] = util::cv_to_dlib(pupil);
317  }
318 }
319 
320 void EyeLike::visualize(util::Data& data) {
321  if (data.landmarks.num_parts() < 5) {
322  return;
323  }
324  // Ensures output of this pipeline step is visualized
325  this->process(data);
326 
327  // define offsets
328  dlib::point offset_x(5, 0);
329  dlib::point offset_y(0, 5);
330 
331  dlib::extract_image_chips(data.image,
332  util::get_eyes_chip_details(data.landmarks), data.eyes);
333 
334 
335  // assign eyes to new images
336  dlib::array<dlib::array2d<dlib::bgr_pixel>> eyes(3);
337  dlib::assign_image(eyes[2], data.image);
338  for (auto i = decltype(data.eyes.size()){0};
339  i < data.eyes.size(); ++i) {
340  dlib::rectangle eye = util::get_eye_region(i, data.landmarks);
341  dlib::assign_image(eyes[i], data.eyes[i]);
342 
343  dlib::draw_line(eyes[i],
344  data.centers[i] + offset_x,
345  data.centers[i] - offset_x,
346  dlib::rgb_pixel(255, 0, 0));
347  dlib::draw_line(eyes[i],
348  data.centers[i] + offset_y,
349  data.centers[i] - offset_y,
350  dlib::rgb_pixel(255, 0, 0));
351 
352  dlib::draw_line(eyes[2],
353  data.centers[i] + offset_x + eye.tl_corner(),
354  data.centers[i] - offset_x + eye.tl_corner(),
355  dlib::rgb_pixel(255, 0, 0));
356  dlib::draw_line(eyes[2],
357  data.centers[i] + offset_y + eye.tl_corner(),
358  data.centers[i] - offset_y + eye.tl_corner(),
359  dlib::rgb_pixel(255, 0, 0));
360  }
361  dlib::resize_image(0.5, eyes[2]);
362 
363  this->widget->set_image(dlib::tile_images(eyes));
364 }
365 
366 } // namespace pipeline
367 
368 } // namespace gaze
void minMaxLoc(InputArray src, double *minVal, double *maxVal=0, Point *minLoc=0, Point *maxLoc=0, InputArray mask=noArray())
void cvtColor(InputArray src, OutputArray dst, int code, int dstCn=0)
dlib::rectangle get_eye_region(int eye, dlib::full_object_detection object_detection)
Definition: eye_like.cpp:239
dlib::array< dlib::array2d< double > > eyes
Definition: data.h:69
dlib::array< dlib::point > centers
Definition: data.h:75
void rectangle(InputOutputArray img, Point pt1, Point pt2, const Scalar &color, int thickness=1, int lineType=LINE_8, int shift=0)
static MatExpr zeros(int rows, int cols, int type)
void magnitude(InputArray x, InputArray y, OutputArray magnitude)
void resize(InputArray src, OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=INTER_LINEAR)
dlib::array2d< dlib::bgr_pixel > image
Definition: data.h:52
_GLIBCXX14_CONSTEXPR const _Tp & max(const _Tp &__a, const _Tp &__b)
unsigned char uchar
dlib::full_object_detection landmarks
Definition: data.h:64
COLOR_RGB2GRAY
static softfloat one()
uchar * ptr(int i0=0)
void GaussianBlur(InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY=0, int borderType=BORDER_DEFAULT)
Wraps the data acquired per frame into a single instance.
Definition: data.h:27
void meanStdDev(InputArray src, OutputArray mean, OutputArray stddev, InputArray mask=noArray())
cv::Mat source_image
Definition: data.h:47
_GLIBCXX14_CONSTEXPR const _Tp & min(const _Tp &__a, const _Tp &__b)
complex< _Tp > sqrt(const complex< _Tp > &)
Point2i Point
MatExpr t() const