Де Блекхат?


27

Виклик

Напишіть код, який, даючи зображення панелі з випадкового коміксу xkcd, повертає трибунове значення, якщо Blackhat знаходиться в коміксі або фальси, якщо ні.

Хто такий Блекхат?

Блекхат - це неофіційне ім'я, яке дається персонажу в коміксах xkcd, який носить чорну шапку:

Взяте зі сторінки Поясніть xkcd на Blackhat

Капелюх Блекхата завжди прямобічний, чорний і виглядає так само, як на зображенні вище.

Інші персонажі також можуть мати шапки та волосся, але жоден не матиме шапки, які мають чорний та прямий бік.

Вхідні дані

Зображення може бути введене в будь-якому іншому випадку, будь то шлях до зображення або байти через STDIN. Вам не потрібно брати URL-адресу як вхідну інформацію.

Правила

Жорстке кодування відповіді не заборонено, але вона не оцінюється.

Вам не дозволяється отримувати доступ до Інтернету, щоб отримати відповідь.

Приклади

Усі зображення, обрізані із зображень із https://xkcd.com

Blackhat знаходиться в панелі (повернення truthy)


Blackhat немає на панелі (повернення falsey)


Тестова батарея

З 20 зображеннями, які містять Blackhat, можна знайти тут: https://beta-decay.github.io/blackhat.zip

З 20 зображеннями, які не містять Blackhat, можна знайти тут: https://beta-decay.github.io/no_blackhat.zip

Якщо ви хочете, щоб більше зображень для тестування ваших програм (щоб пройти підготовку до таємницьких тестових випадків), ви можете знайти список усіх видань Blackhat тут: http://www.explainxkcd.com/wiki/index.php/Category: Comics_featuring_Black_Hat

Перемога

Перемагає програма, яка правильно визначає, чи є Блекхат у комічному чи ні для більшості зображень. Ваш заголовок повинен включати ваш рахунок у відсотках.

У разі переривання зв’язаних програм надаватимуться «таємничі» зображення (тобто ті, про які я лише знаю). Код, який найбільш правильно визначає, виграє тайбрек.

Образи таємниці будуть розкриті разом з балами.

Зауважте: схоже, що ім'ям Рендала може бути Хат Гай. Хоча я вважаю за краще Блекхат.


12
Я не буду здивований, якщо Mathematica має для цього вбудований. ( Для довідки )
J. Sallé

5
Пропозиція щодо іншого вимикача краватки: мати інший менший набір зображень (скажімо, 5 справжніх випадків і 5 помилкових), які тут не розкриваються, а переможець вимикача краватки - той, хто найкраще узагальнює ці невідомі зображення. Це стимулюватиме більш загальні розумніші рішення проти тих, які підходять до цих конкретних образів.
sundar

3
Тестові випадки з поліцією та з RIAA / MPAA - просто зло. Хороший тестовий акумулятор, @BetaDecay.
sundar


1
@ Night2 Вибачте! Я тільки планував, щоб будь-який з них був краваткою. Хороша робота на 100%, хоча!
Бета-розпад

Відповіді:


16

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є результатом його виявлення.


Тіребрек не є кодом гольфу. Натомість програми подаються прихованими тестовими кейсами, доки не буде вирішено перерив зв'язку. Тоді я скажу вам результат та опублікую тестові випадки :)
Бета-розпад

1
@BetaDecay: Дякую за роз’яснення, це речення (найкоротший виграш на нічию) було в моїй голові від попередніх версій запитання, тому я думав, що якщо нічия трапиться на прихованих тестових випадках, то найкоротший код виграє. Моє ліжко!
Ніч2

7
Ви виграєте приз також за найменш ймовірну мову обробки зображень :)
Anush

@Anush Ну принаймні PHP має imagerotateвбудований, так що ...
user202729

Що мені подобається в PHP, це те, що він має деякі основні функціональні можливості майже для будь-чого. Він накопичує GD стільки років, і GD фактично задовольняє найпоширеніші потреби роботи із зображеннями. Але що мені більше подобається в PHP, це те, що завжди є деякі розширення / пакети, які дадуть вам більше (через те, що вони мають величезну спільноту). Наприклад, є розширення OpenCV для PHP, які дозволяють виконувати фактичну обробку зображень!
Ніч2

8

Матлаб, 87,5%

function hat=is_blackhat_here2(filepath)

img_hsv = rgb2hsv(imread(filepath));
img_v = img_hsv(:,:,3);

bw = imdilate(imerode( ~im2bw(img_v), strel('disk', 4, 8)), strel('disk', 4, 8));
bw = bwlabel(bw, 8);
bw = imdilate(imerode(bw, strel('disk', 1, 4)), strel('disk', 1, 4));
bw = bwlabel(bw, 4);

region_stats = regionprops(logical(bw), 'all');
hat = false;
for i = 1 : numel(region_stats)
    if mean(img_v(region_stats(i).PixelIdxList)) < 0.15 ...
            && region_stats(i).Area > 30 ...
            && region_stats(i).Solidity > 0.88 ...
            && region_stats(i).Eccentricity > 0.6 ...
            && region_stats(i).Eccentricity < 1 ...
            && abs(region_stats(i).Orientation) < 75...
            && region_stats(i).MinorAxisLength / region_stats(i).MajorAxisLength < 0.5;
        hat = true;
        break;
    end
end

Вдосконалення попередньої версії із додаванням деяких перевірок форми регіонів-кандидатів.

Помилки класифікації в наборі HAT : зображення 4, 14, 15, 17 .

Помилки класифікації в наборі NON HAT : зображення 4 .

Деякі приклади виправлених класифікованих зображень: введіть тут опис зображення введіть тут опис зображення

Приклад неправильного класифікованого зображення:

введіть тут опис зображення

СТАРА ВЕРСІЯ (77,5%)

function hat=is_blackhat_here(filepath)

img_hsv = rgb2hsv(imread(filepath));
img_v = img_hsv(:,:,3);
bw = imerode(~im2bw(img_v), strel('disk', 5, 8));

hat =  mean(img_v(bw)) < 0.04;

Підхід, заснований на ерозії зображення, подібний до рішення, запропонованого Mnemonic, але заснований на V каналі зображення HSV. Причому перевіряється середнє значення каналу вибраної області (не його розмір).

Помилки класифікації в наборі HAT : зображення 4, 5, 10 .

Помилки класифікації в наборі NON HAT : зображення 4, 5, 6, 7, 13, 14 .


7

Піт , 62,5%

<214.O.n'z

Приймає ім'я файлу зображень на stdin. Повертається, Trueякщо середнє значення всіх його кольорів RGB більше 214. Ви читали це право: мабуть, зображення чорного кольору, як правило, яскравіші, ніж зображення без чорного кольору.

(Напевно, хтось може зробити краще - це не !)


2
Я був вражений силою Pyth, поки не зрозумів: D
бета-розпад

На мить я подумав: "З тих пір, коли Pyth має вбудований для розпізнавання чорних зображень",
Луїс Феліпе Де ісус Муноз,

2
62,5% - це 25 із 40 зображень. Випадкове вгадування програми (з фіксованими насінням, що - щось подібне) матиме можливість від робити принаймні так добре, як це. i=2540(40i)2407.7%
користувач202729

6

Python 2, 65% 72,5% 77,5% (= 31/40)

import cv2
import numpy as np
from scipy import misc

def blackhat(path):
    im = misc.imread(path)
    black = (im[:, :, 0] < 10) & (im[:, :, 1] < 10) & (im[:, :, 2] < 10)
    black = black.astype(np.ubyte)

    black = cv2.erode(black, np.ones((3, 3)), iterations=3)

    return 5 < np.sum(black) < 2000

Це визначає, які пікселі чорні, потім розмиває невеликі суміжні шматки. Звичайно, тут є можливість для вдосконалення.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.