00001 #include <stdio.h>
00002 #include <stdlib.h>
00003 #include <math.h>
00004
00005 #include "NuppelVideoPlayer.h"
00006
00007 #include "CommDetector2.h"
00008 #include "FrameAnalyzer.h"
00009 #include "pgm.h"
00010 #include "PGMConverter.h"
00011 #include "EdgeDetector.h"
00012 #include "BlankFrameDetector.h"
00013 #include "TemplateFinder.h"
00014 #include "TemplateMatcher.h"
00015
00016
00017 #include <qfile.h>
00018 #include <qfileinfo.h>
00019
00020 using namespace commDetector2;
00021 using namespace frameAnalyzer;
00022
00023 namespace {
00024
00025 int
00026 pgm_set(const AVPicture *pict, int height)
00027 {
00028 const int width = pict->linesize[0];
00029 const int size = height * width;
00030 int score, ii;
00031
00032 score = 0;
00033 for (ii = 0; ii < size; ii++)
00034 if (pict->data[0][ii])
00035 score++;
00036 return score;
00037 }
00038
00039 int
00040 pgm_match(const AVPicture *tmpl, const AVPicture *test, int height,
00041 int radius, unsigned short *pscore)
00042 {
00043
00044 const int width = tmpl->linesize[0];
00045 int score, rr, cc;
00046
00047 if (width != test->linesize[0])
00048 {
00049 VERBOSE(VB_COMMFLAG, QString("pgm_match widths don't match: %1 != %2")
00050 .arg(width).arg(test->linesize[0]));
00051 return -1;
00052 }
00053
00054 score = 0;
00055 for (rr = 0; rr < height; rr++)
00056 {
00057 for (cc = 0; cc < width; cc++)
00058 {
00059 int r2min, r2max, r2, c2min, c2max, c2;
00060
00061 if (!tmpl->data[0][rr * width + cc])
00062 continue;
00063
00064 r2min = max(0, rr - radius);
00065 r2max = min(height, rr + radius);
00066
00067 c2min = max(0, cc - radius);
00068 c2max = min(width, cc + radius);
00069
00070 for (r2 = r2min; r2 <= r2max; r2++)
00071 {
00072 for (c2 = c2min; c2 <= c2max; c2++)
00073 {
00074 if (test->data[0][r2 * width + c2])
00075 {
00076 score++;
00077 goto next_pixel;
00078 }
00079 }
00080 }
00081 next_pixel:
00082 ;
00083 }
00084 }
00085
00086 *pscore = score;
00087 return 0;
00088 }
00089
00090 bool
00091 readMatches(QString filename, unsigned short *matches, long long nframes)
00092 {
00093 FILE *fp;
00094 long long frameno;
00095
00096 if (!(fp = fopen(filename, "r")))
00097 return false;
00098
00099 for (frameno = 0; frameno < nframes; frameno++)
00100 {
00101 int nitems = fscanf(fp, "%hu", &matches[frameno]);
00102 if (nitems != 1)
00103 {
00104 VERBOSE(VB_COMMFLAG, QString("Not enough data in %1: frame %2")
00105 .arg(filename).arg(frameno));
00106 goto error;
00107 }
00108 }
00109
00110 if (fclose(fp))
00111 VERBOSE(VB_COMMFLAG, QString("Error closing %1: %2")
00112 .arg(filename).arg(strerror(errno)));
00113 return true;
00114
00115 error:
00116 if (fclose(fp))
00117 VERBOSE(VB_COMMFLAG, QString("Error closing %1: %2")
00118 .arg(filename).arg(strerror(errno)));
00119 return false;
00120 }
00121
00122 bool
00123 writeMatches(QString filename, unsigned short *matches, long long nframes)
00124 {
00125 FILE *fp;
00126 long long frameno;
00127
00128 if (!(fp = fopen(filename, "w")))
00129 return false;
00130
00131 for (frameno = 0; frameno < nframes; frameno++)
00132 (void)fprintf(fp, "%hu\n", matches[frameno]);
00133
00134 if (fclose(fp))
00135 VERBOSE(VB_COMMFLAG, QString("Error closing %1: %2")
00136 .arg(filename).arg(strerror(errno)));
00137 return true;
00138 }
00139
00140 int
00141 finishedDebug(long long nframes, const unsigned short *matches,
00142 const unsigned char *match)
00143 {
00144 unsigned short low, high, score;
00145 long long startframe;
00146
00147 score = matches[0];
00148 low = score;
00149 high = score;
00150 startframe = 0;
00151
00152 for (long long frameno = 1; frameno < nframes; frameno++)
00153 {
00154 score = matches[frameno];
00155 if (match[frameno - 1] == match[frameno])
00156 {
00157 if (score < low)
00158 low = score;
00159 if (score > high)
00160 high = score;
00161 continue;
00162 }
00163
00164 VERBOSE(VB_COMMFLAG, QString("Frame %1-%2: %3 L-H: %4-%5 (%6)")
00165 .arg(startframe, 6).arg(frameno - 1, 6)
00166 .arg(match[frameno - 1] ? "logo " : " no-logo")
00167 .arg(low, 4).arg(high, 4).arg(frameno - startframe, 5));
00168
00169 low = score;
00170 high = score;
00171 startframe = frameno;
00172 }
00173
00174 return 0;
00175 }
00176
00177 int
00178 sort_ascending(const void *aa, const void *bb)
00179 {
00180 return *(unsigned short*)aa - *(unsigned short*)bb;
00181 }
00182
00183 long long
00184 matchspn(long long nframes, unsigned char *match, long long frameno,
00185 unsigned char acceptval)
00186 {
00187
00188
00189
00190
00191 while (frameno < nframes && match[frameno] == acceptval)
00192 frameno++;
00193 return frameno;
00194 }
00195
00196 unsigned int
00197 range_area(const unsigned short *freq, unsigned short start, unsigned short end)
00198 {
00199
00200 const unsigned short width = end - start;
00201 unsigned short matchcnt;
00202 unsigned int sum, nsamples;
00203
00204 sum = 0;
00205 nsamples = 0;
00206 for (matchcnt = start; matchcnt < end; matchcnt++)
00207 {
00208 if (freq[matchcnt])
00209 {
00210 sum += freq[matchcnt];
00211 nsamples++;
00212 }
00213 }
00214 if (!nsamples)
00215 return 0;
00216 return width * sum / nsamples;
00217 }
00218
00219 unsigned short
00220 pick_mintmpledges(const unsigned short *matches, long long nframes)
00221 {
00222
00223
00224
00225
00226
00227
00228
00229
00230
00231
00232
00233
00234
00235
00236
00237
00238
00239
00240
00241
00242 static const float LEFTWIDTH = 0.04;
00243 static const float MIDDLEWIDTH = 0.04;
00244 static const float RIGHTWIDTH = 0.04;
00245
00246 static const float MATCHSTART = 0.20;
00247 static const float MATCHEND = 0.80;
00248
00249 unsigned short matchrange, matchstart, matchend;
00250 unsigned short leftwidth, middlewidth, rightwidth;
00251 unsigned short *sorted, minmatch, maxmatch, *freq;
00252 int nfreq, matchcnt, local_minimum;
00253 unsigned int maxdelta;
00254
00255 sorted = new unsigned short[nframes];
00256 memcpy(sorted, matches, nframes * sizeof(*matches));
00257 qsort(sorted, nframes, sizeof(*sorted), sort_ascending);
00258 minmatch = sorted[0];
00259 maxmatch = sorted[nframes - 1];
00260 matchrange = maxmatch - minmatch;
00261
00262
00263 leftwidth = (unsigned short)(LEFTWIDTH * matchrange);
00264 middlewidth = (unsigned short)(MIDDLEWIDTH * matchrange);
00265 rightwidth = (unsigned short)(RIGHTWIDTH * matchrange);
00266
00267 nfreq = maxmatch + 1;
00268 freq = new unsigned short[nfreq];
00269 memset(freq, 0, nfreq * sizeof(*freq));
00270 for (long long frameno = 0; frameno < nframes; frameno++)
00271 freq[matches[frameno]]++;
00272
00273 matchstart = minmatch + (unsigned short)(MATCHSTART * matchrange);
00274 matchend = minmatch + (unsigned short)(MATCHEND * matchrange);
00275
00276 local_minimum = matchstart;
00277 maxdelta = 0;
00278 for (matchcnt = matchstart + leftwidth + middlewidth / 2;
00279 matchcnt < matchend - rightwidth - middlewidth / 2;
00280 matchcnt++)
00281 {
00282 unsigned short p0, p1, p2, p3;
00283 unsigned int leftscore, middlescore, rightscore;
00284
00285 p0 = matchcnt - leftwidth - middlewidth / 2;
00286 p1 = p0 + leftwidth;
00287 p2 = p1 + middlewidth;
00288 p3 = p2 + rightwidth;
00289
00290 leftscore = range_area(freq, p0, p1);
00291 middlescore = range_area(freq, p1, p2);
00292 rightscore = range_area(freq, p2, p3);
00293 if (middlescore < leftscore && middlescore < rightscore)
00294 {
00295 unsigned int delta = (leftscore - middlescore) +
00296 (rightscore - middlescore);
00297 if (delta > maxdelta)
00298 {
00299 local_minimum = matchcnt;
00300 maxdelta = delta;
00301 }
00302 }
00303 }
00304
00305 VERBOSE(VB_COMMFLAG, QString("pick_mintmpledges minmatch=%1 maxmatch=%2"
00306 " matchstart=%3 matchend=%4 widths=%5,%6,%7 local_minimum=%8")
00307 .arg(minmatch).arg(maxmatch).arg(matchstart).arg(matchend)
00308 .arg(leftwidth).arg(middlewidth).arg(rightwidth)
00309 .arg(local_minimum));
00310
00311 delete []freq;
00312 delete []sorted;
00313 return local_minimum;
00314 }
00315
00316 };
00317
00318 TemplateMatcher::TemplateMatcher(PGMConverter *pgmc, EdgeDetector *ed,
00319 TemplateFinder *tf, QString debugdir)
00320 : FrameAnalyzer()
00321 , pgmConverter(pgmc)
00322 , edgeDetector(ed)
00323 , templateFinder(tf)
00324 , matches(NULL)
00325 , match(NULL)
00326 , debugLevel(0)
00327 , debugdir(debugdir)
00328 #ifdef PGM_CONVERT_GREYSCALE
00329 , debugdata(debugdir + "/TemplateMatcher-pgm.txt")
00330 #else
00331 , debugdata(debugdir + "/TemplateMatcher-yuv.txt")
00332 #endif
00333 , nvp(NULL)
00334 , debug_matches(false)
00335 , debug_removerunts(false)
00336 , matches_done(false)
00337 {
00338 memset(&cropped, 0, sizeof(cropped));
00339 memset(&analyze_time, 0, sizeof(analyze_time));
00340
00341
00342
00343
00344
00345
00346
00347 debugLevel = gContext->GetNumSetting("TemplateMatcherDebugLevel", 0);
00348
00349 if (debugLevel >= 1)
00350 {
00351 createDebugDirectory(debugdir,
00352 QString("TemplateMatcher debugLevel %1").arg(debugLevel));
00353 debug_matches = true;
00354 if (debugLevel >= 2)
00355 debug_removerunts = true;
00356 }
00357 }
00358
00359 TemplateMatcher::~TemplateMatcher(void)
00360 {
00361 if (matches)
00362 delete []matches;
00363 if (match)
00364 delete []match;
00365 avpicture_free(&cropped);
00366 }
00367
00368 enum FrameAnalyzer::analyzeFrameResult
00369 TemplateMatcher::nuppelVideoPlayerInited(NuppelVideoPlayer *_nvp,
00370 long long nframes)
00371 {
00372 nvp = _nvp;
00373 fps = nvp->GetFrameRate();
00374
00375 if (!(tmpl = templateFinder->getTemplate(&tmplrow, &tmplcol,
00376 &tmplwidth, &tmplheight)))
00377 {
00378 VERBOSE(VB_COMMFLAG, QString("TemplateMatcher::nuppelVideoPlayerInited:"
00379 " no template"));
00380 return ANALYZE_FATAL;
00381 }
00382
00383 if (avpicture_alloc(&cropped, PIX_FMT_GRAY8, tmplwidth, tmplheight))
00384 {
00385 VERBOSE(VB_COMMFLAG, QString("TemplateMatcher::nuppelVideoPlayerInited "
00386 "avpicture_alloc cropped (%1x%2) failed").
00387 arg(tmplwidth).arg(tmplheight));
00388 return ANALYZE_FATAL;
00389 }
00390
00391 if (pgmConverter->nuppelVideoPlayerInited(nvp))
00392 goto free_cropped;
00393
00394 matches = new unsigned short[nframes];
00395 memset(matches, 0, nframes * sizeof(*matches));
00396
00397 match = new unsigned char[nframes];
00398
00399 if (debug_matches)
00400 {
00401 if (readMatches(debugdata, matches, nframes))
00402 {
00403 VERBOSE(VB_COMMFLAG, QString(
00404 "TemplateMatcher::nuppelVideoPlayerInited read %1")
00405 .arg(debugdata));
00406 matches_done = true;
00407 }
00408 }
00409
00410 if (matches_done)
00411 return ANALYZE_FINISHED;
00412
00413 return ANALYZE_OK;
00414
00415 free_cropped:
00416 avpicture_free(&cropped);
00417 return ANALYZE_FATAL;
00418 }
00419
00420 enum FrameAnalyzer::analyzeFrameResult
00421 TemplateMatcher::analyzeFrame(const VideoFrame *frame, long long frameno,
00422 long long *pNextFrame)
00423 {
00424
00425
00426
00427
00428
00429
00430
00431
00432
00433
00434
00435
00436
00437
00438
00439 const int FRAMESGMPCTILE = 70;
00440
00441
00442
00443
00444
00445
00446
00447
00448
00449
00450
00451
00452
00453
00454
00455
00456
00457 const int JITTER_RADIUS = 0;
00458
00459 const AVPicture *pgm;
00460 const AVPicture *edges;
00461 int pgmwidth, pgmheight;
00462 struct timeval start, end, elapsed;
00463
00464 *pNextFrame = NEXTFRAME;
00465
00466 if (!(pgm = pgmConverter->getImage(frame, frameno, &pgmwidth, &pgmheight)))
00467 goto error;
00468
00469 (void)gettimeofday(&start, NULL);
00470
00471 if (pgm_crop(&cropped, pgm, pgmheight, tmplrow, tmplcol,
00472 tmplwidth, tmplheight))
00473 goto error;
00474
00475 if (!(edges = edgeDetector->detectEdges(&cropped, tmplheight,
00476 FRAMESGMPCTILE)))
00477 goto error;
00478
00479 if (pgm_match(tmpl, edges, tmplheight, JITTER_RADIUS, &matches[frameno]))
00480 goto error;
00481
00482 (void)gettimeofday(&end, NULL);
00483 timersub(&end, &start, &elapsed);
00484 timeradd(&analyze_time, &elapsed, &analyze_time);
00485
00486 return ANALYZE_OK;
00487
00488 error:
00489 VERBOSE(VB_COMMFLAG, QString(
00490 "TemplateMatcher::analyzeFrame error at frame %1 of %2")
00491 .arg(frameno));
00492 return ANALYZE_ERROR;
00493 }
00494
00495 int
00496 TemplateMatcher::finished(long long nframes, bool final)
00497 {
00498
00499
00500
00501
00502
00503
00504
00505
00506
00507 const int MINBREAKLEN = (int)roundf(45 * fps);
00508 const int MINSEGLEN = (int)roundf(105 * fps);
00509
00510 int tmpledges, mintmpledges;
00511 int minbreaklen, minseglen;
00512 long long brkb;
00513 FrameAnalyzer::FrameMap::Iterator bb;
00514
00515 if (!matches_done && debug_matches)
00516 {
00517 if (final && writeMatches(debugdata, matches, nframes))
00518 {
00519 VERBOSE(VB_COMMFLAG, QString("TemplateMatcher::finished wrote %1")
00520 .arg(debugdata));
00521 matches_done = true;
00522 }
00523 }
00524
00525 tmpledges = pgm_set(tmpl, tmplheight);
00526 mintmpledges = pick_mintmpledges(matches, nframes);
00527
00528 VERBOSE(VB_COMMFLAG, QString("TemplateMatcher::finished %1x%2@(%3,%4),"
00529 " %5 edge pixels, want %6")
00530 .arg(tmplwidth).arg(tmplheight).arg(tmplcol).arg(tmplrow)
00531 .arg(tmpledges).arg(mintmpledges));
00532
00533 for (long long ii = 0; ii < nframes; ii++)
00534 match[ii] = matches[ii] >= mintmpledges ? 1 : 0;
00535
00536 if (debugLevel >= 2)
00537 {
00538 if (final && finishedDebug(nframes, matches, match))
00539 goto error;
00540 }
00541
00542
00543
00544
00545 breakMap.clear();
00546 brkb = match[0] ? matchspn(nframes, match, 0, match[0]) : 0;
00547 while (brkb < nframes)
00548 {
00549
00550 long long brke = matchspn(nframes, match, brkb, match[brkb]);
00551 long long brklen = brke - brkb;
00552 breakMap.insert(brkb, brklen);
00553
00554
00555 brkb = matchspn(nframes, match, brke, match[brke]);
00556 }
00557
00558
00559 minbreaklen = 1;
00560 minseglen = 1;
00561 for (;;)
00562 {
00563 bool f1 = false;
00564 bool f2 = false;
00565 if (minbreaklen <= MINBREAKLEN)
00566 {
00567 f1 = removeShortBreaks(&breakMap, fps, minbreaklen,
00568 debug_removerunts);
00569 minbreaklen++;
00570 }
00571 if (minseglen <= MINSEGLEN)
00572 {
00573 f2 = removeShortSegments(&breakMap, nframes, fps, minseglen,
00574 debug_removerunts);
00575 minseglen++;
00576 }
00577 if (minbreaklen > MINBREAKLEN && minseglen > MINSEGLEN)
00578 break;
00579 if (debug_removerunts && (f1 || f2))
00580 frameAnalyzerReportMap(&breakMap, fps, "** TM Break");
00581 }
00582
00583
00584
00585
00586 frameAnalyzerReportMap(&breakMap, fps, "TM Break");
00587
00588 return 0;
00589
00590 error:
00591 return -1;
00592 }
00593
00594 int
00595 TemplateMatcher::reportTime(void) const
00596 {
00597 if (pgmConverter->reportTime())
00598 return -1;
00599
00600 VERBOSE(VB_COMMFLAG, QString("TM Time: analyze=%1s")
00601 .arg(strftimeval(&analyze_time)));
00602 return 0;
00603 }
00604
00605 int
00606 TemplateMatcher::templateCoverage(long long nframes, bool final) const
00607 {
00608
00609
00610
00611
00612
00613
00614
00615
00616
00617
00618
00619 const int MINBREAKS = nframes * 20 / 100;
00620 const int MAXBREAKS = nframes * 45 / 100;
00621
00622 const long long brklen = frameAnalyzerMapSum(&breakMap);
00623 const bool good = brklen >= MINBREAKS && brklen <= MAXBREAKS;
00624
00625 if (debugLevel >= 1)
00626 {
00627 if (!tmpl)
00628 {
00629 VERBOSE(VB_COMMFLAG, QString("TemplateMatcher: no template"
00630 " (wanted %2-%3%)")
00631 .arg(100 * MINBREAKS / nframes)
00632 .arg(100 * MAXBREAKS / nframes));
00633 }
00634 else if (!final)
00635 {
00636 VERBOSE(VB_COMMFLAG, QString("TemplateMatcher has %1% breaks"
00637 " (real-time flagging)")
00638 .arg(100 * brklen / nframes));
00639 }
00640 else if (good)
00641 {
00642 VERBOSE(VB_COMMFLAG, QString("TemplateMatcher has %1% breaks")
00643 .arg(100 * brklen / nframes));
00644 }
00645 else
00646 {
00647 VERBOSE(VB_COMMFLAG, QString("TemplateMatcher has %1% breaks"
00648 " (wanted %2-%3%)")
00649 .arg(100 * brklen / nframes)
00650 .arg(100 * MINBREAKS / nframes)
00651 .arg(100 * MAXBREAKS / nframes));
00652 }
00653 }
00654
00655 if (!final)
00656 return 0;
00657
00658 return brklen < MINBREAKS ? 1 : brklen <= MAXBREAKS ? 0 : -1;
00659 }
00660
00661 int
00662 TemplateMatcher::adjustForBlanks(const BlankFrameDetector *blankFrameDetector,
00663 long long nframes)
00664 {
00665 const bool skipCommBlanks = blankFrameDetector->getSkipCommBlanks();
00666 const FrameAnalyzer::FrameMap *blankMap = blankFrameDetector->getBlanks();
00667
00668
00669
00670
00671
00672
00673
00674
00675
00676
00677
00678
00679
00680
00681
00682
00683
00684
00685
00686
00687
00688
00689
00690
00691
00692
00693
00694
00695
00696
00697
00698
00699
00700
00701
00702
00703
00704
00705
00706
00707
00708
00709
00710
00711
00712
00713
00714
00715
00716
00717
00718
00719
00720
00721
00722
00723
00724
00725
00726
00727
00728
00729
00730
00731
00732
00733
00734
00735 const int BLANK_NEARBY = (int)roundf(0.5 * fps);
00736 const int TEMPLATE_DISAPPEARS_EARLY = (int)roundf(25 * fps);
00737 const int TEMPLATE_DISAPPEARS_LATE = (int)roundf(0 * fps);
00738 const int TEMPLATE_REAPPEARS_LATE = (int)roundf(35 * fps);
00739 const int TEMPLATE_REAPPEARS_EARLY = (int)roundf(1.5 * fps);
00740
00741 VERBOSE(VB_COMMFLAG, QString("TemplateMatcher adjusting for blanks"));
00742
00743 FrameAnalyzer::FrameMap::Iterator ii = breakMap.begin();
00744 long long prevbrke = 0;
00745 while (ii != breakMap.end())
00746 {
00747 FrameAnalyzer::FrameMap::Iterator iinext = ii;
00748 ++iinext;
00749
00750
00751
00752
00753
00754
00755 const long long brkb = ii.key();
00756 const long long brke = brkb + ii.data();
00757 FrameAnalyzer::FrameMap::const_iterator jj = blankMap->constEnd();
00758 if (brkb > 0)
00759 {
00760 jj = frameMapSearchForwards(blankMap,
00761 max(prevbrke,
00762 brkb - max(BLANK_NEARBY, TEMPLATE_DISAPPEARS_LATE)),
00763 min(brke,
00764 brkb + max(BLANK_NEARBY, TEMPLATE_DISAPPEARS_EARLY)));
00765 }
00766 long long newbrkb = brkb;
00767 if (jj != blankMap->constEnd())
00768 {
00769 newbrkb = jj.key();
00770 if (!skipCommBlanks)
00771 newbrkb += jj.data();
00772 }
00773
00774
00775
00776
00777
00778 FrameAnalyzer::FrameMap::const_iterator kk = frameMapSearchBackwards(
00779 blankMap,
00780 max(newbrkb,
00781 brke - max(BLANK_NEARBY, TEMPLATE_REAPPEARS_LATE)),
00782 min(iinext == breakMap.end() ? nframes : iinext.key(),
00783 brke + max(BLANK_NEARBY, TEMPLATE_REAPPEARS_EARLY)));
00784 long long newbrke = brke;
00785 if (kk != blankMap->constEnd())
00786 {
00787 newbrke = kk.key();
00788 if (skipCommBlanks)
00789 newbrke += kk.data();
00790 }
00791
00792
00793
00794
00795 long long newbrklen = newbrke - newbrkb;
00796 if (newbrkb != brkb)
00797 {
00798 breakMap.remove(ii);
00799 if (newbrkb < nframes && newbrklen)
00800 breakMap.insert(newbrkb, newbrklen);
00801 }
00802 else if (newbrke != brke)
00803 {
00804 if (newbrklen)
00805 breakMap.replace(newbrkb, newbrklen);
00806 else
00807 breakMap.remove(ii);
00808 }
00809
00810 prevbrke = newbrke;
00811 ii = iinext;
00812 }
00813
00814
00815
00816
00817 frameAnalyzerReportMap(&breakMap, fps, "TM Break");
00818 return 0;
00819 }
00820
00821 int
00822 TemplateMatcher::computeBreaks(FrameAnalyzer::FrameMap *breaks)
00823 {
00824 breaks->clear();
00825 for (FrameAnalyzer::FrameMap::Iterator bb = breakMap.begin();
00826 bb != breakMap.end();
00827 ++bb)
00828 {
00829 breaks->insert(bb.key(), bb.data());
00830 }
00831 return 0;
00832 }
00833
00834