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
6extern "C" {
7#include "libavcodec/avcodec.h" /* AVFrame */
8}
9
10// MythTV
11#include "libmythbase/compat.h"
15
16// Commercial Flagging headers
17#include "BorderDetector.h"
18#include "CommDetector2.h"
19#include "FrameAnalyzer.h"
20#include "TemplateFinder.h"
21
22using namespace frameAnalyzer;
23using 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
34int
35BorderDetector::MythPlayerInited([[maybe_unused]] const MythPlayer *player)
36{
37 m_timeReported = false;
38 memset(&m_analyzeTime, 0, sizeof(m_analyzeTime));
39 return 0;
40}
41
42void
44{
45 m_logoFinder = finder;
46 if (m_logoFinder == nullptr)
47 return;
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
57void
59 long long _frameno)
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 bool monochromatic = false;
136
137 try
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 bool found = false;
148 for (int cc = mincol; !found && 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,
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 found = true;
167 break; /* Next column. */
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 if (newcol != saved + 1 + HORIZSLOP)
179 {
180 newcol = std::min(maxcol1, saved + 1 + HORIZSLOP);
181 newwidth = std::max(0, maxcol1 - newcol);
182 left = true;
183 }
184
185 if (!newwidth)
186 throw "monochromatic";
187
188 mincol = newcol;
189
190 /*
191 * Find right edge. Keep same minval/maxval (pillarboxing colors) as
192 * left edge.
193 */
194 bool right = false;
195 lines = 0;
196 saved = maxcol1 - 1;
197 found = false;
198 for (int cc = maxcol1 - 1; !found && 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,
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 found = true;
217 break; /* Next column. */
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 if (newwidth != saved - mincol - HORIZSLOP)
229 {
230 newwidth = std::max(0, saved - mincol - HORIZSLOP);
231 right = true;
232 }
233
234 if (!newwidth)
235 throw "monochromatic";
236
237 if (top || bottom)
238 break; /* Do not repeat letterboxing check. */
239
240 maxcol1 = mincol + newwidth;
241
242 /* Find top edge. */
243 top = false;
244 minval = UCHAR_MAX;
245 maxval = 0;
246 lines = 0;
247 saved = minrow;
248 found = false;
249 for (int rr = minrow; !found && 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,
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 found = true;
268 break; /* Next row. */
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 if (newrow != saved + 1 + VERTSLOP)
280 {
281 newrow = std::min(maxrow1, saved + 1 + VERTSLOP);
282 newheight = std::max(0, maxrow1 - newrow);
283 top = true;
284 }
285
286 if (!newheight)
287 throw "monochromatic";
288
289 minrow = newrow;
290
291 /* Find bottom edge. Keep same minval/maxval as top edge. */
292 bottom = false;
293 lines = 0;
294 saved = maxrow1 - 1;
295 found = true;
296 for (int rr = maxrow1 - 1; !found && 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,
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 found = true;
315 break; /* Next row. */
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 if (newheight != saved - minrow - VERTSLOP)
327 {
328 newheight = std::max(0, saved - minrow - VERTSLOP);
329 bottom = true;
330 }
331
332 if (!newheight)
333 throw "monochromatic";
334
335 if (left || right)
336 break; /* Do not repeat pillarboxing check. */
337
338 maxrow1 = minrow + newheight;
339 }
340 }
341 catch (char const* e)
342 {
343 monochromatic = true;
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 = monochromatic;
352}
353
354int
355BorderDetector::getDimensions(const AVFrame *pgm, int pgmheight,
356 long long _frameno, int *prow, int *pcol, int *pwidth, int *pheight)
357{
358 auto start = nowAsDuration<std::chrono::microseconds>();
359
360 if (_frameno == kUncached || _frameno != m_frameNo)
361 getDimensionsReal(pgm, pgmheight, _frameno);
362
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
374int
376{
377 if (!m_timeReported)
378 {
379 LOG(VB_COMMFLAG, LOG_INFO, QString("BD Time: analyze=%1s")
381 m_timeReported = true;
382 }
383 return 0;
384}
385
386/* vim: set expandtab tabstop=4 shiftwidth=4: */
AVFrame AVFrame
std::chrono::microseconds m_analyzeTime
int getDimensions(const AVFrame *pgm, int pgmheight, long long frameno, int *prow, int *pcol, int *pwidth, int *pheight)
void getDimensionsReal(const AVFrame *pgm, int pgmheight, long long frameno)
static const long long kUncached
TemplateFinder * m_logoFinder
void setLogoState(TemplateFinder *finder)
int reportTime(void)
int MythPlayerInited(const MythPlayer *player)
long long m_frameNo
const struct AVFrame * m_logo
int GetNumSetting(const QString &key, int defaultval=0)
const struct AVFrame * getTemplate(int *prow, int *pcol, int *pwidth, int *pheight) const
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
QString strftimeval(std::chrono::microseconds usecs)
bool rrccinrect(int rr, int cc, int rrow, int rcol, int rwidth, int rheight)