Based on array indexing and slicing in numpy, this is my approach for the forward:
import numpy as np
def rgb2hsv(rgb):
""" convert RGB to HSV color space
:param rgb: np.ndarray
:return: np.ndarray
"""
rgb = rgb.astype('float')
maxv = np.amax(rgb, axis=2)
maxc = np.argmax(rgb, axis=2)
minv = np.amin(rgb, axis=2)
minc = np.argmin(rgb, axis=2)
hsv = np.zeros(rgb.shape, dtype='float')
hsv[maxc == minc, 0] = np.zeros(hsv[maxc == minc, 0].shape)
hsv[maxc == 0, 0] = (((rgb[..., 1] - rgb[..., 2]) * 60.0 / (maxv - minv + np.spacing(1))) % 360.0)[maxc == 0]
hsv[maxc == 1, 0] = (((rgb[..., 2] - rgb[..., 0]) * 60.0 / (maxv - minv + np.spacing(1))) + 120.0)[maxc == 1]
hsv[maxc == 2, 0] = (((rgb[..., 0] - rgb[..., 1]) * 60.0 / (maxv - minv + np.spacing(1))) + 240.0)[maxc == 2]
hsv[maxv == 0, 1] = np.zeros(hsv[maxv == 0, 1].shape)
hsv[maxv != 0, 1] = (1 - minv / (maxv + np.spacing(1)))[maxv != 0]
hsv[..., 2] = maxv
return hsv
and backward color space conversion:
def hsv2rgb(hsv):
""" convert HSV to RGB color space
:param hsv: np.ndarray
:return: np.ndarray
"""
hi = np.floor(hsv[..., 0] / 60.0) % 6
hi = hi.astype('uint8')
v = hsv[..., 2].astype('float')
f = (hsv[..., 0] / 60.0) - np.floor(hsv[..., 0] / 60.0)
p = v * (1.0 - hsv[..., 1])
q = v * (1.0 - (f * hsv[..., 1]))
t = v * (1.0 - ((1.0 - f) * hsv[..., 1]))
rgb = np.zeros(hsv.shape)
rgb[hi == 0, :] = np.dstack((v, t, p))[hi == 0, :]
rgb[hi == 1, :] = np.dstack((q, v, p))[hi == 1, :]
rgb[hi == 2, :] = np.dstack((p, v, t))[hi == 2, :]
rgb[hi == 3, :] = np.dstack((p, q, v))[hi == 3, :]
rgb[hi == 4, :] = np.dstack((t, p, v))[hi == 4, :]
rgb[hi == 5, :] = np.dstack((v, p, q))[hi == 5, :]
return rgb
I was motivated to write these lines as I wasn't convinced by a pixel-wise conversion due to the computational overload and also did not want to rely on another library such as OpenCV.
Feel free to suggest a modification to make this solution more elegant and generic.