如果RGB行(numpy数组)的count_amount是偶数或非偶数,那么相关条件如何排除或不排除RGB应用的最后一行?

lh80um4z  于 2023-08-05  发布在  其他
关注(0)|答案(2)|浏览(97)

我继续编写代码(当前版本将在介绍之后发布),并设置了一个算法,根据特定的模式编辑图像。代码与我之前的文章有关:
How can a specific row of (RGB) values in numpy be sought for, in order to replace said row with a different set of (RGB) values?
所述模式(可以在代码的“if”部分中找到:

  • 在对数组的RGB(行)值进行计数并且计数的数量等于偶数之后,然后编辑具有该值的每行,包括具有该值的最后一行。
  • 在对数组的RGB(行)值进行计数后,并且计数的数量等于非偶数,然后编辑具有该值的每行,不包括具有该值的最后一行。

当前的代码确实需要高效,目前对于一个1366x768.PNG的图像,它需要~Month<来计算。当计算完成时,则应该保存所得到的图像。
我相信“偶数”的条件已经生效,我不相信“非偶数”的条件已经生效。
如何有效地对这种模式进行编码?我怎样才能使代码更高效,这样如果代码运行,它就能在同一天完成计算?
在此声明之后,当前代码将被发布,注解存在于所述代码中,感谢您的每一条评论和每一个答案。

from PIL import Image
import numpy as np
path = "insert_path_here"                           #PATH OF IMAGE THAT IS TO BE EDITED
A = np.array(Image.open(path).convert('RGB'))       #NUMPY ARRAY FROM PATH INTO RGB VALUE

R = 1; G = 1; B = 1
for R in range(1, 254):
    for G in range(1, 254):
        for B in range(1, 254):
            C = (((A == [R, G, B]).all(axis=2)).sum())    #COUNT HOW OFTEN RGB IS PRESENT (IN ROW)
            CE = C[C % 2 == 0];                           #CONDITION "IF COUNT IS EVEN AMOUNT"
            CO = C[C % 2 != 0];                           #CONDITION "IF COUNT IS NOT EVEN AMOUNT"
            #print(C, CE, CO);                            #PRINT COUNT VALUES IF NEEDED
            print(R, G, B)                                #PRINT "CURRENT RGB" VALUES (IN TERMINAL)
            if CE.size != 0:                              #IF ROW NOT EMPTY (!NEEDED TO RUN!)
                #print("EVEN",C-0)                        #PRINT HOW MANY ROWS ARE TO BE PROCESSED (IF EVEN)
                A[(A == [R, G, B]).all(axis=2)] = [1, 1, 1] #HAS TO READ EVERY ROW
                #print(A)
            if CO.size != 0:                              #IF ROW NOT EMPTY (!NEEDED TO RUN!
                #print("ODD",C-1)                         #PRINT HOW MANY ROWS ARE TO BE PROCESSED (IF ODD)
                A[(A == [R, G, B]).all(axis=2)] = [255, 255, 255] #HOW TO EXCLUDE LAST ROW OF SAID VALUE?
                #print(A)
Image.fromarray(A).save('NEWIMG.PNG')

字符串
A[(A == [R, G, B]).all(axis=2)]搜索行时,if CO.size != 0:应用的部分是使RGB值应用的最后一行保持先前状态的部分。(不改)

wxclj1h5

wxclj1h51#

您似乎将RGB作为一个集合来处理,而不是单独读/写每个通道。
在这种情况下,将RGB打包为单个值可以大大提高性能并简化实现。
例如,您可以使用下列公式:

packed = r * 1000**2 + g * 1000 + b

字符串
例如,(50, 100, 200)可以打包为50100200
你可以这样计算自己,但还有一个更有效的方法。将图像转换为RGBA,即8位x 4个通道,然后只把它当作一个32位整数的数组。

image = Image.open(path)
image = np.array(image.convert("RGBA"), dtype=np.uint8).view(np.uint32)
print(image.shape)  # (768, 1366, 1)
print(image.dtype)  # uint32
print(image[0, 0])  # [4289506476]


这与以下计算相同(在little-endian环境中):

packed = a * 256**3 + b * 256**2 + g * 256 + r


请注意,我给出的这个公式是为了让你知道,但你不会自己做这个计算。例如,如果您想要特定颜色的压缩值,可以执行下列动作:

r, g, b = (50, 100, 200)
packed = np.array([r, g, b, 255], dtype=np.uint8).view(np.uint32)[0]
print(packed)  # 4291322930


一旦您有了压缩值图像,就不再需要使用花哨的方法来处理颜色。

  • 如果要替换颜色:A[A == color] = new_color的值。
  • 如果要计数颜色:np.count_nonzero(A == color)的值。
  • 如果要对所有颜色进行计数:np.unique(A, return_counts=True)的值。

为了排除最后一行,np.unique具有返回每种颜色第一次出现的索引的功能。因此,通过将图像上下翻转,可以检索最后一次出现的索引。“这允许我们只比较每种颜色的最后一行。
下面是完整的代码:

from PIL import Image
import numpy as np

def count_colors(image):
    image = np.array(image.convert("RGBA"), dtype=np.uint8).view(np.uint32)

    color_for_even = np.array([1, 1, 1, 255], dtype=np.uint8).view(np.uint32)[0]
    color_for_odd = np.array([255, 255, 255, 255], dtype=np.uint8).view(np.uint32)[0]

    # return_index will return the FIRST occurrences of each color.
    # So, if we flip the image, it will return the LAST occurrences.
    colors, indices, counts = np.unique(image[::-1, ::-1], return_index=True, return_counts=True)

    # Count colors.
    even_condition = np.isin(image, colors[(counts % 2) == 0])
    odd_condition = ~even_condition

    # Since the indices are for the flipped image, flip them back to match the original image.
    last_index_of_each_color = image.size - indices - 1
    last_row_of_each_color = last_index_of_each_color // image.shape[1]  # row = index // width

    # We know the last_row_of_each_color, but the color may appear more than once in the last row.
    # So, for each last row, we extend to a boolean map that represents the columns in which the color appears.
    column_mask_for_each_last_row = image[last_row_of_each_color].squeeze() == colors[..., np.newaxis]

    # This is what we want to do, but it does not work in numpy.
    # odd_condition[last_row_of_each_color][column_mask_for_each_last_row] = False

    # Instead, we need to do this. Logically the same.
    color_idx, column_indices_for_each_last_row = np.where(column_mask_for_each_last_row)
    odd_condition[last_row_of_each_color[color_idx], column_indices_for_each_last_row] = False

    # Finally, we can fill colors.
    image[even_condition] = color_for_even
    image[odd_condition] = color_for_odd

    # Don't forget to restore the image to its original dtype.
    image = image.view(np.uint8)

    # Also, don't forget to convert the image to RGB if necessary.
    return Image.fromarray(image).convert("RGB")

input_image = Image.open("./input.png").convert("RGB")
result_image = count_colors(input_image)
result_image.save("./output.png")


此函数扫描一行中的每种唯一颜色,因此执行速度在很大程度上取决于图像中显示的颜色数。对于随机生成的1366 x 768的图像,它需要5秒多的时间,但对于颜色较少的图像,如照片,它会比这快得多。

of1yzvn4

of1yzvn42#

我将首先关注速度要求。在纯python中,长度为256^3的for循环将非常慢。如果可能,您应该尝试使用向量化的numpy操作以提高效率。
开始,我们只需要考虑图像中实际存在的颜色:

colors, counts = np.unique(A.reshape(-1, A.shape[-1]), axis=0, return_counts=True)

字符串
即使有了这个大大减少的颜色子集,在纯python中迭代它们仍然非常慢。对于偶数颜色计数,我们可以使用np.isin重写代码,以完全消除for循环:

# Define custom pixel dtype for faster isin comparison
# See https://stackoverflow.com/a/61878847/13145954 for more info
pixel = np.dtype([("r", np.uint8), ("g", np.uint8), ("b", np.uint8)])

data = A.reshape(-1, A.shape[-1]).view(pixel).squeeze(-1)
even_colors = colors[counts % 2 == 0].view(pixel).squeeze(-1)
# data is a view of A, so modifying data modifies A
data[np.isin(data, even_colors)] = (1, 1, 1)


然后,我们可以处理更小的剩余情况下,颜色计数是奇数。我使用了for循环,因为我找不到一种方法来矢量化它。我们忽略一种颜色只出现一次的情况,因为在这种情况下,我们不会修改它唯一出现的行,因为该行也是最后一行

for color in colors[(counts % 2 == 1) & (counts > 1)]:
    # (i,j) coords where color appears
    coords = np.argwhere((A == color).all(axis=2))
    # max row where the color occurs
    max_row_with_color = coords[:, 0].max()
    # (i,j) coords where the color occurs, excluding last row
    filtered_coords = coords[coords[:, 0] != max_row_with_color]

    A[filtered_coords[:, 0], filtered_coords[:, 1]] = [255, 255, 255]


把它们放在一起:

import numpy as np
import tqdm

# Generate random image, A
A = np.random.randint(0, 256, (1273, 768, 3), dtype=np.uint8)

# Extract all colors in image to avoid looping over 256^3 colors, most of which will be unused
colors, counts = np.unique(A.reshape(-1, A.shape[-1]), axis=0, return_counts=True)

## Replace even colors

# Define custom pixel dtype for faster isin comparison
# See https://stackoverflow.com/a/61878847/13145954 for more info
pixel = np.dtype([("r", np.uint8), ("g", np.uint8), ("b", np.uint8)])

data = A.reshape(-1, A.shape[-1]).view(pixel).squeeze(-1)
even_colors = colors[counts % 2 == 0].view(pixel).squeeze(-1)
# data is a view of A, so modifying data modifies A
data[np.isin(data, even_colors)] = (1, 1, 1)

## Replace odd colors

# Manually loop over colors with odd counts
# We can ignore colors that only appear once, as we don't touch the last row
for color in tqdm.tqdm(colors[(counts % 2 == 1) & (counts > 1)]):
    # (i,j) coords where color appears
    coords = np.argwhere((A == color).all(axis=2))
    # max row where the color occurs
    max_row_with_color = coords[:, 0].max()
    # (i,j) coords where the color occurs, excluding last row
    filtered_coords = coords[coords[:, 0] != max_row_with_color]

    A[filtered_coords[:, 0], filtered_coords[:, 1]] = [255, 255, 255]


上面的代码在网上找到的随机1200x600图像上运行了大约2分钟。

相关问题