使用numpy函数替换for循环以减少图像尺寸和颜色数量

trnvg8h3  于 2023-08-05  发布在  其他
关注(0)|答案(1)|浏览(75)

首先,我对Python及其库相对较新。我尝试使用Pillow循环遍历图像的所有像素,将图像划分为原始尺寸的一小部分,方法是从原始图像的所有颜色中提取最接近的RGB颜色作为划分矩阵。使用以下函数,对于具有大量颜色的平均图像,这需要超过 * 三个小时 *:

def divide_image_by_rgb(image_file, division=4):
    input_img = Image.open(image_file)
    width, height = input_img.size
    pixels_count = width * height

    if division < 2:
        raise Exception("Invalid division!")

    white = (255, 255, 255)
    width_out = width // division
    height_out = height // division
    output_img = Image.new('RGB', (width_out, height_out), white)
    all_colors = list(get_colors_count(input_img).keys())

    for x in range(width_out):
        for y in range(height_out):
            pixels = []
            [r, g, b] = [0, 0, 0]
            for d1 in range(division):
                for d2 in range(division):
                    pos_x = x * division + d1
                    pos_y = y * division + d2
                    if pos_x > width or pos_y > height:
                        continue

                    pixel = input_img.getpixel((pos_x, pos_y))
                    pixels.append(pixel)
                    r += pixel[0] ** 2
                    g += pixel[1] ** 2
                    b += pixel[2] ** 2

            div = division ** 2
            mean_color = (
                round(sqrt(r // div)),
                round(sqrt(g // div)),
                round(sqrt(b // div))
            )
            mean_color = closest(all_colors, mean_color)
            output_img.putpixel((x, y), mean_color)
            # log_reduce_progress(changed, pixels_count, height, width, idx, x, y)

    output_file = f"resized.[{division}].{os.path.basename(image_file)}"

    output_img.save(output_file)
    return output_file


def closest(colors, color):
    colors = np.array(colors)
    color = np.array(color)
    distances = np.sqrt(np.sum((colors - color) ** 2, axis=1))
    index_of_smallest = np.where(distances == np.amin(distances))
    smallest_distance = tuple(colors[index_of_smallest][0])
    return smallest_distance

字符串

目标:我想用numpy替换显式循环
输入图片:输入图像类似于以下内容:

PNG,大小:(6.32MP)尺寸:2228x2836,深度:24位,颜色计数:632576

结果

PNG,大小:(0.39MP)尺寸:557x709,深度:24位,颜色计数:110896

为什么:我想减少图像尺寸和颜色,用最少的颜色进行平滑,图像的色调,均匀性和组成不应该丢失。我使用 libimagequant,* quantization ADAPTIVE*,从给定的 RGB palette csv中减少颜色,等等。以前,但这种方法给我最接近的颜色组合所需的结果,我使用结果图像作为RGB调色板,以减少原始图像的颜色相对于主颜色。
**尝试:**我尝试用numpy函数来做这件事,但失败了,每次更改,我的错误和困惑都会增加:

def resize_image_manual_ex(image_file, division=4):

    input_img = Image.open(image_file)
    width, height = input_img.size
    pixels_count = width * height

    white = (255, 255, 255)
    width_out = width // division
    height_out = height // division
    output_img = Image.new('RGB', (width_out, height_out), white)

    # Convert the input image to a NumPy array
    input_array = np.array(input_img)

    # Use NumPy's reshaping to create sub-images
    sub_images = input_array.reshape(height_out, division, width_out, division, 3)

    # Calculate the mean color of each sub-image
    mean_colors = np.mean(sub_images, axis=(1, 3))

    # Find the closest color for each mean color
    all_colors = list(get_colors_count(input_img).keys())
    closest_colors = [closest(all_colors, color) for color in mean_colors]

    for x in range(width_out):
        for y in range(height_out):
            output_img.putpixel((x, y), closest_colors[y, x])

    output_file = f"resized.[{division}].{os.path.basename(image_file)}"

    output_img.save(output_file)
    return output_file

上次错误:

sub_images = input_array.reshape(height_out,division,width_out,division,3)
ValueError:无法将大小为56925的数组整形为shape(57,2,82,2,3)
有没有办法用numpy(或其他)代替 for 循环来减少运行时间?非常感谢您的光临。

shstlldc

shstlldc1#

我想这应该能满足你的要求。我不想清除PIL命令,所以检查一下。

import numpy as np
from PIL import Image
from skimage.util import view_as_blocks
from scipy.spatial import KDTree
import os

def divide_image_by_rgb(image_file, division=4):
    """this is all PIL stuff, ask another question if this doesn't work"""
    input_img = Image.open(image_file)

    if division < 2:
        raise Exception("Invalid division!")

    np_image = np.array(input_img)
    mean_img = mean_divide_np_image(np_image, division)

    output_img = Image.fromarray(mean_img)
    output_file = f"resized.[{division}].{os.path.basename(image_file)}"

    output_img.save(output_file)
    
    return output_file

def mean_divide_np_image(input_img, division):
    width, height, _ = input_img.shape
    width_out = width // division
    height_out = height // division
    np_image = np.array(input_img)[:width_out * division, :height_out * division]
    all_colors = np.unique(np_image.reshape(-1, 3), axis = 0)
    img_blocks = view_as_blocks(np_image, (width_out, height_out, 3))
    mean_img = np.mean(img_blocks**2, axis = (0, 1, 2))
    mean_img = np.sqrt(closest(all_colors**2, mean_img)).astype(input_img.dtype)
    
    return mean_img

def closest(colors, img):
    color_tree = KDTree(colors)
    dist, i = color_tree.query(img)
    closest_color = colors[i]
    return closest_color

字符串
我做过的大事:

  • 向量化了均值函数,所以它不是一个像素一个像素的。还使用了view_as_blocks中的view来保存内存。保留了对RGB值求平均值所需的均方根功能,还通过closest搜索携带平方。
  • closest更改为KDTree功能。对于大量的颜色,这要快得多

相关问题