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