MythTV master
BorderDetector.cpp
Go to the documentation of this file.
1#include <sys/time.h>
2#include <algorithm>
3
4extern "C" {
5#include "libavcodec/avcodec.h" /* AVFrame */
6}
7
8// MythTV
13
14// Commercial Flagging headers
15#include "BorderDetector.h"
16#include "CommDetector2.h"
17#include "FrameAnalyzer.h"
18#include "TemplateFinder.h"
19
20using namespace frameAnalyzer;
21using 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
32int
33BorderDetector::MythPlayerInited([[maybe_unused]] const MythPlayer *player)
34{
35 m_timeReported = false;
36 memset(&m_analyzeTime, 0, sizeof(m_analyzeTime));
37 return 0;
38}
39
40void
42{
43 m_logoFinder = finder;
44 if (m_logoFinder == nullptr)
45 return;
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
55void
57 long long _frameno)
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 bool monochromatic = false;
134
135 try
136 {
137 for (;;)
138 {
139 /* Find left edge. */
140 bool left = false;
141 uchar minval = UCHAR_MAX;
142 uchar maxval = 0;
143 int lines = 0;
144 int saved = mincol;
145 bool found = false;
146 for (int cc = mincol; !found && cc < maxcol1; cc++)
147 {
148 int outliers = 0;
149 bool inrange = true;
150 for (int rr = minrow; rr < maxrow1; rr++)
151 {
152 if (m_logo && rrccinrect(rr, cc, m_logoRow, m_logoCol,
154 continue; /* Exclude logo area from analysis. */
155
156 uchar val = pgm->data[0][(rr * pgmwidth) + cc];
157 int range = std::max(maxval, val) - std::min(minval, val) + 1;
158 if (range > kMaxRange)
159 {
160 if (outliers++ < MAXOUTLIERS)
161 continue; /* Next row. */
162 inrange = false;
163 if (lines++ >= kMaxLines)
164 found = true;
165 break; /* Next column. */
166 }
167 minval = std::min(val, minval);
168 maxval = std::max(val, maxval);
169 }
170 if (inrange)
171 {
172 saved = cc;
173 lines = 0;
174 }
175 }
176 if (newcol != saved + 1 + HORIZSLOP)
177 {
178 newcol = std::min(maxcol1, saved + 1 + HORIZSLOP);
179 newwidth = std::max(0, maxcol1 - newcol);
180 left = true;
181 }
182
183 if (!newwidth)
184 throw "monochromatic";
185
186 mincol = newcol;
187
188 /*
189 * Find right edge. Keep same minval/maxval (pillarboxing colors) as
190 * left edge.
191 */
192 bool right = false;
193 lines = 0;
194 saved = maxcol1 - 1;
195 found = false;
196 for (int cc = maxcol1 - 1; !found && cc >= mincol; cc--)
197 {
198 int outliers = 0;
199 bool inrange = true;
200 for (int rr = minrow; rr < maxrow1; rr++)
201 {
202 if (m_logo && rrccinrect(rr, cc, m_logoRow, m_logoCol,
204 continue; /* Exclude logo area from analysis. */
205
206 uchar val = pgm->data[0][(rr * pgmwidth) + cc];
207 int range = std::max(maxval, val) - std::min(minval, val) + 1;
208 if (range > kMaxRange)
209 {
210 if (outliers++ < MAXOUTLIERS)
211 continue; /* Next row. */
212 inrange = false;
213 if (lines++ >= kMaxLines)
214 found = true;
215 break; /* Next column. */
216 }
217 minval = std::min(val, minval);
218 maxval = std::max(val, maxval);
219 }
220 if (inrange)
221 {
222 saved = cc;
223 lines = 0;
224 }
225 }
226 if (newwidth != saved - mincol - HORIZSLOP)
227 {
228 newwidth = std::max(0, saved - mincol - HORIZSLOP);
229 right = true;
230 }
231
232 if (!newwidth)
233 throw "monochromatic";
234
235 if (top || bottom)
236 break; /* Do not repeat letterboxing check. */
237
238 maxcol1 = mincol + newwidth;
239
240 /* Find top edge. */
241 top = false;
242 minval = UCHAR_MAX;
243 maxval = 0;
244 lines = 0;
245 saved = minrow;
246 found = false;
247 for (int rr = minrow; !found && 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,
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 found = true;
266 break; /* Next row. */
267 }
268 minval = std::min(val, minval);
269 maxval = std::max(val, maxval);
270 }
271 if (inrange)
272 {
273 saved = rr;
274 lines = 0;
275 }
276 }
277 if (newrow != saved + 1 + VERTSLOP)
278 {
279 newrow = std::min(maxrow1, saved + 1 + VERTSLOP);
280 newheight = std::max(0, maxrow1 - newrow);
281 top = true;
282 }
283
284 if (!newheight)
285 throw "monochromatic";
286
287 minrow = newrow;
288
289 /* Find bottom edge. Keep same minval/maxval as top edge. */
290 bottom = false;
291 lines = 0;
292 saved = maxrow1 - 1;
293 found = true;
294 for (int rr = maxrow1 - 1; !found && rr >= minrow; rr--)
295 {
296 int outliers = 0;
297 bool inrange = true;
298 for (int cc = mincol; cc < maxcol1; cc++)
299 {
300 if (m_logo && rrccinrect(rr, cc, m_logoRow, m_logoCol,
302 continue; /* Exclude logo area from analysis. */
303
304 uchar val = pgm->data[0][(rr * pgmwidth) + cc];
305 int range = std::max(maxval, val) - std::min(minval, val) + 1;
306 if (range > kMaxRange)
307 {
308 if (outliers++ < MAXOUTLIERS)
309 continue; /* Next column. */
310 inrange = false;
311 if (lines++ >= kMaxLines)
312 found = true;
313 break; /* Next row. */
314 }
315 minval = std::min(val, minval);
316 maxval = std::max(val, maxval);
317 }
318 if (inrange)
319 {
320 saved = rr;
321 lines = 0;
322 }
323 }
324 if (newheight != saved - minrow - VERTSLOP)
325 {
326 newheight = std::max(0, saved - minrow - VERTSLOP);
327 bottom = true;
328 }
329
330 if (!newheight)
331 throw "monochromatic";
332
333 if (left || right)
334 break; /* Do not repeat pillarboxing check. */
335
336 maxrow1 = minrow + newheight;
337 }
338 }
339 catch (char const* e)
340 {
341 monochromatic = true;
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 = monochromatic;
350}
351
352int
353BorderDetector::getDimensions(const AVFrame *pgm, int pgmheight,
354 long long _frameno, int *prow, int *pcol, int *pwidth, int *pheight)
355{
356 auto start = nowAsDuration<std::chrono::microseconds>();
357
358 if (_frameno == kUncached || _frameno != m_frameNo)
359 getDimensionsReal(pgm, pgmheight, _frameno);
360
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
372int
374{
375 if (!m_timeReported)
376 {
377 LOG(VB_COMMFLAG, LOG_INFO, QString("BD Time: analyze=%1s")
379 m_timeReported = true;
380 }
381 return 0;
382}
383
384/* 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)