pythonimage-processingopencvthresholdadaptive-threshold

How can I improve color thresholding output?


In the above article they have the following image:

enter image description here

And, they wanted to obtain an output like the following:

enter image description here

I ran the following script:

import cv2

window_name = 'image'

img = cv2.imread("photo.png")

cv2.imshow(window_name, img)
cv2.waitKey(0)


gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray = cv2.medianBlur(gray, 5)
edges = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 9, 9)

cv2.imshow(window_name, edges)
cv2.waitKey(0)

color = cv2.bilateralFilter(img, 9, 250, 250)
cartoon = cv2.bitwise_and(color, color, mask=edges)

cv2.imshow(window_name, cartoon)
cv2.waitKey(0)

Firstly, the script is very slow.

Secondly, the output is not what they promised would be:

enter image description here

How can I fix these two issues?


Solution

  • Brief description

    I'm so interested in your question, so I tried your suggested website's code, the code you posted, and myself googled a few to tried. Even discussed with my peers, my professor who taught introductory image processing/computer vision using C# that I took couple years ago.

    Discussion feedback

    Sadly they all respond the same and like what I initially thought, it's not possible to transform/convert directly into the second picture in your post, the posted second picture is most likely to be an artistic graphics photo. Well, maybe you dig deeper maybe there's actually a module or library that can actually transform/convert it 100% like the second picture.

    Examples code testing

    So, I begin trying out the contents of your posted website, snipped a bit there, adjusted some, but overall, no where near to the second cartoon picture.

    1. The code and result of "Converting An Image To A Cartoon Using OpenCV"
    import cv2
    from matplotlib import pyplot as plt
    
    
    # Reading image
    img = cv2.imread("img.png")
    plt.imshow(img)
    
    # Converting to RGB
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    plt.imshow(img)
    
    # Detecting edges of the input image
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    gray = cv2.medianBlur(gray, 9)
    edges = cv2.adaptiveThreshold(
                gray, 255, 
                cv2.ADAPTIVE_THRESH_MEAN_C,
                cv2.THRESH_BINARY, 9, 9
            )
    
    # Cartoonifying the image
    color = cv2.bilateralFilter(img, 9, 250, 250)
    cartoon = cv2.bitwise_and(color, color, mask=edges)
    plt.imshow(cartoon)
    plt.savefig("cartoonify.png")
    plt.show()
    

    result of "Converting An Image To A Cartoon Using
OpenCV"

    1. Moving on, then I tried your code in the post, and it's actually made some differences, and it doesn't run slow or didn't make changes. I ran your code, and it did made some change, the code stays pretty much the same, just added saving image methods at the end, cv2.imwrite().
    import cv2
    import matplotlib.pyplot as plt
    
    window_name = "image"
    
    img = cv2.imread("img.png")
    
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    gray = cv2.medianBlur(gray, 5)
    edges = cv2.adaptiveThreshold(
                gray, 255,
                cv2.ADAPTIVE_THRESH_MEAN_C,
                cv2.THRESH_BINARY,
                9, 9
            )
    
    color = cv2.bilateralFilter(img, 9, 250, 250)
    cartoon = cv2.bitwise_and(color, color, mask=edges)
    
    cv2.imshow(window_name, cartoon)
    cv2.waitKey(0)
    
    cv2.imwrite("cartoon_op.png", cartoon)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    

    op's code's result image

    1. The third, I searched on github, found this code, but for this I used my stackoverlfow profile picture, which it's a headshot, I thought maybe the white background would make more visible difference, but it didn't, compared to previous examples, it's pretty much close.
    import cv2
    import numpy as np
    from tkinter.filedialog import *
    
    photo = askopenfilename()
    img = cv2.imread(photo)
    
    grey = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    grey = cv2.medianBlur(grey, 5)
    edges = cv2.adaptiveThreshold(grey, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 9, 9)
    
    #cartoonize
    color = cv2.bilateralFilter(img, 9, 250, 250)
    cartoon = cv2.bitwise_and(color, color, mask = edges)
    
    cv2.imshow("Image", img)
    cv2.imshow("Cartoon", cartoon)
    
    #save
    cv2.imwrite("cartoon-git.png", cartoon)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    

    headshot

    1. Just before almost finished with the answer, I found this example gives the closest result of cartoonized picture example on Dev - How to cartoonize an image with Python, this example used Elon Musk's photo to demonstrate, although it's the closest to cartoon, but the size somehow just got really small.
    import numpy as np
    import cv2
    
    file_name = "elon.jpg"
    
    def resize_image(image):
        scale_ratio = 0.3
        width = int(image.shape[1] * scale_ratio)
        height = int(image.shape[0] * scale_ratio)
        new_dimensions = (width, height)
        resized = cv2.resize(
                    image, new_dimensions,
                    interpolation=cv2.INTER_AREA
                )
        return resized
    
    def find_countours(image):
        contoured_image = image
        gray = cv2.cvtColor(contoured_image, cv2.COLOR_BGR2GRAY)
        edged = cv2.Canny(gray, 30, 100)
        contours, hierarchy = cv2.findContours(
                                edged, cv2.RETR_EXTERNAL,
                                cv2.CHAIN_APPROX_NONE
                            )
        cv2.drawContours(
            contoured_image, contours, 
            contourIdx=-1, color=1,
            thickness=1
        )
        cv2.imshow("Image after contouring", contoured_image)
        cv2.waitKey(0)
        cv2.destroyAllWindows()
    
        return contoured_image
    
    def color_quantization(image, k=4):
        z = image.reshape((-1, 3))
        z = np.float32(z)
        criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 
                    10000, 0.0001)
        compactness, label, center = cv2.kmeans(z, k, None, criteria,
                                                1, cv2.KMEANS_RANDOM_CENTERS)
        center = np.uint8(center)
        res = center[label.flatten()]
        res2 = res.reshape((image.shape))
    
        return res2
    
    if __name__ == '__main__':
        
        image = cv2.imread(file_name)
        resized_image = resize_image(image)
        coloured = color_quantization(resized_image)
        contoured = find_countours(coloured)
        final_image = contoured
        save_q = input("Save the image? [y]/[n]: ")
    
        if save_q == "y":
            cv2.imwrite("cartoonized_" + file_name, final_image)
            print("Image saved!")
    

    Original Elon.jpg

    orig elon.jpg

    Cartoonized Elon.jpg

    Elon-Must-Cartoonized

    Wrapping up

    I hope this long answer that sounded like no definitive answer helps, it's just what I found interested and decided to share the process of discovering it.