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 {
56  delete [] m_edgeMask;
57  delete [] m_logoFrame;
58  delete [] m_logoMask;
59  delete [] m_logoCheckMask;
60  delete [] m_logoMaxValues;
61  delete [] m_logoMinValues;
62 }
63 
65 {
67 }
68 
70 {
71  m_commDetector = nullptr;
72  LogoDetectorBase::deleteLater();
73 }
74 
76 {
77  int seekIncrement =
79  int maxLoops = m_commDetectLogoSamplesNeeded;
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  auto *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 (uint 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[m_width * m_height * 3 / 2];
144  memset(fakeFrame, 0, m_width * m_height * 3 / 2);
145 #endif
146 
147  for (uint y = 0; y < m_height; y++)
148  {
149  if ((y > (m_height/4)) && (y < (m_height * 3 / 4)))
150  continue;
151 
152  for (uint x = 0; x < m_width; x++)
153  {
154  if ((x > (m_width/4)) && (x < (m_width * 3 / 4)))
155  continue;
156 
157  uint pos = y * m_width + x;
158 
159  if (edgeCounts[pos].m_isEdge > (maxLoops * 0.66))
160  {
161  m_edgeMask[pos].m_isEdge = 1;
162  pixelsInMask++;
163 #ifdef SHOW_DEBUG_WIN
164  fakeFrame[pos] = 0xff;
165 #endif
166 
167  }
168 
169  if (edgeCounts[pos].m_horiz > (maxLoops * 0.66))
170  m_edgeMask[pos].m_horiz = 1;
171 
172  if (edgeCounts[pos].m_vert > (maxLoops * 0.66))
173  m_edgeMask[pos].m_vert = 1;
174 
175  if (edgeCounts[pos].m_ldiag > (maxLoops * 0.66))
176  m_edgeMask[pos].m_ldiag = 1;
177  if (edgeCounts[pos].m_rdiag > (maxLoops * 0.66))
178  m_edgeMask[pos].m_rdiag = 1;
179  }
180  }
181 
182  SetLogoMaskArea();
183 
184  for (uint y = m_logoMinY; y < m_logoMaxY; y++)
185  {
186  for (uint x = m_logoMinX; x < m_logoMaxX; x++)
187  {
188  int neighbors = 0;
189 
190  if (!m_edgeMask[y * m_width + x].m_isEdge)
191  continue;
192 
193  for (uint dy = y - 2; dy <= (y + 2); dy++ )
194  {
195  for (uint dx = x - 2; dx <= (x + 2); dx++ )
196  {
197  if (m_edgeMask[dy * m_width + dx].m_isEdge)
198  neighbors++;
199  }
200  }
201 
202  if (neighbors < 5)
203  m_edgeMask[y * m_width + x].m_isEdge = 0;
204  }
205  }
206 
207  SetLogoMaskArea();
208  LOG(VB_COMMFLAG, LOG_INFO,
209  QString("Testing Logo area: topleft (%1,%2), bottomright (%3,%4)")
212 
213 #ifdef SHOW_DEBUG_WIN
214  for (uint x = m_logoMinX; x < m_logoMaxX; x++)
215  {
216  uint pos = m_logoMinY * m_width + x;
217  fakeFrame[pos] = 0x7f;
218  pos = m_logoMaxY * m_width + x;
219  fakeFrame[pos] = 0x7f;
220  }
221  for (uint y = m_logoMinY; y < m_logoMaxY; y++)
222  {
223  uint pos = y * m_width + m_logoMinX;
224  fakeFrame[pos] = 0x7f;
225  pos = y * m_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).")
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.")
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].m_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  int goodEdges = 0;
371  int badEdges = 0;
372  int testEdges = 0;
373  int testNotEdges = 0;
374 
375  unsigned char* framePtr = frame->buf;
376  int bytesPerLine = frame->pitches[0];
377 
378  for (uint y = m_logoMinY; y <= m_logoMaxY; y++ )
379  {
380  for (uint x = m_logoMinX; x <= m_logoMaxX; x++ )
381  {
382  int pos1 = y * bytesPerLine + x;
383  int edgePos = y * m_width + x;
384  int pos2 = (y - radius) * bytesPerLine + x;
385  int pos3 = (y + radius) * bytesPerLine + x;
386 
387  int pixel = framePtr[pos1];
388 
389  if (m_edgeMask[edgePos].m_horiz)
390  {
391  if ((abs(framePtr[pos1 - radius] - pixel) >= m_logoEdgeDiff) ||
392  (abs(framePtr[pos1 + radius] - pixel) >= m_logoEdgeDiff))
393  goodEdges++;
394  testEdges++;
395  }
396  else
397  {
398  if ((abs(framePtr[pos1 - radius] - pixel) >= m_logoEdgeDiff) ||
399  (abs(framePtr[pos1 + radius] - pixel) >= m_logoEdgeDiff))
400  badEdges++;
401  testNotEdges++;
402  }
403 
404  if (m_edgeMask[edgePos].m_vert)
405  {
406  if ((abs(framePtr[pos2] - pixel) >= m_logoEdgeDiff) ||
407  (abs(framePtr[pos3] - pixel) >= m_logoEdgeDiff))
408  goodEdges++;
409  testEdges++;
410  }
411  else
412  {
413  if ((abs(framePtr[pos2] - pixel) >= m_logoEdgeDiff) ||
414  (abs(framePtr[pos3] - pixel) >= m_logoEdgeDiff))
415  badEdges++;
416  testNotEdges++;
417  }
418  }
419  }
420 
421  m_frameNumber++;
422  double goodEdgeRatio = (testEdges) ?
423  (double)goodEdges / (double)testEdges : 0.0;
424  double badEdgeRatio = (testNotEdges) ?
425  (double)badEdges / (double)testNotEdges : 0.0;
426  return (goodEdgeRatio > m_commDetectLogoGoodEdgeThreshold) &&
427  (badEdgeRatio < m_commDetectLogoBadEdgeThreshold);
428 }
429 
430 bool ClassicLogoDetector::pixelInsideLogo(unsigned int x, unsigned int y)
431 {
432  if (!m_logoInfoAvailable)
433  return false;
434 
435  return ((x > m_logoMinX) && (x < m_logoMaxX) &&
436  (y > m_logoMinY) && (y < m_logoMaxY));
437 }
438 
440  int edgeDiff)
441 {
442  int r = 2;
443  unsigned char *buf = frame->buf;
444  int bytesPerLine = frame->pitches[0];
445 
446  for (uint y = m_commDetectBorder + r; y < (m_height - m_commDetectBorder - r); y++)
447  {
448  if ((y > (m_height/4)) && (y < (m_height * 3 / 4)))
449  continue;
450 
451  for (uint x = m_commDetectBorder + r; x < (m_width - m_commDetectBorder - r); x++)
452  {
453  int edgeCount = 0;
454 
455  if ((x > (m_width/4)) && (x < (m_width * 3 / 4)))
456  continue;
457 
458  uint pos = y * m_width + x;
459  uchar p = buf[y * bytesPerLine + x];
460 
461  if (( abs(buf[y * bytesPerLine + (x - r)] - p) >= edgeDiff) ||
462  ( abs(buf[y * bytesPerLine + (x + r)] - p) >= edgeDiff))
463  {
464  edges[pos].m_horiz++;
465  edgeCount++;
466  }
467  if (( abs(buf[(y - r) * bytesPerLine + x] - p) >= edgeDiff) ||
468  ( abs(buf[(y + r) * bytesPerLine + x] - p) >= edgeDiff))
469  {
470  edges[pos].m_vert++;
471  edgeCount++;
472  }
473 
474  if (( abs(buf[(y - r) * bytesPerLine + (x - r)] - p) >= edgeDiff) ||
475  ( abs(buf[(y + r) * bytesPerLine + (x + r)] - p) >= edgeDiff))
476  {
477  edges[pos].m_ldiag++;
478  edgeCount++;
479  }
480 
481  if (( abs(buf[(y - r) * bytesPerLine + (x + r)] - p) >= edgeDiff) ||
482  ( abs(buf[(y + r) * bytesPerLine + (x - r)] - p) >= edgeDiff))
483  {
484  edges[pos].m_rdiag++;
485  edgeCount++;
486  }
487 
488  if (edgeCount >= 3)
489  edges[pos].m_isEdge++;
490  }
491  }
492 }
493 
494 /* vim: set expandtab tabstop=4 shiftwidth=4: */
EdgeMaskEntry
Definition: ClassicLogoDetector.cpp:16
ClassicLogoDetector::m_logoMaxY
unsigned int m_logoMaxY
Definition: ClassicLogoDetector.h:52
ClassicLogoDetector::m_commDetectLogoSecondsNeeded
int m_commDetectLogoSecondsNeeded
Definition: ClassicLogoDetector.h:36
ClassicCommDetector.h
ClassicLogoDetector::SetLogoMaskArea
void SetLogoMaskArea()
Definition: ClassicLogoDetector.cpp:271
ClassicLogoDetector.h
ClassicLogoDetector::pixelInsideLogo
bool pixelInsideLogo(unsigned int x, unsigned int y) override
Definition: ClassicLogoDetector.cpp:430
ClassicLogoDetector::m_logoMask
unsigned char * m_logoMask
Definition: ClassicLogoDetector.h:45
ClassicLogoDetector::doesThisFrameContainTheFoundLogo
bool doesThisFrameContainTheFoundLogo(VideoFrame *frame) override
Definition: ClassicLogoDetector.cpp:366
VideoFrame::pitches
int pitches[3]
Y, U, & V pitches.
Definition: mythframe.h:161
ClassicLogoDetector::m_logoMaxValues
unsigned char * m_logoMaxValues
Definition: ClassicLogoDetector.h:42
ClassicLogoDetector::m_commDetectLogoGoodEdgeThreshold
double m_commDetectLogoGoodEdgeThreshold
Definition: ClassicLogoDetector.h:37
kEofStateNone
@ kEofStateNone
Definition: decoderbase.h:68
ClassicLogoDetector::m_commDetector
ClassicCommDetector * m_commDetector
Definition: ClassicLogoDetector.h:30
ClassicLogoDetector::m_logoFrame
unsigned char * m_logoFrame
Definition: ClassicLogoDetector.h:44
EdgeMaskEntry::m_rdiag
int m_rdiag
Definition: ClassicLogoDetector.cpp:20
LogoDetectorBase::m_width
unsigned int m_width
Definition: LogoDetectorBase.h:31
arg
arg(title).arg(filename).arg(doDelete))
ClassicLogoDetector::searchForLogo
bool searchForLogo(MythPlayer *player) override
Definition: ClassicLogoDetector.cpp:75
ClassicLogoDetector::m_edgeMask
EdgeMaskEntry * m_edgeMask
Definition: ClassicLogoDetector.h:40
ClassicCommDetector::logoDetectorBreathe
void logoDetectorBreathe()
Definition: ClassicCommDetector.cpp:2517
MythPlayer::GetFrameRate
float GetFrameRate(void) const
Definition: mythplayer.h:219
VideoFrame::buf
unsigned char * buf
Definition: mythframe.h:140
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
mythplayer.h
pixel
static guint32 * pixel
--------------------------------------------------—**
Definition: goom_core.cpp:28
MythPlayer
Definition: mythplayer.h:165
VideoFrame
Definition: mythframe.h:138
ClassicLogoDetector::m_logoMinX
unsigned int m_logoMinX
Definition: ClassicLogoDetector.h:49
tmp
static guint32 * tmp
Definition: goom_core.cpp:30
LogoDetectorBase::m_height
unsigned int m_height
Definition: LogoDetectorBase.h:31
ClassicLogoDetector::DetectEdges
void DetectEdges(VideoFrame *frame, EdgeMaskEntry *edges, int edgeDiff)
Definition: ClassicLogoDetector.cpp:439
MythPlayer::DiscardVideoFrame
void DiscardVideoFrame(VideoFrame *buffer)
Places frame in the available frames queue.
Definition: mythplayer.cpp:996
ClassicLogoDetector::m_logoEdgeDiff
int m_logoEdgeDiff
Definition: ClassicLogoDetector.h:48
ClassicLogoDetector::m_commDetectLogoBadEdgeThreshold
double m_commDetectLogoBadEdgeThreshold
Definition: ClassicLogoDetector.h:38
ClassicLogoDetector::m_commDetectLogoSampleSpacing
int m_commDetectLogoSampleSpacing
Definition: ClassicLogoDetector.h:35
hardwareprofile.config.p
p
Definition: config.py:33
ClassicLogoDetector::deleteLater
virtual void deleteLater(void)
Definition: ClassicLogoDetector.cpp:69
ClassicCommDetector::m_preRoll
long long m_preRoll
Definition: ClassicCommDetector.h:177
EdgeMaskEntry::m_ldiag
int m_ldiag
Definition: ClassicLogoDetector.cpp:21
ClassicCommDetector::m_fullSpeed
bool m_fullSpeed
Definition: ClassicCommDetector.h:173
ClassicLogoDetector::getRequiredAvailableBufferForSearch
unsigned int getRequiredAvailableBufferForSearch() override
Definition: ClassicLogoDetector.cpp:64
ClassicCommDetector
Definition: ClassicCommDetector.h:47
ClassicLogoDetector::ClassicLogoDetector
ClassicLogoDetector(ClassicCommDetector *commDetector, unsigned int width, unsigned int height, unsigned int commdetectborder)
Definition: ClassicLogoDetector.cpp:25
uint
unsigned int uint
Definition: compat.h:140
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:57
EdgeMaskEntry::m_isEdge
int m_isEdge
Definition: ClassicLogoDetector.cpp:17
MythCoreContext::GetNumSetting
int GetNumSetting(const QString &key, int defaultval=0)
Definition: mythcorecontext.cpp:930
LogoDetectorBase
Definition: LogoDetectorBase.h:10
CommDetectorBase::m_bStop
bool m_bStop
Definition: CommDetectorBase.h:55
ClassicLogoDetector::m_logoCheckMask
unsigned char * m_logoCheckMask
Definition: ClassicLogoDetector.h:46
ClassicLogoDetector::~ClassicLogoDetector
~ClassicLogoDetector() override
Definition: ClassicLogoDetector.cpp:54
EdgeMaskEntry::m_vert
int m_vert
Definition: ClassicLogoDetector.cpp:19
ClassicLogoDetector::DumpLogo
void DumpLogo(bool fromCurrentFrame, const unsigned char *framePtr)
Definition: ClassicLogoDetector.cpp:314
EdgeMaskEntry::m_horiz
int m_horiz
Definition: ClassicLogoDetector.cpp:18
mythcorecontext.h
ClassicLogoDetector::m_logoMaxX
unsigned int m_logoMaxX
Definition: ClassicLogoDetector.h:50
MythPlayer::GetRawVideoFrame
VideoFrame * GetRawVideoFrame(long long frameNumber=-1)
Returns a specific frame from the video.
Definition: mythplayer.cpp:4603
ClassicLogoDetector::m_logoMinY
unsigned int m_logoMinY
Definition: ClassicLogoDetector.h:51
ClassicLogoDetector::m_commDetectBorder
unsigned int m_commDetectBorder
Definition: ClassicLogoDetector.h:32
ClassicLogoDetector::m_frameNumber
unsigned int m_frameNumber
Definition: ClassicLogoDetector.h:31
ClassicLogoDetector::m_logoMinValues
unsigned char * m_logoMinValues
Definition: ClassicLogoDetector.h:43
ClassicLogoDetector::m_commDetectLogoSamplesNeeded
int m_commDetectLogoSamplesNeeded
Definition: ClassicLogoDetector.h:34
ClassicLogoDetector::m_logoInfoAvailable
bool m_logoInfoAvailable
Definition: ClassicLogoDetector.h:54
MythCoreContext::GetSetting
QString GetSetting(const QString &key, const QString &defaultval="")
Definition: mythcorecontext.cpp:916