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