MythTV  0.26-pre
BorderDetector.cpp
Go to the documentation of this file.
00001 #include <sys/time.h>
00002 
00003 extern "C" {
00004 #include "libavcodec/avcodec.h"        /* AVPicture */
00005 }
00006 #include "mythcorecontext.h"    /* gContext */
00007 #include "compat.h"
00008 
00009 #include "CommDetector2.h"
00010 #include "FrameAnalyzer.h"
00011 #include "TemplateFinder.h"
00012 #include "BorderDetector.h"
00013 
00014 using namespace frameAnalyzer;
00015 using namespace commDetector2;
00016 
00017 BorderDetector::BorderDetector(void)
00018     : logoFinder(NULL),
00019       logo(NULL),
00020       logorow(-1),
00021       logocol(-1),
00022       logowidth(-1),
00023       logoheight(-1),
00024       frameno(-1),
00025       row(-1),
00026       col(-1),
00027       width(-1),
00028       height(-1),
00029       ismonochromatic(false),
00030       debugLevel(0),
00031       time_reported(false)
00032 {
00033     memset(&analyze_time, 0, sizeof(analyze_time));
00034     debugLevel = gCoreContext->GetNumSetting("BorderDetectorDebugLevel", 0);
00035 
00036     if (debugLevel >= 1)
00037         LOG(VB_COMMFLAG, LOG_INFO,
00038             QString("BorderDetector debugLevel %1").arg(debugLevel));
00039 }
00040 
00041 int
00042 BorderDetector::MythPlayerInited(const MythPlayer *player)
00043 {
00044     (void)player;  /* gcc */
00045     time_reported = false;
00046     memset(&analyze_time, 0, sizeof(analyze_time));
00047     return 0;
00048 }
00049 
00050 void
00051 BorderDetector::setLogoState(TemplateFinder *finder)
00052 {
00053     if ((logoFinder = finder) && (logo = logoFinder->getTemplate(
00054                     &logorow, &logocol, &logowidth, &logoheight)))
00055     {
00056         LOG(VB_COMMFLAG, LOG_INFO,
00057             QString("BorderDetector::setLogoState: %1x%2@(%3,%4)")
00058                 .arg(logowidth).arg(logoheight).arg(logocol).arg(logorow));
00059     }
00060 }
00061 
00062 int
00063 BorderDetector::getDimensions(const AVPicture *pgm, int pgmheight,
00064         long long _frameno, int *prow, int *pcol, int *pwidth, int *pheight)
00065 {
00066     /*
00067      * The basic algorithm is to look for pixels of the same color along all
00068      * four borders of the frame, working inwards until the pixels cease to be
00069      * of uniform color. This way, letterboxing/pillarboxing bars can be of any
00070      * color (varying shades of black-grey).
00071      *
00072      * If there is a logo, exclude its area from border detection.
00073      *
00074      * Return 0 for normal frames; non-zero for monochromatic frames.
00075      */
00076 
00077     /*
00078      * TUNABLES:
00079      *
00080      * Higher values mean more tolerance for noise (e.g., analog recordings).
00081      * However, in the absence of noise, content/logos can be cropped away from
00082      * analysis.
00083      *
00084      * Lower values mean less tolerance for noise. In a noisy recording, the
00085      * transition between pillarbox/letterbox black to content color will be
00086      * detected as an edge, and thwart logo edge detection. In the absence of
00087      * noise, content/logos will be more faithfully analyzed.
00088      */
00089 
00090     /*
00091      * TUNABLE: The maximum range of values allowed for
00092      * letterboxing/pillarboxing bars. Usually the bars are black (0x00), but
00093      * sometimes they are grey (0x71). Sometimes the letterboxing and
00094      * pillarboxing (when one is embedded inside the other) are different
00095      * colors.
00096      */
00097     static const unsigned char  MAXRANGE = 32;
00098 
00099     /*
00100      * TUNABLE: The maximum number of consecutive rows or columns with too many
00101      * outlier points that may be scanned before declaring the existence of a
00102      * border.
00103      */
00104     static const int        MAXLINES = 2;
00105 
00106     const int               pgmwidth = pgm->linesize[0];
00107 
00108     /*
00109      * TUNABLE: The maximum number of outlier points in a single row or column
00110      * with grey values outside of MAXRANGE before declaring the existence of a
00111      * border.
00112      */
00113     const int               MAXOUTLIERS = pgmwidth * 12 / 1000;
00114 
00115     /*
00116      * TUNABLE: Margins to avoid noise at the extreme edges of the signal
00117      * (VBI?). (Really, just a special case of VERTSLOP and HORIZSLOP, below.)
00118      */
00119     const int               VERTMARGIN = max(2, pgmheight * 1 / 60);
00120     const int               HORIZMARGIN = max(2, pgmwidth * 1 / 80);
00121 
00122     /*
00123      * TUNABLE: Slop to accommodate any jagged letterboxing/pillarboxing edges,
00124      * or noise between edges and content. (Really, a more general case of
00125      * VERTMARGIN and HORIZMARGIN, above.)
00126      */
00127     const int               VERTSLOP = max(MAXLINES, pgmheight * 1 / 120);
00128     const int               HORIZSLOP = max(MAXLINES, pgmwidth * 1 / 160);
00129 
00130     struct timeval          start, end, elapsed;
00131     unsigned char           minval, maxval, val;
00132     int                     rr, cc, minrow, mincol, maxrow1, maxcol1, saved;
00133     int                     newrow, newcol, newwidth, newheight;
00134     bool                    top, bottom, left, right, inrange;
00135     int                     range, outliers, lines;
00136 
00137     (void)gettimeofday(&start, NULL);
00138 
00139     if (_frameno != UNCACHED && _frameno == frameno)
00140         goto done;
00141 
00142     top = false;
00143     bottom = false;
00144     left = false;
00145     right = false;
00146 
00147     minrow = VERTMARGIN;
00148     maxrow1 = pgmheight - VERTMARGIN;   /* maxrow + 1 */
00149 
00150     mincol = HORIZMARGIN;
00151     maxcol1 = pgmwidth - HORIZMARGIN;   /* maxcol + 1 */
00152 
00153     newrow = minrow - 1;
00154     newcol = mincol - 1;
00155     newwidth = maxcol1 + 1 - mincol;
00156     newheight = maxrow1 + 1 - minrow;
00157 
00158     for (;;)
00159     {
00160         /* Find left edge. */
00161         left = false;
00162         minval = UCHAR_MAX;
00163         maxval = 0;
00164         lines = 0;
00165         saved = mincol;
00166         for (cc = mincol; cc < maxcol1; cc++)
00167         {
00168             outliers = 0;
00169             inrange = true;
00170             for (rr = minrow; rr < maxrow1; rr++)
00171             {
00172                 if (logo && rrccinrect(rr, cc, logorow, logocol,
00173                             logowidth, logoheight))
00174                     continue;   /* Exclude logo area from analysis. */
00175 
00176                 val = pgm->data[0][rr * pgmwidth + cc];
00177                 range = max(maxval, val) - min(minval, val) + 1;
00178                 if (range > MAXRANGE)
00179                 {
00180                     if (outliers++ < MAXOUTLIERS)
00181                         continue;   /* Next row. */
00182                     inrange = false;
00183                     if (lines++ < MAXLINES)
00184                         break;  /* Next column. */
00185                     goto found_left;
00186                 }
00187                 if (val < minval)
00188                     minval = val;
00189                 if (val > maxval)
00190                     maxval = val;
00191             }
00192             if (inrange)
00193             {
00194                 saved = cc;
00195                 lines = 0;
00196             }
00197         }
00198 found_left:
00199         if (newcol != saved + 1 + HORIZSLOP)
00200         {
00201             newcol = min(maxcol1, saved + 1 + HORIZSLOP);
00202             newwidth = max(0, maxcol1 - newcol);
00203             left = true;
00204         }
00205 
00206         if (!newwidth)
00207             goto monochromatic_frame;
00208 
00209         mincol = newcol;
00210 
00211         /*
00212          * Find right edge. Keep same minval/maxval (pillarboxing colors) as
00213          * left edge.
00214          */
00215         right = false;
00216         lines = 0;
00217         saved = maxcol1 - 1;
00218         for (cc = maxcol1 - 1; cc >= mincol; cc--)
00219         {
00220             outliers = 0;
00221             inrange = true;
00222             for (rr = minrow; rr < maxrow1; rr++)
00223             {
00224                 if (logo && rrccinrect(rr, cc, logorow, logocol,
00225                             logowidth, logoheight))
00226                     continue;   /* Exclude logo area from analysis. */
00227 
00228                 val = pgm->data[0][rr * pgmwidth + cc];
00229                 range = max(maxval, val) - min(minval, val) + 1;
00230                 if (range > MAXRANGE)
00231                 {
00232                     if (outliers++ < MAXOUTLIERS)
00233                         continue;   /* Next row. */
00234                     inrange = false;
00235                     if (lines++ < MAXLINES)
00236                         break;  /* Next column. */
00237                     goto found_right;
00238                 }
00239                 if (val < minval)
00240                     minval = val;
00241                 if (val > maxval)
00242                     maxval = val;
00243             }
00244             if (inrange)
00245             {
00246                 saved = cc;
00247                 lines = 0;
00248             }
00249         }
00250 found_right:
00251         if (newwidth != saved - mincol - HORIZSLOP)
00252         {
00253             newwidth = max(0, saved - mincol - HORIZSLOP);
00254             right = true;
00255         }
00256 
00257         if (!newwidth)
00258             goto monochromatic_frame;
00259 
00260         if (top || bottom)
00261             break;  /* Do not repeat letterboxing check. */
00262 
00263         maxcol1 = mincol + newwidth;
00264 
00265         /* Find top edge. */
00266         top = false;
00267         minval = UCHAR_MAX;
00268         maxval = 0;
00269         lines = 0;
00270         saved = minrow;
00271         for (rr = minrow; rr < maxrow1; rr++)
00272         {
00273             outliers = 0;
00274             inrange = true;
00275             for (cc = mincol; cc < maxcol1; cc++)
00276             {
00277                 if (logo && rrccinrect(rr, cc, logorow, logocol,
00278                             logowidth, logoheight))
00279                     continue;   /* Exclude logo area from analysis. */
00280 
00281                 val = pgm->data[0][rr * pgmwidth + cc];
00282                 range = max(maxval, val) - min(minval, val) + 1;
00283                 if (range > MAXRANGE)
00284                 {
00285                     if (outliers++ < MAXOUTLIERS)
00286                         continue;   /* Next column. */
00287                     inrange = false;
00288                     if (lines++ < MAXLINES)
00289                         break;  /* Next row. */
00290                     goto found_top;
00291                 }
00292                 if (val < minval)
00293                     minval = val;
00294                 if (val > maxval)
00295                     maxval = val;
00296             }
00297             if (inrange)
00298             {
00299                 saved = rr;
00300                 lines = 0;
00301             }
00302         }
00303 found_top:
00304         if (newrow != saved + 1 + VERTSLOP)
00305         {
00306             newrow = min(maxrow1, saved + 1 + VERTSLOP);
00307             newheight = max(0, maxrow1 - newrow);
00308             top = true;
00309         }
00310 
00311         if (!newheight)
00312             goto monochromatic_frame;
00313 
00314         minrow = newrow;
00315 
00316         /* Find bottom edge. Keep same minval/maxval as top edge. */
00317         bottom = false;
00318         lines = 0;
00319         saved = maxrow1 - 1;
00320         for (rr = maxrow1 - 1; rr >= minrow; rr--)
00321         {
00322             outliers = 0;
00323             inrange = true;
00324             for (cc = mincol; cc < maxcol1; cc++)
00325             {
00326                 if (logo && rrccinrect(rr, cc, logorow, logocol,
00327                             logowidth, logoheight))
00328                     continue;   /* Exclude logo area from analysis. */
00329 
00330                 val = pgm->data[0][rr * pgmwidth + cc];
00331                 range = max(maxval, val) - min(minval, val) + 1;
00332                 if (range > MAXRANGE)
00333                 {
00334                     if (outliers++ < MAXOUTLIERS)
00335                         continue;   /* Next column. */
00336                     inrange = false;
00337                     if (lines++ < MAXLINES)
00338                         break;  /* Next row. */
00339                     goto found_bottom;
00340                 }
00341                 if (val < minval)
00342                     minval = val;
00343                 if (val > maxval)
00344                     maxval = val;
00345             }
00346             if (inrange)
00347             {
00348                 saved = rr;
00349                 lines = 0;
00350             }
00351         }
00352 found_bottom:
00353         if (newheight != saved - minrow - VERTSLOP)
00354         {
00355             newheight = max(0, saved - minrow - VERTSLOP);
00356             bottom = true;
00357         }
00358 
00359         if (!newheight)
00360             goto monochromatic_frame;
00361 
00362         if (left || right)
00363             break;  /* Do not repeat pillarboxing check. */
00364 
00365         maxrow1 = minrow + newheight;
00366     }
00367 
00368     frameno = _frameno;
00369     row = newrow;
00370     col = newcol;
00371     width = newwidth;
00372     height = newheight;
00373     ismonochromatic = false;
00374     goto done;
00375 
00376 monochromatic_frame:
00377     frameno = _frameno;
00378     row = newrow;
00379     col = newcol;
00380     width = newwidth;
00381     height = newheight;
00382     ismonochromatic = true;
00383 
00384 done:
00385     *prow = row;
00386     *pcol = col;
00387     *pwidth = width;
00388     *pheight = height;
00389 
00390     (void)gettimeofday(&end, NULL);
00391     timersub(&end, &start, &elapsed);
00392     timeradd(&analyze_time, &elapsed, &analyze_time);
00393 
00394     return ismonochromatic ? -1 : 0;
00395 }
00396 
00397 int
00398 BorderDetector::reportTime(void)
00399 {
00400     if (!time_reported)
00401     {
00402         LOG(VB_COMMFLAG, LOG_INFO, QString("BD Time: analyze=%1s")
00403                 .arg(strftimeval(&analyze_time)));
00404         time_reported = true;
00405     }
00406     return 0;
00407 }
00408 
00409 /* vim: set expandtab tabstop=4 shiftwidth=4: */
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends