Blackhat đâu rồi?


27

Thử thách

Viết mã, được đưa ra một hình ảnh của bảng điều khiển từ truyện tranh xkcd ngẫu nhiên, trả về giá trị trung thực nếu Blackhat có trong truyện tranh hoặc chim ưng nếu không.

Blackhat là ai?

Blackhat là tên không chính thức được đặt cho nhân vật trong truyện tranh xkcd đội mũ đen:

Lấy từ trang Giải thích xkcd trên Blackhat

Mũ của Blackhat luôn thẳng, màu đen và trông giống như trong hình trên.

Các nhân vật khác cũng có thể có mũ và tóc nhưng không ai có mũ màu đen và thẳng.

Đầu vào

Hình ảnh có thể được nhập vào bằng mọi cách bạn muốn cho dù đó là đường dẫn đến hình ảnh hoặc byte thông qua STDIN. Bạn không cần phải lấy một URL làm đầu vào.

Quy tắc

Hardcoding câu trả lời không bị cấm, nhưng nó không được đánh giá cao.

Bạn không được phép truy cập internet để có câu trả lời.

Ví dụ

Tất cả hình ảnh được cắt từ hình ảnh từ https://xkcd.com

Blackhat nằm trong bảng điều khiển (trở lại truthy)


Blackhat không có trong bảng điều khiển (trở lại falsey)


Kiểm tra pin

20 hình ảnh có chứa Blackhat có thể được tìm thấy ở đây: https://beta-decay.github.io/blackhat.zip

20 hình ảnh không chứa Blackhat có thể được tìm thấy ở đây: https://beta-decay.github.io/no_blackhat.zip

Nếu bạn muốn có thêm hình ảnh để kiểm tra các chương trình của mình với (để huấn luyện cho các trường hợp thử nghiệm bí ẩn), bạn có thể tìm thấy danh sách tất cả các lần xuất hiện của Blackhat tại đây: http://www.explainxkcd.com/wiki/index.php/C Category : Truyện tranh_featuring_Black_Hat

Chiến thắng

Chương trình xác định chính xác liệu Blackhat có trong truyện tranh hay không cho hầu hết các hình ảnh chiến thắng. Tiêu đề của bạn nên bao gồm điểm số của bạn dưới dạng phần trăm.

Trong trường hợp tiebreak, các chương trình gắn liền sẽ được cung cấp hình ảnh "bí ẩn" (tức là những chương trình mà chỉ tôi biết). Mã xác định chính xác nhất sẽ thắng tiebreak.

Những hình ảnh bí ẩn sẽ được tiết lộ cùng với điểm số.

Lưu ý: có vẻ như tên của Randall cho anh ta có thể là Hat Guy. Tôi thích Blackhat hơn.


12
Tôi sẽ không ngạc nhiên nếu Mathicala tích hợp sẵn cho điều đó. ( Để tham khảo )
J. Sallé

5
Gợi ý cho một bộ ngắt kết nối khác nhau: có một bộ hình ảnh khác, nhỏ hơn (giả sử 5 trường hợp đúng và 5 sai) chưa được tiết lộ ở đây và người chiến thắng của bộ ngắt kết nối là hình ảnh tổng quát nhất cho những hình ảnh chưa biết này. Điều đó sẽ khuyến khích các giải pháp thông minh hơn chung chung so với các giải pháp phù hợp với những hình ảnh cụ thể này.
- Phục hồi lại

3
Các trường hợp thử nghiệm với cảnh sát và với RIAA / MPAA chỉ là xấu xa. Kiểm tra pin tốt, @BetaDecay.
- Phục hồi Monica


1
@ Night2 Xin lỗi! Tôi chỉ có kế hoạch để làm cho bất kỳ trong số đó đã được buộc. Mặc dù công việc tốt đẹp 100%!
Beta Decay

Câu trả lời:


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

Để chạy nó:

php <filename> <image_path>

Thí dụ:

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

Ghi chú

  • In "đúng" nếu tìm thấy mũ đen và "sai" nếu không tìm thấy.
  • Điều này cũng hoạt động trên các phiên bản trước của PHP, nhưng để an toàn, hãy sử dụng PHP> = 7 với GD .
  • Kịch bản này thực sự cố gắng tìm chiếc mũ và bằng cách đó, nó có thể xoay hình ảnh nhiều lần và mỗi lần kiểm tra hàng ngàn và hàng nghìn pixel và manh mối. Vì vậy, hình ảnh càng lớn hoặc càng có nhiều pixel tối, kịch bản sẽ mất nhiều thời gian hơn để hoàn thành. Mặc dù vậy, sẽ mất vài giây đến một phút cho phần lớn hình ảnh.
  • Tôi rất thích đào tạo kịch bản này nhiều hơn, nhưng tôi không có đủ thời gian để làm điều đó.
  • Kịch bản này không được đánh gôn (một lần nữa vì tôi không có đủ thời gian), nhưng có rất nhiều tiềm năng để chơi golf trong trường hợp hòa.

Một số ví dụ về mũ đen được phát hiện:

nhập mô tả hình ảnh ở đây

Những ví dụ này có được bằng cách vẽ các đường màu đỏ trên các điểm đặc biệt được tìm thấy trên hình ảnh mà tập lệnh quyết định có một chiếc mũ màu đen (hình ảnh có thể có góc quay so với hình gốc).


Thêm

Trước khi đăng ở đây, tôi đã thử nghiệm kịch bản này với một bộ 15 hình ảnh khác, 10 với mũ đen và 5 không có mũ đen và nó cũng đúng với tất cả chúng (100%).

Đây là tệp ZIP chứa hình ảnh thử nghiệm bổ sung mà tôi đã sử dụng: Extra.zip

Trong extra/blackhatthư mục, kết quả phát hiện với các dòng màu đỏ cũng có sẵn. Ví dụ extra/blackhat/1.pnglà hình ảnh thử nghiệm và extra/blackhat/1_r.pnglà kết quả phát hiện của nó.


Các tiebreak không mã golf. Thay vào đó, các chương trình được cho ăn các trường hợp kiểm tra ẩn cho đến khi giải quyết hòa vốn. Sau đó tôi sẽ cho bạn biết kết quả và đăng các trường hợp thử nghiệm :)
Beta Decay

1
@BetaDecay: Cảm ơn bạn đã làm rõ, câu này (chiến thắng ngắn nhất trên cà vạt) nằm trong đầu tôi từ các phiên bản trước của câu hỏi, vì vậy tôi đã nghĩ rằng nếu một sự ràng buộc xảy ra trong các trường hợp kiểm tra ẩn, thì mã ngắn nhất sẽ thắng. Lỗi của tôi!
Đêm2

7
Bạn cũng giành được giải thưởng cho ngôn ngữ xử lý hình ảnh ít có khả năng nhất :)
Anush

@Anush Ít nhất PHP cũng imagerotateđược tích hợp sẵn, vì vậy ...
user202729

Điều tôi thích về PHP là nó có một số chức năng cơ bản cho hầu hết mọi thứ. Nó đã đóng gói GD trong nhiều năm và GD thực sự đáp ứng hầu hết các nhu cầu phổ biến khi làm việc với hình ảnh. Nhưng điều tôi thích hơn ở PHP là luôn có một số phần mở rộng / gói sẽ cung cấp cho bạn nhiều hơn (vì có một cộng đồng lớn). Ví dụ, có các phần mở rộng OpenCV cho PHP cho phép xử lý hình ảnh thực tế được thực hiện!
Đêm2

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

Cải tiến của phiên bản trước, với một số kiểm tra được thêm vào hình dạng của các khu vực ứng cử viên.

Lỗi phân loại trong bộ HAT : hình ảnh 4, 14, 15, 17 .

Lỗi phân loại trong bộ NON HAT : hình ảnh 4 .

Một số ví dụ về hình ảnh được phân loại chính xác: nhập mô tả hình ảnh ở đây nhập mô tả hình ảnh ở đây

Ví dụ về một hình ảnh phân loại sai:

nhập mô tả hình ảnh ở đây

PHIÊN BẢN OLD (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;

Cách tiếp cận dựa trên xói mòn hình ảnh, tương tự như giải pháp được đề xuất bởi Mnemonic, nhưng dựa trên kênh V của hình ảnh HSV. Hơn nữa, giá trị trung bình của kênh của khu vực được chọn được kiểm tra (không phải kích thước của nó).

Lỗi phân loại trong bộ HAT : hình ảnh 4, 5, 10 .

Lỗi phân loại trong bộ NON HAT : hình ảnh 4, 5, 6, 7, 13, 14 .


7

Bình thường , 62,5%

<214.O.n'z

Chấp nhận tên tệp của tệp hình ảnh trên stdin. Trả về Truenếu trung bình của tất cả các thành phần màu RGB của nó lớn hơn 214. Bạn đọc đúng: hình ảnh rõ ràng có xu hướng sáng hơn so với hình ảnh không có màu đen.

(Chắc chắn ai đó có thể làm tốt hơn nữa, đây không phải là !)


2
Tôi ngạc nhiên trước sức mạnh của Pyth cho đến khi tôi nhận ra: D
Beta Decay

Ngay lập tức tôi đã nghĩ "Kể từ khi Pyth tích hợp để nhận ra hình ảnh đen"
Luis felipe De jesus Munoz

2
i=2540(40i)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

Điều này chỉ ra những pixel nào có màu đen, sau đó ăn mòn những mảnh nhỏ liền kề nhau. Chắc chắn phòng để cải thiện ở đây.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.