skip to Main Content

I have started working with images and currently I am trying to rescale and grayscale an image (Size 6000×4000 -> 600×400) to better work with it. For this I am using Numpy and 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()

So what I am doing is chunking the channels into chunks size 10, then mapping the average of the chunk into the topleft corner. In the end I cut off the rest of the picture.

In total this takes around 100 to 130 seconds for me. Is there a faster way to do this? Where am I being inefficient? I’m new so I’m probably doing wrong a lot of stuff. How does Photoshop for example scale pictures up and down so fast?

2

Answers


  1. Instead of looping over every pixel in your image we can use numpy array slicing and some methods to speed things up. I have removed the inner loops and used slicing and the .sum() method of numpy arrays:

    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()
    

    This algorithm is 3-4 times faster by my testing. I hope this helps. Definitely have a look at numpy arrays- they are very useful especially for images and the computation is quicker in a lot of cases.

    Login or Signup to reply.
  2. I wouldn’t use loops in this case, cv2.resize() will do the job.

    Here is a time comparison between the three approaches:

    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
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search