饥饿的图像蛇-第3孔


25

1号洞

蛇乔饿了。

他吃饭,一次只吃一个像素。

他真的很喜欢明亮的像素。

挑战

编程让Joe吃掉他能找到的最明亮的像素,因为他只能上下左右移动。

技术指标

  • Joe必须从图像的左上像素开始。
  • Joe只能水平或垂直移动1次
  • Joe仅有足够的时间移动图片中像素的1/3(移动量是像素的1/3)。如果像素数不是3的倍数,则四舍五入到最接近的整数。
  • 乔可能会越过小道,尽管那算为0亮度
  • 亮度基于r,g和b的总和,因此rgb(0,0,0)的亮度为0,而rgb(255,255,255)的亮度最大。

输入项

您可以随意输入图像。

输出量

  • 显示图片最终结果的图像(黑色被吞噬了像素)。
  • 消耗的亮度量(请在答案中指定范围)

计分

您的课程的评分依据:

  • 乔吃的像素的平均亮度/图片中像素的平均亮度*

*您可以在程序中对此进行硬编码

您的总分将是以下图像的平均分:

测试图像:

在此处输入图片说明

在此处输入图片说明

http://upload.wikimedia.org/wikipedia/en/thumb/f/f4/The_Scream.jpg/800px-The_Scream.jpg

在此处输入图片说明

在此处输入图片说明

在此处输入图片说明


3
有趣的Markdown事实-您可以将图片转换为原始图片的链接:[![image description](SE URL for downsized image)](URL for original image)
卡尔文的爱好2014年

1
要求人们在答案中包括一个示例“被吃”的图像可能是一个想法。
纳撒尼尔(Nathaniel)2014年

Answers:


16

C ++,得分: 1.42042 1.46766

从本质上讲,这是两个现有解决方案的稍微复杂的版本:在四个可能的动作中,它选择一个最大化亮度的动作。但是,它不仅查看目标像素的亮度,还查看目标像素附近像素亮度的加权总和,其中距离目标较近的像素具有更大的权重。

编辑:在邻域计算中使用非线性亮度会稍微提高分数。

用编译g++ joe.cpp -ojoe -std=c++11 -O3 -lcairo。需要开罗。

用运行joe <image-file> [<radius>]<image-file>是输入的PNG图像。 <radius>(可选参数)是求和后的邻域的半径,以像素为单位(越小速度越快,越大(大致)越好。)输出分数和名为的图像out.<image-file>

#include <cairo/cairo.h>
#include <iostream>
#include <iterator>
#include <algorithm>
#include <string>

using namespace std;

int main(int argc, const char* argv[]) {
    auto* img = cairo_image_surface_create_from_png(argv[1]);
    int width = cairo_image_surface_get_width(img),
        height = cairo_image_surface_get_height(img),
        stride = cairo_image_surface_get_stride(img);
    unsigned char* data = cairo_image_surface_get_data(img);

    double* brightness = new double[width * height];
    double total_brightness = 0;
    for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
            const unsigned char* p = data + stride * y + 4 * x;
            total_brightness += brightness[y * width + x] = p[0] + p[1] + p[2];
        }
    }

    const int r = argc > 2 ? stoi(argv[2]) : 64, R = 2 * r + 1;
    double* weight = new double[R * R];
    for (int y = -r; y <= r; ++y) {
        for (int x = -r; x <= r; ++x)
            weight[R * (y + r) + (x + r)] = 1.0 / (x*x + y*y + 1);
    }

    auto neighborhood = [&] (int x, int y) {
        double b = 0;
        int x1 = max(x - r, 0), x2 = min(x + r, width - 1);
        int y1 = max(y - r, 0), y2 = min(y + r, height - 1);
        for (int v = y1; v <= y2; ++v) {
            const double *B = brightness + width * v + x1;
            const double *W = weight + R * (v - (y - r)) + (x1 - (x - r));
            for (int u = x1; u <= x2; ++u, ++B, ++W)
                b += (*W) * (*B) * (*B);
        }
        return b;
    };

    int n = (2 * width * height + 3) / 6;
    int x = 0, y = 0;
    double path_brightness = 0;
    int O[][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
    for (int i = 0; i < n; ++i) {
        if (i % 1000 == 0) cerr << (200 * i + n) / (2 * n) << "%\r";

        path_brightness += brightness[width * y + x]; brightness[width * y + x] = 0;
        unsigned char* p = data + stride * y + 4 * x;
        p[0] = p[1] = 16 * i % 255; p[2] = 0;

        auto O_end = partition(begin(O), end(O), [&] (const int* o) {
            return x + o[0] >= 0 && x + o[0] < width &&
                   y + o[1] >= 0 && y + o[1] < height;
        });
        const int* o_max; double o_max_neighborhood = -1;
        for (auto o = O; o != O_end; ++o) {
            double o_neighborhood = neighborhood(x + (*o)[0], y + (*o)[1]);
            if (o_neighborhood > o_max_neighborhood)
                o_max = *o, o_max_neighborhood = o_neighborhood;
        }
        x += o_max[0]; y += o_max[1];
    }

    cout << (path_brightness * width * height) / (n * total_brightness) << endl;

    cairo_surface_write_to_png(img, (string("out.") + argv[1]).c_str());

    delete []brightness;
    delete []weight;
    cairo_surface_destroy(img);
}

结果

Bridge    1.39945
Balls     1.77714
Scream    1.38349
Fractal   1.31727
Vortex    1.66493
Tornado   1.26366
-----------------
Average   1.46766

桥 球 惊叫 分形 涡流 龙卷风

更多的糖果

漩涡动画 龙卷风动画


我只是在自己的一些示例图像上尝试了您的程序,而一个图像仅在起点附近有很多黑色,并且由于邻域lambda返回NaN而导致程序崩溃。
PlasmaHH 2014年

@PlasmaHH介意分享有问题的图片吗?
2014年

我正在给它放假图像... 400x400纯黑色也可以做到“绝招”。
PlasmaHH 2014年

@PlasmaHH好吧,纯黑色图像的分数不确定,它是零分零分,这就是NaN。但是,它不应该影响邻域计算,也不应该使程序崩溃(尽管这可能取决于浮点环境。)
Ell 2014年

看看o_max指针,if (o_neighborhood > o_max_neighborhood) o_max = *o, o_max_neighborhood = o_neighborhood;只有此代码对其进行设置。但是,由于涉及到nan,所以比较始终为false,因此o_max永远不会设置并未经初始化就使用。
PlasmaHH 2014年

7

Python 3,得分= 1.57

首先,我们的蛇会移动图像,以创建彼此等距的垂直线。

一种

我们可以通过在一条垂直线上相邻接两个点并创建一个端点为端点的循环来扩展此蛇。

|      |
|  =>  +----+
|      +----+
|      |

我们将这些点组织成对,并为每对存储循环的大小和平均亮度值,该值给出了最大的平均亮度。

在每一步中,我们选择对值最大的一对扩展其环路,以在扩展上获得最大的平均亮度,并为该对计算新的最佳环路尺寸和亮度值。

我们将(value,size,point_pair)三胞胎存储在按值排序的堆结构中,这样我们就可以删除最大的元素(在O(1)中)并有效地添加新修改的元素(在O(log n)中)。

当我们达到像素数限制时,我们就停止了,那条蛇将成为最终的蛇。

垂直线之间的距离对结果影响很小,因此选择了恒定的40个像素。

结果

swirl    1.33084397946
chaos    1.76585674741
fractal  1.49085737611
bridge   1.42603926741
balls    1.92235115238
scream   1.48603818637
----------------------
average  1.57033111819

一种 一种 一种 一种 一种 一种

注意:原始的“尖叫”图片不可用,因此我使用了另一张具有类似分辨率的“尖叫”图片。

Gif在“漩涡”图像上显示蛇的延伸过程:

一种

该代码从stdin中获取一个(或多个空格)文件名,并将生成的蛇图像写入png文件,并将分数打印至stdout。

from PIL import Image
import numpy as np
import heapq as hq

def upd_sp(p,st):
    vs,c=0,0
    mv,mp=-1,0
    for i in range(st,gap):
        if p[1]+i<h:
            vs+=v[p[0],p[1]+i]+v[p[0]+1,p[1]+i]
            c+=2
            if vs/c>mv:
                mv=vs/c
                mp=i
    return (-mv,mp)

mrl=[]
bf=input().split()

for bfe in bf:
    mr,mg=0,0    
    for gap in range(40,90,1500):

        im=Image.open(bfe)
        im_d=np.asarray(im).astype(int)

        v=im_d[:,:,0]+im_d[:,:,1]+im_d[:,:,2]

        w,h=v.shape

        fp=[]
        sp=[]
        x,y=0,0
        d=1

        go=True
        while go:
            if 0<=x+2*d<w:
                fp+=[(x,y)]
                fp+=[(x+d,y)]
                sp+=[(x-(d<0),y)]
                x+=2*d
                continue
            if y+gap<h:
                for k in range(gap):
                    fp+=[(x,y+k)]
                y+=gap
                d=-d
                continue
            go=False

        sh=[]
        px=im.load()

        pl=[]

        for p in fp:
            pl+=[v[p[0],p[1]]]
            px[p[1],p[0]]=(0,127,0)   

        for p in sp:
            mv,mp=upd_sp(p,1)
            if mv<=0:
                hq.heappush(sh,(mv,1,mp+1,p))

        empty=False
        pleft=h*w//3
        pleft-=len(fp)
        while pleft>gap*2 and not empty:

            if len(sh)>0:
                es,eb,ee,p=hq.heappop(sh)
            else:
                empty=True
            pleft-=(ee-eb)*2

            mv,mp=upd_sp(p,ee)
            if mv<=0:
                hq.heappush(sh,(mv,ee,mp+1,p))    

            for o in range(eb,ee):
                pl+=[v[p[0],p[1]+o]]
                pl+=[v[p[0]+1,p[1]+o]]
                px[p[1]+o,p[0]]=(0,127,0)   
                px[p[1]+o,p[0]+1]=(0,127,0)

        pl+=[0]*pleft

        sb=sum(pl)/len(pl)
        ob=np.sum(v)/(h*w)

        im.save(bfe[:-4]+'snaked.png')

        if sb/ob>mr:
            mr=sb/ob
            mg=gap

    print(bfe,mr)
    mrl+=[mr]

print(sum(mrl)/len(mrl))

5

Python 2(分数:0.0797116)

只是一个非常简单且幼稚的贪心算法,可以使球滚动。

#!/usr/bin/python

from PIL import Image

OFFSETS = [(-1, 0), (0, -1), (1, 0), (0, 1)]
def test_img(filename):
    img = Image.open(filename)

    joe, eaten = (0, 0), []
    img_w, img_h = img.size
    all_pixels = [
        sum(img.getpixel((x, y)))
        for x in xrange(img_w)
        for y in xrange(img_h)
    ]
    total_brightness = float(sum(all_pixels)) / len(all_pixels)

    for _ in xrange(0, (img_w*img_h)/3):
        max_offset, max_brightness = (0, 0), 0
        for o in OFFSETS:
            try:
                brightness = sum(img.getpixel((joe[0] + o[0], joe[1] + o[1])))
            except IndexError:
                brightness = -1
            if brightness >= max_brightness:
                max_offset = o
                max_brightness = brightness

        joe = (joe[0] + max_offset[0], joe[1] + max_offset[1])
        eaten.append(max_brightness)
        img.putpixel(joe, (0, 0, 0))

    eaten_brightness = float(sum(eaten)) / len(eaten)
    print('%d of %d (score %f)' % (eaten_brightness, total_brightness, eaten_brightness / total_brightness))
    img.show()

test_img('img0.jpg')
test_img('img1.png')
test_img('img2.jpg')
test_img('img3.jpg')
test_img('img4.jpg')

输出:

llama@llama:~/Code/python/ppcg40069hungrysnake$ ./hungrysnake.py 
15 of 260 (score 0.060699)
9 of 132 (score 0.074200)
16 of 300 (score 0.055557)
4 of 369 (score 0.010836)
79 of 400 (score 0.197266)

1
我认为您的得分已经结束。这是普通人的亮度吃了平均亮度为整个画面......随机乔应该得到的分数大约1
弹力疯子

@StretchManiac嗯,我没有发现任何错误。sum of brightnesses of eaten pixels / amount of eaten pixels正确的公式对吗?也许这只是一个非常糟糕的算法;)
Doorknob

@DoorknobThe average brightness of pixels Joe eats / The average brightness of the pixels in the picture*
hmatt1 2014年

对于橙色和黑色涡旋形(不是蓝色),我得到的平均亮度为68.0846 ...似乎与您的亮度都不匹配。也许sum(getPixel(...))没有返回您想要的?(我有点像python新手)。顺便说一句,有6张图片,但您只有5个输出。您还能以某种方式给图片加上标签吗?
拉伸疯子

@StretchManiac您如何计算亮度?我的只是对R,G和B值求和。抱歉,我错过了倒数第二个的漩涡状(我想您在评论中提到的那个)。我要在早上补充一点(我现在必须睡觉)。
门把手

5

Java(分数:0.6949)

一种简单的算法,可以将当前像素周围的四个像素中的最亮像素吃掉。在平局的情况下,吃掉的像素是随机的,从而导致得分不同,并且每次执行都会产生图像。这样,下面的分数是每个图像上50次执行的平均值。

运行它,要么编辑的三个参数在源(现有为类的常量),或通过命令行的形式将它们传递java HungryImageSnake <source> <iterations> <printScores>,其中<source>是吃的图像的源文件,<iterations>是(次数吃图像的数量取平均分数并在所有迭代中保存最佳分数),并且<printScores>打印每个迭代的分数为true,否则为false。

import javax.imageio.ImageIO;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Random;

public class HungryImageSnake {

    private static final String SOURCE = "tornado.jpg";
    private static final int ITERATIONS = 50;
    private static final boolean PRINT_SCORES = true;

    public static void main(String[] args) {
        try {
            String source = args.length > 0 ? args[0] : SOURCE;
            int iterations = args.length > 1 ? Integer.parseInt(args[1]) : ITERATIONS;
            boolean printScores = args.length > 2 ? Boolean.parseBoolean(args[2]) : PRINT_SCORES;

            System.out.printf("Reading '%s'...%n", source);
            System.out.printf("Performing %d meals...%n", iterations);
            BufferedImage image = ImageIO.read(new File(source));
            double totalScore = 0;
            double bestScore = 0;
            BufferedImage bestImage = null;
            for (int i = 0; i < iterations; i++) {
                HungryImageSnake snake = new HungryImageSnake(image);
                while (snake.isHungry()) {
                    snake.eat();
                }
                double score = snake.getScore();
                if (printScores) {
                    System.out.printf("    %d: score of %.4f%n", i + 1, score);
                }
                totalScore += score;
                if (bestImage == null || score > bestScore) {
                    bestScore = score;
                    bestImage = snake.getImage();
                }
            }
            System.out.printf("Average score: %.4f%n", totalScore / iterations);
            String formattedScore = String.format("%.4f", bestScore);
            String output = source.replaceFirst("^(.*?)(\\.[^.]+)?$", "$1-b" + formattedScore + "$2");
            ImageIO.write(bestImage, source.substring(source.lastIndexOf('.') + 1), new File(output));
            System.out.printf("Wrote best image (score: %s) to '%s'.%n", bestScore, output);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private int x;
    private int y;
    private int movesLeft;
    private int maximumMoves;
    private double eatenAverageBrightness;
    private double originalAverageBrightness;
    private BufferedImage image;

    public HungryImageSnake(BufferedImage image) {
        this.image = copyImage(image);
        int size = image.getWidth() * image.getHeight();
        this.maximumMoves = size / 3;
        this.movesLeft = this.maximumMoves;
        int totalBrightness = 0;
        for (int x = 0; x < image.getWidth(); x++) {
            for (int y = 0; y < image.getHeight(); y++) {
                totalBrightness += getBrightnessAt(x, y);
            }
        }
        this.originalAverageBrightness = totalBrightness / (double) size;
    }

    public BufferedImage getImage() {
        return image;
    }

    public double getEatenAverageBrightness() {
        return eatenAverageBrightness;
    }

    public double getOriginalAverageBrightness() {
        return originalAverageBrightness;
    }

    public double getScore() {
        return eatenAverageBrightness / originalAverageBrightness;
    }

    public boolean isHungry() {
        return movesLeft > 0;
    }

    public void eat() {
        if (!isHungry()) {
            return;
        }
        int[][] options = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
        shuffleArray(options); // prevent snake from getting stuck in corners
        int[] bestOption = null;
        int bestBrightness = 0;
        for (int[] option : options) {
            int optionX = this.x + option[0];
            int optionY = this.y + option[1];
            if (exists(optionX, optionY)) {
                int brightness = getBrightnessAt(optionX, optionY);
                if (bestOption == null || brightness > bestBrightness) {
                    bestOption = new int[]{ optionX, optionY };
                    bestBrightness = brightness;
                }
            }
        }

        image.setRGB(bestOption[0], bestOption[1], 0);
        this.movesLeft--;
        this.x = bestOption[0];
        this.y = bestOption[1];
        this.eatenAverageBrightness += bestBrightness / (double) maximumMoves;
    }

    private boolean exists(int x, int y) {
        return x >= 0 && x < image.getWidth() && y >= 0 && y < image.getHeight();
    }

    private int getBrightnessAt(int x, int y) {
        int rgb = image.getRGB(x, y);
        int r = (rgb >> 16) & 0xFF;
        int g = (rgb >> 8) & 0xFF;
        int b = rgb & 0xFF;
        return r + g + b;
    }

    private static <T> void shuffleArray(T[] array) {
        Random random = new Random();
        for (int i = array.length - 1; i > 0; i--) {
            int index = random.nextInt(i + 1);
            T temp = array[index];
            array[index] = array[i];
            array[i] = temp;
        }
    }

    private static BufferedImage copyImage(BufferedImage source){
        BufferedImage b = new BufferedImage(source.getWidth(), source.getHeight(), source.getType());
        Graphics g = b.getGraphics();
        g.drawImage(source, 0, 0, null);
        g.dispose();
        return b;
    }
}

图像平均得分超过五十次:

Bridge - 0.7739
Spheres - 0.5580
Scream - 0.8197
Fractal - 0.3850
Vortex - 0.9158
Tornado - 0.7172

在相同的五十次迭代中按图像获得的最佳分数:

Bridge - 0.8996
Spheres - 0.8741
Scream - 0.9183
Fractal - 0.5720
Vortex - 1.1520
Tornado - 0.9281

得分最高的跑步图片:

桥-0.8996 领域-0.8741 尖叫声-0.9183 分形-0.5720 涡流-1.1520 龙卷风-0.9281

从图像中可以明显看出,实际上吞噬的像素远远少于三分之一,因为蛇偶尔会卡在已经吞噬的像素中,在此之前,蛇会停留在死区中,直到其运动的随机性使它陷入图像的可食用部分。

同样,由于蛇重新吃了像素,分数也被向下偏置,因为死像素的零亮度再次被计入平均值。如果将评分算法修改为仅除以导致吃掉新像素的移动次数,而不是所有移动(包括那些蛇吃掉之前已经吃掉的现在死像素的移动),我希望看到更高的分数)。

当然,更好的方法是为每个像素创建某种亮度试探法,并找到width * height / 3具有最高平均亮度的像素路径,但是我怀疑这种方法在运行时是否是最佳选择,尤其是在较大的图像上可能的排列将非常大。稍后,我可能会对此方法进行某种形式的测试,如果可以的话,将其发布在单独的答案中。


如果有人对我的回答有多大困扰(由于其中的图片),请告诉我,我将删除这些图片,转而使用链接或其他节省空间的替代方法。或者,如果有人想要获得所有“已吃”图像的全分辨率原件,也请在评论中告诉我。
FThompson,2014年

4

Python 2,得分:1.205

我前段时间整理了一个快速的Python解决方案,但忘了发布。这里是。它找到图像中最丰富的块,然后移动到每个块,并吃掉所有最好的块。

结果

bridge.jpg: 591.97866/515.41501 =                               1.14855 
BallsRender.png: 493.24711/387.80635 =                          1.27189 
Mandel_zoom_04_seehorse_tail.jpg: 792.23990/624.60579 =         1.26838 
Red_Vortex_Apophysis_Fractal_Flame.jpg: 368.26121/323.08463 =   1.13983 
The_Scream.jpg: 687.18565/555.05221 =                           1.23806
swirl.jpg: 762.89469/655.73767 =                                1.16341

AVERAGE                                                         1.205

示例图片

加工过的桥图片

Python 2.7代码

from pygame.locals import *
import pygame, sys, random

fn = sys.argv[1]

screen = pygame.display.set_mode((1900,1000))
pic = pygame.image.load(fn)
pic.convert()
W,H = pic.get_size()
enough = W*H/3
screen.blit(pic, (0,0))

ORTH = [(-1,0), (1,0), (0,-1), (0,1)]
def orth(p):
    return [(p[0]+dx, p[1]+dy) for dx,dy in ORTH]

def look(p):
    x,y = p
    if 0 <= x < W and 0 <= y < H:
        return sum(pic.get_at(p))
    else:
        return -1

# index picture
locs = [(x,y) for x in range(W) for y in range(H)]
grid = dict( (p,sum(pic.get_at(p))) for p in locs )
rank = sorted( grid.values() )
median = rank[ len(rank)/2 ]
dark = dict( (k,v) for k,v in grid if v < median )
good = dict( (k,v) for k,v in grid if v > median )
pictotal = sum(rank)
picavg = 1.0 * pictotal/(W*H)
print('Indexed')

# compute zone values:
block = 16
xblocks, yblocks = (W-1)/block, (H-1)/block
zones = dict( ((zx,zy),0) for zx in range(xblocks) for zy in range(yblocks) )
for x,y in locs:
    div = (x/block, y/block)
    if div in zones:
        colsum = sum( pic.get_at((x,y)) )
        zones[div] += colsum

# choose best zones:
zonelist = sorted( (v,k) for k,v in zones.items() )
cut = int(xblocks * yblocks * 0.33)
bestzones = dict( (k,v) for v,k in zonelist[-cut:] )

# make segment paths:
segdrop = [(0,1)] * (block-1)
segpass = [(1,0)] * block
segloop = [(0,-1)] * (block-1) + [(1,0)] + [(0,1)] * (block-1)
segeat = ( segloop+[(1,0)] ) * (block/2)
segshort = [(1,0)] * 2 + ( segloop+[(1,0)] ) * (block/2 - 1)

def segtopath(path, seg, xdir):
    for dx,dy in seg:
        x,y = path[-1]
        newloc = (x+dx*xdir, y+dy)
        path.append(newloc)

# design good path:
xdir = 1
path = [(0,0)]
segtopath(path, segdrop, xdir)
shortzone = True
while True:
    x,y = path[-1]
    zone = (x/block, y/block)
    if zone in bestzones:
        if shortzone:
            seg = segshort
        else:
            seg = segeat
    else:
        seg = segpass
    segtopath(path, seg, xdir)
    shortzone = False
    # check end of x block run:
    x,y = path[-1]
    zone = (x/block, y/block)
    if not ( 0 <= x < xblocks*block ):
        del path[-1]
        segtopath(path, segdrop, xdir)
        shortzone = True
        xdir = xdir * -1
    if len(path) > enough:
        break
print('Path Found')

# show path on picture:
loc = path.pop(0)
eaten = 1
joetotal = grid[loc]
i = 0
while eaten <= enough:
    loc = path[i]
    i += 1
    pic.set_at(loc, (0,0,0))
    joetotal += grid[loc]
    eaten += 1
    if i % 1000 == 0:
        screen.blit(pic, (0,0))
        pygame.display.flip()
        for event in pygame.event.get():
            if event.type == QUIT: sys.exit(0)

# save picture and wait:
screen.blit(pic, (0,0))
pygame.display.flip()
pygame.image.save(pic, 'output/'+fn)
joeavg = 1.0 * joetotal/eaten
print '%s: %7.5f/%7.5f = %7.5f' % (fn, joeavg, picavg, joeavg/picavg)
while True:
    for event in pygame.event.get():
        if event.type == QUIT: sys.exit(0)
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.