블랙 햇은 어디에?


27

도전

임의의 xkcd 만화에서 패널 이미지를 제공하여 Blackhat이 만화에 있으면 true 값을, 그렇지 않으면 false를 반환하는 코드를 작성하십시오.

블랙 햇은 누구입니까?

Blackhat 은 검은 모자를 쓰고있는 xkcd 만화의 캐릭터에 부여 된 비공식 이름입니다.

Blackhat의 Explain xkcd 페이지에서 가져온

블랙 햇의 모자는 항상 똑 바르고 검은 색이며 위 이미지와 동일하게 보입니다.

다른 캐릭터들도 모자와 머리카락을 가질 수 있지만 검은 색과 직선을 가진 모자는 없습니다.

입력

이미지는 STDIN을 통해 이미지 또는 바이트 경로인지 여부에 관계없이 원하는대로 입력 될 수 있습니다. URL을 입력으로 사용할 필요는 없습니다.

규칙

답변을 하드 코딩하는 것은 금지되지 않지만 감사하지 않습니다.

답변을 얻기 위해 인터넷에 액세스 할 수 없습니다.

https://xkcd.com의 이미지에서 자른 모든 이미지

블랙 햇이 패널에 있습니다 (return 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이 만화에 있는지 여부를 정확하게 식별하는 프로그램이 승리합니다. 헤더에는 점수가 백분율로 포함되어야합니다.

타이 브레이크의 경우, 묶인 프로그램은 "미스터리"이미지 (즉, 내가 아는 것)를받습니다. 가장 정확하게 식별하는 코드가 순위 결정에서 승리합니다.

미스터리 이미지는 점수와 함께 공개됩니다.

참고 : 랜달의 이름은 Hat Guy 일 수 있습니다. 그래도 Blackhat을 선호합니다.


12
Mathematica에 내장되어 있다면 놀라지 않을 것입니다. ( 참고로 )
J. Sallé

5
다른 타이 브레이커에 대한 제안 : 여기에 공개되지 않은 다른 더 작은 이미지 세트 (예 : 5 개의 실제 사례 및 5 개의 거짓)가 있으며, 타이 브레이커의 승자는이 알 수없는 이미지에 가장 잘 맞는 것입니다. 그것은 이러한 특정 이미지에 비해 더 일반적인 스마트 솔루션에 인센티브를 줄 것입니다.
sundar-복원 모니카

3
경찰과 RIAA / MPAA의 테스트 사례는 사악합니다. 좋은 테스트 배터리, @BetaDecay.
sundar-복원 모니카


1
@ Night2 죄송합니다! 나는 넥타이를 만들려고 계획했다. 그래도 100 % 잘 했어요!
Beta Decay

답변:


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그 결과입니다.


순위는 코드 골프가 아닙니다. 대신, 타이 브레이크가 해결 될 때까지 프로그램에 숨겨진 테스트 사례가 제공됩니다. 그런 다음 결과를 말하고 테스트 사례를 게시합니다 :)
Beta Decay

1
@ BetaDecay : 설명을 해주셔서 감사합니다.이 문장 (동점에서 가장 짧은 승리)은 이전 버전의 질문에서 내 머리에 있었으므로 숨겨진 테스트 사례에서 넥타이가 발생하면 가장 짧은 코드가 승리한다고 생각했습니다. 내 잘못이야!
Night2

7
당신은 가장 가능성이 낮은 이미지 처리 언어에 대한 상을 수상 :)
Anush

@Anush 적어도 PHP는 imagerotate내장되어 있으므로 ...
user202729

내가 PHP에 대해 좋아하는 것은 거의 모든 것에 대한 기본 기능이 있다는 것입니다. 수년간 GD를 번들로 제공했으며 GD는 실제로 이미지 작업의 가장 일반적인 요구를 충족시킵니다. 그러나 PHP에 대해 더 좋아하는 점은 거대한 커뮤니티가 있기 때문에 항상 더 많은 확장 기능 / 패키지가 있다는 것입니다. 예를 들어, 실제 이미지 처리가 가능한 PHPOpenCV 확장 이 있습니다!
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;

Mnemonic에서 제안한 솔루션과 유사하지만 HSV 이미지의 V 채널을 기반으로 이미지 침식을 기반으로 접근합니다. 또한 선택한 영역의 채널 평균값이 확인됩니다 (크기가 아님).

HAT 세트의 분류 오류 : 이미지 4, 5, 10 .

NON HAT 세트의 분류 오류 : 이미지 4, 5, 6, 7, 13, 14 .


7

Pyth , 62.5 %

<214.O.n'z

stdin에서 이미지 파일의 파일 이름을 허용합니다. True모든 RGB 색상 구성 요소의 평균이 214보다 큰 경우를 반환 합니다. 오른쪽 내용을 읽습니다. 블랙 햇 이미지는 블랙 햇이없는 이미지보다 밝습니다.

(물론 누군가 더 잘할 수 있습니다. 이것은 가 아닙니다 !)


2
나는이 실현 될 때까지 나는 Pyth의 힘에 놀랐다 : D
베타 붕괴

"Pyth가 블랙 햇 이미지를 인식하기 위해 내장 된 이후로"생각했습니다
Luis felipe De jesus Munoz

2
62.5 %는 40 개 이미지 중 25 개입니다. 랜덤 추측 프로그램 (고정 된 시드를 사용하는 것)은나는=2540(40나는)2407.7%적어도 그만큼 잘하는 것.
user202729

6

파이썬 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.