黑帽在哪里?


27

挑战

编写代码,给定一个来自随机xkcd漫画的面板图像,如果Blackhat出现在漫画中,则返回真实值;否则返回falsey。

谁是黑帽?

Blackhat是给戴着黑帽子的xkcd漫画中的角色赋予的非正式名称:

取自Blackhat的Explain xkcd页面

Blackhat的帽子始终是直边的,黑色的,外观与上图中的相同。

其他角色可能也有帽子和头发,但没有一个人会戴着黑色和直边的帽子。

输入值

您可以通过STDIN以任何方式输入图像,无论是图像的路径还是字节。您不需要将URL作为输入。

规则

禁止对答案进行硬编码,但是不胜感激。

您无权访问互联网以获得答案。

例子

所有图片均来自https://xkcd.com中的图片

面板中有Blackhat(返回truthy


黑帽不在面板中(返回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是否在漫画中占大多数。标头应包括分数。

在抢七的情况下,捆绑的程序将获得“神秘”图像(即,只有我知道的图像)。确定最正确的代码将赢得平局。

神秘的图像将与分数一同显示。

注意: Randall的名字似乎是Hat Guy。我更喜欢Blackhat。


12
如果Mathematica具有内置功能,我不会感到惊讶。(供参考
J.Sallé18年

5
建议使用不同的决胜局:拥有一组较小的图像(例如5个真实案例和5个假),这些图像在这里没有透露,并且决胜局的赢家是最能概括这些未知图像的图像。与过度适应这些特定图像的解决方案相比,这将激励更通用的更智能解决方案。
sundar-恢复莫妮卡

3
警察和RIAA / MPAA的测试案例简直是邪恶的。好的测试电池,@ BetaDecay。
sundar-恢复莫妮卡


1
@ Night2对不起!我只打算打领带。不错,虽然100%都可以!
Beta Decay '18

Answers:


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是检测图像。


抢七不是代码高尔夫。取而代之的是,向程序提供隐藏的测试用例,直到解决抢七。然后,我将告诉您结果并发布测试用例:)
Beta Decay,

1
@BetaDecay:感谢您的澄清,这句话(最短的并发胜利)在我以前的问题版本中是我的脑海,所以我想如果隐藏测试用例发生并发,则最短的代码会获胜。我的错!
夜间2

7
您也以最不可能的图像处理语言赢得了奖:)
Anush

@Anush至少PHP imagerotate内置了,所以...
user202729 '18

我喜欢PHP的地方在于它几乎具有所有功能。它已经捆绑了GD多年,实际上GD满足了处理图像的最常见需求。但是我更喜欢PHP的是,总是有一些扩展/软件包可以为您提供更多(因为拥有庞大的社区)。例如,有适用于PHP的OpenCV扩展,可以完成实际的图像处理!
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

对先前版本的增强,对候选区域的形状进行了一些检查。

在分类误差HAT组:图像4,14,15,17

NON HAT set中的分类错误:图像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提出的解决方案,但基于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
Beta Decay

我一下子想到了“自从Pyth内置了用于识别黑帽图像的内容以来”
Luis felipe De jesus Munoz

2
一世=254040一世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

这样可以找出哪些像素为黑色,然后侵蚀掉连续的小块。当然这里还有改进的空间。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.