MythTV  0.26-pre
HistogramAnalyzer.cpp
Go to the documentation of this file.
00001 // POSIX headers
00002 #include <sys/time.h> // for gettimeofday
00003 
00004 // ANSI C headers
00005 #include <cmath>
00006 
00007 // MythTV headers
00008 #include "mythcorecontext.h"
00009 #include "mythplayer.h"
00010 #include "mythlogging.h"
00011 
00012 // Commercial Flagging headers
00013 #include "CommDetector2.h"
00014 #include "FrameAnalyzer.h"
00015 #include "PGMConverter.h"
00016 #include "BorderDetector.h"
00017 #include "quickselect.h"
00018 #include "TemplateFinder.h"
00019 #include "HistogramAnalyzer.h"
00020 
00021 using namespace commDetector2;
00022 using namespace frameAnalyzer;
00023 
00024 namespace {
00025 
00026 bool
00027 readData(QString filename, float *mean, unsigned char *median, float *stddev,
00028         int *frow, int *fcol, int *fwidth, int *fheight,
00029         HistogramAnalyzer::Histogram *histogram, unsigned char *monochromatic,
00030         long long nframes)
00031 {
00032     FILE            *fp;
00033     long long       frameno;
00034     int             counter[UCHAR_MAX + 1];
00035 
00036     QByteArray fname = filename.toLocal8Bit();
00037     if (!(fp = fopen(fname.constData(), "r")))
00038         return false;
00039 
00040     for (frameno = 0; frameno < nframes; frameno++)
00041     {
00042         int monochromaticval, medianval, widthval, heightval, colval, rowval;
00043         float meanval, stddevval;
00044         int nitems = fscanf(fp, "%20d %20f %20d %20f %20d %20d %20d %20d",
00045                 &monochromaticval, &meanval, &medianval, &stddevval,
00046                 &widthval, &heightval, &colval, &rowval);
00047         if (nitems != 8)
00048         {
00049             LOG(VB_COMMFLAG, LOG_ERR,
00050                 QString("Not enough data in %1: frame %2")
00051                     .arg(filename).arg(frameno));
00052             goto error;
00053         }
00054         if (monochromaticval < 0 || monochromaticval > 1 ||
00055                 medianval < 0 || (uint)medianval > UCHAR_MAX ||
00056                 widthval < 0 || heightval < 0 || colval < 0 || rowval < 0)
00057         {
00058             LOG(VB_COMMFLAG, LOG_ERR,
00059                 QString("Data out of range in %1: frame %2")
00060                     .arg(filename).arg(frameno));
00061             goto error;
00062         }
00063         for (unsigned int ii = 0; ii < sizeof(counter)/sizeof(*counter); ii++)
00064         {
00065             if ((nitems = fscanf(fp, "%20x", &counter[ii])) != 1)
00066             {
00067                 LOG(VB_COMMFLAG, LOG_ERR,
00068                     QString("Not enough data in %1: frame %2")
00069                         .arg(filename).arg(frameno));
00070                 goto error;
00071             }
00072             if (counter[ii] < 0 || (uint)(counter[ii]) > UCHAR_MAX)
00073             {
00074                 LOG(VB_COMMFLAG, LOG_ERR,
00075                     QString("Data out of range in %1: frame %2")
00076                         .arg(filename).arg(frameno));
00077                 goto error;
00078             }
00079         }
00080         mean[frameno] = meanval;
00081         median[frameno] = medianval;
00082         stddev[frameno] = stddevval;
00083         frow[frameno] = rowval;
00084         fcol[frameno] = colval;
00085         fwidth[frameno] = widthval;
00086         fheight[frameno] = heightval;
00087         for (unsigned int ii = 0; ii < sizeof(counter)/sizeof(*counter); ii++)
00088             histogram[frameno][ii] = counter[ii];
00089         monochromatic[frameno] = !widthval || !heightval ? 1 : 0;
00090         /*
00091          * monochromaticval not used; it's written to file for debugging
00092          * convenience
00093          */
00094     }
00095     if (fclose(fp))
00096         LOG(VB_COMMFLAG, LOG_ERR, QString("Error closing %1: %2")
00097                 .arg(filename).arg(strerror(errno)));
00098     return true;
00099 
00100 error:
00101     if (fclose(fp))
00102         LOG(VB_COMMFLAG, LOG_ERR, QString("Error closing %1: %2")
00103                 .arg(filename).arg(strerror(errno)));
00104     return false;
00105 }
00106 
00107 bool
00108 writeData(QString filename, float *mean, unsigned char *median, float *stddev,
00109         int *frow, int *fcol, int *fwidth, int *fheight,
00110         HistogramAnalyzer::Histogram *histogram, unsigned char *monochromatic,
00111         long long nframes)
00112 {
00113     FILE            *fp;
00114     long long       frameno;
00115 
00116     QByteArray fname = filename.toLocal8Bit();
00117     if (!(fp = fopen(fname, "w")))
00118         return false;
00119     for (frameno = 0; frameno < nframes; frameno++)
00120     {
00121         (void)fprintf(fp, "%3u %10.6f %3u %10.6f %5u %5u %5u %5u",
00122                       monochromatic[frameno],
00123                       mean[frameno], median[frameno], stddev[frameno],
00124                       fwidth[frameno], fheight[frameno],
00125                       fcol[frameno], frow[frameno]);
00126         for (unsigned int ii = 0; ii < UCHAR_MAX + 1; ii++)
00127             (void)fprintf(fp, " %02x", histogram[frameno][ii]);
00128         (void)fprintf(fp, "\n");
00129     }
00130     if (fclose(fp))
00131         LOG(VB_COMMFLAG, LOG_ERR, QString("Error closing %1: %2")
00132                 .arg(filename).arg(strerror(errno)));
00133     return true;
00134 }
00135 
00136 };  /* namespace */
00137 
00138 HistogramAnalyzer::HistogramAnalyzer(PGMConverter *pgmc, BorderDetector *bd,
00139         QString debugdir)
00140     : pgmConverter(pgmc)
00141     , borderDetector(bd)
00142     , logoFinder(NULL)
00143     , logo(NULL)
00144     , logowidth(-1)
00145     , logoheight(-1)
00146     , logorr1(-1)
00147     , logocc1(-1)
00148     , logorr2(-1)
00149     , logocc2(-1)
00150     , mean(NULL)
00151     , median(NULL)
00152     , stddev(NULL)
00153     , frow(NULL)
00154     , fcol(NULL)
00155     , fwidth(NULL)
00156     , fheight(NULL)
00157     , histogram(NULL)
00158     , monochromatic(NULL)
00159     , buf(NULL)
00160     , lastframeno(-1)
00161     , debugLevel(0)
00162 #ifdef PGM_CONVERT_GREYSCALE
00163     , debugdata(debugdir + "/HistogramAnalyzer-pgm.txt")
00164 #else  /* !PGM_CONVERT_GREYSCALE */
00165     , debugdata(debugdir + "/HistogramAnalyzer-yuv.txt")
00166 #endif /* !PGM_CONVERT_GREYSCALE */
00167     , debug_histval(false)
00168     , histval_done(false)
00169 {
00170     memset(histval, 0, sizeof(int) * (UCHAR_MAX + 1));
00171     memset(&analyze_time, 0, sizeof(analyze_time));
00172 
00173     /*
00174      * debugLevel:
00175      *      0: no debugging
00176      *      1: cache frame information into debugdata [1 file]
00177      */
00178     debugLevel = gCoreContext->GetNumSetting("HistogramAnalyzerDebugLevel", 0);
00179 
00180     if (debugLevel >= 1)
00181     {
00182         createDebugDirectory(debugdir,
00183             QString("HistogramAnalyzer debugLevel %1").arg(debugLevel));
00184         debug_histval = true;
00185     }
00186 }
00187 
00188 HistogramAnalyzer::~HistogramAnalyzer()
00189 {
00190     if (monochromatic)
00191         delete []monochromatic;
00192     if (mean)
00193         delete []mean;
00194     if (median)
00195         delete []median;
00196     if (stddev)
00197         delete []stddev;
00198     if (frow)
00199         delete []frow;
00200     if (fcol)
00201         delete []fcol;
00202     if (fwidth)
00203         delete []fwidth;
00204     if (fheight)
00205         delete []fheight;
00206     if (histogram)
00207         delete []histogram;
00208     if (buf)
00209         delete []buf;
00210 }
00211 
00212 enum FrameAnalyzer::analyzeFrameResult
00213 HistogramAnalyzer::MythPlayerInited(MythPlayer *player, long long nframes)
00214 {
00215     if (histval_done)
00216         return FrameAnalyzer::ANALYZE_FINISHED;
00217 
00218     if (monochromatic)
00219         return FrameAnalyzer::ANALYZE_OK;
00220 
00221     QSize buf_dim = player->GetVideoBufferSize();
00222     unsigned int width  = buf_dim.width();
00223     unsigned int height = buf_dim.height();
00224 
00225     if (logoFinder && (logo = logoFinder->getTemplate(&logorr1, &logocc1,
00226                     &logowidth, &logoheight)))
00227     {
00228         logorr2 = logorr1 + logoheight - 1;
00229         logocc2 = logocc1 + logowidth - 1;
00230     }
00231     QString details = logo ? QString("logo %1x%2@(%3,%4)")
00232         .arg(logowidth).arg(logoheight).arg(logocc1).arg(logorr1) :
00233             QString("no logo");
00234 
00235     LOG(VB_COMMFLAG, LOG_INFO,
00236         QString("HistogramAnalyzer::MythPlayerInited %1x%2: %3")
00237             .arg(width).arg(height).arg(details));
00238 
00239     if (pgmConverter->MythPlayerInited(player))
00240         return FrameAnalyzer::ANALYZE_FATAL;
00241 
00242     if (borderDetector->MythPlayerInited(player))
00243         return FrameAnalyzer::ANALYZE_FATAL;
00244 
00245     mean = new float[nframes];
00246     median = new unsigned char[nframes];
00247     stddev = new float[nframes];
00248     frow = new int[nframes];
00249     fcol = new int[nframes];
00250     fwidth = new int[nframes];
00251     fheight = new int[nframes];
00252     histogram = new Histogram[nframes];
00253     monochromatic = new unsigned char[nframes];
00254 
00255     memset(mean, 0, nframes * sizeof(*mean));
00256     memset(median, 0, nframes * sizeof(*median));
00257     memset(stddev, 0, nframes * sizeof(*stddev));
00258     memset(frow, 0, nframes * sizeof(*frow));
00259     memset(fcol, 0, nframes * sizeof(*fcol));
00260     memset(fwidth, 0, nframes * sizeof(*fwidth));
00261     memset(fheight, 0, nframes * sizeof(*fheight));
00262     memset(histogram, 0, nframes * sizeof(*histogram));
00263     memset(monochromatic, 0, nframes * sizeof(*monochromatic));
00264 
00265     unsigned int npixels = width * height;
00266     buf = new unsigned char[npixels];
00267 
00268     if (debug_histval)
00269     {
00270         if (readData(debugdata, mean, median, stddev, frow, fcol,
00271                     fwidth, fheight, histogram, monochromatic, nframes))
00272         {
00273             LOG(VB_COMMFLAG, LOG_INFO,
00274                 QString("HistogramAnalyzer::MythPlayerInited read %1")
00275                     .arg(debugdata));
00276             histval_done = true;
00277             return FrameAnalyzer::ANALYZE_FINISHED;
00278         }
00279     }
00280 
00281     return FrameAnalyzer::ANALYZE_OK;
00282 }
00283 
00284 void
00285 HistogramAnalyzer::setLogoState(TemplateFinder *finder)
00286 {
00287     logoFinder = finder;
00288 }
00289 
00290 enum FrameAnalyzer::analyzeFrameResult
00291 HistogramAnalyzer::analyzeFrame(const VideoFrame *frame, long long frameno)
00292 {
00293     /*
00294      * Various statistical computations over pixel values: mean, median,
00295      * (running) standard deviation over sample population.
00296      */
00297     static const int    DEFAULT_COLOR = 0;
00298 
00299     /*
00300      * TUNABLE:
00301      *
00302      * Sampling coarseness of each frame. Higher values will allow analysis to
00303      * proceed faster (lower resolution), but might be less accurate. Lower
00304      * values will examine more pixels (higher resolution), but will run
00305      * slower.
00306      */
00307     static const int    RINC = 4;
00308     static const int    CINC = 4;
00309 #define ROUNDUP(a,b)    (((a) + (b) - 1) / (b) * (b))
00310 
00311     const AVPicture     *pgm;
00312     int                 pgmwidth, pgmheight;
00313     bool                ismonochromatic;
00314     int                 croprow, cropcol, cropwidth, cropheight;
00315     unsigned int        borderpixels, livepixels, npixels, halfnpixels;
00316     unsigned char       *pp, bordercolor;
00317     unsigned long long  sumval, sumsquares;
00318     int                 rr, cc, rr1, cc1, rr2, cc2, rr3, cc3;
00319     struct timeval      start, end, elapsed;
00320 
00321     if (lastframeno != UNCACHED && lastframeno == frameno)
00322         return FrameAnalyzer::ANALYZE_OK;
00323 
00324     if (!(pgm = pgmConverter->getImage(frame, frameno, &pgmwidth, &pgmheight)))
00325         goto error;
00326 
00327     ismonochromatic = borderDetector->getDimensions(pgm, pgmheight, frameno,
00328             &croprow, &cropcol, &cropwidth, &cropheight) != 0;
00329 
00330     gettimeofday(&start, NULL);
00331 
00332     frow[frameno] = croprow;
00333     fcol[frameno] = cropcol;
00334     fwidth[frameno] = cropwidth;
00335     fheight[frameno] = cropheight;
00336 
00337     if (ismonochromatic)
00338     {
00339         /* Optimization for monochromatic frames; just sample center area. */
00340         croprow = pgmheight * 3 / 8;
00341         cropheight = pgmheight / 4;
00342         cropcol = pgmwidth * 3 / 8;
00343         cropwidth = pgmwidth / 4;
00344     }
00345 
00346     rr1 = ROUNDUP(croprow, RINC);
00347     cc1 = ROUNDUP(cropcol, CINC);
00348     rr2 = ROUNDUP(croprow + cropheight, RINC);
00349     cc2 = ROUNDUP(cropcol + cropwidth, CINC);
00350     rr3 = ROUNDUP(pgmheight, RINC);
00351     cc3 = ROUNDUP(pgmwidth, CINC);
00352 
00353     borderpixels = (rr1 / RINC) * (cc3 / CINC) +        /* top */
00354         ((rr2 - rr1) / RINC) * (cc1 / CINC) +           /* left */
00355         ((rr2 - rr1) / RINC) * ((cc3 - cc2) / CINC) +   /* right */
00356         ((rr3 - rr2) / RINC) * (cc3 / CINC);            /* bottom */
00357 
00358     sumval = 0;
00359     sumsquares = 0;
00360     livepixels = 0;
00361     pp = &buf[borderpixels];
00362     memset(histval, 0, sizeof(histval));
00363     histval[DEFAULT_COLOR] += borderpixels;
00364     for (rr = rr1; rr < rr2; rr += RINC)
00365     {
00366         int rroffset = rr * pgmwidth;
00367 
00368         for (cc = cc1; cc < cc2; cc += CINC)
00369         {
00370             if (logo && rr >= logorr1 && rr <= logorr2 &&
00371                     cc >= logocc1 && cc <= logocc2)
00372                 continue; /* Exclude logo area from analysis. */
00373 
00374             unsigned char val = pgm->data[0][rroffset + cc];
00375             *pp++ = val;
00376             sumval += val;
00377             sumsquares += val * val;
00378             livepixels++;
00379             histval[val]++;
00380         }
00381     }
00382     npixels = borderpixels + livepixels;
00383 
00384     /* Scale scores down to [0..255]. */
00385     halfnpixels = npixels / 2;
00386     for (unsigned int color = 0; color < UCHAR_MAX + 1; color++)
00387         histogram[frameno][color] =
00388             (histval[color] * UCHAR_MAX + halfnpixels) / npixels;
00389 
00390     bordercolor = 0;
00391     if (ismonochromatic && livepixels)
00392     {
00393         /*
00394          * Fake up the margin pixels to be of the same color as the sampled
00395          * area.
00396          */
00397         bordercolor = (sumval + livepixels - 1) / livepixels;
00398         sumval += borderpixels * bordercolor;
00399         sumsquares += borderpixels * bordercolor * bordercolor;
00400     }
00401 
00402     memset(buf, bordercolor, borderpixels * sizeof(*buf));
00403     monochromatic[frameno] = ismonochromatic ? 1 : 0;
00404     mean[frameno] = (float)sumval / npixels;
00405     median[frameno] = quick_select_median(buf, npixels);
00406     stddev[frameno] = npixels > 1 ?
00407         sqrt((sumsquares - (float)sumval * sumval / npixels) / (npixels - 1)) :
00408             0;
00409 
00410     (void)gettimeofday(&end, NULL);
00411     timersub(&end, &start, &elapsed);
00412     timeradd(&analyze_time, &elapsed, &analyze_time);
00413 
00414     lastframeno = frameno;
00415 
00416     return FrameAnalyzer::ANALYZE_OK;
00417 
00418 error:
00419     LOG(VB_COMMFLAG, LOG_ERR,
00420         QString("HistogramAnalyzer::analyzeFrame error at frame %1")
00421             .arg(frameno));
00422 
00423     return FrameAnalyzer::ANALYZE_ERROR;
00424 }
00425 
00426 int
00427 HistogramAnalyzer::finished(long long nframes, bool final)
00428 {
00429     if (!histval_done && debug_histval)
00430     {
00431         if (final && writeData(debugdata, mean, median, stddev, frow, fcol,
00432                     fwidth, fheight, histogram, monochromatic, nframes))
00433         {
00434             LOG(VB_COMMFLAG, LOG_INFO,
00435                 QString("HistogramAnalyzer::finished wrote %1")
00436                     .arg(debugdata));
00437             histval_done = true;
00438         }
00439     }
00440 
00441     return 0;
00442 }
00443 
00444 int
00445 HistogramAnalyzer::reportTime(void) const
00446 {
00447     if (pgmConverter->reportTime())
00448         return -1;
00449 
00450     if (borderDetector->reportTime())
00451         return -1;
00452 
00453     LOG(VB_COMMFLAG, LOG_INFO, QString("HA Time: analyze=%1s")
00454             .arg(strftimeval(&analyze_time)));
00455     return 0;
00456 }
00457 
00458 /* vim: set expandtab tabstop=4 shiftwidth=4: */
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends