MythTV  master
BorderDetector.cpp
Go to the documentation of this file.
1 #include <sys/time.h>
2 
3 #include "libmythbase/mythconfig.h"
4 
5 extern "C" {
6 #include "libavcodec/avcodec.h" /* AVFrame */
7 }
8 
9 // MythTV
10 #include "libmythbase/compat.h"
11 #include "libmythbase/mythchrono.h"
12 #include "libmythbase/mythcorecontext.h" /* gContext */
13 
14 // Commercial Flagging headers
15 #include "BorderDetector.h"
16 #include "CommDetector2.h"
17 #include "FrameAnalyzer.h"
18 #include "TemplateFinder.h"
19 
20 using namespace frameAnalyzer;
21 using namespace commDetector2;
22 
24 {
25  m_debugLevel = gCoreContext->GetNumSetting("BorderDetectorDebugLevel", 0);
26 
27  if (m_debugLevel >= 1)
28  LOG(VB_COMMFLAG, LOG_INFO,
29  QString("BorderDetector debugLevel %1").arg(m_debugLevel));
30 }
31 
32 int
34 {
35  (void)player; /* gcc */
36  m_timeReported = false;
37  memset(&m_analyzeTime, 0, sizeof(m_analyzeTime));
38  return 0;
39 }
40 
41 void
43 {
44  if ((m_logoFinder = finder) && (m_logo = m_logoFinder->getTemplate(
45  &m_logoRow, &m_logoCol, &m_logoWidth, &m_logoHeight)))
46  {
47  LOG(VB_COMMFLAG, LOG_INFO,
48  QString("BorderDetector::setLogoState: %1x%2@(%3,%4)")
49  .arg(m_logoWidth).arg(m_logoHeight).arg(m_logoCol).arg(m_logoRow));
50  }
51 }
52 
53 int
54 BorderDetector::getDimensions(const AVFrame *pgm, int pgmheight,
55  long long _frameno, int *prow, int *pcol, int *pwidth, int *pheight)
56 {
57  /*
58  * The basic algorithm is to look for pixels of the same color along all
59  * four borders of the frame, working inwards until the pixels cease to be
60  * of uniform color. This way, letterboxing/pillarboxing bars can be of any
61  * color (varying shades of black-grey).
62  *
63  * If there is a logo, exclude its area from border detection.
64  *
65  * Return 0 for normal frames; non-zero for monochromatic frames.
66  */
67 
68  /*
69  * TUNABLES:
70  *
71  * Higher values mean more tolerance for noise (e.g., analog recordings).
72  * However, in the absence of noise, content/logos can be cropped away from
73  * analysis.
74  *
75  * Lower values mean less tolerance for noise. In a noisy recording, the
76  * transition between pillarbox/letterbox black to content color will be
77  * detected as an edge, and thwart logo edge detection. In the absence of
78  * noise, content/logos will be more faithfully analyzed.
79  */
80 
81  /*
82  * TUNABLE: The maximum range of values allowed for
83  * letterboxing/pillarboxing bars. Usually the bars are black (0x00), but
84  * sometimes they are grey (0x71). Sometimes the letterboxing and
85  * pillarboxing (when one is embedded inside the other) are different
86  * colors.
87  */
88  static constexpr unsigned char kMaxRange = 32;
89 
90  /*
91  * TUNABLE: The maximum number of consecutive rows or columns with too many
92  * outlier points that may be scanned before declaring the existence of a
93  * border.
94  */
95  static constexpr int kMaxLines = 2;
96 
97  const int pgmwidth = pgm->linesize[0];
98 
99  /*
100  * TUNABLE: The maximum number of outlier points in a single row or column
101  * with grey values outside of MAXRANGE before declaring the existence of a
102  * border.
103  */
104  const int MAXOUTLIERS = pgmwidth * 12 / 1000;
105 
106  /*
107  * TUNABLE: Margins to avoid noise at the extreme edges of the signal
108  * (VBI?). (Really, just a special case of VERTSLOP and HORIZSLOP, below.)
109  */
110  const int VERTMARGIN = std::max(2, pgmheight * 1 / 60);
111  const int HORIZMARGIN = std::max(2, pgmwidth * 1 / 80);
112 
113  /*
114  * TUNABLE: Slop to accommodate any jagged letterboxing/pillarboxing edges,
115  * or noise between edges and content. (Really, a more general case of
116  * VERTMARGIN and HORIZMARGIN, above.)
117  */
118  const int VERTSLOP = std::max(kMaxLines, pgmheight * 1 / 120);
119  const int HORIZSLOP = std::max(kMaxLines, pgmwidth * 1 / 160);
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  auto start = nowAsDuration<std::chrono::microseconds>();
133 
134  if (_frameno != kUncached && _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 = std::max(maxval, val) - std::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 = std::min(maxcol1, saved + 1 + HORIZSLOP);
181  newwidth = std::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 = std::max(maxval, val) - std::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 = std::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 = std::max(maxval, val) - std::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 = std::min(maxrow1, saved + 1 + VERTSLOP);
286  newheight = std::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 = std::max(maxval, val) - std::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 = std::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  auto end = nowAsDuration<std::chrono::microseconds>();
370  m_analyzeTime += (end - start);
371 
372  return m_isMonochromatic ? -1 : 0;
373 }
374 
375 int
377 {
378  if (!m_timeReported)
379  {
380  LOG(VB_COMMFLAG, LOG_INFO, QString("BD Time: analyze=%1s")
381  .arg(strftimeval(m_analyzeTime)));
382  m_timeReported = true;
383  }
384  return 0;
385 }
386 
387 /* vim: set expandtab tabstop=4 shiftwidth=4: */
BorderDetector::getDimensions
int getDimensions(const AVFrame *pgm, int pgmheight, long long frameno, int *prow, int *pcol, int *pwidth, int *pheight)
Definition: BorderDetector.cpp:54
frameAnalyzer::rrccinrect
bool rrccinrect(int rr, int cc, int rrow, int rcol, int rwidth, int rheight)
Definition: FrameAnalyzer.cpp:10
CommDetector2.h
cc
Definition: cc.h:9
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MythPlayer
Definition: mythplayer.h:84
AVFrame
struct AVFrame AVFrame
Definition: BorderDetector.h:15
TemplateFinder
Definition: TemplateFinder.h:30
compat.h
BorderDetector::MythPlayerInited
int MythPlayerInited(const MythPlayer *player)
Definition: BorderDetector.cpp:33
FrameAnalyzer.h
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:54
commDetector2::strftimeval
QString strftimeval(std::chrono::microseconds usecs)
Definition: CommDetector2.cpp:263
MythCoreContext::GetNumSetting
int GetNumSetting(const QString &key, int defaultval=0)
Definition: mythcorecontext.cpp:910
BorderDetector.h
BorderDetector::setLogoState
void setLogoState(TemplateFinder *finder)
Definition: BorderDetector.cpp:42
BorderDetector::BorderDetector
BorderDetector(void)
Definition: BorderDetector.cpp:23
mythcorecontext.h
commDetector2
Definition: CommDetector2.cpp:189
mythchrono.h
frameAnalyzer
Definition: FrameAnalyzer.cpp:7
TemplateFinder.h
BorderDetector::reportTime
int reportTime(void)
Definition: BorderDetector.cpp:376