added HSV operations to the ColorMixer and fancy imshow.

This commit is contained in:
sccolbert
2009-11-04 23:10:36 +01:00
parent 5b3ff5306c
commit f4a042177f
3 changed files with 421 additions and 0 deletions
+305
View File
@@ -125,3 +125,308 @@ def brightness(np.ndarray[np.uint8_t, ndim=3] img,
img[i, j, k] = 0
else:
img[i, j, k] = <np.uint8_t>op_result
cdef void rgb_2_hsv(float* RGB, float* HSV):
'''Convert an HSV value to RGB.
Automatic clipping.
Parameters
----------
R : float
From 0. - 255.
G : float
From 0. - 255.
B : float
From 0. - 255.
Returns
-------
out : (H, S, V) Floats
Ranges (0...360), (0...1), (0...1)
conversion convention from here:
http://en.wikipedia.org/wiki/HSL_and_HSV
'''
cdef float R, G, B, H, S, V, MAX, MIN
R = RGB[0]
G = RGB[1]
B = RGB[2]
if R > 255:
R = 255
elif R < 0:
R = 0
else:
pass
if G > 255:
G = 255
elif G < 0:
G = 0
else:
pass
if B > 255:
B = 255
elif B < 0:
B = 0
else:
pass
if R < G:
MIN = R
MAX = G
else:
MIN = G
MAX = R
if B < MIN:
MIN = B
elif B > MAX:
MAX = B
else:
pass
V = MAX / 255.
if MAX == MIN:
H = 0.
elif MAX == R:
H = (60 * (G - B) / (MAX - MIN)) % 360
elif MAX == G:
H = 60 * (B - R) / (MAX - MIN) + 120
else:
H = 60 * (R - G) / (MAX - MIN) + 240
if MAX == 0:
S = 0
else:
S = 1 - MIN / MAX
HSV[0] = H
HSV[1] = S
HSV[2] = V
cdef void hsv_2_rgb(float* HSV, float* RGB):
'''Convert an HSV value to RGB.
Automatic clipping.
Parameters
----------
H : float
From 0. - 360.
S : float
From 0. - 1.
V : float
From 0. - 1.
Returns
-------
out : (R, G, B) Floats
Each from 0. - 1.
conversion convention from here:
http://en.wikipedia.org/wiki/HSL_and_HSV
'''
cdef float H, S, V
cdef float f, p, q, t, r, g, b
cdef int hi
H = HSV[0]
S = HSV[1]
V = HSV[2]
if H > 360:
H = 360
elif H < 0:
H = 0
else:
pass
if S > 1:
S = 1
elif S < 0:
S = 0
else:
pass
if V > 1:
V = 1
elif V < 0:
V = 0
else:
pass
hi = (<int>(H / 60.)) % 6
f = (H / 60.) - (<int>(H / 60.))
p = V * (1 - S)
q = V * (1 - f * S)
t = V * (1 - (1 -f) * S)
if hi == 0:
r = V
g = t
b = p
elif hi == 1:
r = q
g = V
b = p
elif hi == 2:
r = p
g = V
b = t
elif hi == 3:
r = p
g = q
b = V
elif hi == 4:
r = t
g = p
b = V
else:
r = V
g = p
b = q
RGB[0] = r
RGB[1] = g
RGB[2] = b
@cython.boundscheck(False)
def hsv_add(np.ndarray[np.uint8_t, ndim=3] img,
np.ndarray[np.uint8_t, ndim=3] stateimg,
float h_amt, float s_amt, float v_amt):
"""Modify the image color by specifying additive HSV Values.
Since the underlying images are RGB, all three values HSV
must be specified at the same time.
The RGB triplet in the image is converted to HSV, the operation
is applied, and then the HSV triplet is converted back to RGB
HSV values are scaled to H(0. - 360.), S(0. - 1.), V(0. - 1.)
then the operation is performed and any overflow is clipped, then the
reverse transform is performed. Those are the ranges to keep in mind,
when passing in values.
Parameters
----------
img : (M, N, 3) ndarray of uint8
Output image.
stateimg : (M, N, 3) ndarray of uint8
Input image.
h_amt : float
Ammount to add to H channel.
s_amt : float
Ammount to add to S channel.
v_amt : float
Ammount to add to V channel.
"""
cdef int height = img.shape[0]
cdef int width = img.shape[1]
cdef float HSV[3]
cdef float RGB[3]
cdef int i, j
for i in range(height):
for j in range(width):
RGB[0] = stateimg[i, j, 0]
RGB[1] = stateimg[i, j, 1]
RGB[2] = stateimg[i, j, 2]
rgb_2_hsv(RGB, HSV)
# Add operation
HSV[0] += h_amt
HSV[1] += s_amt
HSV[2] += v_amt
hsv_2_rgb(HSV, RGB)
RGB[0] *= 255
RGB[1] *= 255
RGB[2] *= 255
img[i, j, 0] = <np.uint8_t>RGB[0]
img[i, j, 1] = <np.uint8_t>RGB[1]
img[i, j, 2] = <np.uint8_t>RGB[2]
@cython.boundscheck(False)
def hsv_multiply(np.ndarray[np.uint8_t, ndim=3] img,
np.ndarray[np.uint8_t, ndim=3] stateimg,
float h_amt, float s_amt, float v_amt):
"""Modify the image color by specifying multiplicative HSV Values.
Since the underlying images are RGB, all three values HSV
must be specified at the same time.
The RGB triplet in the image is converted to HSV, the operation
is applied, and then the HSV triplet is converted back to RGB
HSV values are scaled to H(0. - 360.), S(0. - 1.), V(0. - 1.)
then the operation is performed and any overflow is clipped, then the
reverse transform is performed. Those are the ranges to keep in mind,
when passing in values.
Parameters
----------
img : (M, N, 3) ndarray of uint8
Output image.
stateimg : (M, N, 3) ndarray of uint8
Input image.
h_amt : float
Ammount to add to H channel.
s_amt : float
Ammount to add to S channel.
v_amt : float
Ammount to add to V channel.
"""
cdef int height = img.shape[0]
cdef int width = img.shape[1]
cdef float HSV[3]
cdef float RGB[3]
cdef int i, j
for i in range(height):
for j in range(width):
RGB[0] = stateimg[i, j, 0]
RGB[1] = stateimg[i, j, 1]
RGB[2] = stateimg[i, j, 2]
rgb_2_hsv(RGB, HSV)
# Multiply operation
HSV[0] *= h_amt
HSV[1] *= s_amt
HSV[2] *= v_amt
hsv_2_rgb(HSV, RGB)
RGB[0] *= 255
RGB[1] *= 255
RGB[2] *= 255
img[i, j, 0] = <np.uint8_t>RGB[0]
img[i, j, 1] = <np.uint8_t>RGB[1]
img[i, j, 2] = <np.uint8_t>RGB[2]
+67
View File
@@ -216,6 +216,36 @@ else:
self.rgb_widget.layout.addWidget(self.rgb_add_sliders, 2, 0)
self.rgb_widget.layout.addWidget(self.rgb_mul_sliders, 2, 0)
#---------------------------------------------------------------
# HSV sliders
#---------------------------------------------------------------
# radio buttons
self.hsv_add = QtGui.QRadioButton('Additive')
self.hsv_mul = QtGui.QRadioButton('Multiplicative')
self.hsv_mul.toggled.connect(self.hsv_radio_changed)
self.hsv_add.toggled.connect(self.hsv_radio_changed)
# additive sliders
self.hsv_add_sliders = NSliderBlock(3, [(-360, 360, 0, 'H', 1),
(-100, 100, 0, 'S', .01),
(-100, 100, 0, 'V', .01)],
self.hsv_add_changed)
# multiplicative sliders
self.hsv_mul_sliders = NSliderBlock(3, [(0, 1000, 500, 'H', .002),
(0, 1000, 500, 'S', .002),
(0, 1000, 500, 'V', .002)],
self.hsv_mul_changed)
# layout
self.hsv_widget = QWidget()
self.hsv_widget.layout = QtGui.QGridLayout(self.hsv_widget)
self.hsv_widget.layout.addWidget(self.hsv_add, 0, 0)
self.hsv_widget.layout.addWidget(self.hsv_mul, 1, 0)
self.hsv_widget.layout.addWidget(self.hsv_add_sliders, 2, 0)
self.hsv_widget.layout.addWidget(self.hsv_mul_sliders, 2, 0)
#---------------------------------------------------------------
# Brightness sliders
#---------------------------------------------------------------
@@ -244,6 +274,7 @@ else:
self.layout = QtGui.QGridLayout(self)
self.layout.addWidget(self.combo_box, 0, 0)
self.layout.addWidget(self.rgb_widget, 1, 0)
self.layout.addWidget(self.hsv_widget, 1, 0)
self.layout.addWidget(self.bright_widget, 1, 0)
self.layout.addWidget(self.commit_button, 2, 0)
self.layout.addWidget(self.revert_button, 3, 0)
@@ -256,6 +287,7 @@ else:
self.hide_sliders()
self.rgb_widget.show()
self.rgb_add.setChecked(True)
self.hsv_add.setChecked(True)
def rgb_add_changed(self, name, val):
if not self.rgb_add.isChecked():
@@ -283,6 +315,24 @@ else:
return
self.update()
def hsv_add_changed(self, name, val):
if not self.hsv_add.isChecked():
return
h = self.hsv_add_sliders.sliders['H'].conv_val()
s = self.hsv_add_sliders.sliders['S'].conv_val()
v = self.hsv_add_sliders.sliders['V'].conv_val()
self.mixer.hsv_add(h, s, v)
self.update()
def hsv_mul_changed(self, name, val):
if not self.hsv_mul.isChecked():
return
h = self.hsv_mul_sliders.sliders['H'].conv_val()
s = self.hsv_mul_sliders.sliders['S'].conv_val()
v = self.hsv_mul_sliders.sliders['V'].conv_val()
self.mixer.hsv_multiply(h, s, v)
self.update()
def bright_changed(self, name, val):
# doesnt matter which slider changed we need both
# values
@@ -295,6 +345,7 @@ else:
def reset_sliders(self):
self.rgb_add_sliders.set_sliders({'R': 0, 'G': 0, 'B': 0})
self.rgb_mul_sliders.set_sliders({'R': 500, 'G': 500, 'B': 500})
self.hsv_add_sliders.set_sliders({'H': 0, 'S': 0, 'V': 0})
self.bright_sliders.set_sliders({'+': 0, 'x': 500})
def combo_box_changed(self, index):
@@ -307,6 +358,7 @@ else:
def hide_sliders(self):
self.rgb_widget.hide()
self.hsv_widget.hide()
self.bright_widget.hide()
def rgb_radio_changed(self):
@@ -323,12 +375,27 @@ else:
self.mixer.set_to_stateimg()
self.update()
def hsv_radio_changed(self):
if self.hsv_add.isChecked():
self.hsv_add_sliders.show()
self.hsv_mul_sliders.hide()
elif self.hsv_mul.isChecked():
self.hsv_mul_sliders.show()
self.hsv_add_sliders.hide()
else:
pass
self.reset_sliders()
self.mixer.set_to_stateimg()
self.update()
def show_rgb(self):
self.hide_sliders()
self.rgb_widget.show()
def show_hsv(self):
self.hide_sliders()
self.hsv_widget.show()
def show_bright(self):
self.hide_sliders()
+49
View File
@@ -244,4 +244,53 @@ class ColorMixer(object):
_colormixer.multiply(self.img, self.stateimg, channel, ammount)
def brightness(self, offset, factor):
'''Adjust the brightness off an image with an offset and factor.
Parameters
----------
offset : integer
The ammount to add to each channel.
factor : float
The factor to multiply each channel by.
result = clip((pixel + offset)*factor)
'''
_colormixer.brightness(self.img, self.stateimg, offset, factor)
def hsv_add(self, h_amt, s_amt, v_amt):
'''Adjust the H, S, V channels of an image by a constant ammount.
This is similar to the add() mixer function, but operates over the
entire image at once. Thus all three additive values, H, S, V, must
be supplied simultaneously.
Parameters
----------
h_amt : float
The ammount to add to the hue (-360..360)
s_amt : float
The ammount to add to the saturation (-1..1)
v_amt : float
The ammount to add to the value (-1..1)
'''
_colormixer.hsv_add(self.img, self.stateimg, h_amt, s_amt, v_amt)
def hsv_multiply(self, h_amt, s_amt, v_amt):
'''Adjust the H, S, V channels of an image by a constant ammount.
This is similar to the add() mixer function, but operates over the
entire image at once. Thus all three additive values, H, S, V, must
be supplied simultaneously.
Parameters
----------
h_amt : float
The ammount to multiply to the hue (0..1)
s_amt : float
The ammount to multiply to the saturation (0..1)
v_amt : float
The ammount to multiply to the value (0..1)
'''
_colormixer.hsv_multiply(self.img, self.stateimg, h_amt, s_amt, v_amt)