MythTV  master
HistogramAnalyzer.cpp
Go to the documentation of this file.
1 // POSIX headers
2 #include <sys/time.h> // for gettimeofday
3 
4 // ANSI C headers
5 #include <cmath>
6 
7 // MythTV headers
8 #include "mythcorecontext.h"
9 #include "mythplayer.h"
10 #include "mythlogging.h"
11 
12 // Commercial Flagging headers
13 #include "CommDetector2.h"
14 #include "FrameAnalyzer.h"
15 #include "PGMConverter.h"
16 #include "BorderDetector.h"
17 #include "quickselect.h"
18 #include "TemplateFinder.h"
19 #include "HistogramAnalyzer.h"
20 
21 using namespace commDetector2;
22 using namespace frameAnalyzer;
23 
24 namespace {
25 
26 bool
27 readData(QString filename, float *mean, unsigned char *median, float *stddev,
28  int *frow, int *fcol, int *fwidth, int *fheight,
29  HistogramAnalyzer::Histogram *histogram, unsigned char *monochromatic,
30  long long nframes)
31 {
32  FILE *fp;
33  long long frameno;
34  quint32 counter[UCHAR_MAX + 1];
35 
36  QByteArray fname = filename.toLocal8Bit();
37  if (!(fp = fopen(fname.constData(), "r")))
38  return false;
39 
40  for (frameno = 0; frameno < nframes; frameno++)
41  {
42  int monochromaticval, medianval, widthval, heightval, colval, rowval;
43  float meanval, stddevval;
44  int nitems = fscanf(fp, "%20d %20f %20d %20f %20d %20d %20d %20d",
45  &monochromaticval, &meanval, &medianval, &stddevval,
46  &widthval, &heightval, &colval, &rowval);
47  if (nitems != 8)
48  {
49  LOG(VB_COMMFLAG, LOG_ERR,
50  QString("Not enough data in %1: frame %2")
51  .arg(filename).arg(frameno));
52  goto error;
53  }
54  if (monochromaticval < 0 || monochromaticval > 1 ||
55  medianval < 0 || (uint)medianval > UCHAR_MAX ||
56  widthval < 0 || heightval < 0 || colval < 0 || rowval < 0)
57  {
58  LOG(VB_COMMFLAG, LOG_ERR,
59  QString("Data out of range in %1: frame %2")
60  .arg(filename).arg(frameno));
61  goto error;
62  }
63  for (unsigned int ii = 0; ii < sizeof(counter)/sizeof(*counter); ii++)
64  {
65  if ((nitems = fscanf(fp, "%20x", &counter[ii])) != 1)
66  {
67  LOG(VB_COMMFLAG, LOG_ERR,
68  QString("Not enough data in %1: frame %2")
69  .arg(filename).arg(frameno));
70  goto error;
71  }
72  if (counter[ii] > UCHAR_MAX)
73  {
74  LOG(VB_COMMFLAG, LOG_ERR,
75  QString("Data out of range in %1: frame %2")
76  .arg(filename).arg(frameno));
77  goto error;
78  }
79  }
80  mean[frameno] = meanval;
81  median[frameno] = medianval;
82  stddev[frameno] = stddevval;
83  frow[frameno] = rowval;
84  fcol[frameno] = colval;
85  fwidth[frameno] = widthval;
86  fheight[frameno] = heightval;
87  for (unsigned int ii = 0; ii < sizeof(counter)/sizeof(*counter); ii++)
88  histogram[frameno][ii] = counter[ii];
89  monochromatic[frameno] = !widthval || !heightval ? 1 : 0;
90  /*
91  * monochromaticval not used; it's written to file for debugging
92  * convenience
93  */
94  }
95  if (fclose(fp))
96  LOG(VB_COMMFLAG, LOG_ERR, QString("Error closing %1: %2")
97  .arg(filename).arg(strerror(errno)));
98  return true;
99 
100 error:
101  if (fclose(fp))
102  LOG(VB_COMMFLAG, LOG_ERR, QString("Error closing %1: %2")
103  .arg(filename).arg(strerror(errno)));
104  return false;
105 }
106 
107 bool
108 writeData(QString filename, float *mean, unsigned char *median, float *stddev,
109  int *frow, int *fcol, int *fwidth, int *fheight,
110  HistogramAnalyzer::Histogram *histogram, unsigned char *monochromatic,
111  long long nframes)
112 {
113  FILE *fp;
114  long long frameno;
115 
116  QByteArray fname = filename.toLocal8Bit();
117  if (!(fp = fopen(fname, "w")))
118  return false;
119  for (frameno = 0; frameno < nframes; frameno++)
120  {
121  (void)fprintf(fp, "%3u %10.6f %3u %10.6f %5d %5d %5d %5d",
122  monochromatic[frameno],
123  static_cast<double>(mean[frameno]), median[frameno],
124  static_cast<double>(stddev[frameno]),
125  fwidth[frameno], fheight[frameno],
126  fcol[frameno], frow[frameno]);
127  for (unsigned int ii = 0; ii < UCHAR_MAX + 1; ii++)
128  (void)fprintf(fp, " %02x", histogram[frameno][ii]);
129  (void)fprintf(fp, "\n");
130  }
131  if (fclose(fp))
132  LOG(VB_COMMFLAG, LOG_ERR, QString("Error closing %1: %2")
133  .arg(filename).arg(strerror(errno)));
134  return true;
135 }
136 
137 }; /* namespace */
138 
140  QString debugdir)
141  : pgmConverter(pgmc)
142  , borderDetector(bd)
143  , logoFinder(nullptr)
144  , logo(nullptr)
145  , logowidth(-1)
146  , logoheight(-1)
147  , logorr1(-1)
148  , logocc1(-1)
149  , logorr2(-1)
150  , logocc2(-1)
151  , mean(nullptr)
152  , median(nullptr)
153  , stddev(nullptr)
154  , frow(nullptr)
155  , fcol(nullptr)
156  , fwidth(nullptr)
157  , fheight(nullptr)
158  , histogram(nullptr)
159  , monochromatic(nullptr)
160  , buf(nullptr)
161  , lastframeno(-1)
162  , debugLevel(0)
164  , debugdata(debugdir + "/HistogramAnalyzer-pgm.txt")
165 #else /* !PGM_CONVERT_GREYSCALE */
166  , debugdata(debugdir + "/HistogramAnalyzer-yuv.txt")
167 #endif /* !PGM_CONVERT_GREYSCALE */
168  , debug_histval(false)
169  , histval_done(false)
170 {
171  memset(histval, 0, sizeof(int) * (UCHAR_MAX + 1));
172  memset(&analyze_time, 0, sizeof(analyze_time));
173 
174  /*
175  * debugLevel:
176  * 0: no debugging
177  * 1: cache frame information into debugdata [1 file]
178  */
179  debugLevel = gCoreContext->GetNumSetting("HistogramAnalyzerDebugLevel", 0);
180 
181  if (debugLevel >= 1)
182  {
183  createDebugDirectory(debugdir,
184  QString("HistogramAnalyzer debugLevel %1").arg(debugLevel));
185  debug_histval = true;
186  }
187 }
188 
190 {
191  if (monochromatic)
192  delete []monochromatic;
193  if (mean)
194  delete []mean;
195  if (median)
196  delete []median;
197  if (stddev)
198  delete []stddev;
199  if (frow)
200  delete []frow;
201  if (fcol)
202  delete []fcol;
203  if (fwidth)
204  delete []fwidth;
205  if (fheight)
206  delete []fheight;
207  if (histogram)
208  delete []histogram;
209  if (buf)
210  delete []buf;
211 }
212 
215 {
216  if (histval_done)
218 
219  if (monochromatic)
221 
222  QSize buf_dim = player->GetVideoBufferSize();
223  unsigned int width = buf_dim.width();
224  unsigned int height = buf_dim.height();
225 
227  &logowidth, &logoheight)))
228  {
229  logorr2 = logorr1 + logoheight - 1;
230  logocc2 = logocc1 + logowidth - 1;
231  }
232  QString details = logo ? QString("logo %1x%2@(%3,%4)")
233  .arg(logowidth).arg(logoheight).arg(logocc1).arg(logorr1) :
234  QString("no logo");
235 
236  LOG(VB_COMMFLAG, LOG_INFO,
237  QString("HistogramAnalyzer::MythPlayerInited %1x%2: %3")
238  .arg(width).arg(height).arg(details));
239 
240  if (pgmConverter->MythPlayerInited(player))
242 
243  if (borderDetector->MythPlayerInited(player))
245 
246  mean = new float[nframes];
247  median = new unsigned char[nframes];
248  stddev = new float[nframes];
249  frow = new int[nframes];
250  fcol = new int[nframes];
251  fwidth = new int[nframes];
252  fheight = new int[nframes];
253  histogram = new Histogram[nframes];
254  monochromatic = new unsigned char[nframes];
255 
256  memset(mean, 0, nframes * sizeof(*mean));
257  memset(median, 0, nframes * sizeof(*median));
258  memset(stddev, 0, nframes * sizeof(*stddev));
259  memset(frow, 0, nframes * sizeof(*frow));
260  memset(fcol, 0, nframes * sizeof(*fcol));
261  memset(fwidth, 0, nframes * sizeof(*fwidth));
262  memset(fheight, 0, nframes * sizeof(*fheight));
263  memset(histogram, 0, nframes * sizeof(*histogram));
264  memset(monochromatic, 0, nframes * sizeof(*monochromatic));
265 
266  unsigned int npixels = width * height;
267  buf = new unsigned char[npixels];
268 
269  if (debug_histval)
270  {
271  if (readData(debugdata, mean, median, stddev, frow, fcol,
272  fwidth, fheight, histogram, monochromatic, nframes))
273  {
274  LOG(VB_COMMFLAG, LOG_INFO,
275  QString("HistogramAnalyzer::MythPlayerInited read %1")
276  .arg(debugdata));
277  histval_done = true;
279  }
280  }
281 
283 }
284 
285 void
287 {
288  logoFinder = finder;
289 }
290 
292 HistogramAnalyzer::analyzeFrame(const VideoFrame *frame, long long frameno)
293 {
294  /*
295  * Various statistical computations over pixel values: mean, median,
296  * (running) standard deviation over sample population.
297  */
298  static const int DEFAULT_COLOR = 0;
299 
300  /*
301  * TUNABLE:
302  *
303  * Sampling coarseness of each frame. Higher values will allow analysis to
304  * proceed faster (lower resolution), but might be less accurate. Lower
305  * values will examine more pixels (higher resolution), but will run
306  * slower.
307  */
308  static const int RINC = 4;
309  static const int CINC = 4;
310 #define ROUNDUP(a,b) (((a) + (b) - 1) / (b) * (b))
311 
312  const AVFrame *pgm;
313  int pgmwidth, pgmheight;
314  bool ismonochromatic;
315  int croprow, cropcol, cropwidth, cropheight;
316  unsigned int borderpixels, livepixels, npixels, halfnpixels;
317  unsigned char *pp, bordercolor;
318  unsigned long long sumval, sumsquares;
319  int rr, cc, rr1, cc1, rr2, cc2, rr3, cc3;
320  struct timeval start, end, elapsed;
321 
322  if (lastframeno != UNCACHED && lastframeno == frameno)
324 
325  if (!(pgm = pgmConverter->getImage(frame, frameno, &pgmwidth, &pgmheight)))
326  goto error;
327 
328  ismonochromatic = borderDetector->getDimensions(pgm, pgmheight, frameno,
329  &croprow, &cropcol, &cropwidth, &cropheight) != 0;
330 
331  gettimeofday(&start, nullptr);
332 
333  frow[frameno] = croprow;
334  fcol[frameno] = cropcol;
335  fwidth[frameno] = cropwidth;
336  fheight[frameno] = cropheight;
337 
338  if (ismonochromatic)
339  {
340  /* Optimization for monochromatic frames; just sample center area. */
341  croprow = pgmheight * 3 / 8;
342  cropheight = pgmheight / 4;
343  cropcol = pgmwidth * 3 / 8;
344  cropwidth = pgmwidth / 4;
345  }
346 
347  rr1 = ROUNDUP(croprow, RINC);
348  cc1 = ROUNDUP(cropcol, CINC);
349  rr2 = ROUNDUP(croprow + cropheight, RINC);
350  cc2 = ROUNDUP(cropcol + cropwidth, CINC);
351  rr3 = ROUNDUP(pgmheight, RINC);
352  cc3 = ROUNDUP(pgmwidth, CINC);
353 
354  borderpixels = (rr1 / RINC) * (cc3 / CINC) + /* top */
355  ((rr2 - rr1) / RINC) * (cc1 / CINC) + /* left */
356  ((rr2 - rr1) / RINC) * ((cc3 - cc2) / CINC) + /* right */
357  ((rr3 - rr2) / RINC) * (cc3 / CINC); /* bottom */
358 
359  sumval = 0;
360  sumsquares = 0;
361  livepixels = 0;
362  pp = &buf[borderpixels];
363  memset(histval, 0, sizeof(histval));
364  histval[DEFAULT_COLOR] += borderpixels;
365  for (rr = rr1; rr < rr2; rr += RINC)
366  {
367  int rroffset = rr * pgmwidth;
368 
369  for (cc = cc1; cc < cc2; cc += CINC)
370  {
371  if (logo && rr >= logorr1 && rr <= logorr2 &&
372  cc >= logocc1 && cc <= logocc2)
373  continue; /* Exclude logo area from analysis. */
374 
375  unsigned char val = pgm->data[0][rroffset + cc];
376  *pp++ = val;
377  sumval += val;
378  sumsquares += val * val;
379  livepixels++;
380  histval[val]++;
381  }
382  }
383  npixels = borderpixels + livepixels;
384 
385  /* Scale scores down to [0..255]. */
386  halfnpixels = npixels / 2;
387  for (unsigned int color = 0; color < UCHAR_MAX + 1; color++)
388  histogram[frameno][color] =
389  (histval[color] * UCHAR_MAX + halfnpixels) / npixels;
390 
391  bordercolor = 0;
392  if (ismonochromatic && livepixels)
393  {
394  /*
395  * Fake up the margin pixels to be of the same color as the sampled
396  * area.
397  */
398  bordercolor = (sumval + livepixels - 1) / livepixels;
399  sumval += borderpixels * bordercolor;
400  sumsquares += borderpixels * bordercolor * bordercolor;
401  }
402 
403  memset(buf, bordercolor, borderpixels * sizeof(*buf));
404  monochromatic[frameno] = ismonochromatic ? 1 : 0;
405  mean[frameno] = (float)sumval / npixels;
406  median[frameno] = quick_select_median(buf, npixels);
407  stddev[frameno] = npixels > 1 ?
408  sqrt((sumsquares - (float)sumval * sumval / npixels) / (npixels - 1)) :
409  0;
410 
411  (void)gettimeofday(&end, nullptr);
412  timersub(&end, &start, &elapsed);
413  timeradd(&analyze_time, &elapsed, &analyze_time);
414 
415  lastframeno = frameno;
416 
418 
419 error:
420  LOG(VB_COMMFLAG, LOG_ERR,
421  QString("HistogramAnalyzer::analyzeFrame error at frame %1")
422  .arg(frameno));
423 
425 }
426 
427 int
428 HistogramAnalyzer::finished(long long nframes, bool final)
429 {
430  if (!histval_done && debug_histval)
431  {
432  if (final && writeData(debugdata, mean, median, stddev, frow, fcol,
433  fwidth, fheight, histogram, monochromatic, nframes))
434  {
435  LOG(VB_COMMFLAG, LOG_INFO,
436  QString("HistogramAnalyzer::finished wrote %1")
437  .arg(debugdata));
438  histval_done = true;
439  }
440  }
441 
442  return 0;
443 }
444 
445 int
447 {
448  if (pgmConverter->reportTime())
449  return -1;
450 
451  if (borderDetector->reportTime())
452  return -1;
453 
454  LOG(VB_COMMFLAG, LOG_INFO, QString("HA Time: analyze=%1s")
455  .arg(strftimeval(&analyze_time)));
456  return 0;
457 }
458 
459 /* vim: set expandtab tabstop=4 shiftwidth=4: */
#define PGM_CONVERT_GREYSCALE
Definition: PGMConverter.h:28
Definition: cc.h:13
struct timeval analyze_time
int finished(long long nframes, bool final)
unsigned char * monochromatic
static void error(const char *str,...)
Definition: vbi.c:41
#define timeradd(a, b, result)
Definition: compat.h:293
struct AVFrame AVFrame
BorderDetector * borderDetector
const struct AVFrame * logo
int reportTime(void) const
unsigned int uint
Definition: compat.h:140
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define ROUNDUP(a, b)
HistogramAnalyzer(PGMConverter *pgmc, BorderDetector *bd, QString debugdir)
unsigned char Histogram[UCHAR_MAX+1]
VERBOSE_PREAMBLE false
Definition: verbosedefs.h:85
int MythPlayerInited(const MythPlayer *player)
static int pp(VideoFilter *vf, VideoFrame *frame, int field)
int histval[UCHAR_MAX+1]
const struct AVFrame * getTemplate(int *prow, int *pcol, int *pwidth, int *pheight) const
static const long long UNCACHED
unsigned char * buf
void setLogoState(TemplateFinder *finder)
int MythPlayerInited(const MythPlayer *player)
int FILE
Definition: mythburn.py:110
enum FrameAnalyzer::analyzeFrameResult analyzeFrame(const VideoFrame *frame, long long frameno)
int GetNumSetting(const QString &key, int defaultval=0)
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
QSize GetVideoBufferSize(void) const
Definition: mythplayer.h:173
TemplateFinder * logoFinder
unsigned char quick_select_median(unsigned char *arr, int nelems)
Definition: quickselect.c:68
QString strftimeval(const struct timeval *tv)
enum FrameAnalyzer::analyzeFrameResult MythPlayerInited(MythPlayer *player, long long nframes)
int reportTime(void)
#define timersub(a, b, result)
Definition: compat.h:303
int getDimensions(const AVFrame *pgm, int pgmheight, long long frameno, int *prow, int *pcol, int *pwidth, int *pheight)
PGMConverter * pgmConverter
const AVFrame * getImage(const VideoFrame *frame, long long frameno, int *pwidth, int *pheight)
unsigned char * median
void createDebugDirectory(QString dirname, QString comment)
int reportTime(void)