Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Changing image hue with Python PIL

Using Python PIL, I'm trying to adjust the hue of a given image.

I'm not very comfortable with the jargon of graphics, so what I mean by “adjusting hue” is doing the Photoshop operation called “Hue/saturation”: this is to change the color of the image uniformly as shown below:

  • Original: Original
  • With hue adjusted to +180 (red): hue: -180
  • With hue adjusted to -78 (green): hue: -78

FYI, Photoshop uses a scale of -180 to +180 for this hue setting (where -180 equals +180), that may represents the HSL hue scale (expressed in 0-360 degree).

What I'm looking for is a function that, given an PIL image and a float hue within [0, 1] (or int within [0, 360], it doesn't matter), returns the image with its hue shifted by hue as in the example above.

What I've done so far is ridiculous and obviously doesn't give the desired result. It just half-blend my original image with a color-filled layer.

import Image  im = Image.open('tweeter.png') layer = Image.new('RGB', im.size, 'red') # "hue" selection is done by choosing a color... output = Image.blend(im, layer, 0.5) output.save('output.png', 'PNG') 

(Please-don't-laugh-at-) result: output.png

Thanks in advance!


Solution: here is the unutbu code updated so it fits exactly what I've described.

import Image import numpy as np import colorsys  rgb_to_hsv = np.vectorize(colorsys.rgb_to_hsv) hsv_to_rgb = np.vectorize(colorsys.hsv_to_rgb)  def shift_hue(arr, hout):     r, g, b, a = np.rollaxis(arr, axis=-1)     h, s, v = rgb_to_hsv(r, g, b)     h = hout     r, g, b = hsv_to_rgb(h, s, v)     arr = np.dstack((r, g, b, a))     return arr  def colorize(image, hue):     """     Colorize PIL image `original` with the given     `hue` (hue within 0-360); returns another PIL image.     """     img = image.convert('RGBA')     arr = np.array(np.asarray(img).astype('float'))     new_img = Image.fromarray(shift_hue(arr, hue/360.).astype('uint8'), 'RGBA')      return new_img 
like image 267
zopieux Avatar asked Sep 01 '11 17:09

zopieux


People also ask

How do you change the image Hue in Python?

What you need to do is convert to HSV, then increment all the H values by some degree, then convert back to RGB. Half the work is done for you in an answer (by me) some time ago. It employs another python module called NumPy and converts RGB colorspace to HSV.


2 Answers

There is Python code to convert RGB to HSV (and vice versa) in the colorsys module in the standard library. My first attempt used

rgb_to_hsv=np.vectorize(colorsys.rgb_to_hsv) hsv_to_rgb=np.vectorize(colorsys.hsv_to_rgb) 

to vectorize those functions. Unfortunately, using np.vectorize results in rather slow code.

I was able to obtain roughly a 5 times speed up by translating colorsys.rgb_to_hsv and colorsys.hsv_to_rgb into native numpy operations.

import Image import numpy as np  def rgb_to_hsv(rgb):     # Translated from source of colorsys.rgb_to_hsv     # r,g,b should be a numpy arrays with values between 0 and 255     # rgb_to_hsv returns an array of floats between 0.0 and 1.0.     rgb = rgb.astype('float')     hsv = np.zeros_like(rgb)     # in case an RGBA array was passed, just copy the A channel     hsv[..., 3:] = rgb[..., 3:]     r, g, b = rgb[..., 0], rgb[..., 1], rgb[..., 2]     maxc = np.max(rgb[..., :3], axis=-1)     minc = np.min(rgb[..., :3], axis=-1)     hsv[..., 2] = maxc     mask = maxc != minc     hsv[mask, 1] = (maxc - minc)[mask] / maxc[mask]     rc = np.zeros_like(r)     gc = np.zeros_like(g)     bc = np.zeros_like(b)     rc[mask] = (maxc - r)[mask] / (maxc - minc)[mask]     gc[mask] = (maxc - g)[mask] / (maxc - minc)[mask]     bc[mask] = (maxc - b)[mask] / (maxc - minc)[mask]     hsv[..., 0] = np.select(         [r == maxc, g == maxc], [bc - gc, 2.0 + rc - bc], default=4.0 + gc - rc)     hsv[..., 0] = (hsv[..., 0] / 6.0) % 1.0     return hsv  def hsv_to_rgb(hsv):     # Translated from source of colorsys.hsv_to_rgb     # h,s should be a numpy arrays with values between 0.0 and 1.0     # v should be a numpy array with values between 0.0 and 255.0     # hsv_to_rgb returns an array of uints between 0 and 255.     rgb = np.empty_like(hsv)     rgb[..., 3:] = hsv[..., 3:]     h, s, v = hsv[..., 0], hsv[..., 1], hsv[..., 2]     i = (h * 6.0).astype('uint8')     f = (h * 6.0) - i     p = v * (1.0 - s)     q = v * (1.0 - s * f)     t = v * (1.0 - s * (1.0 - f))     i = i % 6     conditions = [s == 0.0, i == 1, i == 2, i == 3, i == 4, i == 5]     rgb[..., 0] = np.select(conditions, [v, q, p, p, t, v], default=v)     rgb[..., 1] = np.select(conditions, [v, v, v, q, p, p], default=t)     rgb[..., 2] = np.select(conditions, [v, p, t, v, v, q], default=p)     return rgb.astype('uint8')   def shift_hue(arr,hout):     hsv=rgb_to_hsv(arr)     hsv[...,0]=hout     rgb=hsv_to_rgb(hsv)     return rgb  img = Image.open('tweeter.png').convert('RGBA') arr = np.array(img)  if __name__=='__main__':     green_hue = (180-78)/360.0     red_hue = (180-180)/360.0      new_img = Image.fromarray(shift_hue(arr,red_hue), 'RGBA')     new_img.save('tweeter_red.png')      new_img = Image.fromarray(shift_hue(arr,green_hue), 'RGBA')     new_img.save('tweeter_green.png') 

yields

enter image description here

and

enter image description here

like image 134
unutbu Avatar answered Sep 20 '22 05:09

unutbu


With a recent copy of Pillow, one should probably use Image.convert():

def rgb2hsv(image: PIL.Image.Image):     return image.convert('HSV') 
like image 22
K3---rnc Avatar answered Sep 21 '22 05:09

K3---rnc