15

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?

steve8918
  • 1,820
  • 6
  • 27
  • 38

5 Answers5

18

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...

Community
  • 1
  • 1
Abid Rahman K
  • 51,886
  • 31
  • 146
  • 157
  • I am trying to convert this in c++ , how can I quantize the colors – AHF Mar 31 '14 at 12:23
  • 1
    first you find a number in between levels. For example, if 2 levels, take 127 to divide the colors. Then all colors become 0 and 1. If 3 levels, take some 85 and so on. Then dividing will give 0,1,2. Then you expand them to 0,127, 255 etc. – Abid Rahman K Mar 31 '14 at 14:36
11

We can do this quite neatly using numpy, without having to worry about the channels at all!

import cv2
im = cv2.imread('1_tree_small.jpg')
im[im >= 128]= 255
im[im < 128] = 0
cv2.imwrite('out.jpg', im)

output:

enter image description here

input:

enter image description here

fraxel
  • 34,470
  • 11
  • 98
  • 102
  • wow amazing, thanks! I'm a complete newbie with numpy, I didn't realize it was this powerful, I'm going to have to look into this in depth! Thanks again! – steve8918 Jun 16 '12 at 16:45
  • If you want to use this as a artistic or stylistic effect, I highly recommend applying a [Gaussian blur](https://docs.opencv.org/2.4/modules/imgproc/doc/filtering.html#gaussianblur) to the image first, it will clear out ragged edges. You can apply blur again after with a smaller kernel to anti-alias. – Doug K Aug 20 '19 at 08:26
2

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

wcochran
  • 10,089
  • 6
  • 61
  • 69
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;
}
Alex
  • 98
  • 7
1

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