StevenPZChan
StevenPZChan
7 min read

Categories

Tags

继续挑战,依然还是很麻烦


第33题地址beer.html

  • beer1.jpg
  • 网页标题是33 bottles of beer on the wall,题目内容为空,源码里面有隐藏内容:

    <!–
    If you are blinded by the light,
    remove its power, with its might.
    Then from the ashes, fair and square,
    another truth at you will glare.
    –>

隐藏内容还是相当难理解,不过图片的名字是beer1.jpg,按套路来看我们要去找下一张。

from io import BytesIO

import requests
from PIL import Image

with requests.Session() as sess:
    sess.auth = ('kohsamui', 'thailand')
    response = sess.get(
        'http://www.pythonchallenge.com/pc/rock/beer2.jpg').content
    img = Image.open(BytesIO(response))
img

png

图片上写着no, png,显然是要我们去找beer2.png

from io import BytesIO
import requests
from PIL import Image

with requests.Session() as sess:
    sess.auth = ('kohsamui', 'thailand')
    response = sess.get(
        'http://www.pythonchallenge.com/pc/rock/beer2.png').content
    img = Image.open(BytesIO(response))
img

png

from collections import Counter

print(img.size)
c = Counter(img.getdata())
print(sorted(c.items(), key=lambda x: x[0], reverse=True))
(138, 138)
[(194, 272), (193, 1348), (188, 283), (187, 241), (182, 318), (181, 198), (176, 337), (175, 171), (170, 317), (169, 183), (164, 317), (163, 175), (158, 323), (157, 161), (152, 324), (151, 152), (146, 323), (145, 145), (140, 342), (139, 118), (134, 342), (133, 110), (128, 327), (127, 117), (122, 198), (121, 238), (116, 183), (115, 32), (110, 310), (109, 114), (104, 224), (103, 192), (98, 505), (97, 104), (92, 257), (91, 139), (86, 341), (85, 47), (80, 354), (79, 26), (74, 164), (73, 23), (68, 298), (67, 70), (62, 356), (61, 181), (56, 609), (55, 79), (50, 225), (49, 107), (44, 357), (43, 126), (38, 339), (37, 126), (32, 328), (31, 119), (26, 424), (25, 144), (20, 243), (19, 549), (14, 329), (13, 724), (8, 189), (7, 963), (2, 232), (1, 1532)]

我们可以看到像素值是有一定的特点的。我们来尝试理解一下之前题目里的隐藏内容。

If you are blinded by the light,
remove its power, with its might.

  • 光应该指的是最亮的像素值,它把其他的内容遮盖了,所以我们要把它们移除才能看见背后隐藏的东西。

Then from the ashes, fair and square,
another truth at you will glare.

  • 我们尝试移除最亮的像素值194,还剩138*138-272=18772个像素,没什么特点。再观察可以注意到像素值分布是两个连续值为一组的,我们尝试移除最亮的像素值194和193,还剩138*138-272-1348=17424个像素。咦!正好可以组成132*132(=17424)的正方形!
  • 我们简单验证一下。
import math

assert img.width == img.height
count = img.width * img.height
dist = sorted(c.items(), key=lambda x: x[0], reverse=True)
while dist:
    print(math.sqrt(count))
    count -= dist.pop(0)[1]
    count -= dist.pop(0)[1]
138.0
132.0
130.0
128.0
126.0
124.0
122.0
120.0
118.0
116.0
114.0
112.0
110.0
108.0
107.0
105.0
103.0
100.0
98.0
96.0
94.0
93.0
91.0
88.0
84.0
82.0
79.0
76.0
73.0
69.0
63.0
54.0
42.0

果然跟猜想一致。其实再看题目的图片,是一层层的酒瓶,实际上就是要让我们把数据一层层分开来处理。那么我们就把最亮的部分显示出来,然后把剩下的部分重新组成正方形,重复直至用完所有像素。

from collections import Counter
import matplotlib.pyplot as plt
from PIL import Image

data = img.getdata()
c = Counter(data)
pixels = sorted(c.keys(), reverse=True)
num = len(pixels) // 2
num_per_row = math.ceil(math.sqrt(num / (3 / 4)))  # Figure 4:3
rows = math.ceil(num / num_per_row)
for i in range(num):
    side = int(math.sqrt(len(data)))
    img_new = Image.new('L', (side, side))
    img_new.putdata([255 if x == pixels[2 * i] else 0 for x in data])
    data = list(filter(lambda x: x not in pixels[2 * i:2 * (i + 1)], data))

    plt.subplot(rows, num_per_row, i + 1)
    plt.axis('off')
    plt.imshow(img_new, cmap='gray')

png

我们可以看到每幅图中间都是一个字母,而其中有一些字母是被框起来的。自然答案就是gremlins.html(为啥多了一个i?因为这样才是单词小精灵啊)

  • 新页面标题是Temporary End,内容是感谢玩耍 看来是到了结尾了!!!

最后再回过头了总结题目里隐藏内容的解析:

If you are blinded by the light,

  • light是指最亮的像素,它展示了某些信息(字母),但同时把其他信息遮盖(blinded)了(移到了其他随机的位置)

remove its power, with its might.

  • 所以我们需要移除它本身(power),当然还有它伴随的力量(might),因为像素值个数要构成一个平方数嘛

Then from the ashes, fair and square,

  • 然后剩下的散点(ashes)自然要重新组成一个正方形(square)图案,当然还要公平(fair)地对待所有像素,因此这个操作是一个迭代

another truth at you will glare.

  • 你会看到(glare)最后的真相(truth),因为答案隐藏在了被框选的字母里面,还少框了一个i

总结:这一题基本相当于猜谜,工具依然是对图像像素的一些分析和操作,关键在于发现其中的数学规律。

最后,这个挑战也告一段落了,因为作者很久也没再继续出题了。这个项目到此为止了。

本题代码地址33_beer.ipynb