MythTV  master
ClassicLogoDetector.cpp
Go to the documentation of this file.
1 // ANSI C headers
2 #include <cstdlib>
3 #include <thread> // for sleep_for
4 
5 // MythTV headers
6 #include "mythcorecontext.h"
7 #include "mythcommflagplayer.h"
8 #include "libavutil/frame.h"
9 
10 // Commercial Flagging headers
11 #include "ClassicLogoDetector.h"
12 #include "ClassicCommDetector.h"
13 
15 {
16  int m_isEdge;
17  int m_horiz;
18  int m_vert;
19  int m_rdiag;
20  int m_ldiag;
21 };
22 
23 
25  unsigned int w, unsigned int h,
26  unsigned int commdetectborder_in)
27  : LogoDetectorBase(w,h),
28  m_commDetector(commdetector),
29  m_commDetectBorder(commdetectborder_in),
30  m_edgeMask(new EdgeMaskEntry[m_width * m_height]),
31  // cppcheck doesn't understand deleteLater
32  m_logoMaxValues(new unsigned char[m_width * m_height]),
33  m_logoMinValues(new unsigned char[m_width * m_height]),
34  m_logoFrame(new unsigned char[m_width * m_height]),
35  m_logoMask(new unsigned char[m_width * m_height]),
36  m_logoCheckMask(new unsigned char[m_width * m_height])
37 {
39  gCoreContext->GetNumSetting("CommDetectLogoSamplesNeeded", 240);
41  gCoreContext->GetNumSetting("CommDetectLogoSampleSpacing", 2);
45  gCoreContext->GetSetting("CommDetectLogoGoodEdgeThreshold", "0.75")
46  .toDouble();
48  gCoreContext->GetSetting("CommDetectLogoBadEdgeThreshold", "0.85")
49  .toDouble();
50 }
51 
53 {
54  delete [] m_edgeMask;
55  delete [] m_logoFrame;
56  delete [] m_logoMask;
57  delete [] m_logoCheckMask;
58  delete [] m_logoMaxValues;
59  delete [] m_logoMinValues;
60 }
61 
63 {
65 }
66 
68 {
69  m_commDetector = nullptr;
70  LogoDetectorBase::deleteLater();
71 }
72 
74 {
75  int seekIncrement =
77  int maxLoops = m_commDetectLogoSamplesNeeded;
78  const std::array<const int,9> edgeDiffs {5, 7, 10, 15, 20, 30, 40, 50, 60 };
79 
80 
81  LOG(VB_COMMFLAG, LOG_INFO, "Searching for Station Logo");
82 
83  m_logoInfoAvailable = false;
84 
85  auto *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 edgediff : edgeDiffs)
98  {
99  int pixelsInMask = 0;
100 
101  LOG(VB_COMMFLAG, LOG_INFO,
102  QString("Trying with edgeDiff == %1, minPixelsInMask=%2")
103  .arg(edgediff).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  MythVideoFrame* 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(10ms);
128 
129  DetectEdges(vf, edgeCounts, edgediff);
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].m_isEdge > (maxLoops * 0.66))
158  {
159  m_edgeMask[pos].m_isEdge = 1;
160  pixelsInMask++;
161 #ifdef SHOW_DEBUG_WIN
162  fakeFrame[pos] = 0xff;
163 #endif
164 
165  }
166 
167  if (edgeCounts[pos].m_horiz > (maxLoops * 0.66))
168  m_edgeMask[pos].m_horiz = 1;
169 
170  if (edgeCounts[pos].m_vert > (maxLoops * 0.66))
171  m_edgeMask[pos].m_vert = 1;
172 
173  if (edgeCounts[pos].m_ldiag > (maxLoops * 0.66))
174  m_edgeMask[pos].m_ldiag = 1;
175  if (edgeCounts[pos].m_rdiag > (maxLoops * 0.66))
176  m_edgeMask[pos].m_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].m_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].m_isEdge)
196  neighbors++;
197  }
198  }
199 
200  if (neighbors < 5)
201  m_edgeMask[y * m_width + x].m_isEdge = 0;
202  }
203  }
204 
205  SetLogoMaskArea();
206  LOG(VB_COMMFLAG, LOG_INFO,
207  QString("Testing Logo area: topleft (%1,%2), bottomright (%3,%4)")
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 = edgediff;
239 
240  LOG(VB_COMMFLAG, LOG_INFO,
241  QString("Using Logo area: topleft (%1,%2), "
242  "bottomright (%3,%4), pixelsInMask (%5).")
245  .arg(pixelsInMask));
246  break;
247  }
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.")
255  .arg(pixelsInMask));
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  std::string scrPixels {" .oxX"};
315 
316  if (!m_logoInfoAvailable)
317  return;
318 
319  std::cerr << "\nLogo Data ";
320  if (fromCurrentFrame)
321  std::cerr << "from current frame\n";
322 
323  std::cerr << "\n ";
324 
325  for(unsigned int x = m_logoMinX - 2; x <= (m_logoMaxX + 2); x++)
326  std::cerr << (x % 10);
327  std::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  std::cerr << ba.constData();
334  for(unsigned int x = m_logoMinX - 2; x <= (m_logoMaxX + 2); x++)
335  {
336  if (fromCurrentFrame)
337  {
338  std::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: std::cerr << " ";
346  break;
347  case 1: std::cerr << "*";
348  break;
349  case 3: std::cerr << ".";
350  break;
351  }
352  }
353  }
354  std::cerr << "\n";
355  }
356  std::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  MythVideoFrame* 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->m_buffer;
373  int bytesPerLine = frame->m_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->m_buffer;
441  int bytesPerLine = frame->m_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: */
EdgeMaskEntry
Definition: ClassicLogoDetector.cpp:14
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:268
ClassicLogoDetector.h
ClassicLogoDetector::pixelInsideLogo
bool pixelInsideLogo(unsigned int x, unsigned int y) override
Definition: ClassicLogoDetector.cpp:427
ClassicLogoDetector::m_logoMask
unsigned char * m_logoMask
Definition: ClassicLogoDetector.h:45
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:69
ClassicLogoDetector::doesThisFrameContainTheFoundLogo
bool doesThisFrameContainTheFoundLogo(MythVideoFrame *frame) override
Definition: ClassicLogoDetector.cpp:363
ClassicLogoDetector::m_commDetector
ClassicCommDetector * m_commDetector
Definition: ClassicLogoDetector.h:30
ClassicLogoDetector::m_logoFrame
unsigned char * m_logoFrame
Definition: ClassicLogoDetector.h:44
mythcommflagplayer.h
EdgeMaskEntry::m_rdiag
int m_rdiag
Definition: ClassicLogoDetector.cpp:19
LogoDetectorBase::m_width
unsigned int m_width
Definition: LogoDetectorBase.h:31
arg
arg(title).arg(filename).arg(doDelete))
ClassicLogoDetector::m_edgeMask
EdgeMaskEntry * m_edgeMask
Definition: ClassicLogoDetector.h:40
ClassicCommDetector::logoDetectorBreathe
void logoDetectorBreathe()
Definition: ClassicCommDetector.cpp:2502
MythPlayer::GetFrameRate
float GetFrameRate(void) const
Definition: mythplayer.h:133
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
pixel
static guint32 * pixel
--------------------------------------------------—**
Definition: goom_core.cpp:29
ClassicLogoDetector::searchForLogo
bool searchForLogo(MythCommFlagPlayer *player) override
Definition: ClassicLogoDetector.cpp:73
ClassicLogoDetector::m_logoMinX
unsigned int m_logoMinX
Definition: ClassicLogoDetector.h:49
tmp
static guint32 * tmp
Definition: goom_core.cpp:31
LogoDetectorBase::m_height
unsigned int m_height
Definition: LogoDetectorBase.h:31
ClassicLogoDetector::DetectEdges
void DetectEdges(MythVideoFrame *frame, EdgeMaskEntry *edges, int edgeDiff)
Definition: ClassicLogoDetector.cpp:436
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:67
ClassicCommDetector::m_preRoll
long long m_preRoll
Definition: ClassicCommDetector.h:177
MythCommFlagPlayer
Definition: mythcommflagplayer.h:25
EdgeMaskEntry::m_ldiag
int m_ldiag
Definition: ClassicLogoDetector.cpp:20
ClassicCommDetector::m_fullSpeed
bool m_fullSpeed
Definition: ClassicCommDetector.h:173
ClassicLogoDetector::getRequiredAvailableBufferForSearch
unsigned int getRequiredAvailableBufferForSearch() override
Definition: ClassicLogoDetector.cpp:62
ClassicCommDetector
Definition: ClassicCommDetector.h:46
ClassicLogoDetector::ClassicLogoDetector
ClassicLogoDetector(ClassicCommDetector *commDetector, unsigned int width, unsigned int height, unsigned int commdetectborder)
Definition: ClassicLogoDetector.cpp:24
uint
unsigned int uint
Definition: compat.h:140
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:60
EdgeMaskEntry::m_isEdge
int m_isEdge
Definition: ClassicLogoDetector.cpp:16
MythCoreContext::GetNumSetting
int GetNumSetting(const QString &key, int defaultval=0)
Definition: mythcorecontext.cpp:932
LogoDetectorBase
Definition: LogoDetectorBase.h:9
MythVideoFrame::m_pitches
FramePitches m_pitches
Definition: mythframe.h:142
CommDetectorBase::m_bStop
bool m_bStop
Definition: CommDetectorBase.h:54
ClassicLogoDetector::m_logoCheckMask
unsigned char * m_logoCheckMask
Definition: ClassicLogoDetector.h:46
ClassicLogoDetector::~ClassicLogoDetector
~ClassicLogoDetector() override
Definition: ClassicLogoDetector.cpp:52
EdgeMaskEntry::m_vert
int m_vert
Definition: ClassicLogoDetector.cpp:18
ClassicLogoDetector::DumpLogo
void DumpLogo(bool fromCurrentFrame, const unsigned char *framePtr)
Definition: ClassicLogoDetector.cpp:311
EdgeMaskEntry::m_horiz
int m_horiz
Definition: ClassicLogoDetector.cpp:17
mythcorecontext.h
ClassicLogoDetector::m_logoMaxX
unsigned int m_logoMaxX
Definition: ClassicLogoDetector.h:50
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
MythVideoFrame
Definition: mythframe.h:88
ClassicLogoDetector::m_commDetectLogoSamplesNeeded
int m_commDetectLogoSamplesNeeded
Definition: ClassicLogoDetector.h:34
ClassicLogoDetector::m_logoInfoAvailable
bool m_logoInfoAvailable
Definition: ClassicLogoDetector.h:54
MythCommFlagPlayer::GetRawVideoFrame
MythVideoFrame * GetRawVideoFrame(long long FrameNumber=-1)
Returns a specific frame from the video.
Definition: mythcommflagplayer.cpp:226
MythCoreContext::GetSetting
QString GetSetting(const QString &key, const QString &defaultval="")
Definition: mythcorecontext.cpp:918
MythVideoFrame::m_buffer
uint8_t * m_buffer
Definition: mythframe.h:120
MythPlayer::DiscardVideoFrame
void DiscardVideoFrame(MythVideoFrame *buffer)
Places frame in the available frames queue.
Definition: mythplayer.cpp:618