Медленная numpy обработка массива python - PullRequest
0 голосов
/ 07 мая 2020

Я начал работать с изображениями, и в настоящее время я пытаюсь изменить масштаб и оттенки серого изображения (размер 6000x4000 -> 600x400), чтобы лучше работать с ним. Для этого я использую Numpy и PIL.Images.

import PIL.Image as Img
import numpy as np

img = Img.open('rendering/testpic.jpg', 'r')

r, g, b = img.split()
channels = np.array([np.array(r), np.array(g), np.array(b)])

small_channels = []
for channel in channels:
    x_len = len(channel)//10
    y_len = len(channel[0])//10
    for chunk_x in range(x_len):
        for chunk_y in range(y_len):
            pix_sum = 0
            for x_in_chunk in range(10):
                for y_in_chunk in range(10):
                    pix_sum += channel[chunk_x*10+x_in_chunk,chunk_y*10+y_in_chunk]
            channel[chunk_x,chunk_y] = pix_sum // 100
    small_channels.append(channel[:x_len,:y_len])

channels = np.array(small_channels)

grayscale = np.round((channels[0]*0.3+ channels[1]*0.6+ channels[2]*0.1)).astype('uint8')
pixels = np.stack([grayscale, grayscale, grayscale], axis = 2)
new_img = Img.fromarray(pixels)
new_img.show()

Итак, я делаю разбиение каналов на фрагменты размером 10, а затем отображаю среднее значение фрагмента в верхнем левом углу. В конце концов я обрезал остальную часть картинки.

В общей сложности у меня это занимает от 100 до 130 секунд. Есть ли более быстрый способ сделать это? Где я неэффективен? Я новичок, так что я, вероятно, много делаю неправильно. Как Photoshop, например, так быстро масштабирует изображения вверх и вниз?

Ответы [ 2 ]

1 голос
/ 07 мая 2020

Вместо того, чтобы перебирать каждый пиксель в вашем изображении, мы можем использовать нарезку массива numpy и некоторые методы для ускорения работы. Я удалил внутренние циклы и использовал нарезку и метод .sum() массивов numpy:

import PIL.Image as Img
import numpy as np

img = Img.open('rendering/testpic.jpg', 'r')

r, g, b = img.split()
channels = np.array([np.array(r), np.array(g), np.array(b)])

small_channels = []
for channel in channels:
    x_len = len(channel)//10
    y_len = len(channel[0])//10
    for chunk_x in range(x_len):
        for chunk_y in range(y_len):
            # slice all pixels within 10*10 box and sum them
            pix_sum = channel[chunk_x*10:10*(chunk_x+1),chunk_y*10:10*(chunk_y+1)].sum()
            channel[chunk_x, chunk_y] = pix_sum // 100
    small_channels.append(channel[:x_len,:y_len])

channels = np.array(small_channels)

grayscale = np.round((channels[0]*0.3+ channels[1]*0.6+ channels[2]*0.1)).astype('uint8')
pixels = np.stack([grayscale, grayscale, grayscale], axis = 2)
new_img = Img.fromarray(pixels)
new_img.show()

По моим тестам этот алгоритм в 3-4 раза быстрее. Надеюсь, это поможет. Обязательно обратите внимание на массивы numpy - они очень полезны, особенно для изображений, и во многих случаях вычисления выполняются быстрее.

0 голосов
/ 08 мая 2020

Я бы не стал использовать циклы в этом случае, cv2.resize() выполнит работу.

Вот сравнение времени между тремя подходами:

import PIL.Image as Img
import numpy as np
from time import perf_counter
import cv2


def timer(method):
    def timed(*args, **kwargs):
        t1 = perf_counter()
        result = method(*args, **kwargs)
        t2 = perf_counter() 
        print(f'{method.__name__} time: {t2 - t1} seconds')
        return result
    return timed


@timer
def resize_1(image_path, shrink):
    img = Img.open(image_path, 'r')
    r, g, b = img.split()
    channels = np.array([np.array(r), np.array(g), np.array(b)])
    small_channels = []
    for channel in channels:
        x_len = len(channel)//shrink
        y_len = len(channel[0])//shrink
        for chunk_x in range(x_len):
            for chunk_y in range(y_len):
                pix_sum = 0
                for x_in_chunk in range(shrink):
                    for y_in_chunk in range(shrink):
                        pix_sum += channel[chunk_x*shrink+x_in_chunk,chunk_y*shrink+y_in_chunk]
                channel[chunk_x,chunk_y] = pix_sum // 100
        small_channels.append(channel[:x_len,:y_len])
    channels = np.array(small_channels)
    grayscale = np.round((channels[0]*0.3+ channels[1]*0.6+ channels[2]*0.1)).astype('uint8')
    pixels = np.stack([grayscale, grayscale, grayscale], axis = 2)
    return Img.fromarray(pixels)


@timer
def resize_2(image_path, shrink):
    img = Img.open(image_path, 'r')
    r, g, b = img.split()
    channels = np.array([np.array(r), np.array(g), np.array(b)])
    small_channels = []
    for channel in channels:
        x_len = len(channel)//shrink
        y_len = len(channel[0])//shrink
        for chunk_x in range(x_len):
            for chunk_y in range(y_len):
                # slice all pixels within 10*10 box and sum them
                pix_sum = channel[chunk_x*shrink:shrink*(chunk_x+1),
                          chunk_y*shrink:shrink*(chunk_y+1)].sum()
                channel[chunk_x, chunk_y] = pix_sum // 100
        small_channels.append(channel[:x_len,:y_len])
    channels = np.array(small_channels)
    grayscale = np.round((channels[0]*0.3+ channels[1]*0.6+ channels[2]*0.1)).astype('uint8')
    pixels = np.stack([grayscale, grayscale, grayscale], axis = 2)
    return Img.fromarray(pixels)


@timer
def resize_3(image_path, shrink):
    image = cv2.imread(image_path)
    size = image.shape[:-1]
    new_size = tuple(int(item / shrink) for item in size)[::-1]
    resized = cv2.resize(image, tuple(new_size))
    gray = cv2.cvtColor(resized, cv2.COLOR_BGR2GRAY)
    return gray


if __name__ == '__main__':
    img = 'sample_image.png'
    shrink_by = 10
    image1, image2, image3 = [item(img, shrink_by) for item in [resize_1, resize_2, resize_3]]
    image1.show()
    image2.show()
    cv2.imshow('resize_3', image3)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

Out:

resize_1 time: 1.980221013 seconds
resize_2 time: 0.3170622839999999 seconds
resize_3 time: 0.01659756599999973 seconds
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...