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