Last active
May 4, 2019 19:51
-
-
Save KernelA/3060938f1a8716e60d66926e15f9bb2b to your computer and use it in GitHub Desktop.
NumPy direct and inverse conversion RGB to HSI
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import numpy as np | |
def rgb2hsi(rgb: np.ndarray) -> np.ndarray: | |
"""Conversion RGB to HSI | |
h (hue) in [0; 2 pi], s (saturation) in [0; 1], i (intensity) in [0; 1]. | |
If 'rgb' color is black, white or grayscale then h component is undefined (np.nan). | |
Description: | |
http://omar.alkadi.net/wp-content/uploads/DIPch6.pdf (22 p.) | |
:param rgb: RGB color. Color components must be in [0; 1]. | |
:return: Color in HSI color space. | |
""" | |
assert rgb.ndim == 3 and rgb.shape[-1] == 3, "'rgb' has incomplete shape." \ | |
"Expected (..., ..., 3) got {}.".format(rgb.shape) | |
assert ((rgb >= 0) & (rgb <= 1)).all(), "Color components must be in [0; 1]." | |
all_color_equal = np.all(rgb == rgb[..., 0, np.newaxis], axis=-1) | |
not_all_color_equal = ~all_color_equal | |
i = rgb.mean(axis=-1) | |
s = np.zeros_like(i) | |
h = np.zeros_like(i) | |
s[not_all_color_equal] = 1 - 1 / i[not_all_color_equal] * rgb[not_all_color_equal, ...].min(axis=-1) | |
rg_diff = rgb[not_all_color_equal, 0] - rgb[not_all_color_equal, 1] | |
rb_diff = rgb[not_all_color_equal, 0] - rgb[not_all_color_equal, 2] | |
gb_diff = rgb[not_all_color_equal, 1] - rgb[not_all_color_equal, 2] | |
h[not_all_color_equal] = np.arccos(0.5 * (rg_diff + rb_diff) / np.sqrt(rg_diff ** 2 + rb_diff * gb_diff)) | |
cond = (rgb[..., 2] > rgb[..., 1]) & not_all_color_equal | |
h[cond] = 2 * np.pi - h[cond] | |
h = np.clip(h, 0, 2 * np.pi) | |
s = np.clip(s, 0, 1) | |
i = np.clip(i, 0, 1) | |
h[all_color_equal] = np.nan | |
return np.dstack((h, s, i)) | |
def hsi2rgb(hsi: np.ndarray) -> np.ndarray: | |
"""Conversion HSI to RGB | |
h (hue) in [0; 2 pi], s (saturation) in [0; 1], i (intensity) in [0; 1]. | |
If h is np.nan then r (red) = g (green) = b (blue) = i (intensity). | |
Description: | |
http://omar.alkadi.net/wp-content/uploads/DIPch6.pdf (22 p.) | |
:param hsi: HSI color. | |
:return: Color in RGB color space. | |
""" | |
assert hsi.ndim == 3 and hsi.shape[-1] == 3, "'hsi' has incomplete shape." \ | |
"Expected (..., ..., 3) got {}".format(hsi.shape) | |
h = hsi[..., 0] | |
s = hsi[..., 1] | |
i = hsi[..., 2] | |
x = i * (1 - hsi[..., 1]) | |
h_nan = np.isnan(h) | |
not_h_nan = ~h_nan | |
cond1 = np.less(h, np.pi * 2 / 3, where=not_h_nan) | |
y = np.zeros_like(h) | |
z = np.zeros_like(h) | |
y[cond1] = i[cond1] * (1 + s[cond1] * np.cos(h[cond1]) / np.cos(np.pi / 3 - h[cond1])) | |
z[cond1] = 3 * i[cond1] - (x[cond1] + y[cond1]) | |
rest = np.greater_equal(h, 4 * np.pi / 3, where=not_h_nan) | |
h[rest] -= 4 * np.pi / 3 | |
y[rest] = i[rest] * (1 + s[rest] * np.cos(h[rest]) / np.cos(np.pi / 3 - h[rest])) | |
z[rest] = 3 * i[rest] - (x[rest] + y[rest]) | |
cond2 = ~(cond1 | rest) | |
h[cond2] -= 2 * np.pi / 3 | |
y[cond2] = i[cond2] * (1 + s[cond2] * np.cos(h[cond2]) / np.cos(np.pi / 3 - h[cond2])) | |
z[cond2] = 3 * i[cond2] - (x[cond2] + y[cond2]) | |
res = np.dstack((x, y, z)) | |
# y z x | |
res[cond1] = np.roll(res[cond1], shift=-1, axis=-1) | |
# z x y | |
res[rest] = np.roll(res[rest], shift=1, axis=-1) | |
# Fill NaN value of intensity value | |
res[h_nan] = i[h_nan].reshape(-1, 1) | |
return np.clip(res, 0, 1) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment