Amandasaurus
Amandasaurus

Reputation: 60759

Doing the same as ImageMagick's "-level" in python / PIL?

I want to adjust the colour levels of an image in python. I can use any python library that can easily be installed on my Ubuntu desktop. I want to do the same as ImageMagick's -level ( http://www.imagemagick.org/www/command-line-options.html#level ). PIL (Python Image Library) doesn't seem to have it. I have been calling convert on the image and then reading in the file back again, but that seems wasteful. Is there a better / faster way?

Upvotes: 5

Views: 5608

Answers (4)

M.Hefny
M.Hefny

Reputation: 2743

using code from this link here

# Auto leveling for image

    def levels(data, all_same = 0, clip = 0): 

        if data.mode not in ['RGB', 'CMYK']: 
            return data 

        ## get redistriputed histogram scalled smoothly
        lut = _makelut(data, all_same, clip) 

        ## update image points using histogram
        data = data.point(lut) 

        return data 

    def _find_hi_lo(lut, clip): 
        min = None 
        max = None 

        for i in range(len(lut)): 
            if lut[i] > clip: 
                min = i 
                break 

        lut.reverse() 

        for i in range(len(lut)): 
            if lut[i] > clip: 
                max = 255 - i 
                break 

        return min, max 

    def _scale(channels, min, max): 
        lut = []
        # hefny fix
        ratio = float(max-min)
        if ratio == 0:
            ratio = 1

        for i in range (channels): 
            for i in range(256): 
                value = int((i - min)*(255.0/ratio)) 
                if value < 0: 
                    value = 0 
                if value > 255: 
                    value = 255 
                lut.append(value) 

        return lut 


    def _makelut(data, all_same, clip): 

        histogram = data.histogram() 

        lut = [] 
        r, g, b, k = [], [], [], [] 

        channels = len(histogram)/256 

        for i in range(256): 
            r.append(histogram[i]) 
            g.append(histogram[256+i]) 
            b.append(histogram[512+i]) 
        if channels == 4: 
            for i in range(256): 
                k.append(histogram[768+i]) 


        rmin, rmax = _find_hi_lo(r, clip) 
        gmin, gmax = _find_hi_lo(g, clip) 
        bmin, bmax = _find_hi_lo(b, clip) 
        if channels == 4: 
            kmin, kmax = _find_hi_lo(k) 
        else: 
            kmin, kmax = 128, 128 

        if all_same == 1: 

            min_max = [rmin, gmin, bmin, kmin, rmax, gmax, bmax, kmax] 
            min_max.sort() 
            lut = _scale(channels, min_max[0], min_max[-1]) 

        else: 

            r_lut = _scale(1, rmin, rmax) 
            g_lut = _scale(1, gmin, gmax) 
            b_lut = _scale(1, bmin, bmax) 
            if channels == 4: 
                k_lut = _scale(1, kmin, kmax) 

            lut = [] 

            for i in range (256): 
                lut.append(r_lut[i]) 
            for i in range (256): 
                lut.append(g_lut[i]) 
            for i in range (256): 
                lut.append(b_lut[i]) 
            if channels == 4: 
                for i in range (256): 
                    lut.append(k_lut[i]) 

        return lut 

from PIL import ImageEnhance , ImageDraw , Image
img = Image.open(file_path)
img2 = levels(img)

Upvotes: 0

quickbug
quickbug

Reputation: 4848

This is the code that I use. Levels are done, 1) on the brightness channel of the HSV image and, 2) according to the desired amount of blacks and whites pixels in the result.

The code can be modified to avoid to use pillow since openCV use numpy arrays as internal data. If doing so, be aware that openCV native colorspace is BGR. You will have to change the calls to cv.cvtColor() accordingly.

from PIL import Image
import numpy as np
import cv2 as cv

fileName = 'foo.JPG'
fileOut = 'bar.JPG'
imgPil = Image.open(fileName) 
imgCV = np.asarray(imgPil, np.uint8)
hsv = cv.cvtColor(imgCV, cv.COLOR_RGB2HSV)
h,s,v = cv.split(hsv)
ceil = np.percentile(v,95) # 5% of pixels will be white
floor = np.percentile(v,5) # 5% of pixels will be black
a = 255/(ceil-floor)
b = floor*255/(floor-ceil)
v = np.maximum(0,np.minimum(255,v*a+b)).astype(np.uint8)
hsv = cv.merge((h,s,v))
rgb = cv.cvtColor(hsv, cv.COLOR_HSV2RGB)
imgPil = Image.fromarray(rgb)
imgPil.save(fileOut)

Upvotes: 2

tzot
tzot

Reputation: 95991

If I understood correctly the -level option of ImageMagick, then the level_image function I provide should do what you want.

Two things to note:

  • the speed definitely can be improved
  • it currently only works with RGB images
  • the algorithm goes through the HSV colorspace, and affects only the V (brightness) component

The code:

import colorsys

class Level(object):

    def __init__(self, minv, maxv, gamma):
        self.minv= minv/255.0
        self.maxv= maxv/255.0
        self._interval= self.maxv - self.minv
        self._invgamma= 1.0/gamma

    def new_level(self, value):
        if value <= self.minv: return 0.0
        if value >= self.maxv: return 1.0
        return ((value - self.minv)/self._interval)**self._invgamma

    def convert_and_level(self, band_values):
        h, s, v= colorsys.rgb_to_hsv(*(i/255.0 for i in band_values))
        new_v= self.new_level(v)
        return tuple(int(255*i)
                for i
                in colorsys.hsv_to_rgb(h, s, new_v))

def level_image(image, minv=0, maxv=255, gamma=1.0):
    """Level the brightness of image (a PIL.Image instance)
    All values ≤ minv will become 0
    All values ≥ maxv will become 255
    gamma controls the curve for all values between minv and maxv"""

    if image.mode != "RGB":
        raise ValueError("this works with RGB images only")

    new_image= image.copy()

    leveller= Level(minv, maxv, gamma)
    levelled_data= [
        leveller.convert_and_level(data)
        for data in image.getdata()]
    new_image.putdata(levelled_data)
    return new_image

If there is some way to do the RGB→HSV conversion (and vice versa) using PIL, then one can split into the H, S, V bands, use the .point method of the V band and convert back to RGB, speeding up the process by a lot; however, I haven't found such a way.

Upvotes: 7

Niki Yoshiuchi
Niki Yoshiuchi

Reputation: 17581

Why not use PythonMagick? It's a Python interface to Image Magick.

Upvotes: 3

Related Questions