|
MythTV
0.26-pre
|
00001 // POSIX headers 00002 #include <sys/time.h> /* gettimeofday */ 00003 00004 // ANSI C headers 00005 #include <cmath> 00006 #include <cstdlib> 00007 00008 // Qt headers 00009 #include <QFile> 00010 #include <QFileInfo> 00011 #include <QTextStream> 00012 00013 // MythTV headers 00014 #include "mythplayer.h" 00015 #include "mythcorecontext.h" /* gContext */ 00016 #include "frame.h" /* VideoFrame */ 00017 #include "mythmiscutil.h" 00018 #include "mythsystem.h" 00019 #include "exitcodes.h" 00020 00021 // Commercial Flagging headers 00022 #include "CommDetector2.h" 00023 #include "pgm.h" 00024 #include "PGMConverter.h" 00025 #include "BorderDetector.h" 00026 #include "EdgeDetector.h" 00027 #include "TemplateFinder.h" 00028 00029 using namespace commDetector2; 00030 00031 namespace { 00032 00033 int writeJPG(QString prefix, const AVPicture *img, int imgheight) 00034 { 00035 const int imgwidth = img->linesize[0]; 00036 QFileInfo jpgfi(prefix + ".jpg"); 00037 if (!jpgfi.exists()) 00038 { 00039 QFile pgmfile(prefix + ".pgm"); 00040 if (!pgmfile.exists()) 00041 { 00042 QByteArray pfname = pgmfile.fileName().toLocal8Bit(); 00043 if (pgm_write(img->data[0], imgwidth, imgheight, 00044 pfname.constData())) 00045 { 00046 return -1; 00047 } 00048 } 00049 00050 QString cmd = QString("convert -quality 50 -resize 192x144 %1 %2") 00051 .arg(pgmfile.fileName()).arg(jpgfi.filePath()); 00052 if (myth_system(cmd) != GENERIC_EXIT_OK) 00053 return -1; 00054 00055 if (!pgmfile.remove()) 00056 { 00057 LOG(VB_COMMFLAG, LOG_ERR, 00058 QString("TemplateFinder.writeJPG error removing %1 (%2)") 00059 .arg(pgmfile.fileName()).arg(strerror(errno))); 00060 return -1; 00061 } 00062 } 00063 return 0; 00064 } 00065 00066 int 00067 pgm_scorepixels(unsigned int *scores, int width, int row, int col, 00068 const AVPicture *src, int srcheight) 00069 { 00070 /* Every time a pixel is an edge, give it a point. */ 00071 const int srcwidth = src->linesize[0]; 00072 int rr, cc; 00073 00074 for (rr = 0; rr < srcheight; rr++) 00075 { 00076 for (cc = 0; cc < srcwidth; cc++) 00077 { 00078 if (src->data[0][rr * srcwidth + cc]) 00079 scores[(row + rr) * width + col + cc]++; 00080 } 00081 } 00082 00083 return 0; 00084 } 00085 00086 int 00087 sort_ascending(const void *aa, const void *bb) 00088 { 00089 return *(unsigned int*)aa - *(unsigned int*)bb; 00090 } 00091 00092 float 00093 bounding_score(const AVPicture *img, int row, int col, int width, int height) 00094 { 00095 /* Return a value between [0..1] */ 00096 const int imgwidth = img->linesize[0]; 00097 unsigned int score; 00098 int rr, cc, rr2, cc2; 00099 00100 score = 0; 00101 rr2 = row + height; 00102 cc2 = col + width; 00103 for (rr = row; rr < rr2; rr++) 00104 { 00105 for (cc = col; cc < cc2; cc++) 00106 { 00107 if (img->data[0][rr * imgwidth + cc]) 00108 score++; 00109 } 00110 } 00111 return (float)score / (width * height); 00112 } 00113 00114 bool 00115 rowisempty(const AVPicture *img, int row, int col, int width) 00116 { 00117 const int imgwidth = img->linesize[0]; 00118 for (int cc = col; cc < col + width; cc++) 00119 if (img->data[0][row * imgwidth + cc]) 00120 return false; 00121 return true; 00122 } 00123 00124 bool 00125 colisempty(const AVPicture *img, int col, int row, int height) 00126 { 00127 const int imgwidth = img->linesize[0]; 00128 for (int rr = row; rr < row + height; rr++) 00129 if (img->data[0][rr * imgwidth + col]) 00130 return false; 00131 return true; 00132 } 00133 00134 int 00135 bounding_box(const AVPicture *img, int imgheight, 00136 int minrow, int mincol, int maxrow1, int maxcol1, 00137 int *prow, int *pcol, int *pwidth, int *pheight) 00138 { 00139 const int imgwidth = img->linesize[0]; 00140 /* 00141 * TUNABLE: 00142 * 00143 * Maximum logo size, expressed as a percentage of the content area 00144 * (adjusting for letterboxing and pillarboxing). 00145 */ 00146 static const int MAXWIDTHPCT = 20; 00147 static const int MAXHEIGHTPCT = 20; 00148 00149 /* 00150 * TUNABLE: 00151 * 00152 * Safety margin to avoid cutting too much of the logo. 00153 * Higher values cut more, but avoid noise as part of the template.. 00154 * Lower values cut less, but can include noise as part of the template. 00155 */ 00156 const int VERTSLOP = max(4, imgheight * 1 / 15); 00157 const int HORIZSLOP = max(4, imgwidth * 1 / 20); 00158 00159 int maxwidth, maxheight; 00160 int width, height, row, col; 00161 int newrow, newcol, newright, newbottom; 00162 00163 maxwidth = (maxcol1 - mincol) * MAXWIDTHPCT / 100; 00164 maxheight = (maxrow1 - minrow) * MAXHEIGHTPCT / 100; 00165 00166 row = minrow; 00167 col = mincol; 00168 width = maxcol1 - mincol; 00169 height = maxrow1 - minrow; 00170 00171 for (;;) 00172 { 00173 float score, newscore; 00174 int ii; 00175 bool improved = false; 00176 00177 LOG(VB_COMMFLAG, LOG_INFO, QString("bounding_box %1x%2@(%3,%4)") 00178 .arg(width).arg(height).arg(col).arg(row)); 00179 00180 /* Chop top. */ 00181 score = bounding_score(img, row, col, width, height); 00182 newrow = row; 00183 for (ii = 1; ii < height; ii++) 00184 { 00185 if ((newscore = bounding_score(img, row + ii, col, 00186 width, height - ii)) < score) 00187 break; 00188 score = newscore; 00189 newrow = row + ii; 00190 improved = true; 00191 } 00192 00193 /* Chop left. */ 00194 score = bounding_score(img, row, col, width, height); 00195 newcol = col; 00196 for (ii = 1; ii < width; ii++) 00197 { 00198 if ((newscore = bounding_score(img, row, col + ii, 00199 width - ii, height)) < score) 00200 break; 00201 score = newscore; 00202 newcol = col + ii; 00203 improved = true; 00204 } 00205 00206 /* Chop bottom. */ 00207 score = bounding_score(img, row, col, width, height); 00208 newbottom = row + height; 00209 for (ii = 1; ii < height; ii++) 00210 { 00211 if ((newscore = bounding_score(img, row, col, 00212 width, height - ii)) < score) 00213 break; 00214 score = newscore; 00215 newbottom = row + height - ii; 00216 improved = true; 00217 } 00218 00219 /* Chop right. */ 00220 score = bounding_score(img, row, col, width, height); 00221 newright = col + width; 00222 for (ii = 1; ii < width; ii++) 00223 { 00224 if ((newscore = bounding_score(img, row, col, 00225 width - ii, height)) < score) 00226 break; 00227 score = newscore; 00228 newright = col + width - ii; 00229 improved = true; 00230 } 00231 00232 if (!improved) 00233 break; 00234 00235 row = newrow; 00236 col = newcol; 00237 width = newright - newcol; 00238 height = newbottom - newrow; 00239 00240 /* 00241 * Noise edge pixels in the frequency template can sometimes stretch 00242 * the template area to be larger than it should be. 00243 * 00244 * However, noise needs to be distinguished from a uniform distribution 00245 * of noise pixels (e.g., no real statically-located template). So if 00246 * the template area is too "large", then some quadrant must have a 00247 * clear majority of the edge pixels; otherwise we declare failure (no 00248 * template found). 00249 * 00250 * Intuitively, we should simply repeat until a single bounding box is 00251 * converged upon. However, this requires a more sophisticated 00252 * bounding_score function that I don't feel like figuring out. 00253 * Indefinitely repeating with the present bounding_score function will 00254 * tend to chop off too much. Instead, simply do some sanity checks on 00255 * the candidate template's size, and prune the template area and 00256 * repeat if it is too "large". 00257 */ 00258 00259 if (width > maxwidth) 00260 { 00261 /* Too wide; test left and right portions. */ 00262 int chop = width / 3; 00263 int chopwidth = width - chop; 00264 float left, right, minscore, maxscore; 00265 00266 left = bounding_score(img, row, col, chopwidth, height); 00267 right = bounding_score(img, row, col + chop, chopwidth, height); 00268 LOG(VB_COMMFLAG, LOG_INFO, 00269 QString("bounding_box too wide (%1 > %2); left=%3, right=%4") 00270 .arg(width).arg(maxwidth) 00271 .arg(left, 0, 'f', 3).arg(right, 0, 'f', 3)); 00272 minscore = min(left, right); 00273 maxscore = max(left, right); 00274 if (maxscore < 3 * minscore / 2) 00275 { 00276 /* 00277 * Edge pixel distribution too uniform; give up. 00278 * 00279 * XXX: also fails for horizontally-centered templates ... 00280 */ 00281 LOG(VB_COMMFLAG, LOG_ERR, "bounding_box giving up (edge " 00282 "pixels distributed too uniformly)"); 00283 return -1; 00284 } 00285 00286 if (left < right) 00287 col += chop; 00288 width -= chop; 00289 continue; 00290 } 00291 00292 if (height > maxheight) 00293 { 00294 /* Too tall; test upper and lower portions. */ 00295 int chop = height / 3; 00296 int chopheight = height - chop; 00297 float upper, lower, minscore, maxscore; 00298 00299 upper = bounding_score(img, row, col, width, chopheight); 00300 lower = bounding_score(img, row + chop, col, width, chopheight); 00301 LOG(VB_COMMFLAG, LOG_INFO, 00302 QString("bounding_box too tall (%1 > %2); upper=%3, lower=%4") 00303 .arg(height).arg(maxheight) 00304 .arg(upper, 0, 'f', 3).arg(lower, 0, 'f', 3)); 00305 minscore = min(upper, lower); 00306 maxscore = max(upper, lower); 00307 if (maxscore < 3 * minscore / 2) 00308 { 00309 /* 00310 * Edge pixel distribution too uniform; give up. 00311 * 00312 * XXX: also fails for vertically-centered templates ... 00313 */ 00314 LOG(VB_COMMFLAG, LOG_ERR, "bounding_box giving up (edge " 00315 "pixel distribution too uniform)"); 00316 return -1; 00317 } 00318 00319 if (upper < lower) 00320 row += chop; 00321 height -= chop; 00322 continue; 00323 } 00324 00325 break; 00326 } 00327 00328 /* 00329 * The above "chop" algorithm often cuts off the outside edges of the 00330 * logos because the outside edges don't contribute enough to the score. So 00331 * compensate by now expanding the bounding box (up to a *SLOP pixels in 00332 * each direction) to include all edge pixels. 00333 */ 00334 00335 LOG(VB_COMMFLAG, LOG_INFO, 00336 QString("bounding_box %1x%2@(%3,%4); horizslop=%5,vertslop=%6") 00337 .arg(width).arg(height).arg(col).arg(row) 00338 .arg(HORIZSLOP).arg(VERTSLOP)); 00339 00340 /* Expand upwards. */ 00341 newrow = row - 1; 00342 for (;;) 00343 { 00344 if (newrow <= minrow) 00345 { 00346 newrow = minrow; 00347 break; 00348 } 00349 if (row - newrow >= VERTSLOP) 00350 { 00351 newrow = row - VERTSLOP; 00352 break; 00353 } 00354 if (rowisempty(img, newrow, col, width)) 00355 { 00356 newrow++; 00357 break; 00358 } 00359 newrow--; 00360 } 00361 newrow = max(minrow, newrow - 1); /* Empty row on top. */ 00362 00363 /* Expand leftwards. */ 00364 newcol = col - 1; 00365 for (;;) 00366 { 00367 if (newcol <= mincol) 00368 { 00369 newcol = mincol; 00370 break; 00371 } 00372 if (col - newcol >= HORIZSLOP) 00373 { 00374 newcol = col - HORIZSLOP; 00375 break; 00376 } 00377 if (colisempty(img, newcol, row, height)) 00378 { 00379 newcol++; 00380 break; 00381 } 00382 newcol--; 00383 } 00384 newcol = max(mincol, newcol - 1); /* Empty column to left. */ 00385 00386 /* Expand rightwards. */ 00387 newright = col + width; 00388 for (;;) 00389 { 00390 if (newright >= maxcol1) 00391 { 00392 newright = maxcol1; 00393 break; 00394 } 00395 if (newright - (col + width) >= HORIZSLOP) 00396 { 00397 newright = col + width + HORIZSLOP; 00398 break; 00399 } 00400 if (colisempty(img, newright, row, height)) 00401 break; 00402 newright++; 00403 } 00404 newright = min(maxcol1, newright + 1); /* Empty column to right. */ 00405 00406 /* Expand downwards. */ 00407 newbottom = row + height; 00408 for (;;) 00409 { 00410 if (newbottom >= maxrow1) 00411 { 00412 newbottom = maxrow1; 00413 break; 00414 } 00415 if (newbottom - (row + height) >= VERTSLOP) 00416 { 00417 newbottom = row + height + VERTSLOP; 00418 break; 00419 } 00420 if (rowisempty(img, newbottom, col, width)) 00421 break; 00422 newbottom++; 00423 } 00424 newbottom = min(maxrow1, newbottom + 1); /* Empty row on bottom. */ 00425 00426 row = newrow; 00427 col = newcol; 00428 width = newright - newcol; 00429 height = newbottom - newrow; 00430 00431 LOG(VB_COMMFLAG, LOG_INFO, QString("bounding_box %1x%2@(%3,%4)") 00432 .arg(width).arg(height).arg(col).arg(row)); 00433 00434 *prow = row; 00435 *pcol = col; 00436 *pwidth = width; 00437 *pheight = height; 00438 return 0; 00439 } 00440 00441 int 00442 template_alloc(const unsigned int *scores, int width, int height, 00443 int minrow, int mincol, int maxrow1, int maxcol1, AVPicture *tmpl, 00444 int *ptmplrow, int *ptmplcol, int *ptmplwidth, int *ptmplheight, 00445 bool debug_edgecounts, QString debugdir) 00446 { 00447 /* 00448 * TUNABLE: 00449 * 00450 * Higher values select for "stronger" pixels to be in the template, but 00451 * weak pixels might be missed. 00452 * 00453 * Lower values allow more pixels to be included as part of the template, 00454 * but strong non-template pixels might be included. 00455 */ 00456 static const float MINSCOREPCTILE = 0.998; 00457 00458 const int nn = width * height; 00459 int ii, first, last; 00460 unsigned int *sortedscores, threshscore; 00461 AVPicture thresh; 00462 00463 if (avpicture_alloc(&thresh, PIX_FMT_GRAY8, width, height)) 00464 { 00465 LOG(VB_COMMFLAG, LOG_ERR, 00466 QString("template_alloc avpicture_alloc thresh (%1x%2) failed") 00467 .arg(width).arg(height)); 00468 return -1; 00469 } 00470 00471 sortedscores = new unsigned int[nn]; 00472 memcpy(sortedscores, scores, nn * sizeof(*sortedscores)); 00473 qsort(sortedscores, nn, sizeof(*sortedscores), sort_ascending); 00474 00475 if (sortedscores[0] == sortedscores[nn - 1]) 00476 { 00477 /* All pixels in the template area look the same; no template. */ 00478 LOG(VB_COMMFLAG, LOG_ERR, 00479 QString("template_alloc: %1x%2 pixels all identical!") 00480 .arg(width).arg(height)); 00481 goto free_thresh; 00482 } 00483 00484 /* Threshold the edge frequences. */ 00485 00486 ii = (int)roundf(nn * MINSCOREPCTILE); 00487 threshscore = sortedscores[ii]; 00488 for (first = ii; first > 0 && sortedscores[first] == threshscore; first--) 00489 ; 00490 if (sortedscores[first] != threshscore) 00491 first++; 00492 for (last = ii; last < nn - 1 && sortedscores[last] == threshscore; last++) 00493 ; 00494 if (sortedscores[last] != threshscore) 00495 last--; 00496 00497 LOG(VB_COMMFLAG, LOG_INFO, QString("template_alloc wanted %1, got %2-%3") 00498 .arg(MINSCOREPCTILE, 0, 'f', 6) 00499 .arg((float)first / nn, 0, 'f', 6) 00500 .arg((float)last / nn, 0, 'f', 6)); 00501 00502 for (ii = 0; ii < nn; ii++) 00503 thresh.data[0][ii] = scores[ii] >= threshscore ? UCHAR_MAX : 0; 00504 00505 if (debug_edgecounts) 00506 { 00507 /* Scores, rescaled to [0..UCHAR_MAX]. */ 00508 AVPicture scored; 00509 if (avpicture_alloc(&scored, PIX_FMT_GRAY8, width, height)) 00510 { 00511 LOG(VB_COMMFLAG, LOG_ERR, 00512 QString("template_alloc avpicture_alloc scored (%1x%2) failed") 00513 .arg(width).arg(height)); 00514 goto free_thresh; 00515 } 00516 unsigned int maxscore = sortedscores[nn - 1]; 00517 for (ii = 0; ii < nn; ii++) 00518 scored.data[0][ii] = scores[ii] * UCHAR_MAX / maxscore; 00519 int error = writeJPG(debugdir + "/TemplateFinder-scores", &scored, 00520 height); 00521 avpicture_free(&scored); 00522 if (error) 00523 goto free_thresh; 00524 00525 /* Thresholded scores. */ 00526 if (writeJPG(debugdir + "/TemplateFinder-edgecounts", &thresh, height)) 00527 goto free_thresh; 00528 } 00529 00530 /* Crop to a minimal bounding box. */ 00531 00532 if (bounding_box(&thresh, height, minrow, mincol, maxrow1, maxcol1, 00533 ptmplrow, ptmplcol, ptmplwidth, ptmplheight)) 00534 goto free_thresh; 00535 00536 if ((uint)(*ptmplwidth * *ptmplheight) > USHRT_MAX) 00537 { 00538 /* Max value of data type of TemplateMatcher::edgematch */ 00539 LOG(VB_COMMFLAG, LOG_ERR, 00540 QString("template_alloc bounding_box too big (%1x%2)") 00541 .arg(*ptmplwidth).arg(*ptmplheight)); 00542 goto free_thresh; 00543 } 00544 00545 if (avpicture_alloc(tmpl, PIX_FMT_GRAY8, *ptmplwidth, *ptmplheight)) 00546 { 00547 LOG(VB_COMMFLAG, LOG_ERR, 00548 QString("template_alloc avpicture_alloc tmpl (%1x%2) failed") 00549 .arg(*ptmplwidth).arg(*ptmplheight)); 00550 goto free_thresh; 00551 } 00552 00553 if (pgm_crop(tmpl, &thresh, height, *ptmplrow, *ptmplcol, 00554 *ptmplwidth, *ptmplheight)) 00555 goto free_thresh; 00556 00557 delete []sortedscores; 00558 avpicture_free(&thresh); 00559 return 0; 00560 00561 free_thresh: 00562 delete []sortedscores; 00563 avpicture_free(&thresh); 00564 return -1; 00565 } 00566 00567 int 00568 analyzeFrameDebug(long long frameno, const AVPicture *pgm, int pgmheight, 00569 const AVPicture *cropped, const AVPicture *edges, int cropheight, 00570 int croprow, int cropcol, bool debug_frames, QString debugdir) 00571 { 00572 static const int delta = 24; 00573 static int lastrow, lastcol, lastwidth, lastheight; 00574 const int cropwidth = cropped->linesize[0]; 00575 int rowsame, colsame, widthsame, heightsame; 00576 00577 rowsame = abs(lastrow - croprow) <= delta ? 1 : 0; 00578 colsame = abs(lastcol - cropcol) <= delta ? 1 : 0; 00579 widthsame = abs(lastwidth - cropwidth) <= delta ? 1 : 0; 00580 heightsame = abs(lastheight - cropheight) <= delta ? 1 : 0; 00581 00582 if (frameno > 0 && rowsame + colsame + widthsame + heightsame >= 3) 00583 return 0; 00584 00585 LOG(VB_COMMFLAG, LOG_INFO, 00586 QString("TemplateFinder Frame %1: %2x%3@(%4,%5)") 00587 .arg(frameno, 5) 00588 .arg(cropwidth).arg(cropheight) 00589 .arg(cropcol).arg(croprow)); 00590 00591 lastrow = croprow; 00592 lastcol = cropcol; 00593 lastwidth = cropwidth; 00594 lastheight = cropheight; 00595 00596 if (debug_frames) 00597 { 00598 QString base = QString("%1/TemplateFinder-%2") 00599 .arg(debugdir).arg(frameno, 5, 10, QChar('0')); 00600 00601 /* PGM greyscale image of frame. */ 00602 if (writeJPG(base, pgm, pgmheight)) 00603 goto error; 00604 00605 /* Cropped template area of frame. */ 00606 if (writeJPG(base + "-cropped", cropped, cropheight)) 00607 goto error; 00608 00609 /* Edges of cropped template area of frame. */ 00610 if (writeJPG(base + "-edges", edges, cropheight)) 00611 goto error; 00612 } 00613 00614 return 0; 00615 00616 error: 00617 return -1; 00618 } 00619 00620 bool 00621 readTemplate(QString datafile, int *prow, int *pcol, int *pwidth, int *pheight, 00622 QString tmplfile, AVPicture *tmpl, bool *pvalid) 00623 { 00624 QFile dfile(datafile); 00625 QFileInfo dfileinfo(dfile); 00626 00627 if (!dfile.open(QIODevice::ReadOnly)) 00628 return false; 00629 00630 if (!dfileinfo.size()) 00631 { 00632 /* Dummy file: no template. */ 00633 *pvalid = false; 00634 return true; 00635 } 00636 00637 QTextStream stream(&dfile); 00638 stream >> *prow >> *pcol >> *pwidth >> *pheight; 00639 dfile.close(); 00640 00641 if (avpicture_alloc(tmpl, PIX_FMT_GRAY8, *pwidth, *pheight)) 00642 { 00643 LOG(VB_COMMFLAG, LOG_ERR, 00644 QString("readTemplate avpicture_alloc %1 (%2x%3) failed") 00645 .arg(tmplfile).arg(*pwidth).arg(*pheight)); 00646 return false; 00647 } 00648 00649 QByteArray tmfile = tmplfile.toAscii(); 00650 if (pgm_read(tmpl->data[0], *pwidth, *pheight, tmfile.constData())) 00651 { 00652 avpicture_free(tmpl); 00653 return false; 00654 } 00655 00656 *pvalid = true; 00657 return true; 00658 } 00659 00660 void 00661 writeDummyTemplate(QString datafile) 00662 { 00663 /* Leave a 0-byte file. */ 00664 QFile dfile(datafile); 00665 00666 if (!dfile.open(QIODevice::WriteOnly | QIODevice::Truncate) && 00667 dfile.exists()) 00668 (void)dfile.remove(); 00669 } 00670 00671 bool 00672 writeTemplate(QString tmplfile, const AVPicture *tmpl, QString datafile, 00673 int row, int col, int width, int height) 00674 { 00675 QFile tfile(tmplfile); 00676 00677 QByteArray tmfile = tmplfile.toAscii(); 00678 if (pgm_write(tmpl->data[0], width, height, tmfile.constData())) 00679 return false; 00680 00681 QFile dfile(datafile); 00682 if (!dfile.open(QIODevice::WriteOnly)) 00683 return false; 00684 00685 QTextStream stream(&dfile); 00686 stream << row << " " << col << "\n" << width << " " << height << "\n"; 00687 dfile.close(); 00688 return true; 00689 } 00690 00691 }; /* namespace */ 00692 00693 TemplateFinder::TemplateFinder(PGMConverter *pgmc, BorderDetector *bd, 00694 EdgeDetector *ed, MythPlayer *player, int proglen, 00695 QString debugdir) 00696 : FrameAnalyzer() 00697 , pgmConverter(pgmc) 00698 , borderDetector(bd) 00699 , edgeDetector(ed) 00700 , nextFrame(0) 00701 , width(-1) 00702 , height(-1) 00703 , scores(NULL) 00704 , mincontentrow(INT_MAX) 00705 , mincontentcol(INT_MAX) 00706 , maxcontentrow1(INT_MIN) 00707 , maxcontentcol1(INT_MIN) 00708 , tmplrow(-1) 00709 , tmplcol(-1) 00710 , tmplwidth(-1) 00711 , tmplheight(-1) 00712 , cwidth(-1) 00713 , cheight(-1) 00714 , debugLevel(0) 00715 , debugdir(debugdir) 00716 , debugdata(debugdir + "/TemplateFinder.txt") 00717 , debugtmpl(debugdir + "/TemplateFinder.pgm") 00718 , debug_template(false) 00719 , debug_edgecounts(false) 00720 , debug_frames(false) 00721 , tmpl_valid(false) 00722 , tmpl_done(false) 00723 { 00724 /* 00725 * TUNABLE: 00726 * 00727 * The number of frames desired for sampling to build the template. 00728 * 00729 * Higher values should yield a more accurate template, but requires more 00730 * time. 00731 */ 00732 unsigned int samplesNeeded = 300; 00733 00734 /* 00735 * TUNABLE: 00736 * 00737 * The leading amount of time (in seconds) to sample frames for building up 00738 * the possible template, and the interval between frames for analysis. 00739 * This affects how soon flagging can start after a recording has begun 00740 * (a.k.a. "real-time flagging"). 00741 * 00742 * Sample half of the program length or 20 minutes, whichever is less. 00743 */ 00744 sampleTime = min(proglen / 2, 20 * 60); 00745 00746 const float fps = player->GetFrameRate(); 00747 00748 if (samplesNeeded > UINT_MAX) 00749 { 00750 /* Max value of "scores" data type */ 00751 samplesNeeded = UINT_MAX; 00752 } 00753 00754 frameInterval = (int)roundf(sampleTime * fps / samplesNeeded); 00755 endFrame = 0 + frameInterval * samplesNeeded - 1; 00756 00757 LOG(VB_COMMFLAG, LOG_INFO, 00758 QString("TemplateFinder: sampleTime=%1s, samplesNeeded=%2, endFrame=%3") 00759 .arg(sampleTime).arg(samplesNeeded).arg(endFrame)); 00760 00761 memset(&cropped, 0, sizeof(cropped)); 00762 memset(&tmpl, 0, sizeof(tmpl)); 00763 memset(&analyze_time, 0, sizeof(analyze_time)); 00764 00765 /* 00766 * debugLevel: 00767 * 0: no extra debugging 00768 * 1: cache computations into debugdir [O(1) files] 00769 * 2: extra verbosity [O(nframes)] 00770 * 3: dump frames into debugdir [O(nframes) files] 00771 */ 00772 debugLevel = gCoreContext->GetNumSetting("TemplateFinderDebugLevel", 0); 00773 00774 if (debugLevel >= 1) 00775 { 00776 createDebugDirectory(debugdir, 00777 QString("TemplateFinder debugLevel %1").arg(debugLevel)); 00778 00779 debug_template = true; 00780 debug_edgecounts = true; 00781 00782 if (debugLevel >= 3) 00783 debug_frames = true; 00784 } 00785 } 00786 00787 TemplateFinder::~TemplateFinder(void) 00788 { 00789 if (scores) 00790 delete []scores; 00791 avpicture_free(&tmpl); 00792 avpicture_free(&cropped); 00793 } 00794 00795 enum FrameAnalyzer::analyzeFrameResult 00796 TemplateFinder::MythPlayerInited(MythPlayer *player, long long nframes) 00797 { 00798 /* 00799 * Only detect edges in portions of the frame where we expect to find 00800 * a template. This serves two purposes: 00801 * 00802 * - Speed: reduce search space. 00803 * - Correctness (insofar as the assumption of template location is 00804 * correct): don't "pollute" the set of candidate template edges with 00805 * the "content" edges in the non-template portions of the frame. 00806 */ 00807 QString tmpldims, playerdims; 00808 00809 (void)nframes; /* gcc */ 00810 QSize buf_dim = player->GetVideoBufferSize(); 00811 width = buf_dim.width(); 00812 height = buf_dim.height(); 00813 playerdims = QString("%1x%2").arg(width).arg(height); 00814 00815 if (debug_template) 00816 { 00817 if ((tmpl_done = readTemplate(debugdata, &tmplrow, &tmplcol, 00818 &tmplwidth, &tmplheight, debugtmpl, &tmpl, 00819 &tmpl_valid))) 00820 { 00821 tmpldims = tmpl_valid ? QString("%1x%2@(%3,%4)") 00822 .arg(tmplwidth).arg(tmplheight).arg(tmplcol).arg(tmplrow) : 00823 "no template"; 00824 00825 LOG(VB_COMMFLAG, LOG_INFO, 00826 QString("TemplateFinder::MythPlayerInited read %1: %2") 00827 .arg(debugtmpl) 00828 .arg(tmpldims)); 00829 } 00830 } 00831 00832 if (pgmConverter->MythPlayerInited(player)) 00833 goto free_tmpl; 00834 00835 if (borderDetector->MythPlayerInited(player)) 00836 goto free_tmpl; 00837 00838 if (tmpl_done) 00839 { 00840 if (tmpl_valid) 00841 { 00842 LOG(VB_COMMFLAG, LOG_INFO, 00843 QString("TemplateFinder::MythPlayerInited %1 of %2 (%3)") 00844 .arg(tmpldims).arg(playerdims).arg(debugtmpl)); 00845 } 00846 return ANALYZE_FINISHED; 00847 } 00848 00849 LOG(VB_COMMFLAG, LOG_INFO, 00850 QString("TemplateFinder::MythPlayerInited framesize %1") 00851 .arg(playerdims)); 00852 scores = new unsigned int[width * height]; 00853 00854 return ANALYZE_OK; 00855 00856 free_tmpl: 00857 avpicture_free(&tmpl); 00858 return ANALYZE_FATAL; 00859 } 00860 00861 int 00862 TemplateFinder::resetBuffers(int newwidth, int newheight) 00863 { 00864 if (cwidth == newwidth && cheight == newheight) 00865 return 0; 00866 00867 avpicture_free(&cropped); 00868 00869 if (avpicture_alloc(&cropped, PIX_FMT_GRAY8, newwidth, newheight)) 00870 { 00871 LOG(VB_COMMFLAG, LOG_ERR, 00872 QString("TemplateFinder::resetBuffers " 00873 "avpicture_alloc cropped (%1x%2) failed") 00874 .arg(newwidth).arg(newheight)); 00875 return -1; 00876 } 00877 00878 cwidth = newwidth; 00879 cheight = newheight; 00880 return 0; 00881 } 00882 00883 enum FrameAnalyzer::analyzeFrameResult 00884 TemplateFinder::analyzeFrame(const VideoFrame *frame, long long frameno, 00885 long long *pNextFrame) 00886 { 00887 /* 00888 * TUNABLE: 00889 * 00890 * When looking for edges in frames, select some percentile of 00891 * squared-gradient magnitudes (intensities) as candidate edges. (This 00892 * number conventionally should not go any lower than the 95th percentile; 00893 * see edge_mark.) 00894 * 00895 * Higher values result in fewer edges; faint logos might not be picked up. 00896 * Lower values result in more edges; non-logo edges might be picked up. 00897 * 00898 * The TemplateFinder accumulates all its state in the "scores" array to 00899 * be processed later by TemplateFinder::finished. 00900 */ 00901 const int FRAMESGMPCTILE = 90; 00902 00903 /* 00904 * TUNABLE: 00905 * 00906 * Exclude some portion of the center of the frame from edge analysis. 00907 * Elminate false edge-detection logo positives from talking-host types of 00908 * shows where the high-contrast host and clothes (e.g., tie against white 00909 * shirt against dark jacket) dominates the edges. 00910 * 00911 * This has a nice side-effect of reducing the area to be examined (speed 00912 * optimization). 00913 */ 00914 static const float EXCLUDEWIDTH = 0.5; 00915 static const float EXCLUDEHEIGHT = 0.5; 00916 00917 const AVPicture *pgm, *edges; 00918 int pgmwidth, pgmheight; 00919 int croprow, cropcol, cropwidth, cropheight; 00920 int excluderow, excludecol, excludewidth, excludeheight; 00921 struct timeval start, end, elapsed; 00922 00923 if (frameno < nextFrame) 00924 { 00925 *pNextFrame = nextFrame; 00926 return ANALYZE_OK; 00927 } 00928 00929 nextFrame = frameno + frameInterval; 00930 *pNextFrame = min(endFrame, nextFrame); 00931 00932 if (!(pgm = pgmConverter->getImage(frame, frameno, &pgmwidth, &pgmheight))) 00933 goto error; 00934 00935 if (!borderDetector->getDimensions(pgm, pgmheight, frameno, 00936 &croprow, &cropcol, &cropwidth, &cropheight)) 00937 { 00938 /* Not a blank frame. */ 00939 00940 (void)gettimeofday(&start, NULL); 00941 00942 if (croprow < mincontentrow) 00943 mincontentrow = croprow; 00944 if (cropcol < mincontentcol) 00945 mincontentcol = cropcol; 00946 if (cropcol + cropwidth > maxcontentcol1) 00947 maxcontentcol1 = cropcol + cropwidth; 00948 if (croprow + cropheight > maxcontentrow1) 00949 maxcontentrow1 = croprow + cropheight; 00950 00951 if (resetBuffers(cropwidth, cropheight)) 00952 goto error; 00953 00954 if (pgm_crop(&cropped, pgm, pgmheight, croprow, cropcol, 00955 cropwidth, cropheight)) 00956 goto error; 00957 00958 /* 00959 * Translate the excluded area of the screen into "cropped" 00960 * coordinates. 00961 */ 00962 excludewidth = (int)(pgmwidth * EXCLUDEWIDTH); 00963 excludeheight = (int)(pgmheight * EXCLUDEHEIGHT); 00964 excluderow = (pgmheight - excludeheight) / 2 - croprow; 00965 excludecol = (pgmwidth - excludewidth) / 2 - cropcol; 00966 (void)edgeDetector->setExcludeArea(excluderow, excludecol, 00967 excludewidth, excludeheight); 00968 00969 if (!(edges = edgeDetector->detectEdges(&cropped, cropheight, 00970 FRAMESGMPCTILE))) 00971 goto error; 00972 00973 if (pgm_scorepixels(scores, pgmwidth, croprow, cropcol, 00974 edges, cropheight)) 00975 goto error; 00976 00977 if (debugLevel >= 2) 00978 { 00979 if (analyzeFrameDebug(frameno, pgm, pgmheight, &cropped, edges, 00980 cropheight, croprow, cropcol, debug_frames, debugdir)) 00981 goto error; 00982 } 00983 00984 (void)gettimeofday(&end, NULL); 00985 timersub(&end, &start, &elapsed); 00986 timeradd(&analyze_time, &elapsed, &analyze_time); 00987 } 00988 00989 if (nextFrame > endFrame) 00990 return ANALYZE_FINISHED; 00991 00992 return ANALYZE_OK; 00993 00994 error: 00995 LOG(VB_COMMFLAG, LOG_ERR, 00996 QString("TemplateFinder::analyzeFrame error at frame %1") 00997 .arg(frameno)); 00998 00999 if (nextFrame > endFrame) 01000 return ANALYZE_FINISHED; 01001 01002 return ANALYZE_ERROR; 01003 } 01004 01005 int 01006 TemplateFinder::finished(long long nframes, bool final) 01007 { 01008 (void)nframes; /* gcc */ 01009 if (!tmpl_done) 01010 { 01011 if (template_alloc(scores, width, height, 01012 mincontentrow, mincontentcol, 01013 maxcontentrow1, maxcontentcol1, 01014 &tmpl, &tmplrow, &tmplcol, &tmplwidth, &tmplheight, 01015 debug_edgecounts, debugdir)) 01016 { 01017 if (final) 01018 writeDummyTemplate(debugdata); 01019 } 01020 else 01021 { 01022 if (final && debug_template) 01023 { 01024 if (!(tmpl_valid = writeTemplate(debugtmpl, &tmpl, debugdata, 01025 tmplrow, tmplcol, tmplwidth, tmplheight))) 01026 goto free_tmpl; 01027 01028 LOG(VB_COMMFLAG, LOG_INFO, 01029 QString("TemplateFinder::finished wrote %1" 01030 " and %2 [%3x%4@(%5,%6)]") 01031 .arg(debugtmpl).arg(debugdata) 01032 .arg(tmplwidth).arg(tmplheight) 01033 .arg(tmplcol).arg(tmplrow)); 01034 } 01035 } 01036 01037 if (final) 01038 tmpl_done = true; 01039 } 01040 01041 borderDetector->setLogoState(this); 01042 01043 return 0; 01044 01045 free_tmpl: 01046 avpicture_free(&tmpl); 01047 return -1; 01048 } 01049 01050 int 01051 TemplateFinder::reportTime(void) const 01052 { 01053 if (pgmConverter->reportTime()) 01054 return -1; 01055 01056 if (borderDetector->reportTime()) 01057 return -1; 01058 01059 LOG(VB_COMMFLAG, LOG_INFO, QString("TF Time: analyze=%1s") 01060 .arg(strftimeval(&analyze_time))); 01061 return 0; 01062 } 01063 01064 const struct AVPicture * 01065 TemplateFinder::getTemplate(int *prow, int *pcol, int *pwidth, int *pheight) 01066 const 01067 { 01068 if (tmpl_valid) 01069 { 01070 *prow = tmplrow; 01071 *pcol = tmplcol; 01072 *pwidth = tmplwidth; 01073 *pheight = tmplheight; 01074 return &tmpl; 01075 } 01076 return NULL; 01077 } 01078 01079 /* vim: set expandtab tabstop=4 shiftwidth=4: */
1.7.6.1