MythTV  master
BorderDetector.cpp
Go to the documentation of this file.
1 #include <sys/time.h>
2 
3 #include "mythconfig.h"
4 
5 extern "C" {
6 #include "libavcodec/avcodec.h" /* AVFrame */
7 }
8 #include "mythcorecontext.h" /* gContext */
9 #include "compat.h"
10 
11 #include "CommDetector2.h"
12 #include "FrameAnalyzer.h"
13 #include "TemplateFinder.h"
14 #include "BorderDetector.h"
15 
16 using namespace frameAnalyzer;
17 using namespace commDetector2;
18 
20 {
21  m_debugLevel = gCoreContext->GetNumSetting("BorderDetectorDebugLevel", 0);
22 
23  if (m_debugLevel >= 1)
24  LOG(VB_COMMFLAG, LOG_INFO,
25  QString("BorderDetector debugLevel %1").arg(m_debugLevel));
26 }
27 
28 int
30 {
31  (void)player; /* gcc */
32  m_time_reported = false;
33  memset(&m_analyze_time, 0, sizeof(m_analyze_time));
34  return 0;
35 }
36 
37 void
39 {
40  if ((m_logoFinder = finder) && (m_logo = m_logoFinder->getTemplate(
41  &m_logorow, &m_logocol, &m_logowidth, &m_logoheight)))
42  {
43  LOG(VB_COMMFLAG, LOG_INFO,
44  QString("BorderDetector::setLogoState: %1x%2@(%3,%4)")
45  .arg(m_logowidth).arg(m_logoheight).arg(m_logocol).arg(m_logorow));
46  }
47 }
48 
49 int
50 BorderDetector::getDimensions(const AVFrame *pgm, int pgmheight,
51  long long _frameno, int *prow, int *pcol, int *pwidth, int *pheight)
52 {
53  /*
54  * The basic algorithm is to look for pixels of the same color along all
55  * four borders of the frame, working inwards until the pixels cease to be
56  * of uniform color. This way, letterboxing/pillarboxing bars can be of any
57  * color (varying shades of black-grey).
58  *
59  * If there is a logo, exclude its area from border detection.
60  *
61  * Return 0 for normal frames; non-zero for monochromatic frames.
62  */
63 
64  /*
65  * TUNABLES:
66  *
67  * Higher values mean more tolerance for noise (e.g., analog recordings).
68  * However, in the absence of noise, content/logos can be cropped away from
69  * analysis.
70  *
71  * Lower values mean less tolerance for noise. In a noisy recording, the
72  * transition between pillarbox/letterbox black to content color will be
73  * detected as an edge, and thwart logo edge detection. In the absence of
74  * noise, content/logos will be more faithfully analyzed.
75  */
76 
77  /*
78  * TUNABLE: The maximum range of values allowed for
79  * letterboxing/pillarboxing bars. Usually the bars are black (0x00), but
80  * sometimes they are grey (0x71). Sometimes the letterboxing and
81  * pillarboxing (when one is embedded inside the other) are different
82  * colors.
83  */
84  static constexpr unsigned char kMaxRange = 32;
85 
86  /*
87  * TUNABLE: The maximum number of consecutive rows or columns with too many
88  * outlier points that may be scanned before declaring the existence of a
89  * border.
90  */
91  static constexpr int kMaxLines = 2;
92 
93  const int pgmwidth = pgm->linesize[0];
94 
95  /*
96  * TUNABLE: The maximum number of outlier points in a single row or column
97  * with grey values outside of MAXRANGE before declaring the existence of a
98  * border.
99  */
100  const int MAXOUTLIERS = pgmwidth * 12 / 1000;
101 
102  /*
103  * TUNABLE: Margins to avoid noise at the extreme edges of the signal
104  * (VBI?). (Really, just a special case of VERTSLOP and HORIZSLOP, below.)
105  */
106  const int VERTMARGIN = max(2, pgmheight * 1 / 60);
107  const int HORIZMARGIN = max(2, pgmwidth * 1 / 80);
108 
109  /*
110  * TUNABLE: Slop to accommodate any jagged letterboxing/pillarboxing edges,
111  * or noise between edges and content. (Really, a more general case of
112  * VERTMARGIN and HORIZMARGIN, above.)
113  */
114  const int VERTSLOP = max(kMaxLines, pgmheight * 1 / 120);
115  const int HORIZSLOP = max(kMaxLines, pgmwidth * 1 / 160);
116 
117  struct timeval start {};
118  struct timeval end {};
119  struct timeval elapsed {};
120 
121  int minrow = VERTMARGIN;
122  int mincol = HORIZMARGIN;
123  int maxrow1 = pgmheight - VERTMARGIN; /* maxrow + 1 */
124  int maxcol1 = pgmwidth - HORIZMARGIN; /* maxcol + 1 */
125  int newrow = minrow - 1;
126  int newcol = mincol - 1;
127  int newwidth = maxcol1 + 1 - mincol;
128  int newheight = maxrow1 + 1 - minrow;
129  bool top = false;
130  bool bottom = false;
131 
132  (void)gettimeofday(&start, nullptr);
133 
134  if (_frameno != UNCACHED && _frameno == m_frameno)
135  goto done;
136 
137  for (;;)
138  {
139  /* Find left edge. */
140  bool left = false;
141  uchar minval = UCHAR_MAX;
142  uchar maxval = 0;
143  int lines = 0;
144  int saved = mincol;
145  for (int cc = mincol; cc < maxcol1; cc++)
146  {
147  int outliers = 0;
148  bool inrange = true;
149  for (int rr = minrow; rr < maxrow1; rr++)
150  {
151  if (m_logo && rrccinrect(rr, cc, m_logorow, m_logocol,
152  m_logowidth, m_logoheight))
153  continue; /* Exclude logo area from analysis. */
154 
155  uchar val = pgm->data[0][rr * pgmwidth + cc];
156  int range = max(maxval, val) - min(minval, val) + 1;
157  if (range > kMaxRange)
158  {
159  if (outliers++ < MAXOUTLIERS)
160  continue; /* Next row. */
161  inrange = false;
162  if (lines++ < kMaxLines)
163  break; /* Next column. */
164  goto found_left;
165  }
166  if (val < minval)
167  minval = val;
168  if (val > maxval)
169  maxval = val;
170  }
171  if (inrange)
172  {
173  saved = cc;
174  lines = 0;
175  }
176  }
177 found_left:
178  if (newcol != saved + 1 + HORIZSLOP)
179  {
180  newcol = min(maxcol1, saved + 1 + HORIZSLOP);
181  newwidth = max(0, maxcol1 - newcol);
182  left = true;
183  }
184 
185  if (!newwidth)
186  goto monochromatic_frame;
187 
188  mincol = newcol;
189 
190  /*
191  * Find right edge. Keep same minval/maxval (pillarboxing colors) as
192  * left edge.
193  */
194  bool right = false;
195  lines = 0;
196  saved = maxcol1 - 1;
197  for (int cc = maxcol1 - 1; cc >= mincol; cc--)
198  {
199  int outliers = 0;
200  bool inrange = true;
201  for (int rr = minrow; rr < maxrow1; rr++)
202  {
203  if (m_logo && rrccinrect(rr, cc, m_logorow, m_logocol,
204  m_logowidth, m_logoheight))
205  continue; /* Exclude logo area from analysis. */
206 
207  uchar val = pgm->data[0][rr * pgmwidth + cc];
208  int range = max(maxval, val) - min(minval, val) + 1;
209  if (range > kMaxRange)
210  {
211  if (outliers++ < MAXOUTLIERS)
212  continue; /* Next row. */
213  inrange = false;
214  if (lines++ < kMaxLines)
215  break; /* Next column. */
216  goto found_right;
217  }
218  if (val < minval)
219  minval = val;
220  if (val > maxval)
221  maxval = val;
222  }
223  if (inrange)
224  {
225  saved = cc;
226  lines = 0;
227  }
228  }
229 found_right:
230  if (newwidth != saved - mincol - HORIZSLOP)
231  {
232  newwidth = max(0, saved - mincol - HORIZSLOP);
233  right = true;
234  }
235 
236  if (!newwidth)
237  goto monochromatic_frame;
238 
239  if (top || bottom)
240  break; /* Do not repeat letterboxing check. */
241 
242  maxcol1 = mincol + newwidth;
243 
244  /* Find top edge. */
245  top = false;
246  minval = UCHAR_MAX;
247  maxval = 0;
248  lines = 0;
249  saved = minrow;
250  for (int rr = minrow; rr < maxrow1; rr++)
251  {
252  int outliers = 0;
253  bool inrange = true;
254  for (int cc = mincol; cc < maxcol1; cc++)
255  {
256  if (m_logo && rrccinrect(rr, cc, m_logorow, m_logocol,
257  m_logowidth, m_logoheight))
258  continue; /* Exclude logo area from analysis. */
259 
260  uchar val = pgm->data[0][rr * pgmwidth + cc];
261  int range = max(maxval, val) - min(minval, val) + 1;
262  if (range > kMaxRange)
263  {
264  if (outliers++ < MAXOUTLIERS)
265  continue; /* Next column. */
266  inrange = false;
267  if (lines++ < kMaxLines)
268  break; /* Next row. */
269  goto found_top;
270  }
271  if (val < minval)
272  minval = val;
273  if (val > maxval)
274  maxval = val;
275  }
276  if (inrange)
277  {
278  saved = rr;
279  lines = 0;
280  }
281  }
282 found_top:
283  if (newrow != saved + 1 + VERTSLOP)
284  {
285  newrow = min(maxrow1, saved + 1 + VERTSLOP);
286  newheight = max(0, maxrow1 - newrow);
287  top = true;
288  }
289 
290  if (!newheight)
291  goto monochromatic_frame;
292 
293  minrow = newrow;
294 
295  /* Find bottom edge. Keep same minval/maxval as top edge. */
296  bottom = false;
297  lines = 0;
298  saved = maxrow1 - 1;
299  for (int rr = maxrow1 - 1; rr >= minrow; rr--)
300  {
301  int outliers = 0;
302  bool inrange = true;
303  for (int cc = mincol; cc < maxcol1; cc++)
304  {
305  if (m_logo && rrccinrect(rr, cc, m_logorow, m_logocol,
306  m_logowidth, m_logoheight))
307  continue; /* Exclude logo area from analysis. */
308 
309  uchar val = pgm->data[0][rr * pgmwidth + cc];
310  int range = max(maxval, val) - min(minval, val) + 1;
311  if (range > kMaxRange)
312  {
313  if (outliers++ < MAXOUTLIERS)
314  continue; /* Next column. */
315  inrange = false;
316  if (lines++ < kMaxLines)
317  break; /* Next row. */
318  goto found_bottom;
319  }
320  if (val < minval)
321  minval = val;
322  if (val > maxval)
323  maxval = val;
324  }
325  if (inrange)
326  {
327  saved = rr;
328  lines = 0;
329  }
330  }
331 found_bottom:
332  if (newheight != saved - minrow - VERTSLOP)
333  {
334  newheight = max(0, saved - minrow - VERTSLOP);
335  bottom = true;
336  }
337 
338  if (!newheight)
339  goto monochromatic_frame;
340 
341  if (left || right)
342  break; /* Do not repeat pillarboxing check. */
343 
344  maxrow1 = minrow + newheight;
345  }
346 
347  m_frameno = _frameno;
348  m_row = newrow;
349  m_col = newcol;
350  m_width = newwidth;
351  m_height = newheight;
352  m_ismonochromatic = false;
353  goto done;
354 
355 monochromatic_frame:
356  m_frameno = _frameno;
357  m_row = newrow;
358  m_col = newcol;
359  m_width = newwidth;
360  m_height = newheight;
361  m_ismonochromatic = true;
362 
363 done:
364  *prow = m_row;
365  *pcol = m_col;
366  *pwidth = m_width;
367  *pheight = m_height;
368 
369  (void)gettimeofday(&end, nullptr);
370  timersub(&end, &start, &elapsed);
371  timeradd(&m_analyze_time, &elapsed, &m_analyze_time);
372 
373  return m_ismonochromatic ? -1 : 0;
374 }
375 
376 int
378 {
379  if (!m_time_reported)
380  {
381  LOG(VB_COMMFLAG, LOG_INFO, QString("BD Time: analyze=%1s")
382  .arg(strftimeval(&m_analyze_time)));
383  m_time_reported = true;
384  }
385  return 0;
386 }
387 
388 /* vim: set expandtab tabstop=4 shiftwidth=4: */
bool rrccinrect(int rr, int cc, int rrow, int rcol, int rwidth, int rheight)
Definition: cc.h:13
#define timeradd(a, b, result)
Definition: compat.h:300
struct AVFrame AVFrame
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
void setLogoState(TemplateFinder *finder)
int MythPlayerInited(const MythPlayer *player)
int GetNumSetting(const QString &key, int defaultval=0)
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
QString strftimeval(const struct timeval *tv)
int reportTime(void)
#define timersub(a, b, result)
Definition: compat.h:310
int getDimensions(const AVFrame *pgm, int pgmheight, long long frameno, int *prow, int *pcol, int *pwidth, int *pheight)