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