2016-02-11 15:50:51 +01:00
|
|
|
//#include <iostream>
|
2013-08-21 16:25:27 +02:00
|
|
|
#pragma once
|
|
|
|
|
|
|
|
// Utils includes
|
2013-11-11 10:00:37 +01:00
|
|
|
#include <utils/Image.h>
|
2013-08-21 16:25:27 +02:00
|
|
|
|
2013-08-23 07:08:44 +02:00
|
|
|
namespace hyperion
|
2013-08-21 16:25:27 +02:00
|
|
|
{
|
2013-08-21 16:52:03 +02:00
|
|
|
///
|
2013-08-23 07:08:44 +02:00
|
|
|
/// Result structure of the detected blackborder.
|
2013-08-21 16:52:03 +02:00
|
|
|
///
|
2013-08-23 07:08:44 +02:00
|
|
|
struct BlackBorder
|
2013-08-21 16:25:27 +02:00
|
|
|
{
|
2020-11-14 17:58:56 +01:00
|
|
|
/// Flag indicating if the border is unknown
|
2013-10-27 09:25:02 +01:00
|
|
|
bool unknown;
|
2013-08-21 16:25:27 +02:00
|
|
|
|
2013-10-27 09:25:02 +01:00
|
|
|
/// The size of the detected horizontal border
|
|
|
|
int horizontalSize;
|
2013-08-21 16:25:27 +02:00
|
|
|
|
2013-10-27 09:25:02 +01:00
|
|
|
/// The size of the detected vertical border
|
|
|
|
int verticalSize;
|
2013-08-23 18:24:10 +02:00
|
|
|
|
|
|
|
///
|
|
|
|
/// Compares this BlackBorder to the given other BlackBorder
|
|
|
|
///
|
|
|
|
/// @param[in] other The other BlackBorder
|
|
|
|
///
|
|
|
|
/// @return True if this is the same border as other
|
|
|
|
///
|
|
|
|
inline bool operator== (const BlackBorder& other) const
|
|
|
|
{
|
2013-10-27 09:25:02 +01:00
|
|
|
if (unknown)
|
2013-08-23 18:24:10 +02:00
|
|
|
{
|
2013-10-27 09:25:02 +01:00
|
|
|
return other.unknown;
|
2013-08-23 18:24:10 +02:00
|
|
|
}
|
|
|
|
|
2013-10-27 09:25:02 +01:00
|
|
|
return other.unknown==false && horizontalSize==other.horizontalSize && verticalSize==other.verticalSize;
|
2013-08-23 18:24:10 +02:00
|
|
|
}
|
2013-08-23 07:08:44 +02:00
|
|
|
};
|
2013-08-21 16:25:27 +02:00
|
|
|
|
2013-08-21 16:52:03 +02:00
|
|
|
///
|
2013-08-23 07:08:44 +02:00
|
|
|
/// The BlackBorderDetector performs detection of black-borders on a single image.
|
2013-10-27 09:25:02 +01:00
|
|
|
/// The detector will search for the upper left corner of the picture in the frame.
|
|
|
|
/// Based on detected black pixels it will give an estimate of the black-border.
|
2013-08-21 16:52:03 +02:00
|
|
|
///
|
2013-08-23 07:08:44 +02:00
|
|
|
class BlackBorderDetector
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
///
|
|
|
|
/// Constructs a black-border detector
|
2020-11-14 17:58:56 +01:00
|
|
|
/// @param[in] threshold The threshold which the black-border detector should use
|
2013-08-23 07:08:44 +02:00
|
|
|
///
|
2016-02-07 13:26:40 +01:00
|
|
|
BlackBorderDetector(double threshold);
|
2013-08-21 16:25:27 +02:00
|
|
|
|
2013-08-23 07:08:44 +02:00
|
|
|
///
|
|
|
|
/// Performs the actual black-border detection on the given image
|
|
|
|
///
|
|
|
|
/// @param[in] image The image on which detection is performed
|
|
|
|
///
|
|
|
|
/// @return The detected (or not detected) black border info
|
|
|
|
///
|
2016-02-07 13:26:40 +01:00
|
|
|
|
2020-08-08 00:21:19 +02:00
|
|
|
uint8_t calculateThreshold(double blackborderThreshold) const;
|
2016-02-07 13:26:40 +01:00
|
|
|
|
|
|
|
///
|
|
|
|
/// default detection mode (3lines 4side detection)
|
2013-11-11 10:00:37 +01:00
|
|
|
template <typename Pixel_T>
|
2020-08-08 00:21:19 +02:00
|
|
|
BlackBorder process(const Image<Pixel_T> & image) const
|
2013-11-11 10:00:37 +01:00
|
|
|
{
|
2020-11-14 17:58:56 +01:00
|
|
|
// test centre and 33%, 66% of width/height
|
2016-01-19 23:43:00 +01:00
|
|
|
// 33 and 66 will check left and top
|
2020-11-14 17:58:56 +01:00
|
|
|
// centre will check right and bottom sides
|
2016-01-19 23:43:00 +01:00
|
|
|
int width = image.width();
|
|
|
|
int height = image.height();
|
|
|
|
int width33percent = width / 3;
|
|
|
|
int height33percent = height / 3;
|
|
|
|
int width66percent = width33percent * 2;
|
|
|
|
int height66percent = height33percent * 2;
|
|
|
|
int xCenter = width / 2;
|
|
|
|
int yCenter = height / 2;
|
2016-01-02 02:31:13 +01:00
|
|
|
|
|
|
|
|
|
|
|
int firstNonBlackXPixelIndex = -1;
|
|
|
|
int firstNonBlackYPixelIndex = -1;
|
|
|
|
|
2016-02-11 15:50:51 +01:00
|
|
|
width--; // remove 1 pixel to get end pixel index
|
|
|
|
height--;
|
|
|
|
|
2016-01-02 02:31:13 +01:00
|
|
|
// find first X pixel of the image
|
2016-02-07 13:26:40 +01:00
|
|
|
for (int x = 0; x < width33percent; ++x)
|
2016-01-02 02:31:13 +01:00
|
|
|
{
|
2019-01-06 19:49:56 +01:00
|
|
|
if (!isBlack(image((width - x), yCenter))
|
|
|
|
|| !isBlack(image(x, height33percent))
|
|
|
|
|| !isBlack(image(x, height66percent)))
|
2016-01-02 02:31:13 +01:00
|
|
|
{
|
|
|
|
firstNonBlackXPixelIndex = x;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// find first Y pixel of the image
|
2016-02-07 13:26:40 +01:00
|
|
|
for (int y = 0; y < height33percent; ++y)
|
2016-01-02 02:31:13 +01:00
|
|
|
{
|
2019-01-06 19:49:56 +01:00
|
|
|
if (!isBlack(image(xCenter, (height - y)))
|
2020-08-08 00:21:19 +02:00
|
|
|
|| !isBlack(image(width33percent, y))
|
2019-01-06 19:49:56 +01:00
|
|
|
|| !isBlack(image(width66percent, y)))
|
2016-01-02 02:31:13 +01:00
|
|
|
{
|
|
|
|
firstNonBlackYPixelIndex = y;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-11-11 10:00:37 +01:00
|
|
|
// Construct result
|
|
|
|
BlackBorder detectedBorder;
|
|
|
|
detectedBorder.unknown = firstNonBlackXPixelIndex == -1 || firstNonBlackYPixelIndex == -1;
|
|
|
|
detectedBorder.horizontalSize = firstNonBlackYPixelIndex;
|
|
|
|
detectedBorder.verticalSize = firstNonBlackXPixelIndex;
|
|
|
|
return detectedBorder;
|
|
|
|
}
|
2013-08-21 16:25:27 +02:00
|
|
|
|
2016-02-07 13:26:40 +01:00
|
|
|
|
|
|
|
///
|
|
|
|
/// classic detection mode (topleft single line mode)
|
|
|
|
template <typename Pixel_T>
|
2020-08-08 00:21:19 +02:00
|
|
|
BlackBorder process_classic(const Image<Pixel_T> & image) const
|
2016-02-07 13:26:40 +01:00
|
|
|
{
|
|
|
|
// only test the topleft third of the image
|
|
|
|
int width = image.width() /3;
|
|
|
|
int height = image.height() / 3;
|
2021-11-16 18:12:56 +01:00
|
|
|
int maxSize = qMax(width, height);
|
2016-02-07 13:26:40 +01:00
|
|
|
|
|
|
|
int firstNonBlackXPixelIndex = -1;
|
|
|
|
int firstNonBlackYPixelIndex = -1;
|
|
|
|
|
|
|
|
// find some pixel of the image
|
|
|
|
for (int i = 0; i < maxSize; ++i)
|
|
|
|
{
|
2021-11-16 18:12:56 +01:00
|
|
|
int x = qMin(i, width);
|
|
|
|
int y = qMin(i, height);
|
2016-02-07 13:26:40 +01:00
|
|
|
|
|
|
|
const Pixel_T & color = image(x, y);
|
|
|
|
if (!isBlack(color))
|
|
|
|
{
|
|
|
|
firstNonBlackXPixelIndex = x;
|
|
|
|
firstNonBlackYPixelIndex = y;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// expand image to the left
|
|
|
|
for(; firstNonBlackXPixelIndex > 0; --firstNonBlackXPixelIndex)
|
|
|
|
{
|
|
|
|
const Pixel_T & color = image(firstNonBlackXPixelIndex-1, firstNonBlackYPixelIndex);
|
|
|
|
if (isBlack(color))
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// expand image to the top
|
|
|
|
for(; firstNonBlackYPixelIndex > 0; --firstNonBlackYPixelIndex)
|
|
|
|
{
|
|
|
|
const Pixel_T & color = image(firstNonBlackXPixelIndex, firstNonBlackYPixelIndex-1);
|
|
|
|
if (isBlack(color))
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Construct result
|
|
|
|
BlackBorder detectedBorder;
|
|
|
|
detectedBorder.unknown = firstNonBlackXPixelIndex == -1 || firstNonBlackYPixelIndex == -1;
|
|
|
|
detectedBorder.horizontalSize = firstNonBlackYPixelIndex;
|
|
|
|
detectedBorder.verticalSize = firstNonBlackXPixelIndex;
|
|
|
|
return detectedBorder;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-02-11 15:50:51 +01:00
|
|
|
///
|
|
|
|
/// osd detection mode (find x then y at detected x to avoid changes by osd overlays)
|
2016-02-07 13:26:40 +01:00
|
|
|
template <typename Pixel_T>
|
2020-08-08 00:21:19 +02:00
|
|
|
BlackBorder process_osd(const Image<Pixel_T> & image) const
|
2016-02-07 13:26:40 +01:00
|
|
|
{
|
|
|
|
// find X position at height33 and height66 we check from the left side, Ycenter will check from right side
|
|
|
|
// then we try to find a pixel at this X position from top and bottom and right side from top
|
|
|
|
int width = image.width();
|
|
|
|
int height = image.height();
|
|
|
|
int width33percent = width / 3;
|
|
|
|
int height33percent = height / 3;
|
|
|
|
int height66percent = height33percent * 2;
|
|
|
|
int yCenter = height / 2;
|
|
|
|
|
|
|
|
|
|
|
|
int firstNonBlackXPixelIndex = -1;
|
|
|
|
int firstNonBlackYPixelIndex = -1;
|
|
|
|
|
2016-02-11 15:50:51 +01:00
|
|
|
width--; // remove 1 pixel to get end pixel index
|
|
|
|
height--;
|
|
|
|
|
2016-02-07 13:26:40 +01:00
|
|
|
// find first X pixel of the image
|
|
|
|
int x;
|
|
|
|
for (x = 0; x < width33percent; ++x)
|
|
|
|
{
|
2020-08-08 00:21:19 +02:00
|
|
|
if (!isBlack(image((width - x), yCenter))
|
2019-01-06 19:49:56 +01:00
|
|
|
|| !isBlack(image(x, height33percent))
|
|
|
|
|| !isBlack(image(x, height66percent)))
|
2016-02-07 13:26:40 +01:00
|
|
|
{
|
|
|
|
firstNonBlackXPixelIndex = x;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// find first Y pixel of the image
|
|
|
|
for (int y = 0; y < height33percent; ++y)
|
|
|
|
{
|
2019-01-06 19:49:56 +01:00
|
|
|
// left side top + left side bottom + right side top + right side bottom
|
2020-08-08 00:21:19 +02:00
|
|
|
if (!isBlack(image(x, y))
|
2019-01-06 19:49:56 +01:00
|
|
|
|| !isBlack(image(x, (height - y)))
|
2020-08-08 00:21:19 +02:00
|
|
|
|| !isBlack(image((width - x), y))
|
2019-01-06 19:49:56 +01:00
|
|
|
|| !isBlack(image((width - x), (height - y))))
|
2016-02-07 13:26:40 +01:00
|
|
|
{
|
2019-01-06 19:49:56 +01:00
|
|
|
// std::cout << "y " << y << " lt " << int(isBlack(color1)) << " lb " << int(isBlack(color2)) << " rt " << int(isBlack(color3)) << " rb " << int(isBlack(color4)) << std::endl;
|
2016-02-07 13:26:40 +01:00
|
|
|
firstNonBlackYPixelIndex = y;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Construct result
|
|
|
|
BlackBorder detectedBorder;
|
|
|
|
detectedBorder.unknown = firstNonBlackXPixelIndex == -1 || firstNonBlackYPixelIndex == -1;
|
|
|
|
detectedBorder.horizontalSize = firstNonBlackYPixelIndex;
|
|
|
|
detectedBorder.verticalSize = firstNonBlackXPixelIndex;
|
|
|
|
return detectedBorder;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-11-01 19:20:27 +01:00
|
|
|
///
|
|
|
|
/// letterbox detection mode (5lines top-bottom only detection)
|
|
|
|
template <typename Pixel_T>
|
|
|
|
BlackBorder process_letterbox(const Image<Pixel_T> & image) const
|
|
|
|
{
|
|
|
|
// test center and 25%, 75% of width
|
|
|
|
// 25 and 75 will check both top and bottom
|
|
|
|
// center will only check top (minimise false detection of captions)
|
|
|
|
int width = image.width();
|
|
|
|
int height = image.height();
|
|
|
|
int width25percent = width / 4;
|
|
|
|
int height33percent = height / 3;
|
|
|
|
int width75percent = width25percent * 3;
|
|
|
|
int xCenter = width / 2;
|
|
|
|
|
|
|
|
|
|
|
|
int firstNonBlackYPixelIndex = -1;
|
|
|
|
|
|
|
|
height--; // remove 1 pixel to get end pixel index
|
|
|
|
|
|
|
|
// find first Y pixel of the image
|
|
|
|
for (int y = 0; y < height33percent; ++y)
|
|
|
|
{
|
|
|
|
if (!isBlack(image(xCenter, y))
|
|
|
|
|| !isBlack(image(width25percent, y))
|
|
|
|
|| !isBlack(image(width75percent, y))
|
|
|
|
|| !isBlack(image(width25percent, (height - y)))
|
|
|
|
|| !isBlack(image(width75percent, (height - y))))
|
|
|
|
{
|
|
|
|
firstNonBlackYPixelIndex = y;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Construct result
|
|
|
|
BlackBorder detectedBorder;
|
|
|
|
detectedBorder.unknown = firstNonBlackYPixelIndex == -1;
|
|
|
|
detectedBorder.horizontalSize = firstNonBlackYPixelIndex;
|
|
|
|
detectedBorder.verticalSize = 0;
|
|
|
|
return detectedBorder;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-02-07 13:26:40 +01:00
|
|
|
|
2013-08-23 07:08:44 +02:00
|
|
|
private:
|
2013-08-21 16:25:27 +02:00
|
|
|
|
2013-08-23 07:08:44 +02:00
|
|
|
///
|
2020-11-14 17:58:56 +01:00
|
|
|
/// Checks if a given color is considered black and therefore could be part of the border.
|
2013-08-23 07:08:44 +02:00
|
|
|
///
|
|
|
|
/// @param[in] color The color to check
|
|
|
|
///
|
|
|
|
/// @return True if the color is considered black else false
|
|
|
|
///
|
2013-11-11 10:00:37 +01:00
|
|
|
template <typename Pixel_T>
|
2020-08-08 00:21:19 +02:00
|
|
|
inline bool isBlack(const Pixel_T & color) const
|
2013-08-23 07:08:44 +02:00
|
|
|
{
|
|
|
|
// Return the simple compare of the color against black
|
2014-03-17 11:06:30 +01:00
|
|
|
return color.red < _blackborderThreshold && color.green < _blackborderThreshold && color.blue < _blackborderThreshold;
|
2013-08-23 07:08:44 +02:00
|
|
|
}
|
2014-01-20 20:46:38 +01:00
|
|
|
|
|
|
|
private:
|
2020-11-14 17:58:56 +01:00
|
|
|
/// Threshold for the black-border detector [0 .. 255]
|
2014-01-20 20:46:38 +01:00
|
|
|
const uint8_t _blackborderThreshold;
|
2016-02-07 13:26:40 +01:00
|
|
|
|
2013-08-23 07:08:44 +02:00
|
|
|
};
|
|
|
|
} // end namespace hyperion
|