MythTV  master
ClassicLogoDetector.cpp
Go to the documentation of this file.
1 // ANSI C headers
2 #include <cstdlib>
3 #include <chrono> // for milliseconds
4 #include <thread> // for sleep_for
5 
6 // MythTV headers
7 #include "mythcorecontext.h"
8 #include "mythplayer.h"
9 #include "libavutil/frame.h"
10 
11 // Commercial Flagging headers
12 #include "ClassicLogoDetector.h"
13 #include "ClassicCommDetector.h"
14 
15 typedef struct edgemaskentry
16 {
17  int isedge;
18  int horiz;
19  int vert;
20  int rdiag;
21  int ldiag;
22 }
24 
25 
27  unsigned int w, unsigned int h,
28  unsigned int commdetectborder_in)
29  : LogoDetectorBase(w,h),
30  m_commDetector(commdetector),
31  m_commDetectBorder(commdetectborder_in),
32  m_edgeMask(new EdgeMaskEntry[m_width * m_height]),
33  // cppcheck doesn't understand deleteLater
34  // cppcheck-suppress noDestructor
35  m_logoMaxValues(new unsigned char[m_width * m_height]),
36  m_logoMinValues(new unsigned char[m_width * m_height]),
37  m_logoFrame(new unsigned char[m_width * m_height]),
38  m_logoMask(new unsigned char[m_width * m_height]),
39  m_logoCheckMask(new unsigned char[m_width * m_height])
40 {
42  gCoreContext->GetNumSetting("CommDetectLogoSamplesNeeded", 240);
44  gCoreContext->GetNumSetting("CommDetectLogoSampleSpacing", 2);
48  gCoreContext->GetSetting("CommDetectLogoGoodEdgeThreshold", "0.75")
49  .toDouble();
51  gCoreContext->GetSetting("CommDetectLogoBadEdgeThreshold", "0.85")
52  .toDouble();
53 }
54 
56 {
58 }
59 
61 {
62  m_commDetector = nullptr;
63  delete [] m_edgeMask;
64  delete [] m_logoFrame;
65  delete [] m_logoMask;
66  delete [] m_logoCheckMask;
67  delete [] m_logoMaxValues;
68  delete [] m_logoMinValues;
69 
70  LogoDetectorBase::deleteLater();
71 }
72 
74 {
75  int seekIncrement =
77  int maxLoops = m_commDetectLogoSamplesNeeded;
78  EdgeMaskEntry *edgeCounts;
79  unsigned int pos, i, x, y, dx, dy;
80  int edgeDiffs[] = {5, 7, 10, 15, 20, 30, 40, 50, 60, 0 };
81 
82 
83  LOG(VB_COMMFLAG, LOG_INFO, "Searching for Station Logo");
84 
85  m_logoInfoAvailable = false;
86 
87  edgeCounts = new EdgeMaskEntry[m_width * m_height];
88 
89  // Back in 2005, a threshold of 50 minimum pixelsInMask was established.
90  // I don't know whether that was tested against SD or HD resolutions.
91  // I do know that in 2010, mythcommflag was changed to use ffmpeg's
92  // lowres support, effectively dividing the video area by 16.
93  // But the 50 pixel minimum was not adjusted accordingly.
94  // I believe the minimum threshold should vary with the video's area.
95  // I am using 1280x720 (for 720p) video as the baseline.
96  // This should improve logo detection for SD video.
97  int minPixelsInMask = 50 * (m_width*m_height) / (1280*720 / 16);
98 
99  for (i = 0; edgeDiffs[i] != 0 && !m_logoInfoAvailable; i++)
100  {
101  int pixelsInMask = 0;
102 
103  LOG(VB_COMMFLAG, LOG_INFO,
104  QString("Trying with edgeDiff == %1, minPixelsInMask=%2")
105  .arg(edgeDiffs[i]).arg(minPixelsInMask));
106 
107  memset(edgeCounts, 0, sizeof(EdgeMaskEntry) * m_width * m_height);
108  memset(m_edgeMask, 0, sizeof(EdgeMaskEntry) * m_width * m_height);
109 
110  player->DiscardVideoFrame(player->GetRawVideoFrame(0));
111 
112  int loops = 0;
113  long long seekFrame = m_commDetector->m_preRoll + seekIncrement;
114  while (loops < maxLoops && player->GetEof() == kEofStateNone)
115  {
116  VideoFrame* vf = player->GetRawVideoFrame(seekFrame);
117 
118  if ((loops % 50) == 0)
120 
121  if (m_commDetector->m_bStop)
122  {
123  player->DiscardVideoFrame(vf);
124  delete[] edgeCounts;
125  return false;
126  }
127 
129  std::this_thread::sleep_for(std::chrono::milliseconds(10));
130 
131  DetectEdges(vf, edgeCounts, edgeDiffs[i]);
132 
133  seekFrame += seekIncrement;
134  loops++;
135 
136  player->DiscardVideoFrame(vf);
137  }
138 
139  LOG(VB_COMMFLAG, LOG_INFO, "Analyzing edge data");
140 
141 #ifdef SHOW_DEBUG_WIN
142  unsigned char *fakeFrame;
143  fakeFrame = new unsigned char[width * height * 3 / 2];
144  memset(fakeFrame, 0, width * height * 3 / 2);
145 #endif
146 
147  for (y = 0; y < m_height; y++)
148  {
149  if ((y > (m_height/4)) && (y < (m_height * 3 / 4)))
150  continue;
151 
152  for (x = 0; x < m_width; x++)
153  {
154  if ((x > (m_width/4)) && (x < (m_width * 3 / 4)))
155  continue;
156 
157  pos = y * m_width + x;
158 
159  if (edgeCounts[pos].isedge > (maxLoops * 0.66))
160  {
161  m_edgeMask[pos].isedge = 1;
162  pixelsInMask++;
163 #ifdef SHOW_DEBUG_WIN
164  fakeFrame[pos] = 0xff;
165 #endif
166 
167  }
168 
169  if (edgeCounts[pos].horiz > (maxLoops * 0.66))
170  m_edgeMask[pos].horiz = 1;
171 
172  if (edgeCounts[pos].vert > (maxLoops * 0.66))
173  m_edgeMask[pos].vert = 1;
174 
175  if (edgeCounts[pos].ldiag > (maxLoops * 0.66))
176  m_edgeMask[pos].ldiag = 1;
177  if (edgeCounts[pos].rdiag > (maxLoops * 0.66))
178  m_edgeMask[pos].rdiag = 1;
179  }
180  }
181 
182  SetLogoMaskArea();
183 
184  for (y = m_logoMinY; y < m_logoMaxY; y++)
185  {
186  for (x = m_logoMinX; x < m_logoMaxX; x++)
187  {
188  int neighbors = 0;
189 
190  if (!m_edgeMask[y * m_width + x].isedge)
191  continue;
192 
193  for (dy = y - 2; dy <= (y + 2); dy++ )
194  {
195  for (dx = x - 2; dx <= (x + 2); dx++ )
196  {
197  if (m_edgeMask[dy * m_width + dx].isedge)
198  neighbors++;
199  }
200  }
201 
202  if (neighbors < 5)
203  m_edgeMask[y * m_width + x].isedge = 0;
204  }
205  }
206 
207  SetLogoMaskArea();
208  LOG(VB_COMMFLAG, LOG_INFO,
209  QString("Testing Logo area: topleft (%1,%2), bottomright (%3,%4)")
210  .arg(m_logoMinX).arg(m_logoMinY)
211  .arg(m_logoMaxX).arg(m_logoMaxY));
212 
213 #ifdef SHOW_DEBUG_WIN
214  for (x = m_logoMinX; x < m_logoMaxX; x++)
215  {
216  pos = m_logoMinY * width + x;
217  fakeFrame[pos] = 0x7f;
218  pos = m_logoMaxY * width + x;
219  fakeFrame[pos] = 0x7f;
220  }
221  for (y = m_logoMinY; y < m_logoMaxY; y++)
222  {
223  pos = y * width + m_logoMinX;
224  fakeFrame[pos] = 0x7f;
225  pos = y * width + m_logoMaxX;
226  fakeFrame[pos] = 0x7f;
227  }
228 
229  comm_debug_show(fakeFrame);
230  delete [] fakeFrame;
231 
232  cerr << "Hit ENTER to continue" << endl;
233  getchar();
234 #endif
235  if (((m_logoMaxX - m_logoMinX) < (m_width / 4)) &&
236  ((m_logoMaxY - m_logoMinY) < (m_height / 4)) &&
237  (pixelsInMask > minPixelsInMask))
238  {
239  m_logoInfoAvailable = true;
240  m_logoEdgeDiff = edgeDiffs[i];
241 
242  LOG(VB_COMMFLAG, LOG_INFO,
243  QString("Using Logo area: topleft (%1,%2), "
244  "bottomright (%3,%4), pixelsInMask (%5).")
245  .arg(m_logoMinX).arg(m_logoMinY)
246  .arg(m_logoMaxX).arg(m_logoMaxY)
247  .arg(pixelsInMask));
248  }
249  else
250  {
251  LOG(VB_COMMFLAG, LOG_INFO,
252  QString("Rejecting Logo area: topleft (%1,%2), "
253  "bottomright (%3,%4), pixelsInMask (%5). "
254  "Not within specified limits.")
255  .arg(m_logoMinX).arg(m_logoMinY)
256  .arg(m_logoMaxX).arg(m_logoMaxY)
257  .arg(pixelsInMask));
258  }
259  }
260 
261  delete [] edgeCounts;
262 
263  if (!m_logoInfoAvailable)
264  LOG(VB_COMMFLAG, LOG_NOTICE, "No suitable logo area found.");
265 
266  player->DiscardVideoFrame(player->GetRawVideoFrame(0));
267  return m_logoInfoAvailable;
268 }
269 
270 
272 {
273  LOG(VB_COMMFLAG, LOG_INFO, "SetLogoMaskArea()");
274 
275  m_logoMinX = m_width - 1;
276  m_logoMaxX = 0;
277  m_logoMinY = m_height - 1;
278  m_logoMaxY = 0;
279 
280  for (unsigned int y = 0; y < m_height; y++)
281  {
282  for (unsigned int x = 0; x < m_width; x++)
283  {
284  if (m_edgeMask[y * m_width + x].isedge)
285  {
286  if (x < m_logoMinX)
287  m_logoMinX = x;
288  if (y < m_logoMinY)
289  m_logoMinY = y;
290  if (x > m_logoMaxX)
291  m_logoMaxX = x;
292  if (y > m_logoMaxY)
293  m_logoMaxY = y;
294  }
295  }
296  }
297 
298  m_logoMinX -= 5;
299  m_logoMaxX += 5;
300  m_logoMinY -= 5;
301  m_logoMaxY += 5;
302 
303  if (m_logoMinX < 4)
304  m_logoMinX = 4;
305  if (m_logoMaxX > (m_width-5))
306  m_logoMaxX = (m_width-5);
307  if (m_logoMinY < 4)
308  m_logoMinY = 4;
309  if (m_logoMaxY > (m_height-5))
310  m_logoMaxY = (m_height-5);
311 }
312 
313 
314 void ClassicLogoDetector::DumpLogo(bool fromCurrentFrame,
315  const unsigned char* framePtr)
316 {
317  char scrPixels[] = " .oxX";
318 
319  if (!m_logoInfoAvailable)
320  return;
321 
322  cerr << "\nLogo Data ";
323  if (fromCurrentFrame)
324  cerr << "from current frame\n";
325 
326  cerr << "\n ";
327 
328  for(unsigned int x = m_logoMinX - 2; x <= (m_logoMaxX + 2); x++)
329  cerr << (x % 10);
330  cerr << "\n";
331 
332  for(unsigned int y = m_logoMinY - 2; y <= (m_logoMaxY + 2); y++)
333  {
334  QString tmp = QString("%1: ").arg(y, 3);
335  QString ba = tmp.toLatin1();
336  cerr << ba.constData();
337  for(unsigned int x = m_logoMinX - 2; x <= (m_logoMaxX + 2); x++)
338  {
339  if (fromCurrentFrame)
340  {
341  cerr << scrPixels[framePtr[y * m_width + x] / 50];
342  }
343  else
344  {
345  switch (m_logoMask[y * m_width + x])
346  {
347  case 0:
348  case 2: cerr << " ";
349  break;
350  case 1: cerr << "*";
351  break;
352  case 3: cerr << ".";
353  break;
354  }
355  }
356  }
357  cerr << "\n";
358  }
359  cerr.flush();
360 }
361 
362 
363 /* ideas for this method ported back from comskip.c mods by Jere Jones
364  * which are partially mods based on Myth's original commercial skip
365  * code written by Chris Pinkham. */
367  VideoFrame* frame)
368 {
369  int radius = 2;
370  unsigned int x, y;
371  int pos1, pos2, pos3;
372  int edgePos;
373  int pixel;
374  int goodEdges = 0;
375  int badEdges = 0;
376  int testEdges = 0;
377  int testNotEdges = 0;
378 
379  unsigned char* framePtr = frame->buf;
380  int bytesPerLine = frame->pitches[0];
381 
382  for (y = m_logoMinY; y <= m_logoMaxY; y++ )
383  {
384  for (x = m_logoMinX; x <= m_logoMaxX; x++ )
385  {
386  pos1 = y * bytesPerLine + x;
387  edgePos = y * m_width + x;
388  pos2 = (y - radius) * bytesPerLine + x;
389  pos3 = (y + radius) * bytesPerLine + x;
390 
391  pixel = framePtr[pos1];
392 
393  if (m_edgeMask[edgePos].horiz)
394  {
395  if ((abs(framePtr[pos1 - radius] - pixel) >= m_logoEdgeDiff) ||
396  (abs(framePtr[pos1 + radius] - pixel) >= m_logoEdgeDiff))
397  goodEdges++;
398  testEdges++;
399  }
400  else
401  {
402  if ((abs(framePtr[pos1 - radius] - pixel) >= m_logoEdgeDiff) ||
403  (abs(framePtr[pos1 + radius] - pixel) >= m_logoEdgeDiff))
404  badEdges++;
405  testNotEdges++;
406  }
407 
408  if (m_edgeMask[edgePos].vert)
409  {
410  if ((abs(framePtr[pos2] - pixel) >= m_logoEdgeDiff) ||
411  (abs(framePtr[pos3] - pixel) >= m_logoEdgeDiff))
412  goodEdges++;
413  testEdges++;
414  }
415  else
416  {
417  if ((abs(framePtr[pos2] - pixel) >= m_logoEdgeDiff) ||
418  (abs(framePtr[pos3] - pixel) >= m_logoEdgeDiff))
419  badEdges++;
420  testNotEdges++;
421  }
422  }
423  }
424 
425  m_frameNumber++;
426  double goodEdgeRatio = (testEdges) ?
427  (double)goodEdges / (double)testEdges : 0.0;
428  double badEdgeRatio = (testNotEdges) ?
429  (double)badEdges / (double)testNotEdges : 0.0;
430  return (goodEdgeRatio > m_commDetectLogoGoodEdgeThreshold) &&
431  (badEdgeRatio < m_commDetectLogoBadEdgeThreshold);
432 }
433 
434 bool ClassicLogoDetector::pixelInsideLogo(unsigned int x, unsigned int y)
435 {
436  if (!m_logoInfoAvailable)
437  return false;
438 
439  return ((x > m_logoMinX) && (x < m_logoMaxX) &&
440  (y > m_logoMinY) && (y < m_logoMaxY));
441 }
442 
444  int edgeDiff)
445 {
446  int r = 2;
447  unsigned char *buf = frame->buf;
448  int bytesPerLine = frame->pitches[0];
449  unsigned char p;
450  unsigned int pos, x, y;
451 
452  for (y = m_commDetectBorder + r; y < (m_height - m_commDetectBorder - r); y++)
453  {
454  if ((y > (m_height/4)) && (y < (m_height * 3 / 4)))
455  continue;
456 
457  for (x = m_commDetectBorder + r; x < (m_width - m_commDetectBorder - r); x++)
458  {
459  int edgeCount = 0;
460 
461  if ((x > (m_width/4)) && (x < (m_width * 3 / 4)))
462  continue;
463 
464  pos = y * m_width + x;
465  p = buf[y * bytesPerLine + x];
466 
467  if (( abs(buf[y * bytesPerLine + (x - r)] - p) >= edgeDiff) ||
468  ( abs(buf[y * bytesPerLine + (x + r)] - p) >= edgeDiff))
469  {
470  edges[pos].horiz++;
471  edgeCount++;
472  }
473  if (( abs(buf[(y - r) * bytesPerLine + x] - p) >= edgeDiff) ||
474  ( abs(buf[(y + r) * bytesPerLine + x] - p) >= edgeDiff))
475  {
476  edges[pos].vert++;
477  edgeCount++;
478  }
479 
480  if (( abs(buf[(y - r) * bytesPerLine + (x - r)] - p) >= edgeDiff) ||
481  ( abs(buf[(y + r) * bytesPerLine + (x + r)] - p) >= edgeDiff))
482  {
483  edges[pos].ldiag++;
484  edgeCount++;
485  }
486 
487  if (( abs(buf[(y - r) * bytesPerLine + (x + r)] - p) >= edgeDiff) ||
488  ( abs(buf[(y + r) * bytesPerLine + (x - r)] - p) >= edgeDiff))
489  {
490  edges[pos].rdiag++;
491  edgeCount++;
492  }
493 
494  if (edgeCount >= 3)
495  edges[pos].isedge++;
496  }
497  }
498 }
499 
500 /* vim: set expandtab tabstop=4 shiftwidth=4: */
int pitches[3]
Y, U, & V pitches.
Definition: mythframe.h:63
bool doesThisFrameContainTheFoundLogo(VideoFrame *frame) override
EdgeMaskEntry * m_edgeMask
unsigned int m_width
bool pixelInsideLogo(unsigned int x, unsigned int y) override
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
static guint32 * tmp
Definition: goom_core.c:35
unsigned int getRequiredAvailableBufferForSearch() override
unsigned char r
Definition: ParseText.cpp:329
ClassicCommDetector * m_commDetector
unsigned char * m_logoFrame
unsigned char * m_logoMask
unsigned int m_commDetectBorder
QString GetSetting(const QString &key, const QString &defaultval="")
VideoFrame * GetRawVideoFrame(long long frameNumber=-1)
Returns a specific frame from the video.
float GetFrameRate(void) const
Definition: mythplayer.h:176
void DumpLogo(bool fromCurrentFrame, const unsigned char *framePtr)
ClassicLogoDetector(ClassicCommDetector *commDetector, unsigned int width, unsigned int height, unsigned int commdetectborder)
double m_commDetectLogoGoodEdgeThreshold
virtual void deleteLater(void)
unsigned char * m_logoCheckMask
struct edgemaskentry EdgeMaskEntry
int GetNumSetting(const QString &key, int defaultval=0)
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
static guint32 * pixel
--------------------------------------------------—**
Definition: goom_core.c:33
void DiscardVideoFrame(VideoFrame *buffer)
Places frame in the available frames queue.
unsigned int m_height
unsigned char * m_logoMaxValues
void DetectEdges(VideoFrame *frame, EdgeMaskEntry *edges, int edgeDiff)
unsigned char * buf
Definition: mythframe.h:39
unsigned char * m_logoMinValues
bool searchForLogo(MythPlayer *player) override