thersholding limits on HSV image in python - python

I have read an image and I have converted the image to HSV image.
I want to apply threshold limits for hue, saturation , and value components separately.
Hue thershold 0 to 1, saturation thershold 0.28 to 1 and value thershold 0 to 0.55
I want to this application for color masking !
how to apply these limits on my image files.
image_read = cv2.imread('tryimage.jpg')
im = cv2.cvtColor(image_read,cv2.COLOR_RGB2HSV)
im_hue = im[:,:,0]
im_sat = im[:,:,1]
im_val = im[:,:,2]
# how to apply thershold ?
fig, ax = plt.subplots(nrows=1,ncols=3)
ax[0].imshow(im_hue)
ax[1].imshow(im_sat)
ax[2].imshow(im_val)
plt.show()
I have done the same in Matlab, I have taken only the pixels of my interest in each band and then merged these back to get the pixels of my interest.
Here is my matlab code snippet , which I want to do the same in python.
color.hueThresholdLow = 0;
color.hueThresholdHigh = 1;
color.saturationThresholdLow = 0;
color.saturationThresholdHigh = 0.28;
color.valueThresholdLow = 0.38;
color.valueThresholdHigh = 0.97;
maskedRGBImage = color_masking(rgbImage,color);
function color_masking(rgbImage, color)
hsvimage = rgb2hsv(rgbImage);
himage = hsvimage(:,:,1);
simage = hsvimage(:,:2);
vimage = hsvimage(:,:,3);
hMask = (hImage >= color.hueThresholdLow) & (hImage <= color.hueThresholdHigh);
sMask = (sImage >= color.saturationThresholdLow) & (sImage <= color.saturationThresholdHigh);
vMask = (vImage >= color.valueThresholdLow) & (vImage <= color.valueThresholdHigh);
ObjectsMask = uint8(hMask & sMask & vMask);
.....

In python you can write it very similar to matlab. It is usually a good idea to create a function for methods that you might use more than once, but feel free of removing the function declaration if it doesn't suit your needs.
def threshold_hsv(im_hsv, hlow, hhigh, slow, shigh, vlow, vhigh):
im_hue = im_hsv[:,:,0]
im_sat = im_hsv[:,:,1]
im_val = im_hsv[:,:,2]
h_mask = (im_hue >= hlow) & (im_hue <= hhigh)
s_mask = (im_sat >= slow) & (im_sat <= shigh)
v_mask = (im_val >= vlow) & (im_val <= vhigh)
return h_mask & s_mask & v_mask
And then you can call the function with your data as:
>>> object_mask = threshold_hsv(hsvimage, 0, 1, 0, 0.28, 0.38, 0.97)
As you can see, the syntax is pretty similar (if not identical) to that of the matlab. This holds as long as your hsvimage is a numpy array, which is what OpenCV generates in python.

To select values that satisfy your limits (and discard the ones not in the limits), use list comprehensions:
# filtered_pixels is a list of tuples, which are ordered as (h, s, v)
# i.e. filtered_pixels[0][0] = h, filtered_pixels[0][1] = s and
# filtered_pixels[0][2] = v
filtered_pixels = [(im_hue[i], im_sat[i], im_val[i]) for i in range(len(im_hue)) if satisfies_limits(im_hue[i], im_sat[i], im_val[i])]
satisfies_limits is a function that checks whether the passed hue, saturation and value are in the required limits. You can unwrap the above list comprehension to a for loop to if you wish.
To limit all values to the given limits, use the map() builtin:
clamped_hue = map(lambda h: max(hue_min, min(h, hue_max)), im_hue)
# And so on for saturation and value

Related

How to filter a 3D RGB numpy array

I want to get a 2d mask array of green color.
Code:
W = 100; H = 100
Map = np.zeros((W,H,3), dtype=int)
Map[1,1] = [0,255,0]
MResult = (Map[:,:] == [0,255,0])
A = np.zeros((W,H))
A[MResult] = 1
Not works.
I think there is also a conceptual mistake in your code, the height should be the first dimension, so Map should be np.zeros((H,W,3), dtype='int'). Also note that dtype is a string, not the python built-in int.
Now returning to the problem, you need to use numpy's smart indexing to better manipulate the arrays. It seems that you want to set the green channel to 255 in Map, you would do so by doing Map[:,:,1] = 255 note that we use : to say "all the elements in the row and column, and in channel 1 should be set to 255".
Then we get a binary mask for A, we should do MResult = Map[:,:,1] == 255, and in the end we simply do the A[MResult] = 1 as you did.
To check only green channel is not good idea,
we get white color too.
But code:
import numpy as np
W = 100; H = 100
Map = np.zeros((W,H,3), dtype=int)
Map[1,1] = [0,255,0]
A = np.zeros((W,H))
A[(0 == Map[:,:,0]) & (255 == Map[:,:,1]) & (0 == Map[:,:,2])] = 1
now to works.

Color Correction Matrix in XYZ/RGB not working

I am aiming to perform a color correction based on a reference image, using color charts. As a personal goal, I'm trying to correct the colors of an image I previously modified. The chart has been affected by the same layers, of course:
Originals:
Manually modified:
I'm using the following function that I've written myself to get the matrix:
def _get_matrix_transformation(self,
observed_colors: np.ndarray,
reference_colors: np.ndarray):
"""
Args:
observed_colors: colors found in target chart
reference_colors: colors found on source/reference image
Returns:
Nothing.
"""
# case 1
observed_m = [observed_colors[..., i].mean() for i in range(observed_colors.shape[-1])]
observed_colors = (observed_colors - observed_m).astype(np.float32)
reference_m = [reference_colors[..., i].mean() for i in range(reference_colors.shape[-1])]
reference_colors = (reference_colors - reference_m).astype(np.float32)
# XYZ color conversion
observed_XYZ = cv.cvtColor(observed_colors, cv.COLOR_BGR2XYZ)
observed_XYZ = np.reshape(observed_colors, (observed_XYZ.shape[0] * observed_XYZ.shape[1],
observed_XYZ.shape[2]))
reference_XYZ = cv.cvtColor(reference_colors, cv.COLOR_BGR2XYZ)
reference_XYZ = np.reshape(reference_colors, (reference_XYZ.shape[0] * reference_XYZ.shape[1],
reference_XYZ.shape[2]))
# case 2
# mean subtraction in order to use the covariance matrix
# observed_m = [observed_XYZ[..., i].mean() for i in range(observed_XYZ.shape[-1])]
# observed_XYZ = observed_XYZ - observed_m
# reference_m = [reference_XYZ[..., i].mean() for i in range(reference_XYZ.shape[-1])]
# reference_XYZ = reference_XYZ - reference_m
# apply SVD
H = np.dot(reference_XYZ.T, observed_XYZ)
U, S, Vt = np.linalg.svd(H)
# get transformation
self._M = Vt.T * U.T
# consider reflection case
if np.linalg.det(self._M) < 0:
Vt[2, :] *= -1
self._M = Vt.T * U.T
return
I'm applying the correction like this:
def _apply_profile(self, img: np.ndarray) -> np.ndarray:
"""
Args:
img: image to be corrected.
Returns:
Corrected image.
"""
# Revert gamma compression
img = adjust_gamma(img, gamma=1/2.2)
# Apply color correction
corrected_img = cv.cvtColor(img.astype(np.float32), cv.COLOR_BGR2XYZ)
corrected_img = corrected_img.reshape((corrected_img.shape[0]*corrected_img.shape[1], corrected_img.shape[2]))
corrected_img = np.dot(self._M, corrected_img.T).T.reshape(img.shape)
corrected_img = cv.cvtColor(corrected_img.astype(np.float32), cv.COLOR_XYZ2BGR)
corrected_img = np.clip(corrected_img, 0, 255)
# Apply gamma
corrected_img = adjust_gamma(corrected_img.astype(np.uint8), gamma=2.2)
return corrected_img
The result I'm currently getting if the transformation is done in BGR (just commented color conversion functions):
In XYZ (don't pay attention to the resizing, that's because of me):
Now, I'm asking these questions:
Is inverting gamma necessary in this case? If so, am I doing it correctly? Should I implement a LUT that works with other data types such as np.float32?
Subtraction of the mean should be done in XYZ on BGR color space (case 1 vs case 2)?
Is considering the reflection case (as in a rigid body rotation problem) necessary?
Is clipping necessary? And if so, are those the correct values and data types?

Iterate over image and select lightness/saturation values in a given hue range not using nested for loops

Story behind:
I am trying to build a simple hand detection that is adaptive to environmental changes (light mainly) and therefore re-calibrates every few minutes using a histogram of a rough estimate where the hand is (using YOLO-Darknet).
Idea is to get the finger position at the end.
Currently I am getting the hue value of the hand and the lightness and saturation are fixed.
I have written a short nested for loop for the lightness and saturation search where it goes through all the elements in the hue range and finds its max and min value to write it in the array.
Now my question:
I know that python has functionalities for writing such nested loops and list modification/manipulation short and elegant, how do I do this?
Following I have an MWE and an example image.
import numpy as np
import cv2
hue = 0
light = 1
satur = 2
img = cv2.imread('Untitled.png')
hls = cv2.cvtColor(img, cv2.COLOR_BGR2HLS)
# structure of histograms:
# 0, 2, 6 = histogram values for H, L and S
# 1, 3, 5 = bin values for H, L and S
histograms = [0] * 6
# Only 5 bins for a rough estimate of the skin color (so not too much is lost)
histograms[0], histograms[1] = np.histogram(hls[:, :, 0], 5)
histograms[2], histograms[3] = np.histogram(hls[:, :, 1], 5)
histograms[4], histograms[5] = np.histogram(hls[:, :, 2], 5)
# structure of HLS_bins:
# [Hue, Lightness, Saturation] [min, min, min]
# [Hue, Lightness, Saturation] [max, max, max]
HLS_bins = [[0, 200, 30], [0, 255, 255]]
# TODO alternative approach to the one below:
# todo...find the bin for the highest occuring color and select the Lightness
# todo...and Saturation according to the corresponding values
# write in loop (elegant/generalized way) ?
# select the highest occurence of the hue
max_value_hue = max(histograms[0])
max_index_hue = list(histograms[0]).index(max_value_hue)
HLS_bins[0][0] = histograms[1][max_index_hue]
HLS_bins[1][0] = histograms[1][max_index_hue + 1]
min_value_light = 255
max_value_light = 0
min_value_saturation = 255
max_value_saturation = 0
for row in range(np.shape(hls)[0]):
for col in range(np.shape(hls)[1]):
if hls[row][col][hue] > HLS_bins[0][0] and hls[row][col][hue] < HLS_bins[1][0]:
if hls[row][col][light] > max_value_light:
max_value_light = hls[row][col][light]
if hls[row][col][light] < min_value_light:
min_value_light = hls[row][col][light]
if hls[row][col][satur] > max_value_saturation:
max_value_saturation = hls[row][col][satur]
if hls[row][col][satur] < min_value_saturation:
min_value_saturation = hls[row][col][satur]
HLS_bins[0][1] = min_value_light
HLS_bins[1][1] = max_value_light
HLS_bins[0][2] = min_value_saturation
HLS_bins[1][2] = max_value_saturation
HLS_bins = np.array(HLS_bins, dtype="uint8")
print(HLS_bins)
Most should have guessed it already, it's about this part of code:
for row in range(np.shape(hls)[0]):
for col in range(np.shape(hls)[1]):
if hls[row][col][hue] > HLS_bins[0][0] and hls[row][col][hue] < HLS_bins[1][0]:
if hls[row][col][light] > max_value_light:
max_value_light = hls[row][col][light]
if hls[row][col][light] < min_value_light:
min_value_light = hls[row][col][light]
if hls[row][col][satur] > max_value_saturation:
max_value_saturation = hls[row][col][satur]
if hls[row][col][satur] < min_value_saturation:
min_value_saturation = hls[row][col][satur]
So, how to write this nice and elegant?
If what you want is to obtain the max and min values of your Lightness and Saturation (the last 2 channels), a way of doing it is by using the np.max() and np.min() method on your image array directly.
To obtain such values of the desired channels you can Slice them from the image to then query for the values:
import cv2
img = cv2.imread('Untitled.png')
hls = cv2.cvtColor(img, cv2.COLOR_BGR2HLS)
#reading Lightness channel: No. 1
#this slice basically means: "give me all rows and all cols from channel 1"
max_value_light = hls[:,:,1].max()
min_value_light = hls[:,:,1].min()
#reading Saturation channel: No. 2
# and this slice means: "give me all rows and all cols from channel 2"
max_value_saturation = hls[:,:,2].max()
min_value_saturation = hls[:,:,2].min()
Edit: Based on your clarification, if you want to query such max/min values, but only those that fall within certain interval [hue_min, hue_max] you could use np.where() along with max:
#obtain the min hue that falls within the interval
#hue is channel 0, so we slice for all x,y pixels and for that channel
theMin = hls[np.where((hls[:,:,0]>hue_min) & (hls[:,:,0]<hue_max))][:,0].min()
#same story with the rest, compare to your _min and _max and use it's index

Python element-wise vectorised boolean operations to classify image pixels based on their colour

I have an RGB image which I am loading into a 2D array using PIL
img = Image.open(path)
imgData = numpy.array(img)
I need to efficiently translate this into a 2D array of RGB tuples (in some sense a 3D array) the same size containing a rough 'classification' of each pixel - 'red', 'green', 'white' or 'other' - at each index based on which 'colour region' they lie within. This is for purposes of image recognition.
My current implementation uses a element-wise for loop but is very slow (an 8MP image takes 1+ minutes):
for i in range(cols): # for every col
for j in range(rows): # for every row
r,g,b = imgData[i,j]
if b > 220: # white
n = 3
elif r > 230: # red
n = 2
else: # green
n = 1
mapData[i,j] = n
(I realise that the order of the if statements here affects the precedence of the classifications - this is not a major issue for now although I would prefer to define the colour spaces exclusively)
I am running Python 3.6.4 and happy to use NumPy or not. Having done a bunch of research, it seems like there are a number of faster and more 'pythonic' and vectorised ways to do this but I have not been able to get any working.
Any help would be much appreciated
Thanks!
Using np.where makes this pretty fast.
mapData = np.where(imgData[:,:,2] > 220, 3, np.where(imgData[:,:,0]>230, 2, 1))
But when applying this to a picture the only results where ones. Did I miss anything or should the cases be made in a different way?
Your algorithm as of the moment can be captured like this:
r, g, b = imgData[...,0], imgData[...,1], imgData[...,2]
mapData = np.ones_like(r, dtype=int)
mapData[r > 230] = 2
mapData[b > 220] = 3
Note the order of operations in assigning these numbers.
Colour classification is usually done by treating RGB colours as vectors. Normalize each one to the magnitude, then find the distance to your target colour.
For example, the skin detector in smartcrop.js works like this (using pyvips):
def pythag(im):
return sum([x ** 2 for x in im]) ** 0.5
skin = [0.78, 0.57, 0.44]
score = 1 - pythag(img / pythag(img) - skin)
Now score is a float image with values in 0 - 1 which is 1 for pixels most likely to be skin-coloured. Note that it ignores brightness: you'll need another rule to chop off very dark areas.
In your case I guess you'd need an array set of target vectors, then compute all the colour probabilities, and finally label the output pixel with the index of the highest-scoring vector. Something like:
import sys
import pyvips
def pythag(im):
return sum([x ** 2 for x in im]) ** 0.5
def classify(img, target):
return 1 - pythag(img / pythag(img) - target)
# find [index, max] of an array of pyvips images
def argmax(ar):
if len(ar) == 1:
return [0, ar[0]]
else:
index, mx = argmax(ar[:-1])
return [(ar[-1] > mx).ifthenelse(len(ar) - 1, index),
(ar[-1] > mx).ifthenelse(ar[-1], mx)]
skin = [0.78, 0.57, 0.44]
red = [1, 0, 0]
green = [0, 1, 0]
targets = [red, green, skin]
# we're not doing any coordinate transformations, so we can stream the image
img = pyvips.Image.new_from_file(sys.argv[1], access="sequential")
scores = [classify(img, x) for x in targets]
index, mx = argmax(scores)
index.write_to_file(sys.argv[2])
(plug: pyvips is typically 2x or 3x faster than numpy and needs much less memory)

How to remove rings from convolve healpix map?

I'm applying convolution techniques to convolve 2 datasets, a healpix map with nside = 256 and a primary beam of shape (256, 256) in order to measure the total intensity from the convolved healpix map. My problem is that after convolving my map with the primary beam i get rings in my convolved map. I've tried normalizing it with either lanczos or Gaussian kernel to take care of the rings but all these approaches have failed.
In my code below, i used the query function in scipy to search for the nearest pixels in my healpix map within a given radius and take the sum of the product of the corresponding pixels in the primary beam using map coordinate. The final image i get has rings in it. Please can anyone help me solve this problem? Thanks in advance.
def query_npix(nside, npix, radius):
print 'searching for nearest pixels:......'
t1, t2 = hp.pix2ang(nside, np.arange(npix))
tree = spatial.cKDTree(zip(t1, t2))
dist, ipix_indx = tree.query(zip(t1, t2), k = 150, distance_upper_bound = radius)
r1, r2 = hp.pix2ang(nside, ipix_indx)
ra = r1.T - t1
dec = r2.T - t2
print 'Done searching'
return np.array(dist), np.array(ipix_indx), np.array(ra.T), np.array(dec.T)
def fullSky_convolve(healpix_map, primary_beam_fits, ipix_indx, dist, radius, r1, r2):
measured_map = []
hdulist = openFitsFile(primary_beam_fits)
beam_data = hdulist[0].data
header = hdulist[0].header
nside = hp.get_nside(healpix_map[0, ...])
npix = hp.get_map_size(healpix_map[0, ...]) # total number of pixels in the map must be 12 * nside^2
crpix1, crval1, cdelt1 = [ header.get(x) for x in "CRPIX1", "CRVAL1", "CDELT1" ]
crpix2, crval2, cdelt2 = [ header.get(x) for x in "CRPIX2", "CRVAL2", "CDELT2" ]
# beam centres in pixel coordinates
xc = crpix1-1 + (np.rad2deg(r1.ravel()) - crval1)/(256*cdelt1)
yc = crpix2-1 + (np.rad2deg(r2.ravel()) - crval2)/(256*cdelt2)
#xc = (np.rad2deg(r1.ravel()) )/cdelt1
for j in xrange(4):
print 'started Stokes: %d' %j
for iter in xrange(0 + j, 16, 4):
outpt = np.zeros(shape = npix, dtype=np.float64)
#by = outpt.copy()
# mask beam
bm_data = beam_data[iter]
#masked_beam= beam_data[iter]
shape = bm_data.shape
rad = np.linspace(-shape[0]/2,shape[-1]/2,shape[0])
rad2d = np.sqrt(rad[np.newaxis,:]**2+rad[:,np.newaxis]**2)
mask = rad2d <= radius/abs(cdelt2)
masked_beam = bm_data*mask
s1 = ndimage.map_coordinates(masked_beam, [xc, yc], mode = 'constant')
bm_map = s1.reshape(dist.shape[0], dist.shape[-1])
for itr in xrange(npix):
g_xy = (1.0/(np.sqrt(2*np.pi)*np.std(dist[itr])))*np.exp(-(dist[itr])**2/(2*np.var(dist[itr])))
#weighted_healpix_map = np.convolve(healpix_map[j, ...][ipix_indx[itr]], g_xy/g_xy.sum(), mode='same')
weighted_healpix_map = ndimage.filters.convolve(healpix_map[j, ...][ipix_indx[itr]], g_xy/g_xy.sum(), mode='reflect')
#outpt[itr] = np.sum(weighted_healpix_map*(bm_map[itr]/bm_map[itr].sum()))
outpt[itr] = np.sum(weighted_healpix_map*(bm_map[itr]))
#print 'itr', itr
alpha = file('pap%d.save'%iter, 'wb')
#h_map = ndimage.filters.gaussian_filter(outpt, sigma = 3.)
cPickle.dump(outpt, alpha, protocol = cPickle.HIGHEST_PROTOCOL)
alpha.close()
print 'Just dumped stripp%d.save:-------'%iter
print 'Loading dumped files:-------'
loaded_objects = []
for itr4 in xrange(16):
alpha = file('stripp%d.save'%itr4, 'rb')
loaded_objects.append(cPickle.load(alpha))
alpha.close()
measured_map.append(copy.deepcopy(loaded_objects))
return measured_map
Remember that HEALPix maps can be in either "Ring" or "Nested" format. It sounds like you may need to add the keyword nest=True to your healpy functions like hp.pix2ang. If your input maps are in nested format, this keyword is needed.
For example:
I recently tried using the healpy.smoothing() function, and found my resulting image to have rings (perhaps like you described), upon viewing the output map with healpix.mollview(). The rings disappeared and the image was presented as I expected, after running mollview with the nested=True keyword. Check what ordering schemes your input files use
Reference:
http://healpy.readthedocs.org/en/latest/tutorial.html#creating-and-manipulating-maps
Healpix supports two different ordering schemes, RING or NESTED. By
default, healpy maps are in RING ordering. In order to work with
NESTED ordering, all map related functions support the nest keyword,
for example: hp.mollview(m, nest=True, title="Mollview image NESTED")

Categories

Resources