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