Search code examples
python-3.xopencvquantization

Draw or resize plotted quantized image with nearest neighbour scaling


Following this example of K means clustering I want to recreate the same - only I'm very keen for the final image to contain just the quantized colours (+ white background). As it is, the colour bars get smooshed together to create a pixel line of blended colours.

quantization image

Whilst they look very similar, the image (top half) is what I've got from CV2 it contains 38 colours total. The lower image only has 10 colours and is what I'm after.

Let's look at a bit of that with 6 times magnification:

close up

I've tried :

# OpenCV and Python K-Means Color Clustering
# build a histogram of clusters and then create a figure
# representing the number of pixels labeled to each color
hist = colour_utils.centroid_histogram(clt)
bar = colour_utils.plot_colors(hist, clt.cluster_centers_)
bar = cv2.resize(bar, (460, 345), 0, 0, interpolation = cv2.INTER_NEAREST)

However, the resize seems to have no resizing effect or change the scaling type. I don't know what controls the initial image size either. Confused.

Any ideas?


Solution

  • I recommend you to show the image using cv2.imshow, instead of using matplotlib.

    cv2.imshow shows the image "pixel to pixel" by default, while matplotlib.pyplot matches the image dimensions to the size of the axes.

    bar_bgr = cv2.cvtColor(bar, cv2.COLOR_RGB2BGR)  # Convert RGB to BGR
    cv2.imshow('bar', bar_bgr)
    cv2.waitKey()
    cv2.destroyAllWindows()
    

    In case you want to use matplotlib, take a look at: Display image with a zoom = 1 with Matplotlib imshow() (how to?).


    Code used for testing:

    # import the necessary packages
    import numpy as np
    from sklearn.cluster import KMeans
    import matplotlib.pyplot as plt
    import argparse
    #import utils
    import cv2
    
    def centroid_histogram(clt):
        # grab the number of different clusters and create a histogram
        # based on the number of pixels assigned to each cluster
        numLabels = np.arange(0, len(np.unique(clt.labels_)) + 1)
        (hist, _) = np.histogram(clt.labels_, bins = numLabels)
        # normalize the histogram, such that it sums to one
        hist = hist.astype("float")
        hist /= hist.sum()
        # return the histogram
        return hist
    
    def plot_colors(hist, centroids):
        # initialize the bar chart representing the relative frequency
        # of each of the colors
        bar = np.zeros((50, 300, 3), dtype = "uint8")
        startX = 0
        # loop over the percentage of each cluster and the color of
        # each cluster
        for (percent, color) in zip(hist, centroids):
            # plot the relative percentage of each cluster
            endX = startX + (percent * 300)
            cv2.rectangle(bar, (int(startX), 0), (int(endX), 50),
                color.astype("uint8").tolist(), -1)
            startX = endX
        
        # return the bar chart
        return bar
    
    
    # load the image and convert it from BGR to RGB so that
    # we can dispaly it with matplotlib
    image = cv2.imread('chelsea.png')
    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
    # show our image
    plt.figure()
    plt.axis("off")
    plt.imshow(image)
    
    # reshape the image to be a list of pixels
    image = image.reshape((image.shape[0] * image.shape[1], 3))
    
    # cluster the pixel intensities
    clt = KMeans(n_clusters = 5)
    clt.fit(image)
    
    
    # build a histogram of clusters and then create a figure
    # representing the number of pixels labeled to each color
    hist = centroid_histogram(clt)
    bar = plot_colors(hist, clt.cluster_centers_)
    # show our color bart
    #plt.figure()
    #plt.axis("off")
    #plt.imshow(bar)
    #plt.show()
    
    bar = cv2.resize(bar, (460, 345), 0, 0, interpolation = cv2.INTER_NEAREST)
    
    bar_bgr = cv2.cvtColor(bar, cv2.COLOR_RGB2BGR)  # Convert RGB to BGR
    cv2.imshow('bar', bar_bgr)
    cv2.waitKey()
    cv2.destroyAllWindows()