'Adobe Photoshop-style posterization and OpenCV

It seems Adobe Photoshop does posterization by quantizing each color channel separately, based on the number of levels specified. So for example, if you specify 2 levels, then it will take the R value, and set it to 0 if your R value is less than 128 or 255 if your value is >= 128. It will do the same for G and B.

Is there an efficient way to do this in python with OpenCV besides iterating through each pixel and making that comparison and setting the value separately? Since an image in OpenCV 2.4 is a NumPy ndarray, is there perhaps an efficient way to do this calculation strictly through NumPy?



Solution 1:[1]

Your question specifically seems to be asking about a level of 2. But what about levels more than 2. So i have added a code below which can posterize for any level of color.

import numpy as np
import cv2

im = cv2.imread('messi5.jpg')

n = 2    # Number of levels of quantization

indices = np.arange(0,256)   # List of all colors 

divider = np.linspace(0,255,n+1)[1] # we get a divider

quantiz = np.int0(np.linspace(0,255,n)) # we get quantization colors

color_levels = np.clip(np.int0(indices/divider),0,n-1) # color levels 0,1,2..

palette = quantiz[color_levels] # Creating the palette

im2 = palette[im]  # Applying palette on image

im2 = cv2.convertScaleAbs(im2) # Converting image back to uint8

cv2.imshow('im2',im2)
cv2.waitKey(0)
cv2.destroyAllWindows()

This code uses a method called palette method in Numpy which is really fast than iterating through the pixels. You can find more details how it can be used to speed up code here : Fast Array Manipulation in Numpy

Below are the results I obtained for different levels:

Original Image :

enter image description here

Level 2 :

enter image description here

Level 4 :

enter image description here

Level 8 :

enter image description here

And so on...

Solution 2:[2]

Use cv::LUT(). It is simplest and fastest way.

cv::Mat posterize(const cv::Mat &bgrmat, uint8_t lvls)
{
    cv::Mat lookUpTable(1, 256, CV_8U);
    uchar* p = lookUpTable.ptr();
    float step = 255.0f / lvls;
    for(int i = 0; i < 256; ++i)
        p[i] = static_cast<uchar>(step * std::floor(i / step));
    cv::Mat omat;
    cv::LUT(bgrmat,lookUpTable,omat);
    return omat;
}

Solution 3:[3]

The coolest "posterization" I have seen uses Mean Shift Segmentation. I used the code from the author's GitHub repo to create the following image (you need to uncomment line 27 of Maincpp.cpp to perform the segmentation step).

enter image description here

Solution 4:[4]

Generalization for n levels of the answer from fraxel

import cv2 as cv
import matplotlib.pyplot as plt

im = cv.imread("Lenna.png") 

n = 5

for i in range(n):
    im[(im >= i*255/n) & (im < (i+1)*255/n)] = i*255/(n-1)

plt.imshow(cv.cvtColor(im, cv.COLOR_BGRA2RGB))
plt.show()

n = 2

enter image description here

n = 5

Output with n=5

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Community
Solution 2 Alex
Solution 3 wcochran
Solution 4