10 #include <QTextStream>
30 #include "libavutil/imgutils.h"
40 const int imgwidth = img->linesize[0];
41 QFileInfo jpgfi(
prefix +
".jpg");
44 QFile pgmfile(
prefix +
".pgm");
45 if (!pgmfile.exists())
47 QByteArray pfname = pgmfile.fileName().toLocal8Bit();
48 if (
pgm_write(img->data[0], imgwidth, imgheight,
55 QString cmd = QString(
"convert -quality 50 -resize 192x144 %1 %2")
56 .arg(pgmfile.fileName(), jpgfi.filePath());
60 if (!pgmfile.remove())
62 LOG(VB_COMMFLAG, LOG_ERR,
63 QString(
"TemplateFinder.writeJPG error removing %1 (%2)")
64 .arg(pgmfile.fileName(), strerror(errno)));
73 const AVFrame *src,
int srcheight)
76 const int srcwidth = src->linesize[0];
78 for (
int rr = 0; rr < srcheight; rr++)
80 for (
int cc = 0; cc < srcwidth; cc++)
82 if (src->data[0][(rr * srcwidth) + cc])
83 scores[((row + rr) * width) + col + cc]++;
93 return *(
unsigned int*)aa - *(
unsigned int*)bb;
100 const int imgwidth = img->linesize[0];
103 int rr2 = row + height;
104 int cc2 = col + width;
105 for (
int rr = row; rr < rr2; rr++)
107 for (
int cc = col; cc < cc2; cc++)
109 if (img->data[0][(rr * imgwidth) + cc])
113 return (
float)score / (width * height);
119 const int imgwidth = img->linesize[0];
120 for (
int cc = col; cc < col + width; cc++)
121 if (img->data[0][(row * imgwidth) + cc])
129 const int imgwidth = img->linesize[0];
130 for (
int rr = row; rr < row + height; rr++)
131 if (img->data[0][(rr * imgwidth) + col])
138 int minrow,
int mincol,
int maxrow1,
int maxcol1,
139 int *prow,
int *pcol,
int *pwidth,
int *pheight)
141 const int imgwidth = img->linesize[0];
148 static constexpr
int kMaxWidthPct = 20;
149 static constexpr
int kMaxHeightPct = 20;
158 const int VERTSLOP = std::max(4, imgheight * 1 / 15);
159 const int HORIZSLOP = std::max(4, imgwidth * 1 / 20);
161 int maxwidth = (maxcol1 - mincol) * kMaxWidthPct / 100;
162 int maxheight = (maxrow1 - minrow) * kMaxHeightPct / 100;
166 int width = maxcol1 - mincol;
167 int height = maxrow1 - minrow;
175 bool improved =
false;
177 LOG(VB_COMMFLAG, LOG_INFO, QString(
"bounding_box %1x%2@(%3,%4)")
178 .arg(width).arg(height).arg(col).arg(row));
183 for (
int ii = 1; ii < height; ii++)
187 if (newscore < score)
197 for (
int ii = 1; ii < width; ii++)
201 if (newscore < score)
210 newbottom = row + height;
211 for (
int ii = 1; ii < height; ii++)
215 if (newscore < score)
218 newbottom = row + height - ii;
224 newright = col + width;
225 for (
int ii = 1; ii < width; ii++)
229 if (newscore < score)
232 newright = col + width - ii;
241 width = newright - newcol;
242 height = newbottom - newrow;
263 if (width > maxwidth)
266 int chop = width / 3;
267 int chopwidth = width - chop;
270 float right =
bounding_score(img, row, col + chop, chopwidth, height);
271 LOG(VB_COMMFLAG, LOG_INFO,
272 QString(
"bounding_box too wide (%1 > %2); left=%3, right=%4")
273 .arg(width).arg(maxwidth)
274 .arg(left, 0,
'f', 3).arg(right, 0,
'f', 3));
275 float minscore = std::min(left, right);
276 float maxscore = std::max(left, right);
277 if (maxscore < 3 * minscore / 2)
284 LOG(VB_COMMFLAG, LOG_ERR,
"bounding_box giving up (edge "
285 "pixels distributed too uniformly)");
295 if (height > maxheight)
298 int chop = height / 3;
299 int chopheight = height - chop;
302 float lower =
bounding_score(img, row + chop, col, width, chopheight);
303 LOG(VB_COMMFLAG, LOG_INFO,
304 QString(
"bounding_box too tall (%1 > %2); upper=%3, lower=%4")
305 .arg(height).arg(maxheight)
306 .arg(upper, 0,
'f', 3).arg(lower, 0,
'f', 3));
307 float minscore = std::min(upper, lower);
308 float maxscore = std::max(upper, lower);
309 if (maxscore < 3 * minscore / 2)
316 LOG(VB_COMMFLAG, LOG_ERR,
"bounding_box giving up (edge "
317 "pixel distribution too uniform)");
337 LOG(VB_COMMFLAG, LOG_INFO,
338 QString(
"bounding_box %1x%2@(%3,%4); horizslop=%5,vertslop=%6")
339 .arg(width).arg(height).arg(col).arg(row)
340 .arg(HORIZSLOP).arg(VERTSLOP));
346 if (newrow <= minrow)
351 if (row - newrow >= VERTSLOP)
353 newrow = row - VERTSLOP;
363 newrow = std::max(minrow, newrow - 1);
369 if (newcol <= mincol)
374 if (col - newcol >= HORIZSLOP)
376 newcol = col - HORIZSLOP;
386 newcol = std::max(mincol, newcol - 1);
389 newright = col + width;
392 if (newright >= maxcol1)
397 if (newright - (col + width) >= HORIZSLOP)
399 newright = col + width + HORIZSLOP;
406 newright = std::min(maxcol1, newright + 1);
409 newbottom = row + height;
412 if (newbottom >= maxrow1)
417 if (newbottom - (row + height) >= VERTSLOP)
419 newbottom = row + height + VERTSLOP;
426 newbottom = std::min(maxrow1, newbottom + 1);
430 width = newright - newcol;
431 height = newbottom - newrow;
433 LOG(VB_COMMFLAG, LOG_INFO, QString(
"bounding_box %1x%2@(%3,%4)")
434 .arg(width).arg(height).arg(col).arg(row));
445 int minrow,
int mincol,
int maxrow1,
int maxcol1,
AVFrame *tmpl,
446 int *ptmplrow,
int *ptmplcol,
int *ptmplwidth,
int *ptmplheight,
447 bool debug_edgecounts,
const QString& debugdir)
458 static constexpr
float kMinScorePctile = 0.998;
460 const int nn = width * height;
464 unsigned int threshscore = 0;
467 if (av_image_alloc(thresh.data, thresh.linesize,
468 width, height, AV_PIX_FMT_GRAY8, IMAGE_ALIGN))
470 LOG(VB_COMMFLAG, LOG_ERR,
471 QString(
"template_alloc av_image_alloc thresh (%1x%2) failed")
472 .arg(width).arg(height));
476 uint *sortedscores =
new unsigned int[nn];
477 memcpy(sortedscores, scores, nn *
sizeof(*sortedscores));
480 if (sortedscores[0] == sortedscores[nn - 1])
483 LOG(VB_COMMFLAG, LOG_ERR,
484 QString(
"template_alloc: %1x%2 pixels all identical!")
485 .arg(width).arg(height));
491 ii = (int)roundf(nn * kMinScorePctile);
492 threshscore = sortedscores[ii];
493 for (first = ii; first > 0 && sortedscores[first] == threshscore; first--)
495 if (sortedscores[first] != threshscore)
497 for (last = ii; last < nn - 1 && sortedscores[last] == threshscore; last++)
499 if (sortedscores[last] != threshscore)
502 LOG(VB_COMMFLAG, LOG_INFO, QString(
"template_alloc wanted %1, got %2-%3")
503 .arg(kMinScorePctile, 0,
'f', 6)
504 .arg((
float)first / nn, 0,
'f', 6)
505 .arg((
float)last / nn, 0,
'f', 6));
507 for (ii = 0; ii < nn; ii++)
508 thresh.data[0][ii] = scores[ii] >= threshscore ? UCHAR_MAX : 0;
510 if (debug_edgecounts)
514 if (av_image_alloc(scored.data, scored.linesize,
515 width, height, AV_PIX_FMT_GRAY8, IMAGE_ALIGN))
517 LOG(VB_COMMFLAG, LOG_ERR,
518 QString(
"template_alloc av_image_alloc scored (%1x%2) failed")
519 .arg(width).arg(height));
522 unsigned int maxscore = sortedscores[nn - 1];
523 for (ii = 0; ii < nn; ii++)
524 scored.data[0][ii] = scores[ii] * UCHAR_MAX / maxscore;
525 bool success =
writeJPG(debugdir +
"/TemplateFinder-scores", &scored,
527 av_freep(
reinterpret_cast<void*
>(&scored.data[0]));
532 if (!
writeJPG(debugdir +
"/TemplateFinder-edgecounts", &thresh, height))
538 if (
bounding_box(&thresh, height, minrow, mincol, maxrow1, maxcol1,
539 ptmplrow, ptmplcol, ptmplwidth, ptmplheight))
542 if ((
uint)(*ptmplwidth * *ptmplheight) > USHRT_MAX)
545 LOG(VB_COMMFLAG, LOG_ERR,
546 QString(
"template_alloc bounding_box too big (%1x%2)")
547 .arg(*ptmplwidth).arg(*ptmplheight));
551 if (av_image_alloc(tmpl->data, tmpl->linesize,
552 *ptmplwidth, *ptmplheight, AV_PIX_FMT_GRAY8, IMAGE_ALIGN))
554 LOG(VB_COMMFLAG, LOG_ERR,
555 QString(
"template_alloc av_image_alloc tmpl (%1x%2) failed")
556 .arg(*ptmplwidth).arg(*ptmplheight));
560 if (
pgm_crop(tmpl, &thresh, height, *ptmplrow, *ptmplcol,
561 *ptmplwidth, *ptmplheight))
564 delete []sortedscores;
565 av_freep(
reinterpret_cast<void*
>(&thresh.data[0]));
570 delete []sortedscores;
571 av_freep(
reinterpret_cast<void*
>(&thresh.data[0]));
578 int croprow,
int cropcol,
bool debug_frames,
const QString& debugdir)
580 static constexpr
int kDelta = 24;
581 static int s_lastrow;
582 static int s_lastcol;
583 static int s_lastwidth;
584 static int s_lastheight;
585 const int cropwidth = cropped->linesize[0];
587 int rowsame = abs(s_lastrow - croprow) <= kDelta ? 1 : 0;
588 int colsame = abs(s_lastcol - cropcol) <= kDelta ? 1 : 0;
589 int widthsame = abs(s_lastwidth - cropwidth) <= kDelta ? 1 : 0;
590 int heightsame = abs(s_lastheight - cropheight) <= kDelta ? 1 : 0;
592 if (frameno > 0 && rowsame + colsame + widthsame + heightsame >= 3)
595 LOG(VB_COMMFLAG, LOG_INFO,
596 QString(
"TemplateFinder Frame %1: %2x%3@(%4,%5)")
598 .arg(cropwidth).arg(cropheight)
599 .arg(cropcol).arg(croprow));
603 s_lastwidth = cropwidth;
604 s_lastheight = cropheight;
608 QString base = QString(
"%1/TemplateFinder-%2")
609 .arg(debugdir).arg(frameno, 5, 10, QChar(
'0'));
612 if (!
writeJPG(base, pgm, pgmheight))
616 if (!
writeJPG(base +
"-cropped", cropped, cropheight))
620 if (!
writeJPG(base +
"-edges", edges, cropheight))
628 bool readTemplate(
const QString& datafile,
int *prow,
int *pcol,
int *pwidth,
int *pheight,
629 const QString& tmplfile,
AVFrame *tmpl,
bool *pvalid)
631 QFile dfile(datafile);
632 QFileInfo dfileinfo(dfile);
634 if (!dfile.open(QIODevice::ReadOnly))
637 if (!dfileinfo.size())
644 QTextStream stream(&dfile);
645 stream >> *prow >> *pcol >> *pwidth >> *pheight;
648 if (av_image_alloc(tmpl->data, tmpl->linesize,
649 *pwidth, *pheight, AV_PIX_FMT_GRAY8, IMAGE_ALIGN))
651 LOG(VB_COMMFLAG, LOG_ERR,
652 QString(
"readTemplate av_image_alloc %1 (%2x%3) failed")
653 .arg(tmplfile).arg(*pwidth).arg(*pheight));
657 QByteArray tmfile = tmplfile.toLatin1();
658 if (
pgm_read(tmpl->data[0], *pwidth, *pheight, tmfile.constData()))
660 av_freep(
reinterpret_cast<void*
>(&tmpl->data[0]));
672 QFile dfile(datafile);
674 if (!dfile.open(QIODevice::WriteOnly | QIODevice::Truncate) &&
676 (void)dfile.remove();
681 int row,
int col,
int width,
int height)
683 QFile tfile(tmplfile);
685 QByteArray tmfile = tmplfile.toLatin1();
686 if (
pgm_write(tmpl->data[0], width, height, tmfile.constData()))
689 QFile dfile(datafile);
690 if (!dfile.open(QIODevice::WriteOnly))
693 QTextStream stream(&dfile);
694 stream << row <<
" " << col <<
"\n" << width <<
" " << height <<
"\n";
702 std::shared_ptr<BorderDetector> bd,
703 std::shared_ptr<EdgeDetector> ed,
704 MythPlayer *player, std::chrono::seconds proglen,
705 const QString& debugdir)
706 : m_pgmConverter(std::move(pgmc)),
707 m_borderDetector(std::move(bd)),
708 m_edgeDetector(std::move(ed)),
709 m_sampleTime(std::min(proglen / 2, 20 * 60s)),
710 m_debugDir(debugdir),
711 m_debugData(debugdir +
"/TemplateFinder.txt"),
712 m_debugTmpl(debugdir +
"/TemplateFinder.pgm")
722 unsigned int samplesNeeded = 300;
741 LOG(VB_COMMFLAG, LOG_INFO,
742 QString(
"TemplateFinder: sampleTime=%1s, samplesNeeded=%2, endFrame=%3")
757 QString(
"TemplateFinder debugLevel %1").arg(
m_debugLevel));
770 av_freep(
reinterpret_cast<void*
>(&
m_tmpl.data[0]));
771 av_freep(
reinterpret_cast<void*
>(&
m_cropped.data[0]));
776 [[maybe_unused]]
long long nframes)
806 LOG(VB_COMMFLAG, LOG_INFO,
807 QString(
"TemplateFinder::MythPlayerInited read %1: %2")
822 LOG(VB_COMMFLAG, LOG_INFO,
823 QString(
"TemplateFinder::MythPlayerInited %1 of %2 (%3)")
829 LOG(VB_COMMFLAG, LOG_INFO,
830 QString(
"TemplateFinder::MythPlayerInited framesize %1")
837 av_freep(
reinterpret_cast<void*
>(&
m_tmpl.data[0]));
847 av_freep(
reinterpret_cast<void*
>(&
m_cropped.data[0]));
850 newwidth, newheight, AV_PIX_FMT_GRAY8, IMAGE_ALIGN))
852 LOG(VB_COMMFLAG, LOG_ERR,
853 QString(
"TemplateFinder::resetBuffers "
854 "av_image_alloc cropped (%1x%2) failed")
855 .arg(newwidth).arg(newheight));
866 long long *pNextFrame)
882 const int FRAMESGMPCTILE = 90;
895 static constexpr
float kExcludeWidth = 0.5;
896 static constexpr
float kExcludeHeight = 0.5;
919 &croprow, &cropcol, &cropwidth, &cropheight))
923 auto start = nowAsDuration<std::chrono::microseconds>();
934 cropwidth, cropheight))
941 int excludewidth = (int)(pgmwidth * kExcludeWidth);
942 int excludeheight = (int)(pgmheight * kExcludeHeight);
943 int excluderow = ((pgmheight - excludeheight) / 2) - croprow;
944 int excludecol = ((pgmwidth - excludewidth) / 2) - cropcol;
946 excludewidth, excludeheight);
950 if (edges ==
nullptr)
964 auto end = nowAsDuration<std::chrono::microseconds>();
974 LOG(VB_COMMFLAG, LOG_ERR,
975 QString(
"TemplateFinder::analyzeFrame error at frame %1")
1007 LOG(VB_COMMFLAG, LOG_INFO,
1008 QString(
"TemplateFinder::finished wrote %1"
1009 " and %2 [%3x%4@(%5,%6)]")
1025 av_freep(
reinterpret_cast<void*
>(&
m_tmpl.data[0]));
1038 LOG(VB_COMMFLAG, LOG_INFO, QString(
"TF Time: analyze=%1s")