2

Editors comment:


  • How to count pixels occurences in an image?

I have a set of images where each pixel consists of 3 integers in the range 0-255.

I am interested in finding one pixel that is "representative" (as much as possible) for the entire pixel-population as a whole, and that pixel must occur in the pixel-population. I am determining which pixel is the most common (the median mode) in my set of images makes the most sense.

I am using python, but I am not sure how to go about it. The images are stored as an numpy array with dimensions [n, h, w, c], where n is the number of images, h is the height, w is the widthandc` is the channels (RGB).

Eran Moshe
  • 3,062
  • 2
  • 22
  • 41
Toke Faurby
  • 5,788
  • 9
  • 41
  • 62
  • How is your pixel data represented? An array of array of array? (rows x columns x rgbvalues ?) – Caius Jard Oct 01 '18 at 12:37
  • I have updated the post with a description of the data – Toke Faurby Oct 01 '18 at 13:00
  • The median is not the most common one, that would be the mode. The median is the one that, after sorting, is exactly halfway down the list. It is therefore inherently a 1D property. You have 3D data (3 values per pixel means pixels live in a 3D world). You could try the vector median, which is an extension to multiple dimensions of the median concept, but it is never a true median in more than one dimension. – Cris Luengo Oct 01 '18 at 13:06
  • I think you need to find histogram/pdf of the image. Put the three bytes (0-255) in an integer like this `pixel = (r << 16) | (g << 8) | b`, then find the probability density function of image – user8190410 Oct 01 '18 at 13:44
  • @user8190410: there you need to compute a very large histogram! :) – Cris Luengo Oct 01 '18 at 17:48
  • @CrisLuengo: please look at the answer, it is histogram based :) – user8190410 Oct 01 '18 at 18:58
  • @user8190410: I've seen the answer, it uses a dictionary to hold the histogram. An array of 16 M elements is not all that crazy nowadays, but most of those elements will stay empty, so the dictionary is quite clever. Still, 16 M elements is very large for a histogram. I'm not saying it's wrong, just that it's very large. – Cris Luengo Oct 01 '18 at 19:06

3 Answers3

3

I'm going to assume you need to find the most common element, which as Cris Luengo mentioned is called the mode. I'm also going to assume that the bit depth of the channels is 8-bit (value between 0 and 255, i.e. modulo 256).

Here is an implementation independent approach:

The aim is to maintain a count of all the different kinds of pixels encountered. It makes sense to use a dictionary for this, which would be of the form {pixel_value : count}.

Once this dictionary is populated, we can find the pixel with the highest count.

Now, 'pixels' are not hashable and hence cannot be stored in a dictionary directly. We need a way to assign an integer(which I'll be referring to as the pixel_value) to each unique pixel, i.e., you should be able to convert pixel_value <--> RGB value of a pixel

This function converts RGB values to an integer in the range of 0 to 16,777,215:

def get_pixel_value(pixel):
    return pixel.red + 256*pixel.green + 256*256*pixel.blue 

and to convert pixel_value back into RGB values:

def get_rgb_values(pixel_value):
    red = pixel_value%256
    pixel_value //= 256
    green = pixel_value%256
    pixel_value //= 256
    blue = pixel_value
    return [red,green,blue]

This function can find the most frequent pixel in an image:

def find_most_common_pixel(image):
    histogram = {}  #Dictionary keeps count of different kinds of pixels in image

    for pixel in image:
        pixel_val = get_pixel_value(pixel)
        if pixel_val in histogram:
            histogram[pixel_val] += 1 #Increment count
        else:
            histogram[pixel_val] = 1 #pixel_val encountered for the first time

    mode_pixel_val = max(histogram, key = histogram.get) #Find pixel_val whose count is maximum
    return get_rgb_values(mode_pixel_val)  #Returna a list containing RGB Value of the median pixel

If you wish to find the most frequent pixel in a set of images, simply add another loop for image in image_set and populate the dictionary for all pixel_values in all images.

Aayush Mahajan
  • 3,856
  • 6
  • 25
  • 32
  • 1
    Finally, I would like to mention that it might make more sense to find the average RGB value of the biggest image segment rather than to find a single most common value. Look into [image segmentation, specifically clustering](https://en.wikipedia.org/wiki/Image_segmentation#Clustering_methods) and see if that is more apt to your application. You should also consider that image segmentation consumes a lot more resources. – Aayush Mahajan Oct 01 '18 at 14:00
  • 1
    I very specifically want the mode (thanks for correction) for this specific application - it isn't clustering as such – Toke Faurby Oct 01 '18 at 17:08
  • 1
    The mode is very noise-sensitive. I like the segmentation approach, but you could also think about some edge-preserving smoothing (e.g. a bilateral filter) to make sure that neighboring pixels with similar value become pixels with identical value, and hence contribute to the same dictionary entry and have a chance to build up a higher peak. Some form of discretization of pixel values is also possible. – Cris Luengo Oct 01 '18 at 17:50
  • That is a good point, but these images are not natural images, so it isn't a concern. I don't quite get the segmentation approach - I am only interested in finding one pixel that is "representative" (as much as possible) for the entire pixel-population as a whole, and that pixel must occur in the pixel-population – Toke Faurby Oct 03 '18 at 17:20
0

You can iterate over the x/y of the image. a pixel will be img_array[x, y, :] (the : for the RBG channel) you will add this to a Counter (from collections) Here is an example of the concept over an Image

from PIL import Image
import numpy as np
from collections import Counter

# img_path is the path to your image
cnt = Counter()
img = Image.open(img_path)
img_arr = np.array(img)

for x in range(img_arr.shape[0]):
    for y in range(img_arr.shape[1]):
        cnt[str(img_arr[x, y, :])] += 1

print(cnt)

# Counter({'[255 255 255]': 89916, '[143 143 143]': 1491, '[0 0 0]': 891, '[211 208 209]': 185, ...

A More efficient way to do it is by using the power of numpy and some math manipulation (because we know values are bound [0, 255]

img = Image.open(img_path)
img_arr = np.array(img)
pixels_arr = (img_arr[:, :, 0] + img_arr[:, :, 1]*256 + img_arr[:, :, 2]*(256**2)).flatten()
cnt = Counter(pixels_arr)

# print(cnt)
# Counter({16777215: 89916, 9408399: 1491, 0: 891, 13750483: 185, 14803425: 177, 5263440: 122 ...

# print(cnt.most_common(1))
# [(16777215, 89916)]
pixel_value = cnt.most_common(1)[0][0]

Now a conversion back to the original 3 values is exactly like Aayush Mahajan have writte in his answer. But I've shorten it for the sake of simplicity:

r, b, g = pixel_value%256, (pixel_value//256)%256, pixel_value//(256**2)

So you are using the power of numpy fast computation (and it's significate improvement on run time.

You use Counter which is an extension of python dictionary, dedicated for counting.

Eran Moshe
  • 3,062
  • 2
  • 22
  • 41
0

You can use numpy.unique to get the unique pixels and how often they appear.

import cv2
import numpy as np

# Load the image
image = cv2.imread("image.jpg")

# Reshape the image to a 2D array of pixels
pixels = image.reshape(-1, 3)

# Count the occurrences of each unique pixel-RGB combination
unique_colors, counts = np.unique(pixels, axis=0, return_counts=True)

# Find the index of the most frequent combination
most_frequent_index = np.argmax(counts)

# Get the most frequent pixel-RGB combination
most_frequent_color = unique_colors[most_frequent_index]
Pibe_chorro
  • 99
  • 1
  • 8