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