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