PHP (> = 7), 100% (40/40)
<?php
set_time_limit(0);
class BlackHat
{
const ROTATION_RANGE = 45;
private $image;
private $currentImage;
private $currentImageWidth;
private $currentImageHeight;
public function __construct($path)
{
$this->image = imagecreatefrompng($path);
}
public function hasBlackHat()
{
$angles = [0];
for ($i = 1; $i <= self::ROTATION_RANGE; $i++) {
$angles[] = $i;
$angles[] = -$i;
}
foreach ($angles as $angle) {
if ($angle == 0) {
$this->currentImage = $this->image;
} else {
$this->currentImage = $this->rotate($angle);
}
$this->currentImageWidth = imagesx($this->currentImage);
$this->currentImageHeight = imagesy($this->currentImage);
if ($this->findBlackHat()) return true;
}
return false;
}
private function findBlackHat()
{
for ($y = 0; $y < $this->currentImageHeight; $y++) {
for ($x = 0; $x < $this->currentImageWidth; $x++) {
if ($this->isBlackish($x, $y) && $this->isHat($x, $y)) return true;
}
}
return false;
}
private function isHat($x, $y)
{
$hatWidth = $this->getBlackishSequenceSize($x, $y, 'right');
if ($hatWidth < 10) return false;
$hatHeight = $this->getBlackishSequenceSize($x, $y, 'bottom');
$hatLeftRim = $hatRightRim = 0;
for (; ; $hatHeight--) {
if ($hatHeight < 5) return false;
$hatLeftRim = $this->getBlackishSequenceSize($x, $y + $hatHeight, 'left');
if ($hatLeftRim < 3) continue;
$hatRightRim = $this->getBlackishSequenceSize($x + $hatWidth, $y + $hatHeight, 'right');
if ($hatRightRim < 2) $hatRightRim = $this->getBlackishSequenceSize($x + $hatWidth, $y + $hatHeight, 'right', 'isLessBlackish');
if ($hatRightRim < 2) continue;
break;
}
$ratio = $hatWidth / $hatHeight;
if ($ratio < 2 || $ratio > 4.2) return false;
$widthRatio = $hatWidth / ($hatLeftRim + $hatRightRim);
if ($widthRatio < 0.83) return false;
if ($hatHeight / $hatLeftRim < 1 || $hatHeight / $hatRightRim < 1) return false;
$pointsScore = 0;
if ($this->isSurroundedBy($x, $y, 3, true, true, false, false)) $pointsScore++;
if ($this->isSurroundedBy($x + $hatWidth, $y, 3, true, false, false, true)) $pointsScore++;
if ($this->isSurroundedBy($x, $y + $hatHeight, 3, false, false, true, false)) $pointsScore++;
if ($this->isSurroundedBy($x + $hatWidth, $y + $hatHeight, 3, false, false, true, false)) $pointsScore++;
if ($this->isSurroundedBy($x - $hatLeftRim, $y + $hatHeight, 3, true, true, true, false)) $pointsScore++;
if ($this->isSurroundedBy($x + $hatWidth + $hatRightRim, $y + $hatHeight, 3, true, false, true, true)) $pointsScore++;
if ($pointsScore < 3 || ($hatHeight >= 19 && $pointsScore < 4) || ($hatHeight >= 28 && $pointsScore < 5)) return false;
$middleCheckSize = ($hatHeight >= 15 ? 3 : 2);
if (!$this->isSurroundedBy($x + (int)($hatWidth / 2), $y, $middleCheckSize, true, null, null, null)) return false;
if (!$this->isSurroundedBy($x + (int)($hatWidth / 2), $y + $hatHeight, $middleCheckSize, null, null, true, null)) {
if (!$this->isSurroundedBy($x + (int)(($hatWidth / 4) * 3), $y + $hatHeight, $middleCheckSize, null, null, true, null)) return false;
}
if (!$this->isSurroundedBy($x, $y + (int)($hatHeight / 2), $middleCheckSize + 1, null, true, null, null)) return false;
if (!$this->isSurroundedBy($x + $hatWidth, $y + (int)($hatHeight / 2), $middleCheckSize, null, null, null, true)) return false;
$badBlacks = 0;
for ($i = 1; $i <= 3; $i++) {
if ($y - $i >= 0) {
if ($this->isBlackish($x, $y - $i)) $badBlacks++;
}
if ($x - $i >= 0 && $y - $i >= 0) {
if ($this->isBlackish($x - $i, $y - $i)) $badBlacks++;
}
}
if ($badBlacks > 2) return false;
$total = ($hatWidth + 1) * ($hatHeight + 1);
$blacks = 0;
for ($i = $x; $i <= $x + $hatWidth; $i++) {
for ($j = $y; $j <= $y + $hatHeight; $j++) {
$isBlack = $this->isBlackish($i, $j);
if ($isBlack) $blacks++;
}
}
if (($total / $blacks > 1.15)) return false;
return true;
}
private function getColor($x, $y)
{
return imagecolorsforindex($this->currentImage, imagecolorat($this->currentImage, $x, $y));
}
private function isBlackish($x, $y)
{
$color = $this->getColor($x, $y);
return ($color['red'] < 78 && $color['green'] < 78 && $color['blue'] < 78 && $color['alpha'] < 30);
}
private function isLessBlackish($x, $y)
{
$color = $this->getColor($x, $y);
return ($color['red'] < 96 && $color['green'] < 96 && $color['blue'] < 96 && $color['alpha'] < 40);
}
private function getBlackishSequenceSize($x, $y, $direction, $fn = 'isBlackish')
{
$size = 0;
if ($direction == 'right') {
for ($x++; ; $x++) {
if ($x >= $this->currentImageWidth) break;
if (!$this->$fn($x, $y)) break;
$size++;
}
} elseif ($direction == 'left') {
for ($x--; ; $x--) {
if ($x < 0) break;
if (!$this->$fn($x, $y)) break;
$size++;
}
} elseif ($direction == 'bottom') {
for ($y++; ; $y++) {
if ($y >= $this->currentImageHeight) break;
if (!$this->$fn($x, $y)) break;
$size++;
}
}
return $size;
}
private function isSurroundedBy($x, $y, $size, $top = null, $left = null, $bottom = null, $right = null)
{
if ($top !== null) {
$flag = false;
for ($i = 1; $i <= $size; $i++) {
if ($y - $i < 0) break;
$isBlackish = $this->isBlackish($x, $y - $i);
if (
($top && !$isBlackish) ||
(!$top && $isBlackish)
) {
$flag = true;
} elseif ($flag) {
return false;
}
}
if (!$flag) return false;
}
if ($left !== null) {
$flag = false;
for ($i = 1; $i <= $size; $i++) {
if ($x - $i < 0) break;
$isBlackish = $this->isBlackish($x - $i, $y);
if (
($left && !$isBlackish) ||
(!$left && $isBlackish)
) {
$flag = true;
} elseif ($flag) {
return false;
}
}
if (!$flag) return false;
}
if ($bottom !== null) {
$flag = false;
for ($i = 1; $i <= $size; $i++) {
if ($y + $i >= $this->currentImageHeight) break;
$isBlackish = $this->isBlackish($x, $y + $i);
if (
($bottom && !$isBlackish) ||
(!$bottom && $isBlackish)
) {
$flag = true;
} elseif ($flag) {
return false;
}
}
if (!$flag) return false;
}
if ($right !== null) {
$flag = false;
for ($i = 1; $i <= $size; $i++) {
if ($x + $i >= $this->currentImageWidth) break;
$isBlackish = $this->isBlackish($x + $i, $y);
if (
($right && !$isBlackish) ||
(!$right && $isBlackish)
) {
$flag = true;
} elseif ($flag) {
return false;
}
}
if (!$flag) return false;
}
return true;
}
private function rotate($angle)
{
return imagerotate($this->image, $angle, imagecolorallocate($this->image, 255, 255, 255));
}
}
$bh = new BlackHat($argv[1]);
echo $bh->hasBlackHat() ? 'true' : 'false';
Щоб запустити його:
php <filename> <image_path>
Приклад:
php black_hat.php "/tmp/blackhat/1.PNG"
Примітки
- Друкує "true", якщо знаходить чорну шапку та "false", якщо не знаходить її.
- Це має працювати і в попередніх версіях PHP, але для безпечності використовуйте PHP> = 7 з GD .
- Цей сценарій насправді намагається знайти капелюх, і, роблячи це, він може обертати зображення багато разів і кожен раз перевіряти тисячі і тисячі пікселів і підказок. Отже, чим більшим є зображення, чи більше темних пікселів, сценарій займе більше часу для його завершення. Однак для більшості зображень це потребує декількох секунд до хвилини.
- Я хотів би більше тренувати цей сценарій, але мені не вистачає часу на це.
- Цей сценарій не є полем для гольфу (знову ж таки тому, що у мене недостатньо часу), але він має багато потенціалу для гри в гольф у разі нічиїх матчів.
Деякі приклади виявлених чорних шапок:
Ці приклади набуваються шляхом малювання червоних ліній на спеціальних точках, знайдених на зображенні, за яким сценарій вирішив, що має чорну шапку (зображення можуть мати обертання порівняно з оригінальними).
Додатково
Перш ніж публікувати тут, я протестував цей скрипт на іншому наборі з 15 зображень, 10 з чорною шапочкою та 5 без чорної шапки, і він вийшов правильним для всіх із них (100%).
Ось файл ZIP, який містить додаткові тестові зображення, які я використав: extra.zip
У extra/blackhat
каталозі також доступні результати виявлення з червоними лініями. Наприклад extra/blackhat/1.png
, тестове зображення і extra/blackhat/1_r.png
є результатом його виявлення.