CPP_Birdtracker
A C++ object tracking program specifically for lunar bird tracking
frame_extraction.cpp
Go to the documentation of this file.
1 /*
2  * CPP_Birdtracker/frame_extraction.cpp - LunAero video bird tracking software
3  * Copyright (C) <2020> <Wesley T. Honeycutt>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 /*
20  *
21  * This is a more efficient frame data extraction program for the LunAero birdtracker project
22  *
23  *
24  */
25 
26 #include "frame_extraction.hpp"
27 
38 static Mat shift_frame(Mat in_frame, int shiftx, int shifty) {
39  Mat zero_mask = Mat::zeros(in_frame.size(), in_frame.type());
40 
41  if (DEBUG_COUT) {
42  LOGGING.open(LOGOUT, std::ios_base::app);
43  LOGGING << "SHIFT_X: " << shiftx << std::endl << "SHIFT_Y: " << shifty << std::endl;
44  LOGGING.close();
45  }
46 
47  if ((shiftx < 0) && (shifty < 0)) { // move right and down
48  if (DEBUG_COUT) {
49  LOGGING.open(LOGOUT, std::ios_base::app);
50  LOGGING << "shifting case 1 - move right and down" << std::endl;
51  LOGGING.close();
52  }
53  in_frame(Rect(0, 0, BOXSIZE-abs(shiftx), BOXSIZE-abs(shifty)))
54  .copyTo(zero_mask(Rect(abs(shiftx), abs(shifty), BOXSIZE-abs(shiftx), BOXSIZE-abs(shifty))));
55  } else if (shiftx < 0) { // move right and maybe up
56  if (DEBUG_COUT) {
57  LOGGING.open(LOGOUT, std::ios_base::app);
58  LOGGING << "shifting case 2 - move right and maybe up" << std::endl;
59  LOGGING.close();
60  }
61  in_frame(Rect(0, abs(shifty), BOXSIZE-abs(shiftx), BOXSIZE-abs(shifty)))
62  .copyTo(zero_mask(Rect(abs(shiftx), 0, BOXSIZE-abs(shiftx), BOXSIZE-abs(shifty))));
63  } else if (shifty < 0) { // move down and maybe left
64  if (DEBUG_COUT) {
65  LOGGING.open(LOGOUT, std::ios_base::app);
66  LOGGING << "shifting case 3 - move down and maybe left" << std::endl;
67  LOGGING.close();
68  }
69  in_frame(Rect(abs(shiftx), 0, BOXSIZE-abs(shiftx), BOXSIZE-abs(shifty)))
70  .copyTo(zero_mask(Rect(0, abs(shifty), BOXSIZE-abs(shiftx), BOXSIZE-abs(shifty))));
71  } else { // positive moves up and left
72  if (DEBUG_COUT) {
73  LOGGING.open(LOGOUT, std::ios_base::app);
74  LOGGING << "shifting case 4 - move up and left" << std::endl;
75  LOGGING.close();
76  }
77  in_frame(Rect(abs(shiftx), abs(shifty), BOXSIZE-abs(shiftx), BOXSIZE-abs(shifty)))
78  .copyTo(zero_mask(Rect(0, 0, BOXSIZE-abs(shiftx), BOXSIZE-abs(shifty))));
79  }
80 
81  in_frame = zero_mask;
82 
83  return in_frame;
84 }
85 
101 static Mat corner_matching(Mat in_frame, vector<Point> contour, int plusx, int plusy) {
102  int shiftx = 0;
103  int shifty = 0;
104 
105 // Point local_tl = boundingRect(contour).tl();
106 // Point local_br = boundingRect(contour).br();
107 //
108 // if (DEBUG_COUT) {
109 // LOGGING.open(LOGOUT, std::ios_base::app);
110 // LOGGING
111 // << "PLUSX: " << plusx << " PLUSY: " << plusy << std::endl
112 // << "LOCAL_TL = (" << local_tl.x << ", " << local_tl.y << ")" << std::endl
113 // << "LOCAL_BR = (" << local_br.x << ", " << local_br.y << ")" << std::endl
114 // << "ORIG_TL = (" << ORIG_TL.x << ", " << ORIG_TL.y << ")" << std::endl
115 // << "ORIG_BR = (" << ORIG_BR.x << ", " << ORIG_BR.y << ")" << std::endl;
116 // LOGGING.close();
117 // }
118 //
119 // if ((abs(plusx) > EDGETHRESH) && (abs(plusx) > EDGETHRESH)) {
120 // if (plusx > 0) {
121 // if (plusy > 0) {
122 // // +x, +y (touching right and bottom edges)
123 // shiftx = (local_tl.x - ORIG_TL.x);
124 // shifty = (local_tl.y - ORIG_TL.y);
125 // } else {
126 // // +x, -y (touching right and top edges)
127 // shiftx = (local_tl.x - ORIG_TL.x);
128 // shifty = (local_br.y - ORIG_BR.y);
129 // }
130 // } else {
131 // if (plusy > 0) {
132 // // -x, +y (touching left and bottom edges)
133 // shiftx = (local_br.x - ORIG_BR.x);
134 // shifty = (local_tl.y - ORIG_TL.y);
135 // } else {
136 // // -x, -y (touching left and top edges)
137 // shiftx = (local_br.x - ORIG_BR.x);
138 // shifty = (local_br.y - ORIG_BR.y);
139 // }
140 // }
141 // } else if (abs(plusx) > EDGETHRESH) {
142 // if (plusx > 0) {
143 // // +x (touching right edge)
144 // shiftx = (local_tl.x - ORIG_TL.x);
145 //
146 // } else {
147 // // -x (touching left edge)
148 // shiftx = (local_br.x - ORIG_BR.x);
149 //
150 // }
151 // } else if (abs(plusy) > EDGETHRESH) {
152 // if (plusy > 0) {
153 // // +y (touching bottom edge)
154 // shifty = (local_tl.y - ORIG_TL.y);
155 //
156 // } else {
157 // // -y (touching top edge)
158 // shifty = (local_br.y - ORIG_BR.y);
159 //
160 // }
161 // }
162 
163  in_frame = shift_frame(in_frame, shiftx/2, shifty/2);
164 
165 
166  return in_frame;
167 }
168 
177 static vector <int> test_edges(Mat in_frame, vector<Point> contour, int te_ret) {
178  int plusx = 0;
179  int plusy = 0;
180 
181  // Fetch the boundary of this primary contour
182  Rect rect = boundingRect(contour);
183  /*
184  * Cases:
185  * -1 : error
186  * 0 : no edge touching
187  * 1 : touching left edge only
188  * 2 : touching right edge only
189  * 3 : touching top and left edge
190  * 4 : touching bottom and left edge
191  * 5 : touching top and right edge
192  * 6 : touching bottom and right edge
193  * 7 : touching top only
194  * 8 : touching bottom only
195  */
196  if ((te_ret == 1) || (te_ret == 3) || (te_ret == 7)) {
197  //touching left edge only
198  plusx = rect.br().x - ORIG_BR.x;
199  plusy = rect.br().y - ORIG_BR.y;
200  } else if ((te_ret == 2) || (te_ret == 6) || (te_ret == 8)) {
201  //touching right edge only
202  plusx = rect.tl().x - ORIG_TL.x;
203  plusy = rect.tl().y - ORIG_TL.y;
204  } else if (te_ret == 4) {
205  //touching bottom and left edge
206  plusx = -(ORIG_TL.x + (ORIG_HORZ - rect.width));
207  plusy = (BOXSIZE - ORIG_BR.y) + (ORIG_VERT - rect.height);
208  } else if (te_ret == 5) {
209  //touching top and right edge
210  plusx = (BOXSIZE - ORIG_TL.x) - rect.width;
211  plusy = -(ORIG_BR.y - rect.height);
212  }
213 
214  if (DEBUG_COUT) {
215  LOGGING.open(LOGOUT, std::ios_base::app);
216  LOGGING << plusx << ", " << plusy << std::endl;
217  LOGGING.close();
218  }
219 
220  vector <int> outplus;
221  outplus.push_back(plusx);
222  outplus.push_back(plusy);
223  return outplus;
224 }
225 
233 static int min_square_dim(Mat in_frame) {
234  BOXSIZE = min(in_frame.rows, in_frame.cols);
235  if (DEBUG_COUT) {
236  LOGGING.open(LOGOUT, std::ios_base::app);
237  LOGGING << "Cutting frame to size: (" << BOXSIZE << ", " << BOXSIZE << ")" << std::endl;
238  LOGGING.close();
239  }
240  return 0;
241 }
242 
249 static vector <int> edge_width(vector<Point> contour) {
250  vector <int> local_vec;
251 
252  Rect box = boundingRect(contour);
253  int top = box.tl().y;
254  int bot = box.br().y;
255 
256  int topout = 0;
257  int botout = 0;
258  for (size_t i = 0; i<contour.size(); i++) {
259  if (contour[i].y == top) {
260  topout += 1;
261  }
262  if (contour[i].y == bot) {
263  botout += 1;
264  }
265  }
266 
267  local_vec.push_back(topout);
268  local_vec.push_back(botout);
269 
270  return local_vec;
271 }
272 
279 static vector <int> edge_height(vector<Point> contour) {
280  vector <int> local_vec;
281 
282  Rect box = boundingRect(contour);
283  int lef = box.tl().x;
284  int rig = box.br().x;
285 
286  int lefout = 0;
287  int rigout = 0;
288  for (size_t i = 0; i<contour.size(); i++) {
289  if (contour[i].y == lef) {
290  lefout += 1;
291  }
292  if (contour[i].y == rig) {
293  rigout += 1;
294  }
295  }
296 
297  local_vec.push_back(lefout);
298  local_vec.push_back(rigout);
299 
300  return local_vec;
301 }
302 
313 static int initial_crop(Mat in_frame, int framecnt) {
314  int oldvalue;
315  // Find largest contour
316  if (box_finder(in_frame, true)) {
317  return 1;
318  }
319  Rect box = BF_BOX;
320  // Create rect representing the image
321  Rect image_rect = Rect({}, in_frame.size());
322 
323  // Edit box corners such that we have a BOXSIZE square
324  Point roi_tl = Point(box.tl().x - ((BOXSIZE - box.width)/2),
325  (box.tl().y- (BOXSIZE - box.height)/2));
326  Point roi_br = Point(((BOXSIZE - box.width)/2) + box.br().x,
327  ((BOXSIZE - box.height)/2) + box.br().y);
328  // Correct for non BOXSIZE rounding errors, only expand BR since we check for invalid later
329  if (roi_br.x - roi_tl.x > BOXSIZE) {
330  oldvalue = (roi_br.x - roi_tl.x) - BOXSIZE;
331  roi_br = Point(roi_br.x - oldvalue, roi_br.y);
332  } else if (roi_br.x - roi_tl.x < BOXSIZE) {
333  oldvalue = BOXSIZE - (roi_br.x - roi_tl.x);
334  roi_br = Point(roi_br.x + oldvalue, roi_br.y);
335  }
336  if (roi_br.y - roi_tl.y > BOXSIZE) {
337  oldvalue = (roi_br.y - roi_tl.y) - BOXSIZE;
338  roi_br = Point(roi_br.x, roi_br.y - oldvalue);
339  } else if (roi_br.y - roi_tl.y < BOXSIZE) {
340  oldvalue = BOXSIZE - (roi_br.y - roi_tl.y);
341  roi_br = Point(roi_br.x, roi_br.y + oldvalue);
342  }
343  // Correct for invalid values in the new box
344  if (roi_tl.x < 0) {
345  oldvalue = -(roi_tl.x);
346  roi_tl = Point(0, roi_tl.y);
347  roi_br = Point(roi_br.x - oldvalue, roi_br.y);
348  }
349  if (roi_tl.y < 0) {
350  oldvalue = -(roi_tl.y);
351  roi_tl = Point(roi_tl.x, 0);
352  roi_br = Point(roi_br.x, roi_br.y - oldvalue);
353  }
354  if (roi_br.x > in_frame.cols) {
355  oldvalue = roi_br.x - in_frame.cols;
356  roi_tl = Point(roi_tl.x - oldvalue, roi_tl.y);
357  roi_br = Point(roi_br.x - oldvalue, roi_br.y);
358  }
359  if (roi_br.y > in_frame.rows) {
360  oldvalue = roi_br.y - in_frame.rows;
361  roi_tl = Point(roi_tl.x, roi_tl.y - oldvalue);
362  roi_br = Point(roi_br.x, roi_br.y - oldvalue);
363  }
364  // Make this our region of interest.
365  Rect roi = Rect(roi_tl, roi_br);
366  // Find intersection, i.e. valid crop region
367  Rect intersection = image_rect & roi;
368  // Adjust the intersection to have the correct BOXSIZE
369  intersection = Rect(Point(intersection.x, intersection.y), Size(BOXSIZE, BOXSIZE));
370  // Move intersection to the result coordinate space
371  Rect inter_roi = intersection - roi.tl();
372 
373  // Crop the image to the intersection
374  Mat precrop = in_frame(intersection);
375 
376  // If we have positive coordinates, then blackout the region. If not, just pass the precrop along
377  if ((inter_roi.x > 0) || (inter_roi.y > 0)) {
378  // Create black image and copy intersection
379  Mat zero_mask = Mat::zeros(Size(BOXSIZE, BOXSIZE), in_frame.type());
380  // Assign a rectangular region in precrop...
381  precrop(Rect(Point(0, 0),
382  Size(BOXSIZE-inter_roi.x, BOXSIZE-inter_roi.y)
383  ))
384  // ...and copy the black masked moon there.
385  .copyTo(zero_mask(
386  Rect(
387  Point(inter_roi.x, inter_roi.y),
388  Size(BOXSIZE-inter_roi.x, BOXSIZE-inter_roi.y)
389  )
390  ));
391 
392  in_frame = zero_mask;
393  } else {
394  // If negative values exist, whatever, just pass it along and call it a day.
395  in_frame = precrop;
396  }
397  IC_FRAME = in_frame;
398 
399  return 0;
400 }
401 
422 static int touching_edges(Mat in_frame, vector<Point> contour) {
423  // fetch the boundary rectangle for our large contour
424  Rect box = boundingRect(contour);
425 
426  if (box.tl().x == 0) {
427  if (box.tl().y == 0) {
428  return 3;
429  } else if (box.br().y == in_frame.rows) {
430  return 4;
431  } else {
432  return 1;
433  }
434  } else if (box.br().x == in_frame.cols) {
435  if (box.tl().y == 0) {
436  return 5;
437  } else if (box.br().y == in_frame.rows) {
438  return 6;
439  } else {
440  return 2;
441  }
442  } else {
443  if (box.tl().y == 0) {
444  return 7;
445  } else if (box.br().y == in_frame.rows) {
446  return 8;
447  } else {
448  return 0;
449  }
450  }
451 
452  // This should never be reached
453  return -1;
454 }
455 
456 static Mat traditional_centering(Mat in_frame, vector <vector<Point>> contours, int largest, Rect box) {
457  // Generate masks
458  Mat mask(Size(in_frame.rows, in_frame.cols), in_frame.type(), Scalar(0));
459  Mat zero_mask(Size(BOXSIZE, BOXSIZE), in_frame.type(), Scalar(0));
460  // Create a mat with just the moon
461  Mat item(in_frame(box));
462 
463  // Apply contour to mask
464  drawContours(mask, contours, largest, 255, FILLED);
465 
466  // Transfer item to mask
467  item.copyTo(item, mask(box));
468 
469  // Calculate the center
470  Point center(BOXSIZE/2, BOXSIZE/2);
471  Rect center_box(center.x - box.width/2, center.y - box.height/2, box.width, box.height);
472 
473  // Copy the item mask to the centered box on our zero mask
474  item.copyTo(zero_mask(center_box));
475 
476  // done
477  in_frame = zero_mask;
478  return in_frame;
479 }
480 
490 static int first_frame(Mat in_frame, int framecnt) {
491  Mat temp_frame;
492 
493  // Determine the minimum dimensions of the input video
494  min_square_dim(in_frame);
495 
496  // Do a first pass/rough crop
497  initial_crop(in_frame.clone(), framecnt);
498 
499  in_frame = IC_FRAME;
500  // Make sure the black of night stays black so we can get the edge of the moon
501  threshold(in_frame.clone(), temp_frame, BLACKOUT_THRESH, 255, THRESH_TOZERO);
502  // Get contours
503  vector <vector<Point>> contours = contours_only(temp_frame);
504 
505  if (DEBUG_COUT) {
506  LOGGING.open(LOGOUT, std::ios_base::app);
507  LOGGING
508  << "Number of detected contours in ellipse frame: "
509  << contours.size()
510  << std::endl;
511  LOGGING.close();
512  }
513 
514  // Find which contour is the largest
515  int largest_contour_index = largest_contour(contours);
516  drawContours(in_frame, contours, largest_contour_index, 255, 2, LINE_8);
517 
518  // Store original area and perimeter of first frame
519  Rect box = boundingRect(contours[largest_contour_index]);
520  ORIG_AREA = box.area();
521  ORIG_PERI = arcLength(contours[largest_contour_index], true);
522  ORIG_VERT = box.height;
523  ORIG_HORZ = box.width;
524 // ORIG_TL = box.tl();
525 // ORIG_BR = box.br();
526  ORIG_TL = Point((BOXSIZE - box.width)/2, (BOXSIZE - box.height)/2);
527  ORIG_BR = Point(BOXSIZE - ((BOXSIZE - box.width)/2), BOXSIZE - ((BOXSIZE - box.height)/2));
528 
529  if (DEBUG_COUT) {
530  LOGGING.open(LOGOUT, std::ios_base::app);
531  LOGGING
532  << "box width: " << box.width << std::endl
533  << "box height: " << box.height << std::endl
534  << "box x: " << box.x << std::endl
535  << "box y: " << box.y << std::endl
536  << "box area: " << box.area() << std::endl;
537  LOGGING.close();
538  }
539 
540 // // Create rect representing the image
541 // Rect image_rect = Rect({}, in_frame.size());
542 // Point roi_tl = Point(box.tl().x - ((BOXSIZE - box.width)/2), (box.tl().y- (BOXSIZE - box.height)/2));
543 // Point roi_br = Point(((BOXSIZE - box.width)/2) + box.br().x, ((BOXSIZE - box.height)/2) + box.br().y);
544 // Rect roi = Rect(roi_tl, roi_br);
545 //
546 // // Find intersection, i.e. valid crop region
547 // Rect intersection = image_rect & roi;
548 //
549 // // Move intersection to the result coordinate space
550 // Rect inter_roi = intersection - roi.tl();
551 //
552 // // Create black image and copy intersection
553 // Mat crop = Mat::zeros(roi.size(), in_frame.type());
554 // in_frame(intersection).copyTo(crop(inter_roi));
555 //
556 // in_frame = crop;
557 
558 // in_frame = traditional_centering(in_frame.clone(), contours, largest_contour_index, box);
559 
560  if (DEBUG_COUT) {
561  LOGGING.open(LOGOUT, std::ios_base::app);
562  LOGGING
563  << "ORIG_AREA = "
564  << ORIG_AREA
565  << std::endl
566  << "ORIG_PERI = "
567  << ORIG_PERI
568  << std::endl;
569  LOGGING.close();
570  }
571 
572  if (DEBUG_COUT) {
573  LOGGING.open(LOGOUT, std::ios_base::app);
574  LOGGING
575  << "ORIG_TL = "
576  << ORIG_TL
577  << std::endl
578  << "ORIG_BR = "
579  << ORIG_BR
580  << std::endl;
581  LOGGING.close();
582  }
583 
584 
585 // vector <int> outplus = test_edges(in_frame, contours[largest_contour_index]);
586 //
587 // int edge_top = 0;
588 // int edge_bot = 0;
589 // int edge_lef = 0;
590 // int edge_rig = 0;
591 //
592 // if ((abs(outplus[0]) > EDGETHRESH) || (abs(outplus[1]) > EDGETHRESH)) {
593 // if ((outplus[0] > 0) || (outplus[0] < 0)) {
594 // vector <int> local_edge = edge_width(contours[largest_contour_index]);
595 // edge_top = local_edge[0];
596 // edge_bot = local_edge[1];
597 // }
598 // if ((outplus[1] > 0) || (outplus[1] < 0)) {
599 // vector <int> local_edge = edge_height(contours[largest_contour_index]);
600 // edge_lef = local_edge[0];
601 // edge_rig = local_edge[1];
602 // }
603 // }
604 /*
605  // Open the outfile to append list of major ellipses
606  std::ofstream outell;
607  outell.open(ELLIPSEDATA, std::ios_base::app);
608  outell
609  << framecnt
610  << ","
611  << box.x + (box.width/2)
612  << ","
613  << box.y + (box.height/2)
614  << ","
615  << box.width
616  << ","
617  << box.height
618  << ","
619  << box.area()
620  << ","
621  << edge_top
622  << ","
623  << edge_bot
624  << ","
625  << edge_lef
626  << ","
627  << edge_rig
628  << std::endl;
629  outell.close();*/
630 
631 // return in_frame;
632  return 0;
633 }
634 
646 static int halo_noise_and_center(Mat in_frame, int framecnt) {
647  Mat temp_frame;
648  // Do a first pass/rough crop
649  if (initial_crop(in_frame.clone(), framecnt)) {
650  return 1;
651  }
652  in_frame = IC_FRAME;
653  // Make sure the black of night stays black so we can get the edge of the moon
654  threshold(in_frame.clone(), temp_frame, BLACKOUT_THRESH, 255, THRESH_TOZERO);
655  vector <vector<Point>> contours = contours_only(temp_frame);
656  int largest = largest_contour(contours);
657  Rect box = boundingRect(contours[largest]);
658 
659  int edge_top = 0;
660  int edge_bot = 0;
661  int edge_lef = 0;
662  int edge_rig = 0;
663  int te_ret = touching_edges(in_frame, contours[largest]);
664 
665  if (te_ret > 0) {
666 // if ((abs(outplus[0]) > EDGETHRESH) || (abs(outplus[1]) > EDGETHRESH)) {
667  if (DEBUG_COUT) {
668  LOGGING.open(LOGOUT, std::ios_base::app);
669  LOGGING << "Activating Corner Matching" << std::endl;
670  LOGGING.close();
671  }
672  vector <int> outplus = test_edges(in_frame, contours[largest], te_ret);
673  in_frame = shift_frame(in_frame, outplus[0], outplus[1]);
674  if ((outplus[0] > 0) || (outplus[0] < 0)) {
675  vector <int> local_edge = edge_width(contours[largest]);
676  edge_top = local_edge[0];
677  edge_bot = local_edge[1];
678  }
679  if ((outplus[1] > 0) || (outplus[1] < 0)) {
680  vector <int> local_edge = edge_height(contours[largest]);
681  edge_lef = local_edge[0];
682  edge_rig = local_edge[1];
683  }
684  } else if (te_ret == 0) {
685  if (DEBUG_COUT) {
686  LOGGING.open(LOGOUT, std::ios_base::app);
687  LOGGING << "Centering Traditionally" << std::endl;
688  LOGGING.close();
689  }
690  // Traditional centering
691  in_frame = traditional_centering(in_frame.clone(), contours, largest, box);
692  } else {
693  std::cerr << "WARNING: Returned improper value when testing touching edges" << std::endl;
694  }
695 
696  // Find largest contour box on cropped frame
697  box_finder(in_frame, true);
698  box = BF_BOX;
699  // write that data to our boxes file.
700  box_data(box, framecnt);
701 
702  // Open the outfile to append list of major ellipses
703  std::ofstream outell;
704  outell.open(ELLIPSEDATA, std::ios_base::app);
705  outell
706  << framecnt
707  << ","
708  << box.x + (box.width/2)
709  << ","
710  << box.y + (box.height/2)
711  << ","
712  << box.width
713  << ","
714  << box.height
715  << ","
716  << box.area()
717  << ","
718  << edge_top
719  << ","
720  << edge_bot
721  << ","
722  << edge_lef
723  << ","
724  << edge_rig
725  << std::endl;
726  outell.close();
727 
728  HNC_FRAME = in_frame;
729  return 0;
730 }
731 
738 void signal_callback_handler(int signum) {
739  if (signum == 2) {
740  std::cerr << "Caught ctrl+c interrupt signal: " << std::endl;
741  }
742  // Terminate program
743  SIG_ALERT = signum;
744  return;
745 }
746 
757 static Mat apply_dynamic_mask(Mat in_frame, vector <Point> contour, int maskwidth) {
758  vector <vector <Point>> contours;
759  contours.push_back(contour);
760  drawContours(in_frame, contours, 0, 0, maskwidth, LINE_8);
761  return in_frame;
762 }
763 
771 static int largest_contour(vector <vector<Point>> contours) {
772  int largest_contour_index = -1;
773  int largest_area = 0;
774 
775  // Finds the largest contour in the "canny_output" of the input frame
776  for( size_t i = 0; i< contours.size(); i++ ) {
777  double area = contourArea(contours[i]);
778 
779  if (area > largest_area) {
780  largest_area = area;
781  largest_contour_index = i;
782  }
783  }
784  return largest_contour_index;
785 }
786 
794 static vector <vector<Point>> contours_only(Mat in_frame) {
795  vector <vector<Point>> contours;
796  vector<Vec4i> hierarchy;
797  findContours(in_frame, contours, hierarchy, RETR_TREE, CHAIN_APPROX_NONE);
798  return contours;
799 }
800 
808 static int box_finder(Mat in_frame, bool do_thresh) {
809 
810  // Make sure the black of night stays black so we can get the edge of the moon
811  if (do_thresh) {
812  threshold(in_frame.clone(), in_frame, BLACKOUT_THRESH, 255, THRESH_TOZERO);
813  }
814  vector <vector<Point>> contours = contours_only(in_frame);
815 
816  if (DEBUG_COUT) {
817  LOGGING.open(LOGOUT, std::ios_base::app);
818  LOGGING
819  << "Number of detected contours in ellipse frame: "
820  << contours.size()
821  << std::endl;
822  LOGGING.close();
823  }
824 
825  if (contours.size() < 1) {
826  return 1;
827  }
828 
829  int largest_contour_index = largest_contour(contours);
830 
831  // Find the bounding box for the large contour
832  BF_BOX = boundingRect(contours[largest_contour_index]);
833 
834  return 0;
835 }
836 
837 
845 static int box_data(Rect box, int framecnt) {
846 
847  if (OUTPUT_FRAMES && TIGHT_CROP) {
848  std::ofstream outfile;
849  outfile.open(BOXDATA, std::ios_base::app);
850  outfile
851  << framecnt << ","
852  << box.tl().x << ","
853  << box.tl().y << ","
854  << box.br().x << ","
855  << box.br().y << ","
856  << box.x << ","
857  << box.y << ","
858  << box.width << ","
859  << box.height << ","
860  << box.area() << std::endl;
861  outfile.close();
862  }
863  return 0;
864 }
865 
872 static int show_usage(string name) {
873  std::cerr << "Usage: " << " <option(s)> \t\tSOURCES\tDescription\n"
874  << "Options:\n"
875  << "\t-h,--help\t\t\tShow this help message\n"
876  << "\t-v,--version\t\t\tPrint version info to STDOUT\n"
877  << "\t-i,--input\t\tINPUT\tSpecify path to input video\n"
878  << "\t-c,--config-file \tINPUT\tSpecify config file (default settings.cfg)\n"
879  << "\t-osf,--osf-path \tINPUT\tSpeify path to osf video\n"
880  << std::endl;
881  return 0;
882 }
883 
891 static vector <Point> qhe_bigone(Mat in_frame) {
892  GaussianBlur(in_frame.clone(), in_frame,
896  BORDER_DEFAULT
897  );
898  threshold(in_frame.clone(), in_frame, 1, 255, THRESH_BINARY);
899  vector <vector <Point>> local_contours = contours_only(in_frame);
900  int largest_contour_index = largest_contour(local_contours);
901  if (largest_contour_index < 0) {
902  vector <Point> empty;
903  empty.push_back(Point(-1, -1));
904  return empty;
905  } else {
906  vector <Point> bigone = local_contours[largest_contour_index];
907  return bigone;
908  }
909 }
910 
921 static vector <vector<Point>> quiet_halo_elim(vector <vector<Point>> contours, vector <Point> bigone) {
922  float distance;
923  vector <vector<Point>> out_contours;
924 
925  bool caught_mask = false;
926 
927  for (size_t i = 0; i < contours.size(); i++) {
928  caught_mask = false;
929  // Skip the big one
930 // if (i == largest_contour_index) {
931 // continue;
932 // }
933  Moments M = moments(contours[i]);
934  int x_cen = (M.m10/M.m00);
935  int y_cen = (M.m01/M.m00);
936  if ((x_cen < 0) || (y_cen < 0)) {
937  x_cen = contours[i][0].x;
938  y_cen = contours[i][0].y;
939  }
940  for (size_t j = 0; j < bigone.size(); j++) {
941  distance = sqrt(pow((x_cen - bigone[j].x), 2) + pow((y_cen - bigone[j].y), 2));
942  if (distance < QHE_WIDTH) {
943  caught_mask = true;
944  break;
945  }
946  }
947  if (!caught_mask) {
948  out_contours.push_back(contours[i]);
949  }
950  }
951  return out_contours;
952 }
953 
964 int tier_one(int framecnt, Mat in_frame, vector <Point> bigone) {
965  Point2f center;
966  float radius;
967  float bigradius = 0;
968  std::ofstream outfile;
969  adaptiveThreshold(in_frame.clone(), in_frame,
970  T1_AT_MAX,
971  ADAPTIVE_THRESH_GAUSSIAN_C,
972  THRESH_BINARY_INV,
975  );
976  // Apply dynamic mask
977  in_frame = apply_dynamic_mask(in_frame.clone(), bigone, T1_DYMASK);
978 
979  vector <vector<Point>> contours = contours_only(in_frame);
980  if (contours.size() > 1) {
981  contours = quiet_halo_elim(contours, bigone);
982 
983  if (DEBUG_COUT) {
984  LOGGING.open(LOGOUT, std::ios_base::app);
985  LOGGING
986  << "Number of contours in tier 1 pass for frame "
987  << framecnt
988  << ": "
989  << contours.size()
990  << std::endl;
991  LOGGING.close();
992  }
993  int largest_contour_index = largest_contour(contours);
994  if (largest_contour_index > -1) {
995  minEnclosingCircle(contours[largest_contour_index], center, bigradius);
996  }
997  outfile.open(TIER1FILE, std::ios_base::app);
998  // Cycle through the contours
999  for (auto vec : contours) {
1000  // Greater than one includes lunar ellipse
1001  if (vec.size() > 1) {
1002  minEnclosingCircle(vec, center, radius);
1003  if (radius != bigradius) {
1004  // Open the outfile to append
1005  outfile
1006  << framecnt
1007  << ","
1008  << static_cast<int>(center.x)
1009  << ","
1010  << static_cast<int>(center.y)
1011  << ","
1012  << radius
1013  << std::endl;
1014  }
1015  }
1016  }
1017  outfile.close();
1018  } else {
1019  if (DEBUG_COUT) {
1020  LOGGING.open(LOGOUT, std::ios_base::app);
1021  LOGGING
1022  << "Found too few contours for frame " << framecnt << " Tier "
1023  << "1 "
1024  << "skipping this tier for this frame."
1025  << std::endl;
1026  LOGGING.close();
1027  }
1028  }
1029  return 0;
1030 }
1031 
1042 int tier_two(int framecnt, Mat in_frame, vector <Point> bigone) {
1043  Point2f center;
1044  float radius;
1045  float bigradius = 0;
1046  std::ofstream outfile;
1047  adaptiveThreshold(in_frame.clone(), in_frame,
1048  T2_AT_MAX,
1049  ADAPTIVE_THRESH_GAUSSIAN_C,
1050  THRESH_BINARY_INV,
1053  );
1054  // Apply dynamic mask
1055  in_frame = apply_dynamic_mask(in_frame.clone(), bigone, T2_DYMASK);
1056  vector <vector<Point>> contours = contours_only(in_frame);
1057  if (contours.size() > 1) {
1058  contours = quiet_halo_elim(contours, bigone);
1059 
1060  if (DEBUG_COUT) {
1061  LOGGING.open(LOGOUT, std::ios_base::app);
1062  LOGGING
1063  << "Number of contours in tier 2 pass for frame "
1064  << framecnt
1065  << ": "
1066  << contours.size()
1067  << std::endl;
1068  LOGGING.close();
1069  }
1070  int largest_contour_index = largest_contour(contours);
1071  if (largest_contour_index > -1) {
1072  minEnclosingCircle(contours[largest_contour_index], center, bigradius);
1073  }
1074  outfile.open(TIER2FILE, std::ios_base::app);
1075  // Cycle through the contours
1076  for (auto vec : contours) {
1077  // Greater than one includes lunar ellipse
1078  if (vec.size() > 1) {
1079  minEnclosingCircle(vec, center, radius);
1080  if (radius != bigradius) {
1081  // Open the outfile to append
1082  outfile
1083  << framecnt
1084  << ","
1085  << static_cast<int>(center.x)
1086  << ","
1087  << static_cast<int>(center.y)
1088  << ","
1089  << radius
1090  << std::endl;
1091  }
1092  }
1093  }
1094  outfile.close();
1095  } else {
1096  if (DEBUG_COUT) {
1097  LOGGING.open(LOGOUT, std::ios_base::app);
1098  LOGGING
1099  << "Found too few contours for frame " << framecnt << " Tier "
1100  << "2 "
1101  << "skipping this tier for this frame."
1102  << std::endl;
1103  LOGGING.close();
1104  }
1105  }
1106  return 0;
1107 }
1108 
1122 int tier_three(int framecnt, Mat in_frame, Mat old_frame, vector <Point> bigone) {
1123  Point2f center;
1124  float radius;
1125  float bigradius = 0;
1126  std::ofstream outfile;
1127  Mat scaleframe;
1128 
1129  /* Eli Method for Tier 3 */
1130  Laplacian(in_frame.clone(), in_frame, CV_32F,
1131  T3_LAP_KERNEL,
1132  T3_LAP_SCALE,
1133  T3_LAP_DELTA,
1134  BORDER_DEFAULT
1135  );
1136  Laplacian(old_frame.clone(), old_frame, CV_32F,
1137  T3_LAP_KERNEL,
1138  T3_LAP_SCALE,
1139  T3_LAP_DELTA,
1140  BORDER_DEFAULT
1141  );
1142  GaussianBlur(in_frame.clone(), in_frame,
1144  T3_GB_SIGMA_X,
1145  T3_GB_SIGMA_Y,
1146  BORDER_DEFAULT
1147  );
1148  GaussianBlur(old_frame.clone(), old_frame,
1150  T3_GB_SIGMA_X,
1151  T3_GB_SIGMA_Y,
1152  BORDER_DEFAULT
1153  );
1154  scaleframe = in_frame.clone() - old_frame.clone();
1155  scaleframe = scaleframe.clone() > T3_CUTOFF_THRESH;
1156  /* end Eli Method */
1157  // Apply dynamic mask
1158  scaleframe = apply_dynamic_mask(scaleframe.clone(), bigone, T3_DYMASK);
1159  vector <vector<Point>> contours = contours_only(scaleframe);
1160  if (contours.size() > 0) {
1161  contours = quiet_halo_elim(contours, bigone);
1162 
1163  if (DEBUG_COUT) {
1164  LOGGING.open(LOGOUT, std::ios_base::app);
1165  LOGGING
1166  << "Number of contours in tier 3 pass for frame "
1167  << framecnt
1168  << ": "
1169  << contours.size()
1170  << std::endl;
1171  LOGGING.close();
1172  }
1173  int largest_contour_index = largest_contour(contours);
1174  if (largest_contour_index > -1) {
1175  minEnclosingCircle(contours[largest_contour_index], center, bigradius);
1176  }
1177  outfile.open(TIER3FILE, std::ios_base::app);
1178  // Cycle through the contours
1179  for (auto vec : contours) {
1180  // Greater than one includes lunar ellipse
1181  if (vec.size() > 1) {
1182  minEnclosingCircle(vec, center, radius);
1183  if (radius != bigradius) {
1184  // Open the outfile to append
1185  outfile
1186  << framecnt
1187  << ","
1188  << static_cast<int>(center.x)
1189  << ","
1190  << static_cast<int>(center.y)
1191  << ","
1192  << radius
1193  << std::endl;
1194  }
1195  }
1196  }
1197  outfile.close();
1198  } else {
1199  if (DEBUG_COUT) {
1200  LOGGING.open(LOGOUT, std::ios_base::app);
1201  LOGGING
1202  << "Found too few contours for frame " << framecnt << " Tier "
1203  << "3 "
1204  << "skipping this tier for this frame."
1205  << std::endl;
1206  LOGGING.close();
1207  }
1208  }
1209  return 0;
1210 }
1211 
1229 int tier_four(int framecnt, Mat in_frame, Mat old_frame, vector <Point> bigone) {
1230  Point2f center;
1231  float radius;
1232  float bigradius = 0;
1233  std::ofstream outfile;
1234  Mat scaleframe;
1235 
1236  /* UnCanny v2 */
1237 
1238  subtract(in_frame.clone(), old_frame.clone(), in_frame);
1239  adaptiveThreshold(in_frame.clone(), in_frame,
1240  T4_AT_MAX,
1241  ADAPTIVE_THRESH_GAUSSIAN_C,
1242  THRESH_BINARY_INV,
1245  );
1246  Mat ix1, iy1, ii1;
1247  Sobel(in_frame.clone(), ix1, CV_32F, 1, 0);
1248  Sobel(in_frame.clone(), iy1, CV_32F, 0, 1);
1249  pow(ix1.clone(), T4_POWER, ix1);
1250  pow(iy1.clone(), T4_POWER, iy1);
1251  add(ix1, iy1, ii1);
1252  sqrt(ii1.clone(), ii1);
1253  convertScaleAbs(ii1.clone(), ii1);
1254  GaussianBlur(ii1.clone(), ii1,
1256  T4_GB_SIGMA_X,
1257  T4_GB_SIGMA_Y,
1258  BORDER_DEFAULT
1259  );
1260  ximgproc::thinning(ii1.clone(), scaleframe, T4_THINNING);
1261 
1262  /* end UnCanny v2 */
1263 
1264  // Apply dynamic mask
1265  scaleframe = apply_dynamic_mask(scaleframe.clone(), bigone, T4_DYMASK);
1266  vector <vector<Point>> contours = contours_only(scaleframe);
1267  if (contours.size() > 0) {
1268  contours = quiet_halo_elim(contours, bigone);
1269 
1270  if (DEBUG_COUT) {
1271  LOGGING.open(LOGOUT, std::ios_base::app);
1272  LOGGING
1273  << "Number of contours in tier 4 pass for frame "
1274  << framecnt
1275  << ": "
1276  << contours.size()
1277  << std::endl;
1278  LOGGING.close();
1279  }
1280  int largest_contour_index = largest_contour(contours);
1281  if (largest_contour_index > -1) {
1282  minEnclosingCircle(contours[largest_contour_index], center, bigradius);
1283  }
1284  outfile.open(TIER4FILE, std::ios_base::app);
1285  // Cycle through the contours
1286  for (auto vec : contours) {
1287  // Greater than one includes lunar ellipse
1288  if (vec.size() > 1) {
1289  minEnclosingCircle(vec, center, radius);
1290  if (radius != bigradius) {
1291  // Open the outfile to append
1292  outfile
1293  << framecnt
1294  << ","
1295  << static_cast<int>(center.x)
1296  << ","
1297  << static_cast<int>(center.y)
1298  << ","
1299  << radius
1300  << std::endl;
1301  }
1302 
1303  }
1304  }
1305  outfile.close();
1306  } else {
1307  if (DEBUG_COUT) {
1308  LOGGING.open(LOGOUT, std::ios_base::app);
1309  LOGGING
1310  << "Found too few contours for frame " << framecnt << " Tier "
1311  << "4 "
1312  << "skipping this tier for this frame."
1313  << std::endl;
1314  LOGGING.close();
1315  }
1316  }
1317  return 0;
1318 }
1319 
1328 int parse_checklist(std::string name, std::string value) {
1329  // Boolean cases
1330  if (name == "DEBUG_COUT"
1331  || name == "DEBUG_FRAMES"
1332  || name == "OUTPUT_FRAMES"
1333  || name == "EMPTY_FRAMES"
1334  || name == "GEN_SLIDESHOW"
1335  || name == "SIMP_ELL"
1336  || name == "CONCAT_TIERS"
1337  || name == "TIGHT_CROP"
1338  ) {
1339  // Define booleans
1340  bool result;
1341  if (value == "true" || value == "True" || value == "TRUE") {
1342  result = true;
1343  } else if (value == "false" || value == "False" || value == "FALSE") {
1344  result = false;
1345  } else {
1346  std::cerr << "Invalid boolean value in settings.cfg item: " << name << std::endl;
1347  return 1;
1348  }
1349 
1350  if (name == "DEBUG_COUT") {
1351  DEBUG_COUT = result;
1352  } else if (name == "DEBUG_FRAMES") {
1353  DEBUG_FRAMES = result;
1354  } else if (name == "OUTPUT_FRAMES") {
1355  OUTPUT_FRAMES = result;
1356  } else if (name == "EMPTY_FRAMES") {
1357  EMPTY_FRAMES = result;
1358  } else if (name == "GEN_SLIDESHOW") {
1359  GEN_SLIDESHOW = result;
1360  } else if (name == "SIMP_ELL") {
1361  SIMP_ELL = result;
1362  } else if (name == "CONCAT_TIERS") {
1363  CONCAT_TIERS = result;
1364  } else if (name == "TIGHT_CROP") {
1365  TIGHT_CROP = result;
1366  }
1367  }
1368  // Int cases
1369  else if (
1370  name == "EDGETHRESH"
1371  || name == "QHE_WIDTH"
1372  || name == "BLACKOUT_THRESH"
1373  || name == "CONVERT_FPS"
1374  || name == "NON_ZERO_START"
1375  || name == "T1_AT_BLOCKSIZE"
1376  || name == "T1_DYMASK"
1377  || name == "T2_AT_BLOCKSIZE"
1378  || name == "T2_DYMASK"
1379  || name == "T3_LAP_KERNEL"
1380  || name == "T3_GB_KERNEL_X"
1381  || name == "T3_GB_KERNEL_Y"
1382  || name == "T3_CUTOFF_THRESH"
1383  || name == "T3_DYMASK"
1384  || name == "T4_AT_BLOCKSIZE"
1385  || name == "T4_GB_KERNEL_X"
1386  || name == "T4_GB_KERNEL_Y"
1387  || name == "T4_THINNING"
1388  || name == "T4_DYMASK"
1389  || name == "QHE_GB_KERNEL_X"
1390  || name == "QHE_GB_KERNEL_Y"
1391  ) {
1392  // Store value as apprpriate int
1393  int result = std::stoi(value);
1394  if (name == "EDGETHRESH") {
1395  EDGETHRESH = result;
1396  } else if (name == "QHE_WIDTH") {
1397  QHE_WIDTH = result;
1398  } else if (name == "BLACKOUT_THRESH") {
1399  BLACKOUT_THRESH = result;
1400  } else if (name == "CONVERT_FPS") {
1401  CONVERT_FPS = result;
1402  } else if (name == "NON_ZERO_START") {
1403  NON_ZERO_START = result;
1404  } else if (name == "T1_AT_BLOCKSIZE") {
1405  T1_AT_BLOCKSIZE = result;
1406  } else if (name == "T1_DYMASK") {
1407  T1_DYMASK = result;
1408  } else if (name == "T2_AT_BLOCKSIZE") {
1409  T2_AT_BLOCKSIZE = result;
1410  } else if (name == "T2_DYMASK") {
1411  T2_DYMASK = result;
1412  } else if (name == "T3_LAP_KERNEL") {
1413  T3_LAP_KERNEL = result;
1414  } else if (name == "T3_GB_KERNEL_X") {
1415  T3_GB_KERNEL_X = result;
1416  } else if (name == "T3_GB_KERNEL_Y") {
1417  T3_GB_KERNEL_Y = result;
1418  } else if (name == "T3_CUTOFF_THRESH") {
1419  T3_CUTOFF_THRESH = result;
1420  } else if (name == "T3_DYMASK") {
1421  T3_DYMASK = result;
1422  } else if (name == "T4_AT_BLOCKSIZE") {
1423  T4_AT_BLOCKSIZE = result;
1424  } else if (name == "T4_GB_KERNEL_X") {
1425  T4_GB_KERNEL_X = result;
1426  } else if (name == "T4_GB_KERNEL_Y") {
1427  T4_GB_KERNEL_Y = result;
1428  } else if (name == "T4_THINNING") {
1429  T4_THINNING = result;
1430  } else if (name == "T4_DYMASK") {
1431  T4_DYMASK = result;
1432  } else if (name == "QHE_GB_KERNEL_X") {
1433  QHE_GB_KERNEL_X = result;
1434  } else if (name == "QHE_GB_KERNEL_Y") {
1435  QHE_GB_KERNEL_Y = result;
1436  }
1437 
1438  }
1439  // Double cases
1440  else if (
1441  name == "T1_AT_MAX"
1442  || name == "T1_AT_CONSTANT"
1443  || name == "T2_AT_MAX"
1444  || name == "T2_AT_CONSTANT"
1445  || name == "T3_LAP_SCALE"
1446  || name == "T3_LAP_DELTA"
1447  || name == "T3_GB_SIGMA_X"
1448  || name == "T3_GB_SIGMA_Y"
1449  || name == "T4_AT_MAX"
1450  || name == "T4_AT_CONSTANT"
1451  || name == "T4_POWER"
1452  || name == "T4_GB_SIGMA_X"
1453  || name == "T4_GB_SIGMA_Y"
1454  || name == "QHE_GB_SIGMA_X"
1455  || name == "QHE_GB_SIGMA_Y"
1456  ) {
1457  // Store value as relevant double
1458  double result = std::stod(value);
1459  if (name == "T1_AT_MAX") {
1460  T1_AT_MAX = result;
1461  } else if (name == "T1_AT_CONSTANT") {
1462  T1_AT_CONSTANT = result;
1463  } else if (name == "T2_AT_MAX") {
1464  T2_AT_MAX = result;
1465  } else if (name == "T2_AT_CONSTANT") {
1466  T2_AT_CONSTANT = result;
1467  } else if (name == "T3_LAP_SCALE") {
1468  T3_LAP_SCALE = result;
1469  } else if (name == "T3_LAP_DELTA") {
1470  T3_LAP_DELTA = result;
1471  } else if (name == "T3_GB_SIGMA_X") {
1472  T3_GB_SIGMA_X = result;
1473  } else if (name == "T3_GB_SIGMA_Y") {
1474  T3_GB_SIGMA_Y = result;
1475  } else if (name == "T4_AT_MAX") {
1476  T4_AT_MAX = result;
1477  } else if (name == "T4_AT_CONSTANT") {
1478  T4_AT_CONSTANT = result;
1479  } else if (name == "T4_POWER") {
1480  T4_POWER = result;
1481  } else if (name == "T4_GB_SIGMA_X") {
1482  T4_GB_SIGMA_X = result;
1483  } else if (name == "T4_GB_SIGMA_Y") {
1484  T4_GB_SIGMA_Y = result;
1485  } else if (name == "QHE_GB_SIGMA_X") {
1486  QHE_GB_SIGMA_X = result;
1487  } else if (name == "QHE_GB_SIGMA_Y") {
1488  QHE_GB_SIGMA_Y = result;
1489  }
1490  } else if (
1491  // String cases
1492  name == "OSFPROJECT"
1493  || name == "OUTPUTDIR"
1494  ) {
1495  // Store value as appropriate string
1496  if (name == "OSFPROJECT") {
1497  OSFPROJECT = value;
1498  } else if (name == "OUTPUTDIR") {
1499  OUTPUTDIR = value;
1500  }
1501  } else {
1502  std::cerr << "Did not recognize entry " << name << " in config file, skipping" << std::endl;
1503  }
1504  return 0;
1505 }
1506 
1513 static std::string out_frame_gen(int framecnt) {
1514  std::stringstream outstream;
1515  outstream << OUTPUTDIR << "frames/" << std::setw(10) << std::setfill('0') << framecnt << ".png";
1516  std::string outstring = outstream.str();
1517  return outstring;
1518 }
1519 
1526 std::string space_space(std::string instring) {
1527  std::string outstring;
1528  for (int i = 0; i < instring.size(); i++) {
1529  if (instring[i] == ' ') {
1530  outstring += '\\';
1531  outstring += ' ';
1532  } else {
1533  outstring += instring[i];
1534  }
1535  }
1536  return outstring;
1537 }
1538 
1539  static int edit_contours_for_crop() {
1540  std::string line;
1541  std::string temp_loc = OUTPUTDIR + "data/temp.csv";
1542  vector <std::string> tfiles;
1543  tfiles.push_back(TIER1FILE);
1544  tfiles.push_back(TIER2FILE);
1545  tfiles.push_back(TIER3FILE);
1546  tfiles.push_back(TIER4FILE);
1547  int tcnt = 1;
1548  if (DEBUG_COUT) {
1549  LOGGING.open(LOGOUT, std::ios_base::app);
1550  LOGGING << "TC_W, TC_H: " << TC_W << ", " << TC_H << std::endl
1551  << "TC_W, TC_H: " << (BOXSIZE - TC_W)/2 << ", " << (BOXSIZE - TC_H)/2 << std::endl;
1552  LOGGING.close();
1553  }
1554  for (auto i : tfiles) {
1555  std::ifstream infile(i);
1556  if (!infile.is_open()) {
1557  std::cerr
1558  << "WARNING: Could not open tier file: "
1559  << i
1560  << std::endl;
1561  return 1;
1562  }
1563 
1564  // Skip header of input file.
1565  if (infile.good()) {
1566  // Extract the first line in the file
1567  std::getline(infile, line);
1568  }
1569 
1570  // Create output file with headers.
1571  std::ofstream outputfile(temp_loc);
1572  if (DEBUG_COUT) {
1573  LOGGING.open(LOGOUT, std::ios_base::app);
1574  LOGGING << "opened file: " << temp_loc << std::endl;
1575  LOGGING.close();
1576  }
1577  if (outputfile.good()) {
1578  outputfile
1579  << "frame number,x pos,y pos,radius"
1580  << std::endl;
1581  } else {
1582  std::cerr
1583  << "Could not open temp.csv output file"
1584  << std::endl;
1585  return 2;
1586  }
1587  outputfile.close();
1588 
1589  usleep(1000000);
1590 
1591  // Read lines of the input file
1592  int val;
1593  outputfile.open(temp_loc, std::ios_base::app);
1594  while (std::getline(infile, line)) {
1595  // Create a stringstream of the current line
1596  std::stringstream ss(line);
1597  // Create a holder for the substrings
1598  std::string token;
1599  // Keep track of the current column index
1600  int col = -1;
1601  // Extract each col
1602  while (std::getline(ss, token, ',')) {
1603  col++;
1604  if (col == 1) {
1605  val = std::stoi(token) - (BOXSIZE - TC_W)/2;
1606  token = std::to_string(val);
1607  outputfile << token << ",";
1608  } else if (col == 2) {
1609  val = std::stoi(token) - (BOXSIZE - TC_H)/2;
1610  token = std::to_string(val);
1611  outputfile << token << ",";
1612  } else if (col == 3) {
1613  outputfile << token << std::endl;
1614  col = -1;
1615  } else {
1616  outputfile << token << ",";
1617  }
1618 
1619  // If the next token is a comma, ignore it and move on
1620  if (ss.peek() == ',') {
1621  ss.ignore();
1622  }
1623  }
1624  }
1625  infile.close();
1626  outputfile.close();
1627 
1628  if (std::remove(i.c_str()) != 0) {
1629  std::cerr << "Failure to remove file " << space_space(i) << " with error " << strerror(errno) << std::endl;
1630  }
1631  if (DEBUG_COUT) {
1632  LOGGING.open(LOGOUT, std::ios_base::app);
1633  LOGGING << "rm\'d " << i << std::endl
1634  << "mv\'ing " << temp_loc << " to " << i << std::endl;
1635  LOGGING.close();
1636  }
1637  if (std::rename(temp_loc.c_str(), i.c_str()) < 0) {
1638  std::cerr << "Could not move temp.csv to Tier location using rename with error: " << strerror(errno) << std::endl;
1639  return 3;
1640  }
1641 
1642  }
1643 
1644  return 0;
1645 }
1646 
1653  // Open box file
1654  std::ifstream box_file(BOXDATA);
1655  if (!box_file.is_open()) {
1656  std::cerr
1657  << "WARNING: Could not open box file"
1658  << std::endl;
1659  }
1660 
1661  // Prepare variables
1662  std::string line;
1663  int running_size = 0;
1664 
1665  // Read the column names and do nothing with them
1666  if (box_file.good()) {
1667  // Extract the first line in the file
1668  std::getline(box_file, line);
1669  }
1670 
1671  while (std::getline(box_file, line)) {
1672  // Create a stringstream of the current line
1673  std::stringstream ss(line);
1674  // Create a holder for the substrings
1675  std::string token;
1676  // Keep track of the current column index
1677  int col = -1;
1678  // Extract each col
1679  while (std::getline(ss, token, ',')) {
1680  col++;
1681  // Add the current integer to the 'colIdx' column's values vector
1682  if (col == 7) {
1683  if (running_size < std::stoi(token)) {
1684  running_size = std::stoi(token);
1685  }
1686  } else if (col == 8) {
1687  if (running_size < std::stoi(token)) {
1688  running_size = std::stoi(token);
1689  }
1690  } else if (col == 9) {
1691  col = -1;
1692  }
1693  // If the next token is a comma, ignore it and move on
1694  if (ss.peek() == ',') {
1695  ss.ignore();
1696  }
1697  }
1698  }
1699  // Close file
1700  box_file.close();
1701 
1702  return running_size;
1703 }
1704 
1705 static int generate_slideshow() {
1706  //HACK the proper way to do this is using libav. This is a linux hack.
1707  std::string framepath = space_space((OUTPUTDIR + "frames/"));
1708  std::string temppath = OUTPUTDIR + "temp.mp4";
1709  std::string outpath = OUTPUTDIR + "output.mp4";
1710  std::string command;
1711  command = "ffmpeg -y -hide_banner -loglevel warning -framerate 30 -i " + framepath + "%10d.png " + framepath + "../output.mp4";
1712  if (DEBUG_COUT) {
1713  LOGGING.open(LOGOUT, std::ios_base::app);
1714  LOGGING << "Beginning slideshow generation" << std::endl;
1715  LOGGING.close();
1716  }
1717  system(command.c_str());
1718  if (DEBUG_COUT) {
1719  LOGGING.open(LOGOUT, std::ios_base::app);
1720  LOGGING << "Slideshow created" << std::endl;
1721  LOGGING.close();
1722  }
1723  if (TIGHT_CROP) {
1724  int max_box;
1725  max_box = get_max_ellipse_params();
1726  if (max_box == 0) {
1727  std::cerr << "Max ellipse apparently is 0. Something is wrong." << std::endl;
1728  return 1;
1729  }
1730  if (DEBUG_COUT) {
1731  LOGGING.open(LOGOUT, std::ios_base::app);
1732  LOGGING << "Max Box: " << max_box << std::endl;
1733  LOGGING.close();
1734  }
1735  TC_W = std::min(max_box + 5, BOXSIZE);
1736  TC_H = TC_W;
1737  int center_x = BOXSIZE/2 - TC_W/2;
1738  int center_y = BOXSIZE/2 - TC_H/2;
1739  command = "ffmpeg -y -hide_banner -loglevel warning -i " + framepath + "../output.mp4 -vf \"crop=" + std::to_string(TC_W) + ":" + std::to_string(TC_H) + ":" + std::to_string(center_x) + ":" + std::to_string(center_y) + "\" " + framepath + "../temp.mp4";
1740  if (DEBUG_COUT) {
1741  LOGGING.open(LOGOUT, std::ios_base::app);
1742  LOGGING
1743  << command
1744  << std::endl;
1745  LOGGING.close();
1746  }
1747  system(command.c_str());
1748  if (DEBUG_COUT) {
1749  LOGGING.open(LOGOUT, std::ios_base::app);
1750  LOGGING << "Slideshow given a tight crop" << std::endl;
1751  LOGGING.close();
1752  }
1753  if (fs::file_size("./Birdtracker_Output/temp.mp4") > 0) {
1754  if (std::rename(temppath.c_str(), outpath.c_str()) != 0) {
1755  std::cerr << "Failed to move temp.mp4 to output.mp4 with error: "
1756  << strerror(errno) << std::endl;
1757  }
1758  } else {
1759  if (std::remove(temppath.c_str()) != 0) {
1760  std::cerr << "Failed to remove old temp.mp4 file with error: "
1761  << strerror(errno) << std::endl;
1762  }
1763  }
1764  }
1765  return 0;
1766 }
1767 
1774 static int concat_tiers() {
1775  // Open Tier 1 file
1776  std::ifstream t1_file(TIER1FILE);
1777  if (!t1_file.is_open()) {
1778  std::cerr
1779  << "Could not open Tier 1 data"
1780  << std::endl;
1781  return 1;
1782  }
1783  // Open Tier 2 file
1784  std::ifstream t2_file(TIER2FILE);
1785  if (!t2_file.is_open()) {
1786  std::cerr
1787  << "Could not open Tier 2 data"
1788  << std::endl;
1789  return 2;
1790  }
1791  // Open Tier 3 file
1792  std::ifstream t3_file(TIER3FILE);
1793  if (!t3_file.is_open()) {
1794  std::cerr
1795  << "Could not open Tier 3 data"
1796  << std::endl;
1797  return 3;
1798  }
1799  // Open Tier 4 file
1800  std::ifstream t4_file(TIER4FILE);
1801  if (!t4_file.is_open()) {
1802  std::cerr
1803  << "Could not open Tier 4 data"
1804  << std::endl;
1805  return 4;
1806  }
1807 
1808  // Prepare output file
1809  std::string mix_loc = OUTPUTDIR + "data/mixed_tiers.csv";
1810  std::ofstream outputfile(mix_loc);
1811  outputfile.open(mix_loc);
1812  outputfile.close();
1813 
1814  // Prepare variables
1815  std::string line;
1816 
1817  // Read the column names and do nothing with them
1818  if ((t1_file.good()) && (t2_file.good()) && (t3_file.good()) && (t4_file.good())) {
1819  // Extract the first line in the file
1820  std::getline(t1_file, line);
1821  std::getline(t2_file, line);
1822  std::getline(t3_file, line);
1823  std::getline(t4_file, line);
1824  }
1825 
1826  // Write column labels for the new file
1827  outputfile.open(mix_loc);
1828  if (outputfile.good()) {
1829  outputfile
1830  << "frame number,x pos,y pos,radius,tier"
1831  << std::endl;
1832  outputfile.close();
1833  } else {
1834  std::cerr
1835  << "Could not open mixed_tiers.csv output file"
1836  << std::endl;
1837  return 5;
1838  }
1839 
1840  vector <std::ifstream *> tfiles;
1841  tfiles.push_back(&t1_file);
1842  tfiles.push_back(&t2_file);
1843  tfiles.push_back(&t3_file);
1844  tfiles.push_back(&t4_file);
1845  int tcnt = 1;
1846  outputfile.open(mix_loc, std::ios_base::app);
1847  for (auto i : tfiles) {
1848  if (DEBUG_COUT) {
1849  LOGGING.open(LOGOUT, std::ios_base::app);
1850  LOGGING << "Concat begin on file: " << tcnt << std::endl;
1851  LOGGING.close();
1852  }
1853  while (std::getline(*i, line)) {
1854  line = line + "," + std::to_string(tcnt);
1855  outputfile
1856  << line
1857  << std::endl;
1858  }
1859  tcnt++;
1860  }
1861  outputfile.close();
1862 
1863  //HACK these commands require a linux terminal
1864  std::string commandstr;
1865  commandstr = "(head -n 1 "
1866  + space_space(mix_loc)
1867  + " && tail -n +2 "
1868  + space_space(mix_loc)
1869  + " | sort --field-separator=',' -n -k 1,1)"
1870  + " > "
1872  + "data/temp.csv";
1873  if (DEBUG_COUT) {
1874  LOGGING.open(LOGOUT, std::ios_base::app);
1875  LOGGING << "Concat command string: " << commandstr << std::endl;
1876  LOGGING.close();
1877  }
1878  if (system(commandstr.c_str()) != 0) {
1879  std::cerr << "WARNING: Failed to concat tiers with error: "
1880  << strerror(errno) << std::endl;
1881  return 0;
1882  }
1883 
1884  if (std::rename((OUTPUTDIR + "data/temp.csv").c_str(), mix_loc.c_str()) != 0) {
1885  std::cerr << "WARNING: Failed to move temporary file to mixed_tiers.csv with error: "
1886  << strerror(errno) << std::endl;
1887  return 0;
1888  }
1889 
1890  return 0;
1891 }
1892 
1899 static int off_screen_ellipse() {
1900  // Open ellipse file
1901  std::ifstream ellipse_file(ELLIPSEDATA);
1902  if (!ellipse_file.is_open()) {
1903  std::cerr
1904  << "Could not open ellipse file"
1905  << std::endl;
1906  return 1;
1907  }
1908 
1909  // Prepare output file
1910  std::string simp_loc = OUTPUTDIR + "data/offscreen_moon.csv";
1911  std::ofstream outputfile(simp_loc);
1912  outputfile.open(simp_loc);
1913  outputfile.close();
1914 
1915  // Prepare variables
1916  std::string line;
1917 
1918  // Read the column names and do nothing with them
1919  if (ellipse_file.good()) {
1920  // Extract the first line in the file
1921  std::getline(ellipse_file, line);
1922  }
1923 
1924  // Write column labels for the new file
1925  outputfile.open(simp_loc);
1926  if (outputfile.good()) {
1927  outputfile
1928  << "frame number,points on top edge,points on bot edge,points on left edge,points on right edge"
1929  << std::endl;
1930  outputfile.close();
1931  } else {
1932  std::cerr
1933  << "Could not open offscreen_moon.csv output file"
1934  << std::endl;
1935  return 1;
1936  }
1937 
1938  // Read data, line by line
1939  while (std::getline(ellipse_file, line)) {
1940  // Create a stringstream of the current line
1941  std::stringstream ss(line);
1942  // Create a holder for the substrings
1943  std::string token;
1944  // Create holder for output string
1945  std::string out_string;
1946  // Keep track of the current column index
1947  int col = -1;
1948  bool offscreen = false;
1949  // Extract each col
1950  while (std::getline(ss, token, ',')) {
1951  col++;
1952  // Add the current integer to the 'colIdx' column's values vector
1953  if ((col == 6) || (col == 7) || (col == 8) || (col == 9)) {
1954  if (std::stoi(token) != 0) {
1955  offscreen = true;
1956  }
1957  }
1958  if ((col == 0) || (col == 6) || (col == 7) || (col == 8)) {
1959  out_string += token + ',';
1960  } else if (col == 9) {
1961  out_string += token + '\n';
1962  if (offscreen) {
1963  outputfile.open(simp_loc, std::ios_base::app);
1964  outputfile
1965  << out_string;
1966  outputfile.close();
1967  }
1968  out_string = "";
1969  col = -1;
1970  offscreen = false;
1971  }
1972  // If the next token is a comma, ignore it and move on
1973  if (ss.peek() == ',') {
1974  ss.ignore();
1975  }
1976  }
1977  }
1978  // Close file
1979  ellipse_file.close();
1980 
1981  return 0;
1982 }
1983 
1989 static int post_processing() {
1990  // If the program was told to output frames, what should be done with them?
1991  if (OUTPUT_FRAMES) {
1992  if (GEN_SLIDESHOW) {
1993  if (generate_slideshow() != 0) {
1994  EMPTY_FRAMES = false;
1995  std::cerr
1996  << "ERROR: Failed to generate frame slideshow, retaining frame files and exiting"
1997  << std::endl;
1998  return 1;
1999  }
2000  if (edit_contours_for_crop() != 0) {
2001  EMPTY_FRAMES = false;
2002  std::cerr
2003  << "ERROR: Failed to edit contours to match tight crop requirements"
2004  << std::endl;
2005  return 2;
2006  }
2007  }
2008  if (EMPTY_FRAMES) {
2009  if (DEBUG_COUT) {
2010  LOGGING.open(LOGOUT, std::ios_base::app);
2011  LOGGING
2012  << "Removing png files from frames/ directory"
2013  << std::endl;
2014  LOGGING.close();
2015  }
2016  fs::remove_all(OUTPUTDIR + "frames/");
2017  }
2018  }
2019  if (CONCAT_TIERS) {
2020  if (concat_tiers() != 0) {
2021  std::cerr
2022  << "WARNING: Something went wrong stacking the Tier files"
2023  << std::endl;
2024  }
2025  }
2026  if (SIMP_ELL) {
2027  if (off_screen_ellipse() != 0) {
2028  std::cerr
2029  << "WARNING: Something went wrong simplifying the ellipse screen edges"
2030  << std::endl;
2031  }
2032  }
2033  return 0;
2034 }
2035 
2042 std::string tail(std::string const& source, size_t const length) {
2043  if (length >= source.size()) {
2044  return source;
2045  }
2046  return source.substr(source.size() - length);
2047 }
2048 
2056 int main(int argc, char* argv[]) {
2057  // Capture interrupt signals so we don't create zombie processes
2058  // FIXME This doesn't work right with all the forks.
2059  signal(SIGINT, signal_callback_handler);
2060 
2061  // Arg Handler --------------------------------------------------------------------------------
2062 
2063  // Handle arguments
2064  if (argc < 2) {
2065  show_usage(argv[0]);
2066  return 1;
2067  }
2068 
2069  vector <string> sources;
2070  std::string osf_file = "";
2071  std::string input_file = "";
2072  std::string config_file = "settings.cfg";
2073 
2074  for (int i = 1; i < argc; ++i) {
2075  string arg = argv[i];
2076  if ((arg == "-h") || (arg == "--help")) {
2077  show_usage(argv[0]);
2078 
2079  return 0;
2080  } else if ((arg == "-v") || (arg == "--version")) {
2081  std::cout
2082  << "LunAero Frame Extractor"
2083  << std::endl
2084  << "v" << MAJOR_VERSION << "." << MINOR_VERSION << ALPHA_BETA
2085  << std::endl
2086  << "Compiled: " << __TIMESTAMP__
2087  << std::endl
2088  << "Copyright (C) 2020, GPL-3.0"
2089  << std::endl
2090  << "Wesley T. Honeycutt, Oklahoma Biological Survey"
2091  << std::endl;
2092  return 0;
2093  } else if ((arg == "-c") || (arg == "--config-file")) {
2094  if (i + 1 < argc) {
2095  config_file = argv[++i];
2096  } else {
2097  std::cerr << "--config-file option requires one argument" << std::endl;
2098  return 1;
2099  }
2100  } else if ((arg == "-osf") || (arg == "--osf-path")) {
2101  // Make sure we aren't at the end of argv!
2102  if (i + 1 < argc) {
2103  // Increment 'i' so we don't get the argument as the next argv[i].
2104  osf_file = argv[++i];
2105  } else {
2106  std::cerr << "--osf-path option requires one argument." << std::endl;
2107  return 1;
2108  }
2109  } else if ((arg == "-i") || (arg == "--input")) {
2110  // Make sure we aren't at the end of argv!
2111  if (i + 1 < argc) {
2112  // Increment 'i' so we don't get the argument as the next argv[i].
2113  input_file = argv[++i];
2114  } else {
2115  std::cerr << "--input option requires one argument." << std::endl;
2116  return 1;
2117  }
2118  } else {
2119  sources.push_back(argv[i]);
2120  }
2121  }
2122 
2123  // Check that we did not include both an OSF and manual input file.
2124  if (osf_file.length() > 0 && input_file.length() > 0) {
2125  std::cerr << "Either use an input file or an OSF file, not both" << std::endl;
2126  return 1;
2127  } else if (osf_file.length() == 0 && input_file.length() == 0) {
2128  std::cerr << "You must give frame_extraction an input file or an OSF path" << std::endl;
2129  return 1;
2130  } else if (osf_file.length() > 0) {
2131  if (!(osf_file.substr(0, 10) == "osfstorage")) {
2132  std::cerr
2133  << "The path to the OSF video must look like \"osfstorage/path/to/file.mp4\""
2134  << std::endl;
2135  return 1;
2136  }
2137  if (!((osf_file.substr(osf_file.size() - 3) == "mp4") || (osf_file.substr(osf_file.size() - 4) == "h264"))) {
2138  std::cerr
2139  << "Input OSF file must end with mp4 or h264"
2140  << std::endl;
2141  return 1;
2142  }
2143  if (system("osf --version > /dev/null 2>&1")) {
2144  std::cerr << "OSFClient is not available on this system. Install using \"pip3 install osfclient --user\"" << std::endl;
2145  return 1;
2146  }
2147  }
2148 
2149  // Config Handler -----------------------------------------------------------------------------
2150 
2151  std::ifstream config_stream (config_file);
2152  if (config_stream.is_open()) {
2153  std::string line;
2154  while(getline(config_stream, line)) {
2155  line.erase(std::remove_if(line.begin(), line.end(), isspace), line.end());
2156  if(line[0] == '#' || line.empty()) {
2157  continue;
2158  }
2159  auto delimiter_pos = line.find("=");
2160  std::string name = line.substr(0, delimiter_pos);
2161  std::string value = line.substr(delimiter_pos + 1);
2162  if (parse_checklist(name, value)) {
2163  return 1;
2164  }
2165  }
2166  } else {
2167  std::cerr << "Couldn't open config file for reading." << std::endl;
2168  return 1;
2169  }
2170 
2171  if (DEBUG_COUT) {
2172  LOGGING.open(LOGOUT, std::ios_base::app);
2173  LOGGING
2174  << "Config File Loaded from" << config_file << std::endl
2175  << "Using input file: " << input_file << std::endl;
2176  LOGGING.close();
2177  }
2178 
2179  // Touch the output file ----------------------------------------------------------------------
2180 
2181  // Create directories
2182  std::string localpath;
2183  localpath = fs::current_path();
2184  OUTPUTDIR = localpath + OUTPUTDIR;
2185  localpath = OUTPUTDIR + "data";
2186  fs::create_directories(localpath);
2187  std::ofstream outfile;
2188  if (DEBUG_COUT) {
2189  LOGOUT = OUTPUTDIR + "data/log.log";
2190  LOGGING.open(LOGOUT);
2191  LOGGING.close();
2192  }
2193  if (OUTPUT_FRAMES) {
2194  localpath = OUTPUTDIR + "frames";
2195  fs::create_directories(localpath);
2196  }
2197 
2198  // Synthesize Filenames
2199  TIER1FILE = OUTPUTDIR + "data/Tier1.csv";
2200  TIER2FILE = OUTPUTDIR + "data/Tier2.csv";
2201  TIER3FILE = OUTPUTDIR + "data/Tier3.csv";
2202  TIER4FILE = OUTPUTDIR + "data/Tier4.csv";
2203  ELLIPSEDATA = OUTPUTDIR + "data/ellipses.csv";
2204  METADATA = OUTPUTDIR + "data/metadata.csv";
2205  if (OUTPUT_FRAMES && TIGHT_CROP) {
2206  BOXDATA = OUTPUTDIR + "data/boxes.csv";
2207  }
2208 
2209  outfile.open(TIER1FILE);
2210  outfile
2211  << "frame number"
2212  << ","
2213  << "x pos"
2214  << ","
2215  << "y pos"
2216  << ","
2217  << "radius"
2218  << std::endl;
2219  outfile.close();
2220  outfile.open(TIER2FILE);
2221  outfile
2222  << "frame number"
2223  << ","
2224  << "x pos"
2225  << ","
2226  << "y pos"
2227  << ","
2228  << "radius"
2229  << std::endl;
2230  outfile.close();
2231  outfile.open(TIER3FILE);
2232  outfile
2233  << "frame number"
2234  << ","
2235  << "x pos"
2236  << ","
2237  << "y pos"
2238  << ","
2239  << "radius"
2240  << std::endl;
2241  outfile.close();
2242  outfile.open(TIER4FILE);
2243  outfile
2244  << "frame number"
2245  << ","
2246  << "x pos"
2247  << ","
2248  << "y pos"
2249  << ","
2250  << "radius"
2251  << std::endl;
2252  outfile.close();
2253 
2254  // Touch output ellipse file
2255  std::ofstream outell;
2256  outell.open(ELLIPSEDATA);
2257  outell
2258  << "frame number"
2259  << ","
2260  << "moon center x"
2261  << ","
2262  << "moon center y"
2263  << ","
2264  << "moon x diameter"
2265  << ","
2266  << "moon y diameter"
2267  << ","
2268  << "moon enclosing box area"
2269  << ","
2270  << "points on top edge"
2271  << ","
2272  << "points on bot edge"
2273  << ","
2274  << "points on left edge"
2275  << ","
2276  << "points on right edge"
2277  << std::endl;
2278  outell.close();
2279 
2280  // Import OSF file ----------------------------------------------------------------------------
2281 
2282  if (!osf_file.empty()) {
2283  std::string osfcommand = "osf -p ";
2284  osfcommand.append(OSFPROJECT);
2285  osfcommand.append(" fetch ");
2286  osfcommand.append(osf_file);
2287  if (osf_file.substr(osf_file.size() - 3) == "mp4") {
2288  osfcommand.append(" ./local.mp4");
2289  input_file = "local.mp4";
2290  } else {
2291  osfcommand.append(" ./local.h264");
2292  input_file = "local.h264";
2293  }
2294  if (DEBUG_COUT) {
2295  LOGGING.open(LOGOUT, std::ios_base::app);
2296  LOGGING
2297  << "Downloading OSF file: "
2298  << osf_file
2299  << " to local drive with name: "
2300  << input_file
2301  << " using the the command: "
2302  << osfcommand
2303  << std::endl;
2304  LOGGING.close();
2305  }
2306  if (system(osfcommand.c_str())) {
2307  std::cerr << "ERROR: There was a problem while downloading the OSF file." << std::endl;
2308  return 1;
2309  }
2310  }
2311 
2312  // Make sure file exists
2313  if (!std::experimental::filesystem::exists(input_file)) {
2314  std::cerr << "Input file (" << input_file << ") not found. Aborting." << std::endl;
2315  return 1;
2316  }
2317 
2318  // H264 -> MP4 --------------------------------------------------------------------------------
2319  // Note, this method only works with linux afaik
2320  std::string file_ending;
2321 
2322  file_ending = tail(input_file, 3);
2323  if (file_ending == "mp4") {
2324  if (DEBUG_COUT) {
2325  LOGGING.open(LOGOUT, std::ios_base::app);
2326  LOGGING
2327  << "Input file has mp4 filetype ending, proceeding" << std::endl;
2328  LOGGING.close();
2329  }
2330  } else if (file_ending == "264") {
2331  if (DEBUG_COUT) {
2332  LOGGING.open(LOGOUT, std::ios_base::app);
2333  LOGGING
2334  << "Input file has h264 filetype ending, converting video" << std::endl;
2335  LOGGING.close();
2336  }
2337  std::string convert_command = "ffmpeg -y -hide_banner -r ";
2338  convert_command += std::to_string(CONVERT_FPS);
2339  convert_command += " -i ";
2340  convert_command += space_space(input_file);
2341  convert_command += " -c copy ./converted.mp4";
2342  input_file = "converted.mp4";
2343  system(convert_command.c_str());
2344  if (DEBUG_COUT) {
2345  LOGGING.open(LOGOUT, std::ios_base::app);
2346  LOGGING
2347  << "Video converted and stored at ./converted.mp4" << std::endl;
2348  LOGGING.close();
2349  }
2350  } else {
2351  std::cerr << "ERROR: Unrecognized input video filetype ending, mp4 or h264 required"
2352  << std::endl;
2353  return 1;
2354  }
2355 
2356  // Hack method to kick out h264 files
2357  auto delimiter_pos = input_file.find('.');
2358  if (input_file.substr(delimiter_pos + 1) != "mp4") {
2359  std::cerr
2360  << "Input file ("
2361  << input_file
2362  << ") does not end with \"mp4\". Assuming the input file is incompatible"
2363  << std::endl;
2364  return 1;
2365  }
2366 
2367  // Metafile handler ---------------------------------------------------------------------------
2368 
2369  // Touch and create metafile
2370  std::ofstream metafile;
2371  metafile.open(METADATA);
2372  if (!osf_file.empty()) {
2373  metafile << "video:," << osf_file << std::endl;
2374  } else {
2375  metafile << "video:," << input_file << std::endl;
2376  }
2377 
2378  // Create settings metadata space and header
2379  metafile << std::endl << "settings values:," << config_file << std::endl;
2380 
2381  // Write contents of settings to metadata
2382  if (config_stream.is_open()) {
2383  config_stream.clear();
2384  config_stream.seekg(0);
2385  std::string line;
2386  while(getline(config_stream, line)) {
2387  line.erase(std::remove_if(line.begin(), line.end(), isspace), line.end());
2388  if(line[0] == '#' || line.empty()) {
2389  continue;
2390  }
2391  delimiter_pos = line.find("=");
2392  std::string name = line.substr(0, delimiter_pos);
2393  std::string value = line.substr(delimiter_pos + 1);
2394  metafile << name << ":," << value << std::endl;
2395  }
2396  } else {
2397  std::cerr << "Couldn't open config file reporting metadata." << std::endl;
2398  return 1;
2399  }
2400 
2401  // Use ffprobe to fetch and parse video specific metadata
2402  std::string ffmeta_path = localpath + "/fftemp.txt";
2403  std::string command;
2404  if (!osf_file.empty()) {
2405  command = "ffprobe -hide_banner -i "
2406  + space_space(osf_file)
2407  + " -show_format -show_streams -print_format default > "
2408  + space_space(ffmeta_path);
2409  } else {
2410  command = "ffprobe -hide_banner -i "
2411  + input_file
2412  + " -show_format -show_streams -print_format default > "
2413  + space_space(ffmeta_path);
2414  }
2415 
2416  if (DEBUG_COUT) {
2417  LOGGING.open(LOGOUT, std::ios_base::app);
2418  LOGGING << "Recording video metadata using command: "
2419  << command
2420  << std::endl;
2421  LOGGING.close();
2422  }
2423  system(command.c_str());
2424 
2425  // Create space and header for ffprobe metadata
2426  metafile << std::endl << "ffprobe metadata," << std::endl;
2427 
2428  // Create output of ffprobe from command and write to metadata file
2429  std::ifstream ffmeta_stream(ffmeta_path);
2430  if (ffmeta_stream.is_open()) {
2431  std::string line;
2432  while(getline(ffmeta_stream, line)) {
2433  line.erase(std::remove_if(line.begin(), line.end(), isspace), line.end());
2434  if(line[0] == '[' || line.empty()) {
2435  continue;
2436  }
2437  delimiter_pos = line.find("=");
2438  std::string name = line.substr(0, delimiter_pos);
2439  std::string value = line.substr(delimiter_pos + 1);
2440  metafile << name << ":," << value << std::endl;
2441  }
2442  } else {
2443  std::cerr << "Unable to open ff metadata temporary file" << std::endl;
2444  return 1;
2445  }
2446 
2447  // Delete the temporary file
2448  if (std::remove(ffmeta_path.c_str()) != 0) {
2449  std::cerr << "Failed to remove old fftemp.txt file with error: "
2450  << strerror(errno) << std::endl;
2451  }
2452 
2453  // Done with the metadata
2454  metafile.close();
2455 
2456  if (OUTPUT_FRAMES && TIGHT_CROP) {
2457  outfile.open(BOXDATA);
2458  outfile
2459  << "frame number"
2460  << ","
2461  << "box tl x"
2462  << ","
2463  << "box tl y"
2464  << ","
2465  << "box br x"
2466  << ","
2467  << "box br y"
2468  << ","
2469  << "box x"
2470  << ","
2471  << "box y"
2472  << ","
2473  << "box width"
2474  << ","
2475  << "box height"
2476  << ","
2477  << "box area"
2478  << std::endl;
2479  outfile.close();
2480  }
2481 
2482  // Instance and Assign ------------------------------------------------------------------------
2483  // Memory map variables we want to share across forks
2484  // mm_gotframe reports that the frame acquisition was completed
2485  // mm_gotconts reports that video processing was completed
2486  // mm_localfrm reports that the current frame was grabbed, proceed getting next frame
2487  // mm_ranprocs reports that the contours were recorded
2488  // mm_frmcount stores the frame counter
2489  auto *mm_killed = static_cast <bool *>(mmap(NULL, sizeof(bool), \
2490  PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0));
2491  auto *mm_frameavail = static_cast <bool *>(mmap(NULL, sizeof(bool), \
2492  PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0));
2493  auto *mm_gotframe1 = static_cast <bool *>(mmap(NULL, sizeof(bool), \
2494  PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0));
2495  auto *mm_gotframe2 = static_cast <bool *>(mmap(NULL, sizeof(bool), \
2496  PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0));
2497  auto *mm_gotframe3 = static_cast <bool *>(mmap(NULL, sizeof(bool), \
2498  PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0));
2499  auto *mm_tier1 = static_cast <bool *>(mmap(NULL, sizeof(bool), \
2500  PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0));
2501  auto *mm_tier2 = static_cast <bool *>(mmap(NULL, sizeof(bool), \
2502  PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0));
2503  auto *mm_tier3 = static_cast <bool *>(mmap(NULL, sizeof(bool), \
2504  PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0));
2505  auto *mm_frmcount = static_cast <int *>(mmap(NULL, sizeof(int), \
2506  PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0));
2507  *mm_frmcount = -1;
2508 
2509  // OpenCV specific variables
2510  Mat frame;
2511 
2512  // Memory and fork management inits
2513  int frame_fd = open("/tmp/file", O_CREAT|O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
2514  int frame_fd2 = open("/tmp/file2", O_CREAT|O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
2515 
2516  // Other local variables
2517  int thresh = 0;
2518 
2519 
2520  // Tell OpenCV to open our video and fetch the first frame ------------------------------------
2521  VideoCapture cap(input_file); // open the default camera
2522  if (!cap.isOpened()) // check if we succeeded
2523  return -1;
2524 
2525  // Get the first frame with a clear moon
2526  bool running = true;
2527  while (running) {
2528  cap >> frame;
2529  if (frame.empty()) {
2530  std::cerr << "ERROR: Could not find a frame without the moon touching the edge. Exiting"
2531  << std::endl
2532  << "If the video looks good to you and you see this message, increase the value of "
2533  << "BLACKOUT_THRESH in the settings.cfg file, and re-run."
2534  << std::endl;
2535  return -1;
2536  }
2537  Mat temp_frame;
2538  ++*mm_frmcount;
2539  cvtColor(frame.clone(), frame, COLOR_BGR2GRAY);
2540  threshold(frame.clone(), temp_frame, BLACKOUT_THRESH, 255, THRESH_TOZERO);
2541  vector <vector<Point>> contours = contours_only(temp_frame);
2542  int largest = largest_contour(contours);
2543  int val = touching_edges(frame, contours[largest]);
2544  if (val == 0) {
2545  running = false;
2546  }
2547  }
2548 
2549  // Process this frame to establish initial values
2550  first_frame(frame.clone(), *mm_frmcount);
2551  if (DEBUG_COUT) {
2552  LOGGING.open(LOGOUT, std::ios_base::app);
2553  LOGGING << "passed first frame, used frame " << *mm_frmcount << " as corners" << std::endl;
2554  LOGGING.close();
2555  }
2556  // Release and restart the video from the beginning.
2557  cap.release();
2558  *mm_frmcount = -1;
2559  cap.open(input_file);
2560  if (!cap.isOpened()) // check if we succeeded
2561  return -1;
2562 
2563  // Get the first frame
2564  cap >> frame;
2565  ++*mm_frmcount;
2566 
2567  // DEBUG Skip frames for debugging
2568  while (*mm_frmcount < NON_ZERO_START) {
2569  ++*mm_frmcount;
2570  cap >> frame;
2571  }
2572 
2573  cvtColor(frame, frame, COLOR_BGR2GRAY);
2574  halo_noise_and_center(frame.clone(), *mm_frmcount);
2575  frame = HNC_FRAME;
2576 
2577  if (OUTPUT_FRAMES) {
2578  std::string output_loc = out_frame_gen(*mm_frmcount);
2579  imwrite(output_loc, frame);
2580  if (DEBUG_COUT) {
2581  LOGGING.open(LOGOUT, std::ios_base::app);
2582  LOGGING << "frame saved at " << output_loc << std::endl;
2583  LOGGING.close();
2584  }
2585  }
2586 
2587  // Memory management init continued -----------------------------------------------------------
2588  // Memory map slot for video frame
2589  // This must be called after the first test frame so we have the correct size info
2590  // e.g. we expect 6220800 bytes for a 3 channel 1920x1080 image
2591  const size_t shmem_size = frame.total() * frame.elemSize();
2592 
2593  if (DEBUG_COUT) {
2594  LOGGING.open(LOGOUT, std::ios_base::app);
2595  LOGGING
2596  << "frame dimensions: "
2597  << frame.size().width
2598  << " x "
2599  << frame.size().height
2600  << " x "
2601  << frame.elemSize()
2602  << " = "
2603  << shmem_size
2604  << std::endl
2605  << "frame_fd: "
2606  << frame_fd
2607  << std::endl;
2608  LOGGING.close();
2609  }
2610 
2611  // Resize the frame_fd to fit the frame size we are using
2612  if (ftruncate(frame_fd, shmem_size) != 0) {
2613  std::cerr << "failed to truncate resize file descriptor" << std::endl;
2614  return 1;
2615  }
2616  // Resize the frame_fd to fit the frame size we are using
2617  if (ftruncate(frame_fd2, shmem_size) != 0) {
2618  std::cerr << "failed to truncate resize file descriptor 2" << std::endl;
2619  return 1;
2620  }
2621 
2622 
2623  unsigned char *buf = static_cast <unsigned char*>(mmap(NULL, shmem_size, PROT_READ|PROT_WRITE, MAP_SHARED, frame_fd, 0));
2624  memcpy(buf, frame.ptr(), shmem_size);
2625  if (DEBUG_COUT) {
2626  LOGGING.open(LOGOUT, std::ios_base::app);
2627  LOGGING << "memcpy'd buf1 with size:" << sizeof(*buf) << std::endl;
2628  LOGGING.close();
2629  }
2630 
2631  unsigned char *buf2 = static_cast <unsigned char*>(mmap(NULL, shmem_size, PROT_READ|PROT_WRITE, MAP_SHARED, frame_fd2, 0));
2632  memcpy(buf2, frame.ptr(), shmem_size);
2633  if (DEBUG_COUT) {
2634  LOGGING.open(LOGOUT, std::ios_base::app);
2635  LOGGING << "memcpy'd buf2 with size:" << sizeof(*buf2) << std::endl;
2636  LOGGING.close();
2637  }
2638 
2639  // Store first frame in memory
2640  memcpy(buf, frame.ptr(), shmem_size);
2641  *mm_frameavail = true;
2642 
2643  // Delared PID here so we can stash the fork in an if statement.
2644  int pid0;
2645  int pid1;
2646  int pid2;
2647 
2648  // Set all the mm_ codes
2649  *mm_killed = false;
2650  *mm_frameavail = true;
2651  *mm_tier1 = true;
2652  *mm_tier2 = true;
2653  *mm_tier3 = true;
2654 
2655  // Main Loop ----------------------------------------------------------------------------------
2656  pid0 = fork();
2657 
2658  if (pid0 > 0) {
2659  // FORK 0 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2660  while (cap.isOpened()) {
2661  // Check for ctrl C
2662  if (SIG_ALERT != 0) {
2663  *mm_killed = true;
2664  cap.release();
2665  break;
2666  }
2667  while ((*mm_tier1 == false) || (*mm_tier2 == false) || (*mm_tier3 == false)) {
2668  usleep(50);
2669  }
2670  *mm_frameavail = false;
2671  *mm_tier1 = false;
2672  *mm_tier2 = false;
2673  *mm_tier3 = false;
2674 
2675 
2676  // Cycle ring buffer
2677  memcpy(buf2, buf, shmem_size);
2678  // Get new frame; operate and store
2679  cap >> frame;
2680  if (frame.empty()) {
2681  LOGGING.open(LOGOUT, std::ios_base::app);
2682  LOGGING << "Reached end of frames. Exiting" << std::endl;
2683  LOGGING.close();
2684  break;
2685  }
2686  ++*mm_frmcount;
2687 
2688  if (DEBUG_COUT) {
2689  LOGGING.open(LOGOUT, std::ios_base::app);
2690  LOGGING
2691  << "----------------------- frame number: "
2692  << *mm_frmcount
2693  << std::endl;
2694  LOGGING.close();
2695  }
2696 
2697  // Image processing operations
2698  cvtColor(frame, frame, COLOR_BGR2GRAY);
2699  if (halo_noise_and_center(frame.clone(), *mm_frmcount)) {
2700  std::cerr << "ERROR: Encountered empty frame. Ending this run. "
2701  << "If you believe the moon is refound later in the video, break video into parts"
2702  << " and re-run each part" << std::endl;
2703  *mm_killed = true;
2704  cap.release();
2705  break;
2706  }
2707  frame = HNC_FRAME;
2708  if (DEBUG_COUT) {
2709  LOGGING.open(LOGOUT, std::ios_base::app);
2710  LOGGING
2711  << "frame dimensions: "
2712  << frame.size().width
2713  << " x "
2714  << frame.size().height
2715  << " x "
2716  << frame.elemSize()
2717  << " = "
2718  << shmem_size
2719  << std::endl;
2720  LOGGING.close();
2721  }
2722  // Store this image into the memory buffer
2723  memcpy(buf, frame.ptr(), shmem_size);
2724 
2725  // Report that the frame was stored
2726  *mm_frameavail = true;
2727 
2728  if (OUTPUT_FRAMES) {
2729  std::string output_loc = out_frame_gen(*mm_frmcount);
2730  imwrite(output_loc, frame);
2731  if (DEBUG_COUT) {
2732  LOGGING.open(LOGOUT, std::ios_base::app);
2733  LOGGING << "frame saved at " << output_loc << std::endl;
2734  LOGGING.close();
2735  }
2736  }
2737 
2738  if (DEBUG_FRAMES) {
2739  imshow("fg_mask", frame);
2740  waitKey(1);
2741  }
2742  }
2743  *mm_killed = true;
2744  } else {
2745  pid1 = fork();
2746  if (pid1 > 0) {
2747  // FORK 1 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2748  while (*mm_killed == false) {
2749  while ((*mm_frameavail == false) || (*mm_tier1 == true) || (*mm_tier2 == true) || (*mm_tier3 == true)) {
2750  usleep(50);
2751  }
2752  // Grab the frame and store it locally
2753  Mat local_frame = cv::Mat(Size(frame.size().width, frame.size().height), CV_8UC1, buf, 1 * frame.size().width);
2754  Mat local_frame_1 = local_frame.clone();
2755  Mat local_frame_2 = local_frame.clone();
2756  int local_count = *mm_frmcount;
2757  vector <Point> bigone = qhe_bigone(local_frame);
2758  if ((bigone[0].x < 0) && (bigone[0].y < 0)) {
2759  std::cerr
2760  << "WARNING: largest frame returned error, beware T1 and T2 for frame: "
2761  << local_count
2762  << std::endl;
2763  }
2764  tier_one(local_count, local_frame_1.clone(), bigone);
2765  tier_two(local_count, local_frame_2.clone(), bigone);
2766  *mm_tier1 = true;
2767  *mm_tier2 = true;
2768  }
2769  } else {
2770  // FORK 2 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2771  while (*mm_killed == false) {
2772  while ((*mm_frameavail == false) || (*mm_tier1 == true) || (*mm_tier2 == true) || (*mm_tier3 == true)) {
2773  usleep(50);
2774  }
2775  int local_count = *mm_frmcount;
2776  if (local_count == 0) {
2777  // Skip the zeroth frame since there will be no old_frame
2778  if (DEBUG_COUT) {
2779  LOGGING.open(LOGOUT, std::ios_base::app);
2780  LOGGING
2781  << "skipping early frame tier3 in frame: "
2782  << local_count
2783  << std::endl;
2784  LOGGING.close();
2785  }
2786  } else {
2787 
2788  // Grab the previous frame + current frame and store them locally
2789  Mat local_frame = cv::Mat(Size(frame.size().width, frame.size().height), CV_8UC1, buf, 1 * frame.size().width);
2790  Mat oldframe = cv::Mat(Size(frame.size().width, frame.size().height), CV_8UC1, buf2, 1 * frame.size().width);
2791  vector <Point> bigone = qhe_bigone(local_frame);
2792  if ((bigone[0].x < 0) && (bigone[0].y < 0)) {
2793  std::cerr
2794  << "WARNING: largest frame returned error, beware T3 and T4 for frame: "
2795  << local_count
2796  << std::endl;
2797  }
2798  tier_three(local_count, local_frame.clone(), oldframe.clone(), bigone);
2799  tier_four(local_count, local_frame.clone(), oldframe.clone(), bigone);
2800  *mm_tier3 = true;
2801  }
2802  }
2803 
2804  }
2805  }
2806  usleep(1000000);
2807 
2808  BOXSIZE = 1080;
2809  if (post_processing() != 0) {
2810  std::cerr
2811  << "Exiting frame_extraction with errors"
2812  << std::endl;
2813  return 1;
2814  }
2815  system("");
2816  exit(SIG_ALERT);
2817  return 0;
2818 }
static Mat shift_frame(Mat in_frame, int shiftx, int shifty)
bool OUTPUT_FRAMES
std::string tail(std::string const &source, size_t const length)
std::string TIER3FILE
double T4_AT_MAX
int T2_DYMASK
static Mat corner_matching(Mat in_frame, vector< Point > contour, int plusx, int plusy)
std::string OSFPROJECT
static int post_processing()
static int get_max_ellipse_params()
int SIG_ALERT
int tier_four(int framecnt, Mat in_frame, Mat old_frame, vector< Point > bigone)
static int min_square_dim(Mat in_frame)
int tier_one(int framecnt, Mat in_frame, vector< Point > bigone)
double T4_AT_CONSTANT
static int touching_edges(Mat in_frame, vector< Point > contour)
std::string METADATA
std::string TIER2FILE
double QHE_GB_SIGMA_Y
int T4_THINNING
std::ofstream LOGGING
int T4_GB_KERNEL_Y
int T4_GB_KERNEL_X
int tier_three(int framecnt, Mat in_frame, Mat old_frame, vector< Point > bigone)
double ORIG_PERI
int T3_GB_KERNEL_X
static vector< int > edge_height(vector< Point > contour)
int parse_checklist(std::string name, std::string value)
double T1_AT_CONSTANT
int T4_AT_BLOCKSIZE
int CONVERT_FPS
Point ORIG_BR
int T4_DYMASK
std::string ELLIPSEDATA
double T1_AT_MAX
bool DEBUG_FRAMES
double T3_LAP_DELTA
static std::string out_frame_gen(int framecnt)
int MINOR_VERSION
std::string OUTPUTDIR
int EDGETHRESH
int T3_DYMASK
int QHE_GB_KERNEL_X
int QHE_WIDTH
int T3_GB_KERNEL_Y
std::string space_space(std::string instring)
int NON_ZERO_START
void signal_callback_handler(int signum)
std::string ALPHA_BETA
double T3_GB_SIGMA_Y
int BLACKOUT_THRESH
Point ORIG_TL
double T4_POWER
int T3_LAP_KERNEL
bool GEN_SLIDESHOW
double ORIG_AREA
static vector< int > test_edges(Mat in_frame, vector< Point > contour, int te_ret)
std::string BOXDATA
bool CONCAT_TIERS
int BOXSIZE
int ORIG_HORZ
static int off_screen_ellipse()
double T2_AT_CONSTANT
bool SIMP_ELL
bool EMPTY_FRAMES
double QHE_GB_SIGMA_X
std::string TIER4FILE
static int halo_noise_and_center(Mat in_frame, int framecnt)
double T4_GB_SIGMA_X
int main(int argc, char *argv[])
bool DEBUG_COUT
int T1_DYMASK
static Mat traditional_centering(Mat in_frame, vector< vector< Point >> contours, int largest, Rect box)
static int box_finder(Mat in_frame, bool do_thresh)
int TC_H
int ORIG_VERT
static int show_usage(string name)
static vector< int > edge_width(vector< Point > contour)
int MAJOR_VERSION
double T4_GB_SIGMA_Y
double T3_GB_SIGMA_X
int T2_AT_BLOCKSIZE
static int generate_slideshow()
int TC_W
std::string LOGOUT
static int box_data(Rect box, int framecnt)
static int concat_tiers()
static Mat apply_dynamic_mask(Mat in_frame, vector< Point > contour, int maskwidth)
int T3_CUTOFF_THRESH
static int initial_crop(Mat in_frame, int framecnt)
double T3_LAP_SCALE
int QHE_GB_KERNEL_Y
static int largest_contour(vector< vector< Point >> contours)
static int first_frame(Mat in_frame, int framecnt)
static vector< vector< Point > > contours_only(Mat in_frame)
std::string TIER1FILE
int tier_two(int framecnt, Mat in_frame, vector< Point > bigone)
int T1_AT_BLOCKSIZE
static vector< vector< Point > > quiet_halo_elim(vector< vector< Point >> contours, vector< Point > bigone)
static vector< Point > qhe_bigone(Mat in_frame)
static int edit_contours_for_crop()
bool TIGHT_CROP
double T2_AT_MAX