你会如何解释这张图片中的图案?(通过根据像素的 RGB 值重新排序像素生成)

信息处理 图像处理 过滤器 过滤器设计 采样 计算机视觉
2022-02-06 08:34:18

正在玩一些图片并运行下面的 ruby​​ 代码。该代码使用 ImageMagick 库读取输入图像,获取像素数组,重新排序像素,然后使用与原始图像相同的尺寸写出具有重新排序像素的图像。

require 'rmagick'

include Magick

img = ImageList.new("images/test-image.jpg")
pixels = img.get_pixels(0,0,img.columns,img.rows)

# Sort pixels by total intensity

# https://apidock.com/ruby/Array/sort
# The block must implement a comparison between a and b and
# * <0 when b follows a,
# * 0 when a and b are equivalent, 
# * >0 when a follows b

# The result is not guaranteed to be stable.
# When the comparison of two elements returns 0, the order of the elements is unpredictable.

pixels = pixels.sort {|p| p.red + p.green + p.blue}

out_img = Magick::Image.new(img.columns, img.rows)
out_img.store_pixels(0,0, img.columns, img.rows, pixels)
out_img.write("images/test-image-out.jpg")

我试图通过像素的 RGB 值的总和对像素进行排序,但为此我应该使用sort_by而不是sort像素数组。

所以像素没有按它们的 RGB 值的总和进行排序,但我也不知道它们是如何排序的,以及为什么当以这种方式排序时,它们会在下面的示例图片上生成图案

我包含了 RubyArray.sort方法的简短文档,以帮助理解正在发生的事情。

原图: 在此处输入图像描述

具有重新排序像素的图片(使用pixels.sort {|p| p.red + p.green + p.blue}pixels = pixels.sort {|a,b| 1}): 在此处输入图像描述

根据@OlliNiemitalo 和@xiota 的建议,如果不执行pixels = pixels.sort {|p| p.red + p.green + p.blue}代码pixels = pixels.sort {|a,b| 1},则输出是相同的(奇怪的重复模式)。

执行此操作时pixels = pixels.sort {|a,b| b <=> a},输出如下图所示。

在此处输入图像描述

(0..1023).to_a.sort{|p| 1}下面是在 irb (ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin16]) 上运行时发生的情况。仅通过查看数字无法真正分辨出模式是什么。

irb(main):009:0> (0..1023).to_a.sort{|p| 1}
=> [22, 512, 0, 513, 1, 514, 2, 515, 3, 516, 4, 517, 25, 518, 29, 519, 33, 520, 40, 41, 275, 787, 147, 659, 403, 915, 83, 595, 339, 723, 211, 851, 467, 979, 51, 563, 307, 627, 179, 691, 371, 755, 115, 819, 435, 883, 243, 947, 499, 1011, 15, 547, 291, 579, 163, 611, 323, 643, 99, 675, 355, 707, 195, 739, 387, 771, 67, 803, 419, 835, 227, 867, 451, 899, 131, 931, 483, 963, 259, 995, 18, 523, 7, 539, 283, 555, 155, 571, 299, 587, 91, 603, 315, 619, 171, 635, 331, 651, 59, 667, 347, 683, 187, 699, 363, 715, 107, 731, 379, 747, 203, 763, 395, 779, 43, 795, 411, 811, 219, 827, 427, 843, 123, 859, 443, 875, 235, 891, 459, 907, 75, 923, 475, 939, 251, 955, 491, 971, 139, 987, 507, 1003, 267, 1019, 271, 527, 27, 535, 279, 543, 151, 551, 287, 559, 87, 567, 295, 575, 159, 583, 303, 591, 55, 599, 311, 607, 167, 615, 319, 623, 95, 631, 327, 639, 175, 647, 335, 655, 30, 663, 343, 671, 183, 679, 351, 687, 103, 695, 359, 703, 191, 711, 367, 719, 63, 727, 375, 735, 199, 743, 383, 751, 111, 759, 391, 767, 207, 775, 399, 783, 11, 791, 407, 799, 215, 807, 415, 815, 119, 823, 423, 831, 223, 839, 431, 847, 71, 855, 439, 863, 231, 871, 447, 879, 127, 887, 455, 895, 239, 903, 463, 911, 47, 919, 471, 927, 247, 935, 479, 943, 135, 951, 487, 959, 255, 967, 495, 975, 79, 983, 503, 991, 263, 999, 511, 1007, 143, 1015, 36, 1023, 145, 525, 273, 529, 35, 533, 277, 537, 149, 541, 281, 545, 85, 549, 285, 553, 153, 557, 289, 561, 53, 565, 293, 569, 157, 573, 297, 577, 89, 581, 301, 585, 161, 589, 305, 593, 17, 597, 309, 601, 165, 605, 313, 609, 93, 613, 317, 617, 169, 621, 321, 625, 57, 629, 325, 633, 173, 637, 329, 641, 97, 645, 333, 649, 177, 653, 337, 657, 9, 661, 341, 665, 181, 669, 345, 673, 101, 677, 349, 681, 185, 685, 353, 689, 61, 693, 357, 697, 189, 701, 361, 705, 105, 709, 365, 713, 193, 717, 369, 721, 39, 725, 373, 729, 197, 733, 377, 737, 109, 741, 381, 745, 201, 749, 385, 753, 65, 757, 389, 761, 205, 765, 393, 769, 113, 773, 397, 777, 209, 781, 401, 785, 5, 789, 405, 793, 213, 797, 409, 801, 117, 805, 413, 809, 217, 813, 417, 817, 69, 821, 421, 825, 221, 829, 425, 833, 121, 837, 429, 841, 225, 845, 433, 849, 45, 853, 437, 857, 229, 861, 441, 865, 125, 869, 445, 873, 233, 877, 449, 881, 73, 885, 453, 889, 237, 893, 457, 897, 129, 901, 461, 905, 241, 909, 465, 913, 13, 917, 469, 921, 245, 925, 473, 929, 133, 933, 477, 937, 249, 941, 481, 945, 77, 949, 485, 953, 253, 957, 489, 961, 137, 965, 493, 969, 257, 973, 497, 977, 49, 981, 501, 985, 261, 989, 505, 993, 141, 997, 509, 1001, 265, 1005, 20, 1009, 81, 1013, 28, 1017, 269, 1021, 37, 522, 82, 524, 272, 526, 146, 528, 274, 530, 521, 532, 276, 534, 148, 536, 278, 538, 84, 540, 280, 542, 150, 544, 282, 546, 52, 548, 284, 550, 152, 552, 286, 554, 86, 556, 288, 558, 154, 560, 290, 562, 16, 564, 292, 566, 156, 568, 294, 570, 88, 572, 296, 574, 158, 576, 298, 578, 54, 580, 300, 582, 160, 584, 302, 586, 90, 588, 304, 590, 162, 592, 306, 594, 8, 596, 308, 598, 164, 600, 310, 602, 92, 604, 312, 606, 166, 608, 314, 610, 56, 612, 316, 614, 168, 616, 318, 618, 94, 620, 320, 622, 170, 624, 322, 626, 26, 628, 324, 630, 172, 632, 326, 634, 96, 636, 328, 638, 174, 640, 330, 642, 58, 644, 332, 646, 176, 648, 334, 650, 98, 652, 336, 654, 178, 656, 338, 658, 23, 660, 340, 662, 180, 664, 342, 666, 100, 668, 344, 670, 182, 672, 346, 674, 60, 676, 348, 678, 184, 680, 350, 682, 102, 684, 352, 686, 186, 688, 354, 690, 34, 692, 356, 694, 188, 696, 358, 698, 104, 700, 360, 702, 190, 704, 362, 706, 62, 708, 364, 710, 192, 712, 366, 714, 106, 716, 368, 718, 194, 720, 370, 722, 10, 724, 372, 726, 196, 728, 374, 730, 108, 732, 376, 734, 198, 736, 378, 738, 64, 740, 380, 742, 200, 744, 382, 746, 110, 748, 384, 750, 202, 752, 386, 754, 42, 756, 388, 758, 204, 760, 390, 762, 112, 764, 392, 766, 206, 768, 394, 770, 66, 772, 396, 774, 208, 776, 398, 778, 114, 780, 400, 782, 210, 784, 402, 786, 31, 788, 404, 790, 212, 792, 406, 794, 116, 796, 408, 798, 214, 800, 410, 802, 68, 804, 412, 806, 216, 808, 414, 810, 118, 812, 416, 814, 218, 816, 418, 818, 44, 820, 420, 822, 220, 824, 422, 826, 120, 828, 424, 830, 222, 832, 426, 834, 70, 836, 428, 838, 224, 840, 430, 842, 122, 844, 432, 846, 226, 848, 434, 850, 12, 852, 436, 854, 228, 856, 438, 858, 124, 860, 440, 862, 230, 864, 442, 866, 72, 868, 444, 870, 232, 872, 446, 874, 126, 876, 448, 878, 234, 880, 450, 882, 46, 884, 452, 886, 236, 888, 454, 890, 128, 892, 456, 894, 238, 896, 458, 898, 74, 900, 460, 902, 240, 904, 462, 906, 130, 908, 464, 910, 242, 912, 466, 914, 6, 916, 468, 918, 244, 920, 470, 922, 132, 924, 472, 926, 246, 928, 474, 930, 76, 932, 476, 934, 248, 936, 478, 938, 134, 940, 480, 942, 250, 944, 482, 946, 48, 948, 484, 950, 252, 952, 486, 954, 136, 956, 488, 958, 254, 960, 490, 962, 78, 964, 492, 966, 256, 968, 494, 970, 138, 972, 496, 974, 258, 976, 498, 978, 14, 980, 500, 982, 260, 984, 502, 986, 140, 988, 504, 990, 262, 992, 506, 994, 80, 996, 508, 998, 264, 1000, 510, 1002, 142, 1004, 21, 1006, 266, 1008, 19, 1010, 50, 1012, 24, 1014, 268, 1016, 32, 1018, 144, 1020, 38, 1022, 270, 531]

根据@xiota 的附加建议,运行pixels = pixels.sort {|a,b| a <=> b}会创建一种生成的图像的“反向”图像pixels = pixels.sort {|a,b| b <=> a}(注意比较时 a 和 b 位置的变化)。这是输出:

另一个图像

2个回答

为了支持Comparablemixin <=>. _ 但是,在您使用该方法时,您定义了自己的代码块来覆盖运算符,并且您的代码块采用单个参数而不是两个正确的参数,因此定义被破坏并且在排序中无法正常工作。未定义的排序结果将取决于使用的排序算法。Ruby 使用Quicksort算法对数组进行排序,这是一种分治算法Pixel_spaceshiprmpixel.csort<=>,将问题划分为越来越小的子问题。您可以在输出图像中看到这种行为,我认为这是将 Quicksort 与损坏的<=>运算符一起使用的产物。

在某些计算平台上,Ruby 使用该平台的 Quicksort 实现(请参阅rb_sortarray.c因此未定义的结果也因平台而异,如对问题的评论所示。参见Ruby 自己的快速排序实现。对于想要花时间解决这个问题的人,您可以将每个比较替换为 constant ,并简化代码。ruby_qsortutil.cutil.c<=>1

让我们将问题的ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin16]平台“排序”0..1023测试数据(标记为 Source #)绘制为位置函数,使用 | 标记以更清楚地查看步长:

在此处输入图像描述

忽略小尺度细节,就好像我们从很远的距离看一个很大的图像,我们可以推断出n=0,1,2,3,, 每个2nd1/(n+2)输出图像的一部分是输入图像的缩放部分的加法混合:

  • 2nd1/2放大的图像的一部分2n带混合重量1/2,
  • 2nd1/4放大的图像的一部分21n混合重量 ,1/4
  • 图像的第 1/8部分放大了,混合权重为21/822n1/8
  • 图像的第 1/16部分放大了,混合权重为21/1623n1/16
  • ...(直到零件尺寸变为零)。

在这里,放大2x意味着如果则所讨论的图像部分的大小会缩小倍并复制(平铺),如果则图像部分被放大倍,并且放大相加混合x<12xx>12x2x1/2x1/2x

该分析中忽略的小尺度细节是隔行扫描,从远处看就像加法混合。此外,一些似乎与数据大小不成比例的无法解释的细节也被忽略了。

这是一个 Python 脚本(抱歉,我不懂 Ruby),它再现了图片的大部分美学品质,不是通过使用上述分析,而是通过模仿上图中的隔行扫描:

from PIL import Image
import requests
from io import BytesIO

response = requests.get("https://i.stack.imgur.com/T520A.jpg")
img = Image.open(BytesIO(response.content))
pix = img.load()
w, h = img.size
pix_flat = []
for y in range(h):
    for x in range(w):
        pix_flat.append(pix[x, y])

for y in range(h):
    for x in range(w):
        if (x > 0 or y > 0):  # Do not touch pix[0, 0]
            i = x + w*y     
            j = i
            while j < w*h//2:
                j *= 2
            k = i
            while k & 1:
                j >>= 1
                k >>= 1
            pix[x, y] = pix_flat[j]

img.save("output.jpg", "JPEG")

结果#1:

在此处输入图像描述

假设源图像在 sRGB 颜色空间中,在 Python 中,我们可以通过在线性颜色空间中用加权求和替换交错来消除垂直条纹:

from PIL import Image
import requests
from io import BytesIO

response = requests.get("https://i.stack.imgur.com/T520A.jpg")
img = Image.open(BytesIO(response.content))
pix = img.load()
w, h = img.size

# These originate from https://stackoverflow.com/questions/34472375/linear-to-srgb-conversion
def srgb2lin(s):  
    if s <= 0.0404482362771082:
        lin = s / 12.92
    else:
        lin = pow(((s + 0.055) / 1.055), 2.4)
    return lin

def lin2srgb(lin):
    if lin > 0.0031308:
        s = 1.055 * (pow(lin, (1.0 / 2.4))) - 0.055
    else:
        s = 12.92 * lin
    return s

pix_flat_lin = []
for y in range(h):
    for x in range(w):
        pix_flat_lin.append([srgb2lin(pix[x, y][k]/255) for k in range(3)])

for y in range(h):
    for x in range(w):
        if (x > 0 or y > 0):  # Do not touch pix[0, 0]
            i = x + w*y     
            j = i
            while j < w*h//2:
                j *= 2
            pixel_lin = [pix_flat_lin[j][k] * 0.5 for k in range(3)]
            j >>= 1
            m = 0.25
            while j:
                pixel_lin = [pixel_lin[k] + pix_flat_lin[j][k] * m for k in range(3)]
                j >>= 1
                m *= 0.5
            pix[x, y] = tuple([int(round(lin2srgb(pixel_lin[k])*255)) for k in range(3)])
    print(str(y+1)+"/"+str(h))

img.save("output2.jpg", "JPEG")

结果#2:

在此处输入图像描述

这仍然有水平条纹。

我不是 Ruby 程序员,所以这个答案会有很多麻烦如果您特别关心跳过的细节,则必须查看 Ruby 源代码。

  • array.sort {|a,b| a <=> b}中, 的sort方法array被一个块调用{ ... }块使用的参数由 给出|a,b|比较“宇宙飞船”运算符是a <=> b. 它返回 1、0、-1,具体取决于是否a“大于” b

  • 在您编写的代码中pixels = pixels.sort {|p| p.red + p.green + p.blue},您将添加三个正整数。结果是另一个被解释为 的正整数1所以sort表现得好像比较总是评估为“更大”。不像您期望的那样根据大小将值分离或重新组合成数组,而是sort根据它们在原始数组中出现的顺序进行拆分。

    当每个其他像素放入不同的阵列时,这会导致创建原始图像的较小副本。这对每个数组都重复,直到我们变成对。然后将数组重新合并在一起以获得您看到的结果。(这是一个主要的手动检查点。您必须查看排序实现才能准确了解数组是如何合并的。)

  • 输出最终看起来与以下任一相同。|p|如果您有或|a,b|因为参数被忽略,这并不重要。

    pixels = pixels.sort {|p| 1}
    pixels = pixels.sort {|a,b| 1}
    

    奇怪的

  • 要获得您正在寻找的结果,您可以使用:

    pixels = pixels.sort {|a,b| (a.red + a.green + a.blue) <=> (b.red + b.green + b.blue)}
    

    期望的结果

  • Olli Niemitalo提到的默认Pixel_spaceship运算符与您想要的功能不同,因此具有条纹外观。使用将颠倒顺序。pixels = pixels.sort {|a,b| a <=> b}b <=> a

    a <=> b