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