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  int edgeDiffs[] = {5, 7, 10, 15, 20, 30, 40, 50, 60, 0 };
79 
80 
81  LOG(VB_COMMFLAG, LOG_INFO, "Searching for Station Logo");
82 
83  m_logoInfoAvailable = false;
84 
85  EdgeMaskEntry *edgeCounts = new EdgeMaskEntry[m_width * m_height];
86 
87  // Back in 2005, a threshold of 50 minimum pixelsInMask was established.
88  // I don't know whether that was tested against SD or HD resolutions.
89  // I do know that in 2010, mythcommflag was changed to use ffmpeg's
90  // lowres support, effectively dividing the video area by 16.
91  // But the 50 pixel minimum was not adjusted accordingly.
92  // I believe the minimum threshold should vary with the video's area.
93  // I am using 1280x720 (for 720p) video as the baseline.
94  // This should improve logo detection for SD video.
95  int minPixelsInMask = 50 * (m_width*m_height) / (1280*720 / 16);
96 
97  for (uint i = 0; edgeDiffs[i] != 0 && !m_logoInfoAvailable; i++)
98  {
99  int pixelsInMask = 0;
100 
101  LOG(VB_COMMFLAG, LOG_INFO,
102  QString("Trying with edgeDiff == %1, minPixelsInMask=%2")
103  .arg(edgeDiffs[i]).arg(minPixelsInMask));
104 
105  memset(edgeCounts, 0, sizeof(EdgeMaskEntry) * m_width * m_height);
106  memset(m_edgeMask, 0, sizeof(EdgeMaskEntry) * m_width * m_height);
107 
108  player->DiscardVideoFrame(player->GetRawVideoFrame(0));
109 
110  int loops = 0;
111  long long seekFrame = m_commDetector->m_preRoll + seekIncrement;
112  while (loops < maxLoops && player->GetEof() == kEofStateNone)
113  {
114  VideoFrame* vf = player->GetRawVideoFrame(seekFrame);
115 
116  if ((loops % 50) == 0)
118 
119  if (m_commDetector->m_bStop)
120  {
121  player->DiscardVideoFrame(vf);
122  delete[] edgeCounts;
123  return false;
124  }
125 
127  std::this_thread::sleep_for(std::chrono::milliseconds(10));
128 
129  DetectEdges(vf, edgeCounts, edgeDiffs[i]);
130 
131  seekFrame += seekIncrement;
132  loops++;
133 
134  player->DiscardVideoFrame(vf);
135  }
136 
137  LOG(VB_COMMFLAG, LOG_INFO, "Analyzing edge data");
138 
139 #ifdef SHOW_DEBUG_WIN
140  unsigned char *fakeFrame;
141  fakeFrame = new unsigned char[m_width * m_height * 3 / 2];
142  memset(fakeFrame, 0, m_width * m_height * 3 / 2);
143 #endif
144 
145  for (uint y = 0; y < m_height; y++)
146  {
147  if ((y > (m_height/4)) && (y < (m_height * 3 / 4)))
148  continue;
149 
150  for (uint x = 0; x < m_width; x++)
151  {
152  if ((x > (m_width/4)) && (x < (m_width * 3 / 4)))
153  continue;
154 
155  uint pos = y * m_width + x;
156 
157  if (edgeCounts[pos].isedge > (maxLoops * 0.66))
158  {
159  m_edgeMask[pos].isedge = 1;
160  pixelsInMask++;
161 #ifdef SHOW_DEBUG_WIN
162  fakeFrame[pos] = 0xff;
163 #endif
164 
165  }
166 
167  if (edgeCounts[pos].horiz > (maxLoops * 0.66))
168  m_edgeMask[pos].horiz = 1;
169 
170  if (edgeCounts[pos].vert > (maxLoops * 0.66))
171  m_edgeMask[pos].vert = 1;
172 
173  if (edgeCounts[pos].ldiag > (maxLoops * 0.66))
174  m_edgeMask[pos].ldiag = 1;
175  if (edgeCounts[pos].rdiag > (maxLoops * 0.66))
176  m_edgeMask[pos].rdiag = 1;
177  }
178  }
179 
180  SetLogoMaskArea();
181 
182  for (uint y = m_logoMinY; y < m_logoMaxY; y++)
183  {
184  for (uint x = m_logoMinX; x < m_logoMaxX; x++)
185  {
186  int neighbors = 0;
187 
188  if (!m_edgeMask[y * m_width + x].isedge)
189  continue;
190 
191  for (uint dy = y - 2; dy <= (y + 2); dy++ )
192  {
193  for (uint dx = x - 2; dx <= (x + 2); dx++ )
194  {
195  if (m_edgeMask[dy * m_width + dx].isedge)
196  neighbors++;
197  }
198  }
199 
200  if (neighbors < 5)
201  m_edgeMask[y * m_width + x].isedge = 0;
202  }
203  }
204 
205  SetLogoMaskArea();
206  LOG(VB_COMMFLAG, LOG_INFO,
207  QString("Testing Logo area: topleft (%1,%2), bottomright (%3,%4)")
208  .arg(m_logoMinX).arg(m_logoMinY)
209  .arg(m_logoMaxX).arg(m_logoMaxY));
210 
211 #ifdef SHOW_DEBUG_WIN
212  for (uint x = m_logoMinX; x < m_logoMaxX; x++)
213  {
214  uint pos = m_logoMinY * m_width + x;
215  fakeFrame[pos] = 0x7f;
216  pos = m_logoMaxY * m_width + x;
217  fakeFrame[pos] = 0x7f;
218  }
219  for (uint y = m_logoMinY; y < m_logoMaxY; y++)
220  {
221  uint pos = y * m_width + m_logoMinX;
222  fakeFrame[pos] = 0x7f;
223  pos = y * m_width + m_logoMaxX;
224  fakeFrame[pos] = 0x7f;
225  }
226 
227  comm_debug_show(fakeFrame);
228  delete [] fakeFrame;
229 
230  cerr << "Hit ENTER to continue" << endl;
231  getchar();
232 #endif
233  if (((m_logoMaxX - m_logoMinX) < (m_width / 4)) &&
234  ((m_logoMaxY - m_logoMinY) < (m_height / 4)) &&
235  (pixelsInMask > minPixelsInMask))
236  {
237  m_logoInfoAvailable = true;
238  m_logoEdgeDiff = edgeDiffs[i];
239 
240  LOG(VB_COMMFLAG, LOG_INFO,
241  QString("Using Logo area: topleft (%1,%2), "
242  "bottomright (%3,%4), pixelsInMask (%5).")
243  .arg(m_logoMinX).arg(m_logoMinY)
244  .arg(m_logoMaxX).arg(m_logoMaxY)
245  .arg(pixelsInMask));
246  }
247  else
248  {
249  LOG(VB_COMMFLAG, LOG_INFO,
250  QString("Rejecting Logo area: topleft (%1,%2), "
251  "bottomright (%3,%4), pixelsInMask (%5). "
252  "Not within specified limits.")
253  .arg(m_logoMinX).arg(m_logoMinY)
254  .arg(m_logoMaxX).arg(m_logoMaxY)
255  .arg(pixelsInMask));
256  }
257  }
258 
259  delete [] edgeCounts;
260 
261  if (!m_logoInfoAvailable)
262  LOG(VB_COMMFLAG, LOG_NOTICE, "No suitable logo area found.");
263 
264  player->DiscardVideoFrame(player->GetRawVideoFrame(0));
265  return m_logoInfoAvailable;
266 }
267 
268 
270 {
271  LOG(VB_COMMFLAG, LOG_INFO, "SetLogoMaskArea()");
272 
273  m_logoMinX = m_width - 1;
274  m_logoMaxX = 0;
275  m_logoMinY = m_height - 1;
276  m_logoMaxY = 0;
277 
278  for (unsigned int y = 0; y < m_height; y++)
279  {
280  for (unsigned int x = 0; x < m_width; x++)
281  {
282  if (m_edgeMask[y * m_width + x].isedge)
283  {
284  if (x < m_logoMinX)
285  m_logoMinX = x;
286  if (y < m_logoMinY)
287  m_logoMinY = y;
288  if (x > m_logoMaxX)
289  m_logoMaxX = x;
290  if (y > m_logoMaxY)
291  m_logoMaxY = y;
292  }
293  }
294  }
295 
296  m_logoMinX -= 5;
297  m_logoMaxX += 5;
298  m_logoMinY -= 5;
299  m_logoMaxY += 5;
300 
301  if (m_logoMinX < 4)
302  m_logoMinX = 4;
303  if (m_logoMaxX > (m_width-5))
304  m_logoMaxX = (m_width-5);
305  if (m_logoMinY < 4)
306  m_logoMinY = 4;
307  if (m_logoMaxY > (m_height-5))
308  m_logoMaxY = (m_height-5);
309 }
310 
311 
312 void ClassicLogoDetector::DumpLogo(bool fromCurrentFrame,
313  const unsigned char* framePtr)
314 {
315  char scrPixels[] = " .oxX";
316 
317  if (!m_logoInfoAvailable)
318  return;
319 
320  cerr << "\nLogo Data ";
321  if (fromCurrentFrame)
322  cerr << "from current frame\n";
323 
324  cerr << "\n ";
325 
326  for(unsigned int x = m_logoMinX - 2; x <= (m_logoMaxX + 2); x++)
327  cerr << (x % 10);
328  cerr << "\n";
329 
330  for(unsigned int y = m_logoMinY - 2; y <= (m_logoMaxY + 2); y++)
331  {
332  QString tmp = QString("%1: ").arg(y, 3);
333  QString ba = tmp.toLatin1();
334  cerr << ba.constData();
335  for(unsigned int x = m_logoMinX - 2; x <= (m_logoMaxX + 2); x++)
336  {
337  if (fromCurrentFrame)
338  {
339  cerr << scrPixels[framePtr[y * m_width + x] / 50];
340  }
341  else
342  {
343  switch (m_logoMask[y * m_width + x])
344  {
345  case 0:
346  case 2: cerr << " ";
347  break;
348  case 1: cerr << "*";
349  break;
350  case 3: cerr << ".";
351  break;
352  }
353  }
354  }
355  cerr << "\n";
356  }
357  cerr.flush();
358 }
359 
360 
361 /* ideas for this method ported back from comskip.c mods by Jere Jones
362  * which are partially mods based on Myth's original commercial skip
363  * code written by Chris Pinkham. */
365  VideoFrame* frame)
366 {
367  int radius = 2;
368  int goodEdges = 0;
369  int badEdges = 0;
370  int testEdges = 0;
371  int testNotEdges = 0;
372 
373  unsigned char* framePtr = frame->buf;
374  int bytesPerLine = frame->pitches[0];
375 
376  for (uint y = m_logoMinY; y <= m_logoMaxY; y++ )
377  {
378  for (uint x = m_logoMinX; x <= m_logoMaxX; x++ )
379  {
380  int pos1 = y * bytesPerLine + x;
381  int edgePos = y * m_width + x;
382  int pos2 = (y - radius) * bytesPerLine + x;
383  int pos3 = (y + radius) * bytesPerLine + x;
384 
385  int pixel = framePtr[pos1];
386 
387  if (m_edgeMask[edgePos].horiz)
388  {
389  if ((abs(framePtr[pos1 - radius] - pixel) >= m_logoEdgeDiff) ||
390  (abs(framePtr[pos1 + radius] - pixel) >= m_logoEdgeDiff))
391  goodEdges++;
392  testEdges++;
393  }
394  else
395  {
396  if ((abs(framePtr[pos1 - radius] - pixel) >= m_logoEdgeDiff) ||
397  (abs(framePtr[pos1 + radius] - pixel) >= m_logoEdgeDiff))
398  badEdges++;
399  testNotEdges++;
400  }
401 
402  if (m_edgeMask[edgePos].vert)
403  {
404  if ((abs(framePtr[pos2] - pixel) >= m_logoEdgeDiff) ||
405  (abs(framePtr[pos3] - pixel) >= m_logoEdgeDiff))
406  goodEdges++;
407  testEdges++;
408  }
409  else
410  {
411  if ((abs(framePtr[pos2] - pixel) >= m_logoEdgeDiff) ||
412  (abs(framePtr[pos3] - pixel) >= m_logoEdgeDiff))
413  badEdges++;
414  testNotEdges++;
415  }
416  }
417  }
418 
419  m_frameNumber++;
420  double goodEdgeRatio = (testEdges) ?
421  (double)goodEdges / (double)testEdges : 0.0;
422  double badEdgeRatio = (testNotEdges) ?
423  (double)badEdges / (double)testNotEdges : 0.0;
424  return (goodEdgeRatio > m_commDetectLogoGoodEdgeThreshold) &&
425  (badEdgeRatio < m_commDetectLogoBadEdgeThreshold);
426 }
427 
428 bool ClassicLogoDetector::pixelInsideLogo(unsigned int x, unsigned int y)
429 {
430  if (!m_logoInfoAvailable)
431  return false;
432 
433  return ((x > m_logoMinX) && (x < m_logoMaxX) &&
434  (y > m_logoMinY) && (y < m_logoMaxY));
435 }
436 
438  int edgeDiff)
439 {
440  int r = 2;
441  unsigned char *buf = frame->buf;
442  int bytesPerLine = frame->pitches[0];
443 
444  for (uint y = m_commDetectBorder + r; y < (m_height - m_commDetectBorder - r); y++)
445  {
446  if ((y > (m_height/4)) && (y < (m_height * 3 / 4)))
447  continue;
448 
449  for (uint x = m_commDetectBorder + r; x < (m_width - m_commDetectBorder - r); x++)
450  {
451  int edgeCount = 0;
452 
453  if ((x > (m_width/4)) && (x < (m_width * 3 / 4)))
454  continue;
455 
456  uint pos = y * m_width + x;
457  uchar p = buf[y * bytesPerLine + x];
458 
459  if (( abs(buf[y * bytesPerLine + (x - r)] - p) >= edgeDiff) ||
460  ( abs(buf[y * bytesPerLine + (x + r)] - p) >= edgeDiff))
461  {
462  edges[pos].horiz++;
463  edgeCount++;
464  }
465  if (( abs(buf[(y - r) * bytesPerLine + x] - p) >= edgeDiff) ||
466  ( abs(buf[(y + r) * bytesPerLine + x] - p) >= edgeDiff))
467  {
468  edges[pos].vert++;
469  edgeCount++;
470  }
471 
472  if (( abs(buf[(y - r) * bytesPerLine + (x - r)] - p) >= edgeDiff) ||
473  ( abs(buf[(y + r) * bytesPerLine + (x + r)] - p) >= edgeDiff))
474  {
475  edges[pos].ldiag++;
476  edgeCount++;
477  }
478 
479  if (( abs(buf[(y - r) * bytesPerLine + (x + r)] - p) >= edgeDiff) ||
480  ( abs(buf[(y + r) * bytesPerLine + (x - r)] - p) >= edgeDiff))
481  {
482  edges[pos].rdiag++;
483  edgeCount++;
484  }
485 
486  if (edgeCount >= 3)
487  edges[pos].isedge++;
488  }
489  }
490 }
491 
492 /* 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
unsigned int uint
Definition: compat.h:140
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