Adding options to enable auto blink, auto breath and random motion
[facial-landmarks-for-cubism.git] / src / facial_landmark_detector.cpp
CommitLineData
830d0ba4
AIL
1/****
2Copyright (c) 2020 Adrian I. Lam
3
4Permission is hereby granted, free of charge, to any person obtaining a copy
5of this software and associated documentation files (the "Software"), to deal
6in the Software without restriction, including without limitation the rights
7to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8copies of the Software, and to permit persons to whom the Software is
9furnished to do so, subject to the following conditions:
10
11The above copyright notice and this permission notice shall be included in all
12copies or substantial portions of the Software.
13
14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20SOFTWARE.
21****/
22
23#include <stdexcept>
24#include <fstream>
25#include <string>
26#include <sstream>
27#include <cmath>
28
29#include <opencv2/opencv.hpp>
30
31#include <dlib/opencv.h>
32#include <dlib/image_processing/frontal_face_detector.h>
33#include <dlib/image_processing.h>
34#include <dlib/image_processing/render_face_detections.h>
35
36#include "facial_landmark_detector.h"
37#include "math_utils.h"
38
39
40static void filterPush(std::deque<double>& buf, double newval,
41 std::size_t numTaps)
42{
43 buf.push_back(newval);
44 while (buf.size() > numTaps)
45 {
46 buf.pop_front();
47 }
48}
49
50FacialLandmarkDetector::FacialLandmarkDetector(std::string cfgPath)
51 : m_stop(false)
52{
53 parseConfig(cfgPath);
54
55 if (!webcam.open(m_cfg.cvVideoCaptureId))
56 {
57 throw std::runtime_error("Unable to open webcam");
58 }
59
60 detector = dlib::get_frontal_face_detector();
61 dlib::deserialize(m_cfg.predictorPath) >> predictor;
62}
63
64FacialLandmarkDetector::Params FacialLandmarkDetector::getParams(void) const
65{
66 Params params;
67
68 params.faceXAngle = avg(m_faceXAngle);
69 params.faceYAngle = avg(m_faceYAngle) + m_cfg.faceYAngleCorrection;
70 // + 10 correct for angle between computer monitor and webcam
71 params.faceZAngle = avg(m_faceZAngle);
72 params.mouthOpenness = avg(m_mouthOpenness);
73 params.mouthForm = avg(m_mouthForm);
74
75 double leftEye = avg(m_leftEyeOpenness, 1);
76 double rightEye = avg(m_rightEyeOpenness, 1);
77 // Just combine the two to get better synchronized blinks
78 // This effectively disables winks, so if we want to
79 // support winks in the future (see below) we will need
80 // a better way to handle this out-of-sync blinks.
81 double bothEyes = (leftEye + rightEye) / 2;
82 leftEye = bothEyes;
83 rightEye = bothEyes;
84 // Detect winks and make them look better
85 // Commenting out - winks are difficult to be detected by the
86 // dlib data set anyway... maybe in the future we can
87 // add a runtime option to enable/disable...
88 /*if (right == 0 && left > 0.2)
89 {
90 left = 1;
91 }
92 else if (left == 0 && right > 0.2)
93 {
94 right = 1;
95 }
96 */
97 params.leftEyeOpenness = leftEye;
98 params.rightEyeOpenness = rightEye;
99
100 if (leftEye <= m_cfg.eyeSmileEyeOpenThreshold &&
101 rightEye <= m_cfg.eyeSmileEyeOpenThreshold &&
102 params.mouthForm > m_cfg.eyeSmileMouthFormThreshold &&
103 params.mouthOpenness > m_cfg.eyeSmileMouthOpenThreshold)
104 {
105 params.leftEyeSmile = 1;
106 params.rightEyeSmile = 1;
107 }
108 else
109 {
110 params.leftEyeSmile = 0;
111 params.rightEyeSmile = 0;
112 }
113
2b1f0c7c
AIL
114 params.autoBlink = m_cfg.autoBlink;
115 params.autoBreath = m_cfg.autoBreath;
116 params.randomMotion = m_cfg.randomMotion;
117
830d0ba4
AIL
118 return params;
119}
120
121void FacialLandmarkDetector::stop(void)
122{
123 m_stop = true;
124}
125
126void FacialLandmarkDetector::mainLoop(void)
127{
128 while (!m_stop)
129 {
130 cv::Mat frame;
131 if (!webcam.read(frame))
132 {
133 throw std::runtime_error("Unable to read from webcam");
134 }
135 cv::Mat flipped;
136 if (m_cfg.lateralInversion)
137 {
138 cv::flip(frame, flipped, 1);
139 }
140 else
141 {
142 flipped = frame;
143 }
144 dlib::cv_image<dlib::bgr_pixel> cimg(flipped);
145
146 if (m_cfg.showWebcamVideo)
147 {
148 win.set_image(cimg);
149 }
150
151 std::vector<dlib::rectangle> faces = detector(cimg);
152
153 if (faces.size() > 0)
154 {
155 dlib::rectangle face = faces[0];
156 dlib::full_object_detection shape = predictor(cimg, face);
157
158 /* The coordinates seem to be rather noisy in general.
159 * We will push everything through some moving average filters
160 * to reduce noise. The number of taps is determined empirically
161 * until we get something good.
162 * An alternative method would be to get some better dataset
163 * for dlib - perhaps even to train on a custom data set just for the user.
164 */
165
166 // Face rotation: X direction (left-right)
167 double faceXRot = calcFaceXAngle(shape);
168 filterPush(m_faceXAngle, faceXRot, m_cfg.faceXAngleNumTaps);
169
170 // Mouth form (smile / laugh) detection
171 double mouthForm = calcMouthForm(shape);
172 filterPush(m_mouthForm, mouthForm, m_cfg.mouthFormNumTaps);
173
174 // Face rotation: Y direction (up-down)
175 double faceYRot = calcFaceYAngle(shape, faceXRot, mouthForm);
176 filterPush(m_faceYAngle, faceYRot, m_cfg.faceYAngleNumTaps);
177
178 // Face rotation: Z direction (head tilt)
179 double faceZRot = calcFaceZAngle(shape);
180 filterPush(m_faceZAngle, faceZRot, m_cfg.faceZAngleNumTaps);
181
182 // Mouth openness
183 double mouthOpen = calcMouthOpenness(shape, mouthForm);
184 filterPush(m_mouthOpenness, mouthOpen, m_cfg.mouthOpenNumTaps);
185
186 // Eye openness
187 double eyeLeftOpen = calcEyeOpenness(LEFT, shape, faceYRot);
188 filterPush(m_leftEyeOpenness, eyeLeftOpen, m_cfg.leftEyeOpenNumTaps);
189 double eyeRightOpen = calcEyeOpenness(RIGHT, shape, faceYRot);
190 filterPush(m_rightEyeOpenness, eyeRightOpen, m_cfg.rightEyeOpenNumTaps);
191
192 // TODO eyebrows?
193
194 if (m_cfg.showWebcamVideo && m_cfg.renderLandmarksOnVideo)
195 {
196 win.clear_overlay();
197 win.add_overlay(dlib::render_face_detections(shape));
198 }
199 }
200 else
201 {
202 if (m_cfg.showWebcamVideo && m_cfg.renderLandmarksOnVideo)
203 {
204 win.clear_overlay();
205 }
206 }
207
208 cv::waitKey(m_cfg.cvWaitKeyMs);
209 }
210}
211
212double FacialLandmarkDetector::calcEyeAspectRatio(
213 dlib::point& p1, dlib::point& p2,
214 dlib::point& p3, dlib::point& p4,
215 dlib::point& p5, dlib::point& p6) const
216{
217 double eyeWidth = dist(p1, p4);
218 double eyeHeight1 = dist(p2, p6);
219 double eyeHeight2 = dist(p3, p5);
220
221 return (eyeHeight1 + eyeHeight2) / (2 * eyeWidth);
222}
223
224double FacialLandmarkDetector::calcEyeOpenness(
225 LeftRight eye,
226 dlib::full_object_detection& shape,
227 double faceYAngle) const
228{
229 double eyeAspectRatio;
230 if (eye == LEFT)
231 {
232 eyeAspectRatio = calcEyeAspectRatio(shape.part(42), shape.part(43), shape.part(44),
233 shape.part(45), shape.part(46), shape.part(47));
234 }
235 else
236 {
237 eyeAspectRatio = calcEyeAspectRatio(shape.part(36), shape.part(37), shape.part(38),
238 shape.part(39), shape.part(40), shape.part(41));
239 }
240
241 // Apply correction due to faceYAngle
242 double corrEyeAspRat = eyeAspectRatio / std::cos(degToRad(faceYAngle));
243
244 return linearScale01(corrEyeAspRat, m_cfg.eyeClosedThreshold, m_cfg.eyeOpenThreshold);
245}
246
247
248
249double FacialLandmarkDetector::calcMouthForm(dlib::full_object_detection& shape) const
250{
251 /* Mouth form parameter: 0 for normal mouth, 1 for fully smiling / laughing.
252 * Compare distance between the two corners of the mouth
253 * to the distance between the two eyes.
254 */
255
256 /* An alternative (my initial attempt) was to compare the corners of
257 * the mouth to the top of the upper lip - they almost lie on a
258 * straight line when smiling / laughing. But that is only true
259 * when facing straight at the camera. When looking up / down,
260 * the angle changes. So here we'll use the distance approach instead.
261 */
262
263 auto eye1 = centroid(shape.part(36), shape.part(37), shape.part(38),
264 shape.part(39), shape.part(40), shape.part(41));
265 auto eye2 = centroid(shape.part(42), shape.part(43), shape.part(44),
266 shape.part(45), shape.part(46), shape.part(47));
267 double distEyes = dist(eye1, eye2);
268 double distMouth = dist(shape.part(48), shape.part(54));
269
270 double form = linearScale01(distMouth / distEyes,
271 m_cfg.mouthNormalThreshold,
272 m_cfg.mouthSmileThreshold);
273
274 return form;
275}
276
277double FacialLandmarkDetector::calcMouthOpenness(
278 dlib::full_object_detection& shape,
279 double mouthForm) const
280{
281 // Use points for the bottom of the upper lip, and top of the lower lip
282 // We have 3 pairs of points available, which give the mouth height
283 // on the left, in the middle, and on the right, resp.
284 // First let's try to use an average of all three.
285 double heightLeft = dist(shape.part(63), shape.part(65));
286 double heightMiddle = dist(shape.part(62), shape.part(66));
287 double heightRight = dist(shape.part(61), shape.part(67));
288
289 double avgHeight = (heightLeft + heightMiddle + heightRight) / 3;
290
291 // Now, normalize it with the width of the mouth.
292 double width = dist(shape.part(60), shape.part(64));
293
294 double normalized = avgHeight / width;
295
296 double scaled = linearScale01(normalized,
297 m_cfg.mouthClosedThreshold,
298 m_cfg.mouthOpenThreshold,
299 true, false);
300
301 // Apply correction according to mouthForm
302 // Notice that when you smile / laugh, width is increased
303 scaled *= (1 + m_cfg.mouthOpenLaughCorrection * mouthForm);
304
305 return scaled;
306}
307
308double FacialLandmarkDetector::calcFaceXAngle(dlib::full_object_detection& shape) const
309{
310 // This function will be easier to understand if you refer to the
311 // diagram in faceXAngle.png
312
313 // Construct the y-axis using (1) average of four points on the nose and
314 // (2) average of four points on the upper lip.
315
316 auto y0 = centroid(shape.part(27), shape.part(28), shape.part(29),
317 shape.part(30));
318 auto y1 = centroid(shape.part(50), shape.part(51), shape.part(52),
319 shape.part(62));
320
321 // Now drop a perpedicular from the left and right edges of the face,
322 // and calculate the ratio between the lengths of these perpendiculars
323
324 auto left = centroid(shape.part(14), shape.part(15), shape.part(16));
325 auto right = centroid(shape.part(0), shape.part(1), shape.part(2));
326
327 // Constructing a perpendicular:
328 // Join the left/right point and the upper lip. The included angle
329 // can now be determined using cosine rule.
330 // Then sine of this angle is the perpendicular divided by the newly
331 // created line.
332 double opp = dist(right, y0);
333 double adj1 = dist(y0, y1);
334 double adj2 = dist(y1, right);
335 double angle = solveCosineRuleAngle(opp, adj1, adj2);
336 double perpRight = adj2 * std::sin(angle);
337
338 opp = dist(left, y0);
339 adj2 = dist(y1, left);
340 angle = solveCosineRuleAngle(opp, adj1, adj2);
341 double perpLeft = adj2 * std::sin(angle);
342
343 // Model the head as a sphere and look from above.
344 double theta = std::asin((perpRight - perpLeft) / (perpRight + perpLeft));
345
346 theta = radToDeg(theta);
347 if (theta < -30) theta = -30;
348 if (theta > 30) theta = 30;
349 return theta;
350}
351
352double FacialLandmarkDetector::calcFaceYAngle(dlib::full_object_detection& shape, double faceXAngle, double mouthForm) const
353{
354 // Use the nose
355 // angle between the two left/right points and the tip
356 double c = dist(shape.part(31), shape.part(35));
357 double a = dist(shape.part(30), shape.part(31));
358 double b = dist(shape.part(30), shape.part(35));
359
360 double angle = solveCosineRuleAngle(c, a, b);
361
362 // This probably varies a lot from person to person...
363
364 // Best is probably to work out some trigonometry again,
365 // but just linear interpolation seems to work ok...
366
367 // Correct for X rotation
368 double corrAngle = angle * (1 + (std::abs(faceXAngle) / 30
369 * m_cfg.faceYAngleXRotCorrection));
370
371 // Correct for smiles / laughs - this increases the angle
372 corrAngle *= (1 - mouthForm * m_cfg.faceYAngleSmileCorrection);
373
374 if (corrAngle >= m_cfg.faceYAngleZeroValue)
375 {
376 return -30 * linearScale01(corrAngle,
377 m_cfg.faceYAngleZeroValue,
378 m_cfg.faceYAngleDownThreshold,
379 false, false);
380 }
381 else
382 {
383 return 30 * (1 - linearScale01(corrAngle,
384 m_cfg.faceYAngleUpThreshold,
385 m_cfg.faceYAngleZeroValue,
386 false, false));
387 }
388}
389
390double FacialLandmarkDetector::calcFaceZAngle(dlib::full_object_detection& shape) const
391{
392 // Use average of eyes and nose
393
394 auto eyeRight = centroid(shape.part(36), shape.part(37), shape.part(38),
395 shape.part(39), shape.part(40), shape.part(41));
396 auto eyeLeft = centroid(shape.part(42), shape.part(43), shape.part(44),
397 shape.part(45), shape.part(46), shape.part(47));
398
399 auto noseLeft = shape.part(35);
400 auto noseRight = shape.part(31);
401
402 double eyeYDiff = eyeRight.y() - eyeLeft.y();
403 double eyeXDiff = eyeRight.x() - eyeLeft.x();
404
405 double angle1 = std::atan(eyeYDiff / eyeXDiff);
406
407 double noseYDiff = noseRight.y() - noseLeft.y();
408 double noseXDiff = noseRight.x() - noseLeft.x();
409
410 double angle2 = std::atan(noseYDiff / noseXDiff);
411
412 return radToDeg((angle1 + angle2) / 2);
413}
414
415void FacialLandmarkDetector::parseConfig(std::string cfgPath)
416{
417 populateDefaultConfig();
418 if (cfgPath != "")
419 {
420 std::ifstream file(cfgPath);
421
422 if (!file)
423 {
424 throw std::runtime_error("Failed to open config file");
425 }
426
427 std::string line;
428 unsigned int lineNum = 0;
429
430 while (std::getline(file, line))
431 {
432 lineNum++;
433
434 if (line[0] == '#')
435 {
436 continue;
437 }
438
439 std::istringstream ss(line);
440 std::string paramName;
441 if (ss >> paramName)
442 {
443 if (paramName == "cvVideoCaptureId")
444 {
445 if (!(ss >> m_cfg.cvVideoCaptureId))
446 {
447 throwConfigError(paramName, "int",
448 line, lineNum);
449 }
450 }
451 else if (paramName == "predictorPath")
452 {
453 if (!(ss >> m_cfg.predictorPath))
454 {
455 throwConfigError(paramName, "std::string",
456 line, lineNum);
457 }
458 }
459 else if (paramName == "faceYAngleCorrection")
460 {
461 if (!(ss >> m_cfg.faceYAngleCorrection))
462 {
463 throwConfigError(paramName, "double",
464 line, lineNum);
465 }
466 }
467 else if (paramName == "eyeSmileEyeOpenThreshold")
468 {
469 if (!(ss >> m_cfg.eyeSmileEyeOpenThreshold))
470 {
471 throwConfigError(paramName, "double",
472 line, lineNum);
473 }
474 }
475 else if (paramName == "eyeSmileMouthFormThreshold")
476 {
477 if (!(ss >> m_cfg.eyeSmileMouthFormThreshold))
478 {
479 throwConfigError(paramName, "double",
480 line, lineNum);
481 }
482 }
483 else if (paramName == "eyeSmileMouthOpenThreshold")
484 {
485 if (!(ss >> m_cfg.eyeSmileMouthOpenThreshold))
486 {
487 throwConfigError(paramName, "double",
488 line, lineNum);
489 }
490 }
491 else if (paramName == "showWebcamVideo")
492 {
493 if (!(ss >> m_cfg.showWebcamVideo))
494 {
495 throwConfigError(paramName, "bool",
496 line, lineNum);
497 }
498 }
499 else if (paramName == "renderLandmarksOnVideo")
500 {
501 if (!(ss >> m_cfg.renderLandmarksOnVideo))
502 {
503 throwConfigError(paramName, "bool",
504 line, lineNum);
505 }
506 }
507 else if (paramName == "lateralInversion")
508 {
509 if (!(ss >> m_cfg.lateralInversion))
510 {
511 throwConfigError(paramName, "bool",
512 line, lineNum);
513 }
514 }
515 else if (paramName == "faceXAngleNumTaps")
516 {
517 if (!(ss >> m_cfg.faceXAngleNumTaps))
518 {
519 throwConfigError(paramName, "std::size_t",
520 line, lineNum);
521 }
522 }
523 else if (paramName == "faceYAngleNumTaps")
524 {
525 if (!(ss >> m_cfg.faceYAngleNumTaps))
526 {
527 throwConfigError(paramName, "std::size_t",
528 line, lineNum);
529 }
530 }
531 else if (paramName == "faceZAngleNumTaps")
532 {
533 if (!(ss >> m_cfg.faceZAngleNumTaps))
534 {
535 throwConfigError(paramName, "std::size_t",
536 line, lineNum);
537 }
538 }
539 else if (paramName == "mouthFormNumTaps")
540 {
541 if (!(ss >> m_cfg.mouthFormNumTaps))
542 {
543 throwConfigError(paramName, "std::size_t",
544 line, lineNum);
545 }
546 }
547 else if (paramName == "mouthOpenNumTaps")
548 {
549 if (!(ss >> m_cfg.mouthOpenNumTaps))
550 {
551 throwConfigError(paramName, "std::size_t",
552 line, lineNum);
553 }
554 }
555 else if (paramName == "leftEyeOpenNumTaps")
556 {
557 if (!(ss >> m_cfg.leftEyeOpenNumTaps))
558 {
559 throwConfigError(paramName, "std::size_t",
560 line, lineNum);
561 }
562 }
563 else if (paramName == "rightEyeOpenNumTaps")
564 {
565 if (!(ss >> m_cfg.rightEyeOpenNumTaps))
566 {
567 throwConfigError(paramName, "std::size_t",
568 line, lineNum);
569 }
570 }
571 else if (paramName == "cvWaitKeyMs")
572 {
573 if (!(ss >> m_cfg.cvWaitKeyMs))
574 {
575 throwConfigError(paramName, "int",
576 line, lineNum);
577 }
578 }
579 else if (paramName == "eyeClosedThreshold")
580 {
581 if (!(ss >> m_cfg.eyeClosedThreshold))
582 {
583 throwConfigError(paramName, "double",
584 line, lineNum);
585 }
586 }
587 else if (paramName == "eyeOpenThreshold")
588 {
589 if (!(ss >> m_cfg.eyeOpenThreshold))
590 {
591 throwConfigError(paramName, "double",
592 line, lineNum);
593 }
594 }
595 else if (paramName == "mouthNormalThreshold")
596 {
597 if (!(ss >> m_cfg.mouthNormalThreshold))
598 {
599 throwConfigError(paramName, "double",
600 line, lineNum);
601 }
602 }
603 else if (paramName == "mouthSmileThreshold")
604 {
605 if (!(ss >> m_cfg.mouthSmileThreshold))
606 {
607 throwConfigError(paramName, "double",
608 line, lineNum);
609 }
610 }
611 else if (paramName == "mouthClosedThreshold")
612 {
613 if (!(ss >> m_cfg.mouthClosedThreshold))
614 {
615 throwConfigError(paramName, "double",
616 line, lineNum);
617 }
618 }
619 else if (paramName == "mouthOpenThreshold")
620 {
621 if (!(ss >> m_cfg.mouthOpenThreshold))
622 {
623 throwConfigError(paramName, "double",
624 line, lineNum);
625 }
626 }
627 else if (paramName == "mouthOpenLaughCorrection")
628 {
629 if (!(ss >> m_cfg.mouthOpenLaughCorrection))
630 {
631 throwConfigError(paramName, "double",
632 line, lineNum);
633 }
634 }
635 else if (paramName == "faceYAngleXRotCorrection")
636 {
637 if (!(ss >> m_cfg.faceYAngleXRotCorrection))
638 {
639 throwConfigError(paramName, "double",
640 line, lineNum);
641 }
642 }
643 else if (paramName == "faceYAngleSmileCorrection")
644 {
645 if (!(ss >> m_cfg.faceYAngleSmileCorrection))
646 {
647 throwConfigError(paramName, "double",
648 line, lineNum);
649 }
650 }
651 else if (paramName == "faceYAngleZeroValue")
652 {
653 if (!(ss >> m_cfg.faceYAngleZeroValue))
654 {
655 throwConfigError(paramName, "double",
656 line, lineNum);
657 }
658 }
659 else if (paramName == "faceYAngleUpThreshold")
660 {
661 if (!(ss >> m_cfg.faceYAngleUpThreshold))
662 {
663 throwConfigError(paramName, "double",
664 line, lineNum);
665 }
666 }
667 else if (paramName == "faceYAngleDownThreshold")
668 {
669 if (!(ss >> m_cfg.faceYAngleDownThreshold))
670 {
671 throwConfigError(paramName, "double",
672 line, lineNum);
673 }
674 }
2b1f0c7c
AIL
675 else if (paramName == "autoBlink")
676 {
677 if (!(ss >> m_cfg.autoBlink))
678 {
679 throwConfigError(paramName, "bool",
680 line, lineNum);
681 }
682 }
683 else if (paramName == "autoBreath")
684 {
685 if (!(ss >> m_cfg.autoBreath))
686 {
687 throwConfigError(paramName, "bool",
688 line, lineNum);
689 }
690 }
691 else if (paramName == "randomMotion")
692 {
693 if (!(ss >> m_cfg.randomMotion))
694 {
695 throwConfigError(paramName, "bool",
696 line, lineNum);
697 }
698 }
830d0ba4
AIL
699 else
700 {
701 std::ostringstream oss;
702 oss << "Unrecognized parameter name at line " << lineNum
703 << ": " << paramName;
704 throw std::runtime_error(oss.str());
705 }
706 }
707 }
708 }
709}
710
711void FacialLandmarkDetector::populateDefaultConfig(void)
712{
713 // These are values that I've personally tested to work OK for my face.
714 // Your milage may vary - hence the config file.
715
716 m_cfg.cvVideoCaptureId = 0;
717 m_cfg.predictorPath = "shape_predictor_68_face_landmarks.dat";
718 m_cfg.faceYAngleCorrection = 10;
719 m_cfg.eyeSmileEyeOpenThreshold = 0.6;
720 m_cfg.eyeSmileMouthFormThreshold = 0.75;
721 m_cfg.eyeSmileMouthOpenThreshold = 0.5;
722 m_cfg.showWebcamVideo = true;
723 m_cfg.renderLandmarksOnVideo = true;
724 m_cfg.lateralInversion = true;
725 m_cfg.cvWaitKeyMs = 5;
726 m_cfg.faceXAngleNumTaps = 11;
727 m_cfg.faceYAngleNumTaps = 11;
728 m_cfg.faceZAngleNumTaps = 11;
729 m_cfg.mouthFormNumTaps = 3;
730 m_cfg.mouthOpenNumTaps = 3;
731 m_cfg.leftEyeOpenNumTaps = 3;
732 m_cfg.rightEyeOpenNumTaps = 3;
733 m_cfg.eyeClosedThreshold = 0.2;
734 m_cfg.eyeOpenThreshold = 0.25;
735 m_cfg.mouthNormalThreshold = 0.75;
736 m_cfg.mouthSmileThreshold = 1.0;
737 m_cfg.mouthClosedThreshold = 0.1;
738 m_cfg.mouthOpenThreshold = 0.4;
739 m_cfg.mouthOpenLaughCorrection = 0.2;
740 m_cfg.faceYAngleXRotCorrection = 0.15;
741 m_cfg.faceYAngleSmileCorrection = 0.075;
742 m_cfg.faceYAngleZeroValue = 1.8;
743 m_cfg.faceYAngleDownThreshold = 2.3;
744 m_cfg.faceYAngleUpThreshold = 1.3;
2b1f0c7c
AIL
745 m_cfg.autoBlink = false;
746 m_cfg.autoBreath = false;
747 m_cfg.randomMotion = false;
830d0ba4
AIL
748}
749
750void FacialLandmarkDetector::throwConfigError(std::string paramName,
751 std::string expectedType,
752 std::string line,
753 unsigned int lineNum)
754{
755 std::ostringstream ss;
756 ss << "Error parsing config file for parameter " << paramName
757 << "\nAt line " << lineNum << ": " << line
758 << "\nExpecting value of type " << expectedType;
759
760 throw std::runtime_error(ss.str());
761}
762