Home > Back-end >  converting a PIL Image to a numpy array, result looks garbled/sheared
converting a PIL Image to a numpy array, result looks garbled/sheared

Time:01-21

I'm trying to write a Python function that takes an RGB image, looks at each pixel's RGB values (0-255) and simply finds the mean (average) of the three values. I wrote the following function:

def avgcolors(img_matrix):
    M, N, D = img_matrix.shape
    new_img = np.zeros((M, N))
    for i in range(M):
        for j in range(N):
            pixel_mean = int(np.round(np.mean(img_matrix[i][j])))
            new_img[i][j] = pixel_mean
    return(new_img)

To me the problem seems very straight forward. Run through the image matrix indexing pixels with i, j. Find the mean of the three values in each pixel and write that to a new image matrix. However, something strange happens when I run the following:

pic = Image.open("red.jpg")
img = np.array(pic.getdata()).reshape(pic.size[0], pic.size[1], 3)
grayscale = avgcolors(img)
plt.imshow(grayscale, cmap='gray')

The "red.jpg" image and the output are shown below: enter image description here

I would have thought that when the script reaches the red pixels it would average out the values 255, 0, 0 to get a grayish pixel with a value of 85. And elsewhere, it would just be black. Instead, I am not getting that and I get strange diagonal artifacts. What am I doing wrong? I can't figure it out.

CodePudding user response:

I've found the problem. The problem was in converting the image to a numpy array. It seems the command

img = np.array(pic.getdata()).reshape(pic.size[0], pic.size[1], 3)

Rearranges the array in a strange way. I've simply changed this line of code for:

img = np.array(pic)

CodePudding user response:

Here is an implementation of this using lists.

image = np.array([
    np.concatenate([[[0,0,0]]*10]),
    np.concatenate([[[0,0,0]]*10]),
    np.concatenate([[[0,0,0]]*10]),
    np.concatenate([[[0,0,0]]*10]),
    np.concatenate([[[0,0,0]]*10]),
    np.concatenate([[[0,0,0]]*4,[[255,0,0]]*2,[[0,0,0]]*4]),
    np.concatenate([[[0,0,0]]*10]),
    np.concatenate([[[0,0,0]]*10]),
    np.concatenate([[[0,0,0]]*10]),
    np.concatenate([[[0,0,0]]*10]),
    np.concatenate([[[0,0,0]]*10])
])

image = image.tolist()

def mean_image_explicit(image):

    rows = []
    
    for i in range(len(image)):
        cols = []
        for j in range(len(image[0])):
            v = sum(image[i][j]) / len(image[i][j])
            cols.append(v)
        rows.append(cols)
            
    return rows

def mean_image(image):

    rows = [[sum(image[i][j]) / len(image[i][j]) for j in range(len(image[0]))] for i in range(len(image))]

    return rows


avg = mean_image(image)

plt.imshow(image)
plt.show()
plt.imshow(avg,cmap='gray')
plt.show()

enter image description here

CodePudding user response:

I hope I'm understanding correctly... if you are trying to compute the mean of an 3-dimensional array on a specific axis, you can use np.array.mean(axis=2):

import numpy as np
import matplotlib.pyplot as plt

image = np.random.randint(0, 255, size=(10, 10, 3), dtype=np.uint8)
print(image.shape) # (10,10,3)
plt.imshow(image)
plt.show()

mean_rgb = image.mean(axis=2)
print(mean_rgb.shape) # (10,10)
plt.imshow(mean_rgb,cmap="Greys")
plt.show()

enter image description here enter image description here

CodePudding user response:

You can simply use img = np.array(pic) because PIL and numpy understand each other like that.

The .getdata() call should not be used. It is less efficient.

The .reshape did garble the data, but doesn't necessarily.

The issue you're having there is that you mixed up width and height.

  • PIL.Image.size is a (width, height) tuple.
  • Numpy requires the shape to be (height, width, channels).

If you use the shape (pic.size[1], pic.size[0], 3), your original code would work just fine.

  • Related