Dov'è Blackhat?


27

Sfida

Scrivi un codice che, dato l'immagine di un pannello da un fumetto casuale di xkcd, restituisca un valore di verità se Blackhat è nel fumetto o in caso contrario falso.

Chi è Blackhat?

Blackhat è il nome non ufficiale dato al personaggio dei fumetti xkcd che indossa un cappello nero:

Tratto dalla pagina Spiega xkcd su Blackhat

Il cappello di Blackhat è sempre dritto, nero e ha lo stesso aspetto dell'immagine sopra.

Altri personaggi possono anche avere cappelli e capelli, ma nessuno avrà cappelli neri e schierati.

Ingresso

L'immagine può essere inserita in qualsiasi modo tu voglia che sia un percorso per l'immagine o byte tramite STDIN. Non dovresti avere bisogno di prendere un URL come input.

Regole

La risposta hardcoding non è vietata, ma non è apprezzata.

Non puoi accedere a Internet per ottenere la risposta.

Esempi

Tutte le immagini ritagliate da immagini da https://xkcd.com

Blackhat è nel pannello (ritorno truthy)


Blackhat non è nel pannello (ritorno falsey)


Test batteria

Le 20 immagini che contengono Blackhat sono disponibili qui: https://beta-decay.github.io/blackhat.zip

Le 20 immagini che non contengono Blackhat sono disponibili qui: https://beta-decay.github.io/no_blackhat.zip

Se vuoi più immagini con cui testare i tuoi programmi (per allenarti per i casi di test misteriosi), puoi trovare un elenco di tutte le apparenze di Blackhat qui: http://www.explainxkcd.com/wiki/index.php/Categoria: Comics_featuring_Black_Hat

vincente

Vince il programma che identifica correttamente se Blackhat è nel fumetto o meno per la maggior parte delle immagini. La tua intestazione dovrebbe includere il tuo punteggio in percentuale.

In caso di pareggio, ai programmi collegati verranno date immagini "misteriose" (cioè quelle che solo io conosco). Il codice che identifica il più correttamente vince il tiebreak.

Le immagini misteriose verranno svelate insieme ai punteggi.

Nota: sembra che il nome di Randall per lui possa essere Hat Guy. Preferisco Blackhat però.


12
Non sarò sorpreso se Mathematica ha un built-in per questo. ( Per riferimento )
J. Sallé,

5
Suggerimento per un diverso tie breaker: avere un set di immagini diverso, più piccolo (diciamo 5 casi veri e 5 falsi) che non sono stati rivelati qui, e il vincitore del tie breaker è quello che generalizza meglio queste immagini sconosciute. Ciò incentiverebbe le soluzioni più generiche più intelligenti rispetto a quelle che si adattano a queste immagini specifiche.
Sundar - Ripristina Monica il

3
I casi di test con la polizia e con la RIAA / MPAA sono semplicemente malvagi. Buona batteria di prova, @BetaDecay.
Sundar - Ripristina Monica


1
@ Night2 Siamo spiacenti! Avevo programmato di fare solo una cravatta. Bel lavoro al 100% però!
Decadimento beta

Risposte:


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';

Per eseguirlo:

php <filename> <image_path>

Esempio:

php black_hat.php "/tmp/blackhat/1.PNG"

Gli appunti

  • Stampa "vero" se trova cappello nero e "falso" se non lo trova.
  • Questo dovrebbe funzionare anche su versioni precedenti di PHP, ma per sicurezza, usa PHP> = 7 con GD .
  • Questo script in realtà cerca di trovare il cappello e, così facendo, potrebbe ruotare l'immagine molte volte e ogni volta controlla migliaia e migliaia di pixel e indizi. Quindi, più grande è l'immagine o più pixel scuri ha, lo script impiegherà più tempo per terminare. Tuttavia, la maggior parte delle immagini dovrebbe richiedere da pochi secondi a un minuto.
  • Mi piacerebbe allenare di più questo script, ma non ho abbastanza tempo per farlo.
  • Questa sceneggiatura non è giocata a golf (di nuovo perché non ho abbastanza tempo), ma ha molto potenziale per giocare a golf in caso di pareggio.

Alcuni esempi di cappelli neri rilevati:

inserisci qui la descrizione dell'immagine

Questi esempi vengono acquisiti disegnando linee rosse su punti speciali trovati nell'immagine che lo script ha deciso di avere un cappello nero (le immagini possono avere una rotazione rispetto a quelle originali).


Extra

Prima di postare qui, ho testato questo script con un altro set di 15 immagini, 10 con cappello nero e 5 senza cappello nero ed è andato bene anche per tutte (100%).

Ecco il file ZIP contenente immagini di prova extra che ho usato: extra.zip

Nella extra/blackhatdirectory sono disponibili anche i risultati del rilevamento con linee rosse. Ad esempio extra/blackhat/1.pngè l'immagine di prova ed extra/blackhat/1_r.pngè il risultato della sua rilevazione.


Il tiebreak non è il golf del codice. Al contrario, i programmi vengono alimentati in casi di test nascosti fino alla risoluzione del pareggio. Ti dirò quindi il risultato e pubblicherò i casi di test :)
Decadimento beta

1
@BetaDecay: Grazie per il chiarimento, questa frase (vittorie più brevi in ​​pareggio) era nella mia testa dalle versioni precedenti della domanda, quindi stavo pensando che se si verifica un pareggio in casi di test nascosti, vince il codice più breve. Colpa mia!
Night2

7
Vinci anche il premio per il linguaggio di elaborazione delle immagini meno probabile :)
Anush,

@Anush Beh, almeno PHP ha imagerotateincorporato, quindi ...
user202729

Quello che mi piace di PHP è che ha alcune funzionalità di base per quasi tutto. Ha raggruppato GD per così tanti anni e GD in realtà soddisfa le esigenze più comuni di lavorare con le immagini. Ma quello che mi piace di più di PHP è che ci sono sempre alcune estensioni / pacchetti che ti daranno di più (a causa della grande comunità). Ad esempio ci sono estensioni OpenCV per PHP che consentono di eseguire l'elaborazione effettiva delle immagini!
Notte 2

8

Matlab, 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

Miglioramento della versione precedente, con alcuni controlli aggiunti sulla forma delle regioni candidate.

Errori di classificazione nel set HAT : immagini 4, 14, 15, 17 .

Errori di classificazione nel set NON HAT : immagini 4 .

Alcuni esempi di immagini classificate corrette: inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine

Esempio di un'immagine classificata errata:

inserisci qui la descrizione dell'immagine

VECCHIA VERSIONE (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;

Approccio basato sull'erosione dell'immagine, simile alla soluzione proposta da Mnemonic, ma basato sul canale V dell'immagine HSV. Inoltre, viene controllato il valore medio del canale dell'area selezionata (non la sua dimensione).

Errori di classificazione nel set HAT : immagini 4, 5, 10 .

Errori di classificazione nel set NON HAT : immagini 4, 5, 6, 7, 13, 14 .


7

Pyth , 62,5%

<214.O.n'z

Accetta il nome file di un file immagine su stdin. ritornaTrue se la media di tutti i suoi componenti di colore RGB è maggiore di 214. Hai letto bene: le immagini apparentemente nere tendono ad essere più luminose delle immagini non nere.

(Sicuramente qualcuno può fare di meglio, questo non è !)


2
Sono rimasto stupito dal potere di Pyth fino a quando ho realizzato: D
Beta Decay il

Per un istante ho pensato "Da quando Pyth ha incorporato un riconoscimento per le immagini di blackhat"
Luis felipe De jesus Munoz,

2
62,5% è 25 su 40 immagini. Un programma di ipotesi casuale (con seme fisso, qualcosa del genere) avrebbe una probabilità diΣio=2540(40io)2407.7%

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

Questo determina quali pixel sono neri, quindi erode piccoli pezzi contigui. Sicuramente margini di miglioramento qui.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.