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])
139 int minrow,
int mincol,
int maxrow1,
int maxcol1,
140 int *prow,
int *pcol,
int *pwidth,
int *pheight)
142 const int imgwidth = img->linesize[0];
149 static constexpr int kMaxWidthPct = 20;
150 static constexpr int kMaxHeightPct = 20;
159 const int VERTSLOP = std::max(4, imgheight * 1 / 15);
160 const int HORIZSLOP = std::max(4, imgwidth * 1 / 20);
162 int maxwidth = (maxcol1 - mincol) * kMaxWidthPct / 100;
163 int maxheight = (maxrow1 - minrow) * kMaxHeightPct / 100;
167 int width = maxcol1 - mincol;
168 int height = maxrow1 - minrow;
176 bool improved =
false;
178 LOG(VB_COMMFLAG, LOG_INFO, QString(
"bounding_box %1x%2@(%3,%4)")
179 .arg(width).arg(height).arg(col).arg(row));
184 for (
int ii = 1; ii < height; ii++)
188 if (newscore < score)
198 for (
int ii = 1; ii < width; ii++)
202 if (newscore < score)
211 newbottom = row + height;
212 for (
int ii = 1; ii < height; ii++)
216 if (newscore < score)
219 newbottom = row + height - ii;
225 newright = col + width;
226 for (
int ii = 1; ii < width; ii++)
230 if (newscore < score)
233 newright = col + width - ii;
242 width = newright - newcol;
243 height = newbottom - newrow;
264 if (width > maxwidth)
267 int chop = width / 3;
268 int chopwidth = width - chop;
271 float right =
bounding_score(img, row, col + chop, chopwidth, height);
272 LOG(VB_COMMFLAG, LOG_INFO,
273 QString(
"bounding_box too wide (%1 > %2); left=%3, right=%4")
274 .arg(width).arg(maxwidth)
275 .arg(left, 0,
'f', 3).arg(right, 0,
'f', 3));
276 float minscore = std::min(left, right);
277 float maxscore = std::max(left, right);
278 if (maxscore < 3 * minscore / 2)
285 LOG(VB_COMMFLAG, LOG_ERR,
"bounding_box giving up (edge "
286 "pixels distributed too uniformly)");
296 if (height > maxheight)
299 int chop = height / 3;
300 int chopheight = height - chop;
303 float lower =
bounding_score(img, row + chop, col, width, chopheight);
304 LOG(VB_COMMFLAG, LOG_INFO,
305 QString(
"bounding_box too tall (%1 > %2); upper=%3, lower=%4")
306 .arg(height).arg(maxheight)
307 .arg(upper, 0,
'f', 3).arg(lower, 0,
'f', 3));
308 float minscore = std::min(upper, lower);
309 float maxscore = std::max(upper, lower);
310 if (maxscore < 3 * minscore / 2)
317 LOG(VB_COMMFLAG, LOG_ERR,
"bounding_box giving up (edge "
318 "pixel distribution too uniform)");
338 LOG(VB_COMMFLAG, LOG_INFO,
339 QString(
"bounding_box %1x%2@(%3,%4); horizslop=%5,vertslop=%6")
340 .arg(width).arg(height).arg(col).arg(row)
341 .arg(HORIZSLOP).arg(VERTSLOP));
347 if (newrow <= minrow)
352 if (row - newrow >= VERTSLOP)
354 newrow = row - VERTSLOP;
364 newrow = std::max(minrow, newrow - 1);
370 if (newcol <= mincol)
375 if (col - newcol >= HORIZSLOP)
377 newcol = col - HORIZSLOP;
387 newcol = std::max(mincol, newcol - 1);
390 newright = col + width;
393 if (newright >= maxcol1)
398 if (newright - (col + width) >= HORIZSLOP)
400 newright = col + width + HORIZSLOP;
407 newright = std::min(maxcol1, newright + 1);
410 newbottom = row + height;
413 if (newbottom >= maxrow1)
418 if (newbottom - (row + height) >= VERTSLOP)
420 newbottom = row + height + VERTSLOP;
427 newbottom = std::min(maxrow1, newbottom + 1);
431 width = newright - newcol;
432 height = newbottom - newrow;
434 LOG(VB_COMMFLAG, LOG_INFO, QString(
"bounding_box %1x%2@(%3,%4)")
435 .arg(width).arg(height).arg(col).arg(row));
446 int minrow,
int mincol,
int maxrow1,
int maxcol1,
AVFrame *tmpl,
447 int *ptmplrow,
int *ptmplcol,
int *ptmplwidth,
int *ptmplheight,
448 bool debug_edgecounts,
const QString& debugdir)
459 static constexpr float kMinScorePctile = 0.998;
461 const int nn = width * height;
465 unsigned int threshscore = 0;
468 if (av_image_alloc(thresh.data, thresh.linesize,
469 width, height, AV_PIX_FMT_GRAY8, IMAGE_ALIGN) < 0)
471 LOG(VB_COMMFLAG, LOG_ERR,
472 QString(
"template_alloc av_image_alloc thresh (%1x%2) failed")
473 .arg(width).arg(height));
477 std::vector<uint> sortedscores;
478 sortedscores.resize(nn);
479 memcpy(sortedscores.data(), scores, nn *
sizeof(
uint));
483 auto cleanup_fn = [&](
int* ) {
484 av_freep(
reinterpret_cast<void*
>(&thresh.data[0]));
486 std::unique_ptr<int,
decltype(cleanup_fn)>
cleanup { &first, cleanup_fn };
488 if (sortedscores[0] == sortedscores[nn - 1])
491 LOG(VB_COMMFLAG, LOG_ERR,
492 QString(
"template_alloc: %1x%2 pixels all identical!")
493 .arg(width).arg(height));
499 ii = (int)roundf(nn * kMinScorePctile);
500 threshscore = sortedscores[ii];
501 for (first = ii; first > 0 && sortedscores[first] == threshscore; first--)
503 if (sortedscores[first] != threshscore)
505 for (last = ii; last < nn - 1 && sortedscores[last] == threshscore; last++)
507 if (sortedscores[last] != threshscore)
510 LOG(VB_COMMFLAG, LOG_INFO, QString(
"template_alloc wanted %1, got %2-%3")
511 .arg(kMinScorePctile, 0,
'f', 6)
512 .arg((
float)first / nn, 0,
'f', 6)
513 .arg((
float)last / nn, 0,
'f', 6));
515 for (ii = 0; ii < nn; ii++)
516 thresh.data[0][ii] = scores[ii] >= threshscore ? UCHAR_MAX : 0;
518 if (debug_edgecounts)
522 if (av_image_alloc(scored.data, scored.linesize,
523 width, height, AV_PIX_FMT_GRAY8, IMAGE_ALIGN) < 0)
525 LOG(VB_COMMFLAG, LOG_ERR,
526 QString(
"template_alloc av_image_alloc scored (%1x%2) failed")
527 .arg(width).arg(height));
530 unsigned int maxscore = sortedscores[nn - 1];
531 for (ii = 0; ii < nn; ii++)
532 scored.data[0][ii] = scores[ii] * UCHAR_MAX / maxscore;
533 bool success =
writeJPG(debugdir +
"/TemplateFinder-scores", &scored,
535 av_freep(
reinterpret_cast<void*
>(&scored.data[0]));
540 if (!
writeJPG(debugdir +
"/TemplateFinder-edgecounts", &thresh, height))
546 if (
bounding_box(&thresh, height, minrow, mincol, maxrow1, maxcol1,
547 ptmplrow, ptmplcol, ptmplwidth, ptmplheight))
550 if ((
uint)(*ptmplwidth * *ptmplheight) > USHRT_MAX)
553 LOG(VB_COMMFLAG, LOG_ERR,
554 QString(
"template_alloc bounding_box too big (%1x%2)")
555 .arg(*ptmplwidth).arg(*ptmplheight));
559 if (av_image_alloc(tmpl->data, tmpl->linesize,
560 *ptmplwidth, *ptmplheight, AV_PIX_FMT_GRAY8, IMAGE_ALIGN) < 0)
562 LOG(VB_COMMFLAG, LOG_ERR,
563 QString(
"template_alloc av_image_alloc tmpl (%1x%2) failed")
564 .arg(*ptmplwidth).arg(*ptmplheight));
568 if (
pgm_crop(tmpl, &thresh, height, *ptmplrow, *ptmplcol,
569 *ptmplwidth, *ptmplheight))
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))
628bool 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())
645 QTextStream stream(&dfile);
646 stream >> *prow >> *pcol >> *pwidth >> *pheight;
649 if (*pwidth < 0 || *pheight < 0)
651 LOG(VB_COMMFLAG, LOG_ERR, QString(
"readTemplate no saved template"));
655 if (av_image_alloc(tmpl->data, tmpl->linesize,
656 *pwidth, *pheight, AV_PIX_FMT_GRAY8, IMAGE_ALIGN) < 0)
658 LOG(VB_COMMFLAG, LOG_ERR,
659 QString(
"readTemplate av_image_alloc %1 (%2x%3) failed")
660 .arg(tmplfile).arg(*pwidth).arg(*pheight));
664 QByteArray tmfile = tmplfile.toLatin1();
665 if (
pgm_read(tmpl->data[0], *pwidth, *pheight, tmfile.constData()))
667 av_freep(
reinterpret_cast<void*
>(&tmpl->data[0]));
679 QFile dfile(datafile);
681 if (!dfile.open(QIODevice::WriteOnly | QIODevice::Truncate) &&
683 (void)dfile.remove();
688 int row,
int col,
int width,
int height)
690 QFile tfile(tmplfile);
692 QByteArray tmfile = tmplfile.toLatin1();
693 if (
pgm_write(tmpl->data[0], width, height, tmfile.constData()))
696 QFile dfile(datafile);
697 if (!dfile.open(QIODevice::WriteOnly))
700 QTextStream stream(&dfile);
701 stream << row <<
" " << col <<
"\n" << width <<
" " << height <<
"\n";
709 std::shared_ptr<BorderDetector> bd,
710 std::shared_ptr<EdgeDetector> ed,
711 MythPlayer *player, std::chrono::seconds proglen,
712 const QString& debugdir)
713 : m_pgmConverter(
std::move(pgmc)),
714 m_borderDetector(
std::move(bd)),
715 m_edgeDetector(
std::move(ed)),
716 m_sampleTime(
std::min(proglen / 2, 20 * 60s)),
717 m_debugDir(debugdir),
718 m_debugData(debugdir +
"/TemplateFinder.txt"),
719 m_debugTmpl(debugdir +
"/TemplateFinder.pgm")
729 unsigned int samplesNeeded = 300;
748 LOG(VB_COMMFLAG, LOG_INFO,
749 QString(
"TemplateFinder: sampleTime=%1s, samplesNeeded=%2, endFrame=%3")
764 QString(
"TemplateFinder debugLevel %1").arg(
m_debugLevel));
777 av_freep(
reinterpret_cast<void*
>(&
m_tmpl.data[0]));
778 av_freep(
reinterpret_cast<void*
>(&
m_cropped.data[0]));
783 [[maybe_unused]]
long long nframes)
813 LOG(VB_COMMFLAG, LOG_INFO,
814 QString(
"TemplateFinder::MythPlayerInited read %1: %2")
821 av_freep(
reinterpret_cast<void*
>(&
m_tmpl.data[0]));
829 LOG(VB_COMMFLAG, LOG_INFO,
830 QString(
"TemplateFinder::MythPlayerInited %1 of %2 (%3)")
836 LOG(VB_COMMFLAG, LOG_INFO,
837 QString(
"TemplateFinder::MythPlayerInited framesize %1")
850 av_freep(
reinterpret_cast<void*
>(&
m_cropped.data[0]));
853 newwidth, newheight, AV_PIX_FMT_GRAY8, IMAGE_ALIGN) < 0)
855 LOG(VB_COMMFLAG, LOG_ERR,
856 QString(
"TemplateFinder::resetBuffers "
857 "av_image_alloc cropped (%1x%2) failed")
858 .arg(newwidth).arg(newheight));
869 long long *pNextFrame)
885 const int FRAMESGMPCTILE = 90;
898 static constexpr float kExcludeWidth = 0.5;
899 static constexpr float kExcludeHeight = 0.5;
924 &croprow, &cropcol, &cropwidth, &cropheight))
928 auto start = nowAsDuration<std::chrono::microseconds>();
939 cropwidth, cropheight))
946 int excludewidth = (int)(pgmwidth * kExcludeWidth);
947 int excludeheight = (int)(pgmheight * kExcludeHeight);
948 int excluderow = ((pgmheight - excludeheight) / 2) - croprow;
949 int excludecol = ((pgmwidth - excludewidth) / 2) - cropcol;
951 excludewidth, excludeheight);
955 if (edges ==
nullptr)
969 auto end = nowAsDuration<std::chrono::microseconds>();
980 LOG(VB_COMMFLAG, LOG_ERR,
981 QString(
"TemplateFinder::analyzeFrame error at frame %1, step %2")
982 .arg(frameno).arg(e));
1013 av_freep(
reinterpret_cast<void*
>(&
m_tmpl.data[0]));
1017 LOG(VB_COMMFLAG, LOG_INFO,
1018 QString(
"TemplateFinder::finished wrote %1"
1019 " and %2 [%3x%4@(%5,%6)]")
1044 LOG(VB_COMMFLAG, LOG_INFO, QString(
"TF Time: analyze=%1s")
int GetNumSetting(const QString &key, int defaultval=0)
QSize GetVideoBufferSize(void) const
float GetFrameRate(void) const
std::shared_ptr< BorderDetector > m_borderDetector
std::chrono::microseconds m_analyzeTime
enum analyzeFrameResult analyzeFrame(const MythVideoFrame *frame, long long frameno, long long *pNextFrame) override
enum analyzeFrameResult MythPlayerInited(MythPlayer *player, long long nframes) override
TemplateFinder(std::shared_ptr< PGMConverter > pgmc, std::shared_ptr< BorderDetector > bd, std::shared_ptr< EdgeDetector > ed, MythPlayer *player, std::chrono::seconds proglen, const QString &debugdir)
int reportTime(void) const override
~TemplateFinder(void) override
std::shared_ptr< PGMConverter > m_pgmConverter
const struct AVFrame * getTemplate(int *prow, int *pcol, int *pwidth, int *pheight) const
std::shared_ptr< EdgeDetector > m_edgeDetector
std::chrono::seconds m_sampleTime
int resetBuffers(int newwidth, int newheight)
int finished(long long nframes, bool final) override
@ GENERIC_EXIT_OK
Exited with no error.
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
uint myth_system(const QString &command, uint flags, std::chrono::seconds timeout)
bool writeJPG(const QString &prefix, const AVFrame *img, int imgheight)
int pgm_scorepixels(unsigned int *scores, int width, int row, int col, const AVFrame *src, int srcheight)
int bounding_box(const AVFrame *img, int imgheight, int minrow, int mincol, int maxrow1, int maxcol1, int *prow, int *pcol, int *pwidth, int *pheight)
bool template_alloc(const unsigned int *scores, int width, int height, int minrow, int mincol, int maxrow1, int maxcol1, AVFrame *tmpl, int *ptmplrow, int *ptmplcol, int *ptmplwidth, int *ptmplheight, bool debug_edgecounts, const QString &debugdir)
bool colisempty(const AVFrame *img, int col, int row, int height)
void writeDummyTemplate(const QString &datafile)
float bounding_score(const AVFrame *img, int row, int col, int width, int height)
bool writeTemplate(const QString &tmplfile, const AVFrame *tmpl, const QString &datafile, int row, int col, int width, int height)
bool rowisempty(const AVFrame *img, int row, int col, int width)
bool analyzeFrameDebug(long long frameno, const AVFrame *pgm, int pgmheight, const AVFrame *cropped, const AVFrame *edges, int cropheight, int croprow, int cropcol, bool debug_frames, const QString &debugdir)
int sort_ascending(const void *aa, const void *bb)
bool readTemplate(const QString &datafile, int *prow, int *pcol, int *pwidth, int *pheight, const QString &tmplfile, AVFrame *tmpl, bool *pvalid)
void createDebugDirectory(const QString &dirname, const QString &comment)
QString strftimeval(std::chrono::microseconds usecs)
int pgm_write(const unsigned char *buf, int width, int height, const char *filename)
int pgm_crop(AVFrame *dst, const AVFrame *src, int srcheight, int srcrow, int srccol, int cropwidth, int cropheight)
int pgm_read(unsigned char *buf, int width, int height, const char *filename)
static QString cleanup(const QString &str)