ブラックハットはどこ?


27

チャレンジ

ランダムなxkcdコミックからパネルの画像が与えられると、Blackhatがコミックにある場合は真実の値を返し、そうでない場合は偽を返すコードを記述します。

ブラックハットとは誰ですか?

Blackhatは、黒い帽子をかぶったxkcdコミックのキャラクターに付けられた非公式の名前です。

BlackhatのExplain xkcdページから取得

ブラックハットの帽子は常に真っすぐな黒で、上の画像と同じように見えます。

他のキャラクターも帽子と髪を持っているかもしれませんが、黒で真っすぐな帽子はありません。

入力

画像は、画像へのパスまたはSTDIN経由のバイトであるかどうかに関係なく入力できます。入力としてURLを使用する必要はありません。

ルール

答えをハードコーディングすることは禁止されていませんが、高く評価されていません。

答えを得るためにインターネットにアクセスすることは許可されていません。

https://xkcd.comの画像から切り取られたすべての画像

Blackhatはパネルにあります(戻るtruthy


Blackhatはパネルにありません(戻るfalsey


テストバッテリー

Blackhatを含む20の画像は、https://beta-decay.github.io/blackhat.zipにあります

Blackhatを含まない20個の画像は、https://beta-decay.github.io/no_blackhat.zipにあります。

(ミステリーテストケースのためにトレーニングするために)プログラムをテストするためにさらに画像が必要な場合は、Blackhatのすべての外観のリストをここで見つけることができます:http://www.explainxkcd.com/wiki/index.php/Category: Comics_featuring_Black_Hat

勝ち

Blackhatがほとんどの画像でコミックに載っているかどうかを正しく識別するプログラムが勝ちです。ヘッダーには、スコアをパーセンテージで含める必要があります。

タイブレークが発生した場合、関連付けられたプログラムには「ミステリー」イメージ(つまり、私だけが知っているイメージ)が与えられます。最も正確に識別するコードがタイブレークに勝ちます。

スコアとともにミステリー画像が明らかになります。

注:ランドールの名前はハットガイかもしれません。私はブラックハットが好きです。


12
Mathematicaがそのための組み込み機能を持っているとしても驚かないでしょう。(参照用
J.サレ

5
別のタイブレーカーの提案:ここでは明らかにされていない、異なる、より小さい画像セット(たとえば、5つの真のケースと5つの偽)があり、タイブレーカーの勝者は、これらの未知の画像に最も一般的なものです。それは、これらの特定の画像に過剰に適合するソリューションよりも、より一般的でスマートなソリューションを奨励します。
スンダ

3
警察とRIAA / MPAAを使用したテストケースは、単なる悪です。良いテスト用バッテリー、@ BetaDecay。
スンダ


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でも機能するはずですが、安全のために、GDで PHP> = 7を使用してください。
  • このスクリプトは実際に帽子を見つけようとします。そうすることで、画像を何度も回転させ、そのたびに何千、何千ものピクセルと手がかりをチェックします。そのため、画像が大きいほど、または暗いピクセルが多いほど、スクリプトの終了に時間がかかります。ただし、ほとんどの画像では数秒から1分かかります。
  • このスクリプトをさらにトレーニングしたいと思いますが、そのための十分な時間がありません。
  • このスクリプトはゴルフをしていません(これも時間が足りないためです)が、タイの場合はゴルフをする可能性がたくさんあります。

検出された黒い帽子の例:

ここに画像の説明を入力してください

これらの例は、スクリプトが黒い帽子を持っていると判断した画像上で見つかった特別なポイントに赤い線を描画することで取得されます(画像は元のものと比較して回転することができます)。


追加

ここに投稿する前に、このスクリプトを別の15枚の画像セットに対してテストしました。10枚はブラックハット、5枚はブラックハットなしで、すべて同じように(100%)正常でした。

ここに、私が使用した追加のテスト画像を含むZIPファイルがあります:extra.zip

ではextra/blackhat、ディレクトリ、赤い線との検出結果も利用できます。たとえばextra/blackhat/1.png、テスト画像でextra/blackhat/1_r.pngあり、その検出結果です。


タイブレークはコードゴルフではありません。代わりに、タイブレークが解決されるまで、プログラムには隠されたテストケースが送られます。その後、結果を
ベータ崩壊

1
@BetaDecay:明確化のおかげで、この文(タイでの最短の勝ち)は質問の以前のバージョンから私の頭の中にありました。私の悪い!
Night2

7
あなたはあまりありそうもない画像処理言語の賞も受賞します:)
Anush

@Anushまあ、少なくともPHPにはimagerotateビルトインがあるので
...-user202729

私がPHPで気に入っているのは、ほぼすべての基本機能を備えていることです。GDは長年にわたってバンドルされており、GDは実際に画像を操作する最も一般的なニーズを満たしています。しかし、私がPHPでより気に入っているのは、(巨大なコミュニティがあるために)より多くの拡張機能/パッケージが常にあるということです。たとえば、実際の画像処理を実行できるPHP用のOpenCV拡張機能があります!
Night2

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

候補領域の形状にいくつかのチェックを追加した、以前のバージョンの機能強化。

分類エラー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;

ニーモニックによって提案された解決策に似ていますが、HSV画像のVチャネルに基づいた、画像収縮に基づくアプローチ。さらに、選択された領域のチャネルの平均値がチェックされます(サイズではありません)。

分類エラー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%は40個の画像のうち25個です。(固定された種子、そのようなもので)ランダム推測プログラムの確率だろうのを少なくともそれと同じくらい良いことをしています。i=2540(40i)2407.7%
user202729

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

これにより、どのピクセルが黒であるかがわかり、隣接する小さな破片が侵食されます。ここには確かに改善の余地があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.