13#include "libmythbase/mythconfig.h"
31 #include "libavutil/imgutils.h"
41 const int imgwidth = img->linesize[0];
42 QFileInfo jpgfi(
prefix +
".jpg");
45 QFile pgmfile(
prefix +
".pgm");
46 if (!pgmfile.exists())
48 QByteArray pfname = pgmfile.fileName().toLocal8Bit();
49 if (
pgm_write(img->data[0], imgwidth, imgheight,
56 QString cmd = QString(
"convert -quality 50 -resize 192x144 %1 %2")
57 .arg(pgmfile.fileName(), jpgfi.filePath());
61 if (!pgmfile.remove())
63 LOG(VB_COMMFLAG, LOG_ERR,
64 QString(
"TemplateFinder.writeJPG error removing %1 (%2)")
65 .arg(pgmfile.fileName(), strerror(errno)));
74 const AVFrame *src,
int srcheight)
77 const int srcwidth = src->linesize[0];
79 for (
int rr = 0; rr < srcheight; rr++)
81 for (
int cc = 0; cc < srcwidth; cc++)
83 if (src->data[0][(rr * srcwidth) + cc])
84 scores[((row + rr) * width) + col + cc]++;
94 return *(
unsigned int*)aa - *(
unsigned int*)bb;
101 const int imgwidth = img->linesize[0];
104 int rr2 = row + height;
105 int cc2 = col + width;
106 for (
int rr = row; rr < rr2; rr++)
108 for (
int cc = col; cc < cc2; cc++)
110 if (img->data[0][(rr * imgwidth) + cc])
114 return (
float)score / (width * height);
120 const int imgwidth = img->linesize[0];
121 for (
int cc = col; cc < col + width; cc++)
122 if (img->data[0][(row * imgwidth) + cc])
130 const int imgwidth = img->linesize[0];
131 for (
int rr = row; rr < row + height; rr++)
132 if (img->data[0][(rr * imgwidth) + col])
140 int minrow,
int mincol,
int maxrow1,
int maxcol1,
141 int *prow,
int *pcol,
int *pwidth,
int *pheight)
143 const int imgwidth = img->linesize[0];
150 static constexpr int kMaxWidthPct = 20;
151 static constexpr int kMaxHeightPct = 20;
160 const int VERTSLOP = std::max(4, imgheight * 1 / 15);
161 const int HORIZSLOP = std::max(4, imgwidth * 1 / 20);
163 int maxwidth = (maxcol1 - mincol) * kMaxWidthPct / 100;
164 int maxheight = (maxrow1 - minrow) * kMaxHeightPct / 100;
168 int width = maxcol1 - mincol;
169 int height = maxrow1 - minrow;
177 bool improved =
false;
179 LOG(VB_COMMFLAG, LOG_INFO, QString(
"bounding_box %1x%2@(%3,%4)")
180 .arg(width).arg(height).arg(col).arg(row));
185 for (
int ii = 1; ii < height; ii++)
189 if (newscore < score)
199 for (
int ii = 1; ii < width; ii++)
203 if (newscore < score)
212 newbottom = row + height;
213 for (
int ii = 1; ii < height; ii++)
217 if (newscore < score)
220 newbottom = row + height - ii;
226 newright = col + width;
227 for (
int ii = 1; ii < width; ii++)
231 if (newscore < score)
234 newright = col + width - ii;
243 width = newright - newcol;
244 height = newbottom - newrow;
265 if (width > maxwidth)
268 int chop = width / 3;
269 int chopwidth = width - chop;
272 float right =
bounding_score(img, row, col + chop, chopwidth, height);
273 LOG(VB_COMMFLAG, LOG_INFO,
274 QString(
"bounding_box too wide (%1 > %2); left=%3, right=%4")
275 .arg(width).arg(maxwidth)
276 .arg(left, 0,
'f', 3).arg(right, 0,
'f', 3));
277 float minscore = std::min(left, right);
278 float maxscore = std::max(left, right);
279 if (maxscore < 3 * minscore / 2)
286 LOG(VB_COMMFLAG, LOG_ERR,
"bounding_box giving up (edge "
287 "pixels distributed too uniformly)");
297 if (height > maxheight)
300 int chop = height / 3;
301 int chopheight = height - chop;
304 float lower =
bounding_score(img, row + chop, col, width, chopheight);
305 LOG(VB_COMMFLAG, LOG_INFO,
306 QString(
"bounding_box too tall (%1 > %2); upper=%3, lower=%4")
307 .arg(height).arg(maxheight)
308 .arg(upper, 0,
'f', 3).arg(lower, 0,
'f', 3));
309 float minscore = std::min(upper, lower);
310 float maxscore = std::max(upper, lower);
311 if (maxscore < 3 * minscore / 2)
318 LOG(VB_COMMFLAG, LOG_ERR,
"bounding_box giving up (edge "
319 "pixel distribution too uniform)");
339 LOG(VB_COMMFLAG, LOG_INFO,
340 QString(
"bounding_box %1x%2@(%3,%4); horizslop=%5,vertslop=%6")
341 .arg(width).arg(height).arg(col).arg(row)
342 .arg(HORIZSLOP).arg(VERTSLOP));
348 if (newrow <= minrow)
353 if (row - newrow >= VERTSLOP)
355 newrow = row - VERTSLOP;
365 newrow = std::max(minrow, newrow - 1);
371 if (newcol <= mincol)
376 if (col - newcol >= HORIZSLOP)
378 newcol = col - HORIZSLOP;
388 newcol = std::max(mincol, newcol - 1);
391 newright = col + width;
394 if (newright >= maxcol1)
399 if (newright - (col + width) >= HORIZSLOP)
401 newright = col + width + HORIZSLOP;
408 newright = std::min(maxcol1, newright + 1);
411 newbottom = row + height;
414 if (newbottom >= maxrow1)
419 if (newbottom - (row + height) >= VERTSLOP)
421 newbottom = row + height + VERTSLOP;
428 newbottom = std::min(maxrow1, newbottom + 1);
432 width = newright - newcol;
433 height = newbottom - newrow;
435 LOG(VB_COMMFLAG, LOG_INFO, QString(
"bounding_box %1x%2@(%3,%4)")
436 .arg(width).arg(height).arg(col).arg(row));
447 int minrow,
int mincol,
int maxrow1,
int maxcol1,
AVFrame *tmpl,
448 int *ptmplrow,
int *ptmplcol,
int *ptmplwidth,
int *ptmplheight,
449 bool debug_edgecounts,
const QString& debugdir)
460 static constexpr float kMinScorePctile = 0.998;
462 const int nn = width * height;
466 unsigned int threshscore = 0;
469 if (av_image_alloc(thresh.data, thresh.linesize,
470 width, height, AV_PIX_FMT_GRAY8, IMAGE_ALIGN) < 0)
472 LOG(VB_COMMFLAG, LOG_ERR,
473 QString(
"template_alloc av_image_alloc thresh (%1x%2) failed")
474 .arg(width).arg(height));
478 std::vector<uint> sortedscores;
479 sortedscores.resize(nn);
480 memcpy(sortedscores.data(), scores, nn *
sizeof(
uint));
484 auto cleanup_fn = [&](
int* ) {
485 av_freep(
reinterpret_cast<void*
>(&thresh.data[0]));
487 std::unique_ptr<int,
decltype(cleanup_fn)>
cleanup { &first, cleanup_fn };
489 if (sortedscores[0] == sortedscores[nn - 1])
492 LOG(VB_COMMFLAG, LOG_ERR,
493 QString(
"template_alloc: %1x%2 pixels all identical!")
494 .arg(width).arg(height));
500 ii = (int)roundf(nn * kMinScorePctile);
501 threshscore = sortedscores[ii];
502 for (first = ii; first > 0 && sortedscores[first] == threshscore; first--)
504 if (sortedscores[first] != threshscore)
506 for (last = ii; last < nn - 1 && sortedscores[last] == threshscore; last++)
508 if (sortedscores[last] != threshscore)
511 LOG(VB_COMMFLAG, LOG_INFO, QString(
"template_alloc wanted %1, got %2-%3")
512 .arg(kMinScorePctile, 0,
'f', 6)
513 .arg((
float)first / nn, 0,
'f', 6)
514 .arg((
float)last / nn, 0,
'f', 6));
516 for (ii = 0; ii < nn; ii++)
517 thresh.data[0][ii] = scores[ii] >= threshscore ? UCHAR_MAX : 0;
519 if (debug_edgecounts)
523 if (av_image_alloc(scored.data, scored.linesize,
524 width, height, AV_PIX_FMT_GRAY8, IMAGE_ALIGN) < 0)
526 LOG(VB_COMMFLAG, LOG_ERR,
527 QString(
"template_alloc av_image_alloc scored (%1x%2) failed")
528 .arg(width).arg(height));
531 unsigned int maxscore = sortedscores[nn - 1];
532 for (ii = 0; ii < nn; ii++)
533 scored.data[0][ii] = scores[ii] * UCHAR_MAX / maxscore;
534 bool success =
writeJPG(debugdir +
"/TemplateFinder-scores", &scored,
536 av_freep(
reinterpret_cast<void*
>(&scored.data[0]));
541 if (!
writeJPG(debugdir +
"/TemplateFinder-edgecounts", &thresh, height))
547 if (
bounding_box(&thresh, height, minrow, mincol, maxrow1, maxcol1,
548 ptmplrow, ptmplcol, ptmplwidth, ptmplheight))
551 if ((
uint)(*ptmplwidth * *ptmplheight) > USHRT_MAX)
554 LOG(VB_COMMFLAG, LOG_ERR,
555 QString(
"template_alloc bounding_box too big (%1x%2)")
556 .arg(*ptmplwidth).arg(*ptmplheight));
560 if (av_image_alloc(tmpl->data, tmpl->linesize,
561 *ptmplwidth, *ptmplheight, AV_PIX_FMT_GRAY8, IMAGE_ALIGN) < 0)
563 LOG(VB_COMMFLAG, LOG_ERR,
564 QString(
"template_alloc av_image_alloc tmpl (%1x%2) failed")
565 .arg(*ptmplwidth).arg(*ptmplheight));
569 if (
pgm_crop(tmpl, &thresh, height, *ptmplrow, *ptmplcol,
570 *ptmplwidth, *ptmplheight))
579 int croprow,
int cropcol,
bool debug_frames,
const QString& debugdir)
581 static constexpr int kDelta = 24;
582 static int s_lastrow;
583 static int s_lastcol;
584 static int s_lastwidth;
585 static int s_lastheight;
586 const int cropwidth = cropped->linesize[0];
588 int rowsame = abs(s_lastrow - croprow) <= kDelta ? 1 : 0;
589 int colsame = abs(s_lastcol - cropcol) <= kDelta ? 1 : 0;
590 int widthsame = abs(s_lastwidth - cropwidth) <= kDelta ? 1 : 0;
591 int heightsame = abs(s_lastheight - cropheight) <= kDelta ? 1 : 0;
593 if (frameno > 0 && rowsame + colsame + widthsame + heightsame >= 3)
596 LOG(VB_COMMFLAG, LOG_INFO,
597 QString(
"TemplateFinder Frame %1: %2x%3@(%4,%5)")
599 .arg(cropwidth).arg(cropheight)
600 .arg(cropcol).arg(croprow));
604 s_lastwidth = cropwidth;
605 s_lastheight = cropheight;
609 QString base = QString(
"%1/TemplateFinder-%2")
610 .arg(debugdir).arg(frameno, 5, 10, QChar(
'0'));
613 if (!
writeJPG(base, pgm, pgmheight))
617 if (!
writeJPG(base +
"-cropped", cropped, cropheight))
621 if (!
writeJPG(base +
"-edges", edges, cropheight))
629bool readTemplate(
const QString& datafile,
int *prow,
int *pcol,
int *pwidth,
int *pheight,
630 const QString& tmplfile,
AVFrame *tmpl,
bool *pvalid)
632 QFile dfile(datafile);
633 QFileInfo dfileinfo(dfile);
635 if (!dfile.open(QIODevice::ReadOnly))
638 if (!dfileinfo.size())
646 QTextStream stream(&dfile);
647 stream >> *prow >> *pcol >> *pwidth >> *pheight;
650 if (*pwidth < 0 || *pheight < 0)
652 LOG(VB_COMMFLAG, LOG_ERR, QString(
"readTemplate no saved template"));
656 if (av_image_alloc(tmpl->data, tmpl->linesize,
657 *pwidth, *pheight, AV_PIX_FMT_GRAY8, IMAGE_ALIGN) < 0)
659 LOG(VB_COMMFLAG, LOG_ERR,
660 QString(
"readTemplate av_image_alloc %1 (%2x%3) failed")
661 .arg(tmplfile).arg(*pwidth).arg(*pheight));
665 QByteArray tmfile = tmplfile.toLatin1();
666 if (
pgm_read(tmpl->data[0], *pwidth, *pheight, tmfile.constData()))
668 av_freep(
reinterpret_cast<void*
>(&tmpl->data[0]));
680 QFile dfile(datafile);
682 if (!dfile.open(QIODevice::WriteOnly | QIODevice::Truncate) &&
684 (void)dfile.remove();
689 int row,
int col,
int width,
int height)
691 QFile tfile(tmplfile);
693 QByteArray tmfile = tmplfile.toLatin1();
694 if (
pgm_write(tmpl->data[0], width, height, tmfile.constData()))
697 QFile dfile(datafile);
698 if (!dfile.open(QIODevice::WriteOnly))
701 QTextStream stream(&dfile);
702 stream << row <<
" " << col <<
"\n" << width <<
" " << height <<
"\n";
710 std::shared_ptr<BorderDetector> bd,
711 std::shared_ptr<EdgeDetector> ed,
712 MythPlayer *player, std::chrono::seconds proglen,
713 const QString& debugdir)
714 : m_pgmConverter(std::move(pgmc)),
715 m_borderDetector(std::move(bd)),
716 m_edgeDetector(std::move(ed)),
717 m_sampleTime(std::min(proglen / 2, 20 * 60s)),
718 m_debugDir(debugdir),
719 m_debugData(debugdir +
"/TemplateFinder.txt"),
720 m_debugTmpl(debugdir +
"/TemplateFinder.pgm")
730 unsigned int samplesNeeded = 300;
749 LOG(VB_COMMFLAG, LOG_INFO,
750 QString(
"TemplateFinder: sampleTime=%1s, samplesNeeded=%2, endFrame=%3")
765 QString(
"TemplateFinder debugLevel %1").arg(
m_debugLevel));
778 av_freep(
reinterpret_cast<void*
>(&
m_tmpl.data[0]));
779 av_freep(
reinterpret_cast<void*
>(&
m_cropped.data[0]));
784 [[maybe_unused]]
long long nframes)
814 LOG(VB_COMMFLAG, LOG_INFO,
815 QString(
"TemplateFinder::MythPlayerInited read %1: %2")
822 av_freep(
reinterpret_cast<void*
>(&
m_tmpl.data[0]));
830 LOG(VB_COMMFLAG, LOG_INFO,
831 QString(
"TemplateFinder::MythPlayerInited %1 of %2 (%3)")
837 LOG(VB_COMMFLAG, LOG_INFO,
838 QString(
"TemplateFinder::MythPlayerInited framesize %1")
851 av_freep(
reinterpret_cast<void*
>(&
m_cropped.data[0]));
854 newwidth, newheight, AV_PIX_FMT_GRAY8, IMAGE_ALIGN) < 0)
856 LOG(VB_COMMFLAG, LOG_ERR,
857 QString(
"TemplateFinder::resetBuffers "
858 "av_image_alloc cropped (%1x%2) failed")
859 .arg(newwidth).arg(newheight));
870 long long *pNextFrame)
886 const int FRAMESGMPCTILE = 90;
899 static constexpr float kExcludeWidth = 0.5;
900 static constexpr float kExcludeHeight = 0.5;
925 &croprow, &cropcol, &cropwidth, &cropheight))
929 auto start = nowAsDuration<std::chrono::microseconds>();
940 cropwidth, cropheight))
947 int excludewidth = (int)(pgmwidth * kExcludeWidth);
948 int excludeheight = (int)(pgmheight * kExcludeHeight);
949 int excluderow = ((pgmheight - excludeheight) / 2) - croprow;
950 int excludecol = ((pgmwidth - excludewidth) / 2) - cropcol;
952 excludewidth, excludeheight);
956 if (edges ==
nullptr)
970 auto end = nowAsDuration<std::chrono::microseconds>();
981 LOG(VB_COMMFLAG, LOG_ERR,
982 QString(
"TemplateFinder::analyzeFrame error at frame %1, step %2")
983 .arg(frameno).arg(e));
1014 av_freep(
reinterpret_cast<void*
>(&
m_tmpl.data[0]));
1018 LOG(VB_COMMFLAG, LOG_INFO,
1019 QString(
"TemplateFinder::finished wrote %1"
1020 " and %2 [%3x%4@(%5,%6)]")
1045 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)