27 #include "libavutil/imgutils.h"
35 int pgm_set(
const AVFrame *pict,
int height)
37 const int width = pict->linesize[0];
38 const int size = height * width;
41 for (
int ii = 0; ii < size; ii++)
42 if (pict->data[0][ii])
47 int pgm_match(
const AVFrame *tmpl,
const AVFrame *test,
int height,
48 int radius,
unsigned short *pscore)
51 const int width = tmpl->linesize[0];
53 if (width != test->linesize[0])
55 LOG(VB_COMMFLAG, LOG_ERR,
56 QString(
"pgm_match widths don't match: %1 != %2")
57 .arg(width).arg(test->linesize[0]));
62 for (
int rr = 0; rr < height; rr++)
64 for (
int cc = 0;
cc < width;
cc++)
66 if (!tmpl->data[0][rr * width +
cc])
69 int r2min = std::max(0, rr - radius);
70 int r2max = std::min(height, rr + radius);
72 int c2min = std::max(0,
cc - radius);
73 int c2max = std::min(width,
cc + radius);
75 for (
int r2 = r2min; r2 <= r2max; r2++)
77 for (
int c2 = c2min; c2 <= c2max; c2++)
79 if (test->data[0][r2 * width + c2])
95 bool readMatches(
const QString&
filename,
unsigned short *matches,
long long nframes)
97 QByteArray fname =
filename.toLocal8Bit();
98 FILE *fp = fopen(fname.constData(),
"r");
102 for (
long long frameno = 0; frameno < nframes; frameno++)
104 int nitems = fscanf(fp,
"%20hu", &matches[frameno]);
107 LOG(VB_COMMFLAG, LOG_ERR,
108 QString(
"Not enough data in %1: frame %2")
115 LOG(VB_COMMFLAG, LOG_ERR, QString(
"Error closing %1: %2")
121 LOG(VB_COMMFLAG, LOG_ERR, QString(
"Error closing %1: %2")
126 bool writeMatches(
const QString&
filename,
unsigned short *matches,
long long nframes)
128 QByteArray fname =
filename.toLocal8Bit();
129 FILE *fp = fopen(fname.constData(),
"w");
133 for (
long long frameno = 0; frameno < nframes; frameno++)
134 (
void)fprintf(fp,
"%hu\n", matches[frameno]);
137 LOG(VB_COMMFLAG, LOG_ERR, QString(
"Error closing %1: %2")
142 int finishedDebug(
long long nframes,
const unsigned short *matches,
143 const unsigned char *match)
145 ushort score = matches[0];
148 long long startframe = 0;
150 for (
long long frameno = 1; frameno < nframes; frameno++)
152 score = matches[frameno];
153 if (match[frameno - 1] == match[frameno])
162 LOG(VB_COMMFLAG, LOG_INFO, QString(
"Frame %1-%2: %3 L-H: %4-%5 (%6)")
163 .arg(startframe, 6).arg(frameno - 1, 6)
164 .arg(match[frameno - 1] ?
"logo " :
" no-logo")
165 .arg(low, 4).arg(high, 4).arg(frameno - startframe, 5));
169 startframe = frameno;
177 return *(
unsigned short*)aa - *(
unsigned short*)bb;
180 long long matchspn(
long long nframes,
const unsigned char *match,
long long frameno,
181 unsigned char acceptval)
187 while (frameno < nframes && match[frameno] == acceptval)
192 unsigned int range_area(
const unsigned short *
freq,
unsigned short start,
196 const unsigned short width = end - start;
200 for (ushort matchcnt = start; matchcnt < end; matchcnt++)
204 sum +=
freq[matchcnt];
210 return width * sum / nsamples;
213 unsigned short pick_mintmpledges(
const unsigned short *matches,
236 static constexpr
float kLeftWidth = 0.04;
237 static constexpr
float kMiddleWidth = 0.04;
238 static constexpr
float kRightWidth = 0.04;
240 static constexpr
float kMatchStart = 0.20;
241 static constexpr
float kMatchEnd = 0.80;
243 auto *sorted =
new unsigned short[nframes];
244 memcpy(sorted, matches, nframes *
sizeof(*matches));
246 ushort minmatch = sorted[0];
247 ushort maxmatch = sorted[nframes - 1];
248 ushort matchrange = maxmatch - minmatch;
251 auto leftwidth = (
unsigned short)(kLeftWidth * matchrange);
252 auto middlewidth = (
unsigned short)(kMiddleWidth * matchrange);
253 auto rightwidth = (
unsigned short)(kRightWidth * matchrange);
255 int nfreq = maxmatch + 1;
256 auto *
freq =
new unsigned short[nfreq];
257 memset(
freq, 0, nfreq *
sizeof(*
freq));
258 for (
long long frameno = 0; frameno < nframes; frameno++)
259 freq[matches[frameno]]++;
261 ushort matchstart = minmatch + (
unsigned short)(kMatchStart * matchrange);
262 ushort matchend = minmatch + (
unsigned short)(kMatchEnd * matchrange);
264 int local_minimum = matchstart;
266 for (
int matchcnt = matchstart + leftwidth + middlewidth / 2;
267 matchcnt < matchend - rightwidth - middlewidth / 2;
270 ushort p0 = matchcnt - leftwidth - middlewidth / 2;
271 ushort
p1 = p0 + leftwidth;
272 ushort
p2 =
p1 + middlewidth;
273 ushort p3 =
p2 + rightwidth;
278 if (middlescore < leftscore && middlescore < rightscore)
280 unsigned int delta = (leftscore - middlescore) +
281 (rightscore - middlescore);
282 if (delta > maxdelta)
284 local_minimum = matchcnt;
290 LOG(VB_COMMFLAG, LOG_INFO,
291 QString(
"pick_mintmpledges minmatch=%1 maxmatch=%2"
292 " matchstart=%3 matchend=%4 widths=%5,%6,%7 local_minimum=%8")
293 .arg(minmatch).arg(maxmatch).arg(matchstart).arg(matchend)
294 .arg(leftwidth).arg(middlewidth).arg(rightwidth)
295 .arg(local_minimum));
299 return local_minimum;
305 std::shared_ptr<EdgeDetector> ed,
307 m_pgmConverter(
std::move(pgmc)),
308 m_edgeDetector(
std::move(ed)), m_templateFinder(tf),
309 m_debugDir(debugdir),
311 m_debugData(debugdir +
"/TemplateMatcher-pgm.txt")
313 m_debugData(debugdir +
"/TemplateMatcher-yuv.txt")
327 QString(
"TemplateMatcher debugLevel %1").arg(
m_debugLevel));
352 LOG(VB_COMMFLAG, LOG_ERR,
353 QString(
"TemplateMatcher::MythPlayerInited: no template"));
360 LOG(VB_COMMFLAG, LOG_ERR,
361 QString(
"TemplateMatcher::MythPlayerInited "
362 "av_image_alloc cropped (%1x%2) failed")
373 m_match =
new unsigned char[nframes];
379 LOG(VB_COMMFLAG, LOG_INFO,
380 QString(
"TemplateMatcher::MythPlayerInited read %1")
398 long long *pNextFrame)
415 const int FRAMESGMPCTILE = 70;
433 const int JITTER_RADIUS = 0;
435 const AVFrame *edges =
nullptr;
438 std::chrono::microseconds start {0us};
439 std::chrono::microseconds end {0us};
447 start = nowAsDuration<std::chrono::microseconds>();
454 if (edges ==
nullptr)
460 end = nowAsDuration<std::chrono::microseconds>();
466 LOG(VB_COMMFLAG, LOG_ERR,
467 QString(
"TemplateMatcher::analyzeFrame error at frame %1 of %2")
484 const int MINBREAKLEN = (int)roundf(45 *
m_fps);
485 const int MINSEGLEN = (int)roundf(105 *
m_fps);
495 LOG(VB_COMMFLAG, LOG_INFO,
496 QString(
"TemplateMatcher::finished wrote %1") .arg(
m_debugData));
502 int mintmpledges = pick_mintmpledges(
m_matches, nframes);
504 LOG(VB_COMMFLAG, LOG_INFO,
505 QString(
"TemplateMatcher::finished %1x%2@(%3,%4),"
506 " %5 edge pixels, want %6")
508 .arg(tmpledges).arg(mintmpledges));
510 for (
long long ii = 0; ii < nframes; ii++)
524 while (brkb < nframes)
528 long long brklen = brke - brkb;
542 if (minbreaklen <= MINBREAKLEN)
548 if (minseglen <= MINSEGLEN)
554 if (minbreaklen > MINBREAKLEN && minseglen > MINSEGLEN)
577 LOG(VB_COMMFLAG, LOG_INFO, QString(
"TM Time: analyze=%1s")
596 static const int64_t MINBREAKS { nframes * 20 / 100 };
597 static const int64_t MAXBREAKS { nframes * 45 / 100 };
600 const bool good = brklen >= MINBREAKS && brklen <= MAXBREAKS;
606 LOG(VB_COMMFLAG, LOG_ERR,
607 QString(
"TemplateMatcher: no template (wanted %2-%3%)")
608 .arg(100 * MINBREAKS / nframes)
609 .arg(100 * MAXBREAKS / nframes));
613 LOG(VB_COMMFLAG, LOG_ERR,
614 QString(
"TemplateMatcher has %1% breaks (real-time flagging)")
615 .arg(100 * brklen / nframes));
619 LOG(VB_COMMFLAG, LOG_INFO, QString(
"TemplateMatcher has %1% breaks")
620 .arg(100 * brklen / nframes));
624 LOG(VB_COMMFLAG, LOG_INFO,
625 QString(
"TemplateMatcher has %1% breaks (wanted %2-%3%)")
626 .arg(100 * brklen / nframes)
627 .arg(100 * MINBREAKS / nframes)
628 .arg(100 * MAXBREAKS / nframes));
635 return brklen < MINBREAKS ? 1 : brklen <= MAXBREAKS ? 0 : -1;
711 const int BLANK_NEARBY = (int)roundf(0.5F *
m_fps);
712 const int TEMPLATE_DISAPPEARS_EARLY = (int)roundf(25 *
m_fps);
713 const int TEMPLATE_DISAPPEARS_LATE = (int)roundf(0 *
m_fps);
714 const int TEMPLATE_REAPPEARS_LATE = (int)roundf(35 *
m_fps);
715 const int TEMPLATE_REAPPEARS_EARLY = (int)roundf(1.5F *
m_fps);
717 LOG(VB_COMMFLAG, LOG_INFO, QString(
"TemplateMatcher adjusting for blanks"));
719 FrameAnalyzer::FrameMap::Iterator ii =
m_breakMap.begin();
720 long long prevbrke = 0;
723 FrameAnalyzer::FrameMap::Iterator iinext = ii;
731 const long long brkb = ii.key();
732 const long long brke = brkb + *ii;
733 FrameAnalyzer::FrameMap::const_iterator jj = blankMap->constEnd();
738 brkb - std::max(BLANK_NEARBY, TEMPLATE_DISAPPEARS_LATE)),
740 brkb + std::max(BLANK_NEARBY, TEMPLATE_DISAPPEARS_EARLY)));
742 long long newbrkb = brkb;
743 if (jj != blankMap->constEnd())
746 long long adj = *jj / 2;
759 brke - std::max(BLANK_NEARBY, TEMPLATE_REAPPEARS_LATE)),
760 std::min(iinext ==
m_breakMap.end() ? nframes : iinext.key(),
761 brke + std::max(BLANK_NEARBY, TEMPLATE_REAPPEARS_EARLY)));
762 long long newbrke = brke;
763 if (kk != blankMap->constEnd())
777 long long newbrklen = newbrke - newbrkb;
781 if (newbrkb < nframes && newbrklen)
784 else if (newbrke != brke)
814 breaks->insert(bb.key(), *bb);