3

I extracted an object from an image, so now I have a masked image with a tennis ball and a black background.

I want to extract the color features from the tennis ball alone via a histogram. This is the code I have so far, but by the looks of the histogram, the black background dominates the any of the other colors, which makes the histogram ineffective:

from PIL import Image
from pylab import *

# Import image and convert to gray
image = Image.open("res_300.png")
im = image.convert('L')
im_array = array(im)

# Create a new figure
figure()
gray()

# Show contours with origin upper left corner
contour(im, origin='image')
axis('equal')
axis('off')

# Create histogram
figure()
hist(im_array.flatten(), 128)
show()

Is there a way to draw the histogram from the tennis ball BGR color features while disregarding the black background?

I'm a python rookie. Thank you.

nathancy
  • 42,661
  • 14
  • 115
  • 137
dvargasp
  • 61
  • 1
  • 5

3 Answers3

2

To split the color channels into BGR, we can use cv2.split() then use cv2.calcHist() to extract the color features with a histogram. To remove the dominant black background, we can set the range to [1, 256].

enter image description here

from matplotlib import pyplot as plt
import cv2

image = cv2.imread('1.png')

channels = cv2.split(image)
colors = ("b", "g", "r")

plt.figure()
plt.title("Color Histogram")
plt.xlabel("Bins")
plt.ylabel("# of Pixels")
features = []

# Loop over the image channels (B, G, R)
for (channel, color) in zip(channels, colors):

    # Calculate histogram
    hist = cv2.calcHist([channel], [0], None, [255], [1, 256])
    features.extend(hist)

    # Plot histogram
    plt.plot(hist, color = color)
    plt.xlim([0, 256])

plt.show()
nathancy
  • 42,661
  • 14
  • 115
  • 137
1

There is np.histogram that does so as well. And similar to nathancy's answer, one can set limit from 1 to ignore the black background:

image = cv2.imread('8fk43.png')

plt.figure(figsize=(10,6))

for i, c in zip(range(3), 'bgr'):
    hist,bins = np.histogram(image[:,:,i], bins=range(0,256))

    plt.plot(bins[1:-1], hist[1:], c=c)

plt.show()

Output:

enter image description here

Note: The above approach is somewhat incorrect. Think about the pure green (0,255,0) will be overlooked at the red/blue channels. The correct way would be masking the tennis ball first, and pass the mask into np.histogram as weights.

For example, in this case, one can simply take mask as where all the channels are non-zero:

mask = (image>0).sum(-1)
mask = np.array(mask>0, dtype=np.uint8)

which gives mask:

enter image description here

And then,

plt.figure(figsize=(10,6))

for i, c in zip(range(3), 'bgr'):
    hist,bins = np.histogram(image[:,:,i], bins=range(0,256), weights=mask)

    plt.plot(bins[:-1], hist, c=c)

plt.show()

gives a more reasonable histogram with respect to the actual size of the ball:

enter image description here

Quang Hoang
  • 146,074
  • 10
  • 56
  • 74
0

You could neglect/remove low intensity value (corresponding to black) from all 3 RGB channels. Then take a mean in all 3 channels to get the corresponding RGB value of the desired object.

PaxPrz
  • 1,778
  • 1
  • 13
  • 29