OpenCv RGB Histogram Back-projection not working as expected - python

I use backprojection to locate a person in an image knowing the person's histogram. The issue is that it is not working on skin or on clear clothes.
Here is what I get : Back-projection result
Here is the code I use to compute the BGR histogram of the region of interest :
channels=[0,1,2]
histSize = [8,8,8]
ranges=[0,256, 0,256, 0,256]
#image is in BGR color
bgr_split = cv2.split(roi_img)
#Compute image bgr histogram
hist = cv2.calcHist(bgr_split, channels, mask, histSize, ranges)
cv2.normalize(hist, hist, 0, 255, cv2.NORM_MINMAX)
The histogram I get is consistant with the person's colors, but when I apply a backprojection on the image containing the person, only the dark regions of the person get non zero values as show in Back-projection result.
I tested the backprojection of my histogram on one pixel and I don't understand the result eiter. I get this :
>> hist[2,2,1]
83.539368
>> pix_img = np.uint8(np.array([[[66,66,34]]]))
>> cv2.calcBackProject([pix_img],channels,hist,ranges,1)
array([[0]], dtype=uint8)
The pixel (b=66, g=66, r=34) should correspond to the histogram bin [2,2,1] since histSize = [8,8,8], yet the backprojection returns 0 instead of 141.
Any idea on what I am doing wrong?

After some tests, it looks like the backprojection function applied on the pixel [b,v,r] gives the backprojection on the pixel [b,v,0], the third channel's value is ignored. I guess it is a bug from opencv and I am going to report it.
I bypassed the issue by not using this function and replacing it by:
b,g,r=cv2.split(img/(256/ql))
B = np.uint8(hist[b.ravel(),g.ravel(), r.ravel()])
B = B.reshape(img.shape[:2])
ret, B = cv2.threshold(B,10,255,cv2.THRESH_BINARY)

Related

when I apply median filter to image, it turns purple. why?

I have a image.I added salt & pepper noise to this image. After that I applied 2D median filter to remove noise from image. But after this process, the image converted purple.
And here is my codes.
M=3;
N=3;
modifyA=np.pad(image, [(math.floor(M/2),math.floor(N/2))])
B = np.zeros([(image.shape[0]),(image.shape[1])])
med_indx = round((M*N)/2); #MEDIAN INDEX
for i in range ((modifyA.shape[0])-(M-1)-1):
for j in range ((modifyA.shape[1])-(N-1)-1):
temp = modifyA[i:i+(M-1), j:j+(N-1)] #
#RED,GREEN AND BLUE CHANNELS ARE TRAVERSED SEPARATELY
for k in range (2):
tmp = temp[:,:,k]
B[i,j] = np.median(tmp[:])
B = B.astype(np.uint8)
imgplot = plt.imshow(B)
plt.show()
Where could the error be?
As #gre_gor wrote in their comment, imshow is using a pseudocolor. More specifically, it is using the common colormap viridis by default if the image is not RGB(A).
Take a look at the documentation: https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.imshow.html
To display a grayscale version of your image refer to this part of the doc:
The input may either be actual RGB(A) data, or 2D scalar data, which will be rendered as a pseudocolor image. For displaying a grayscale image set up the colormapping using the parameters cmap='gray', vmin=0, vmax=255.

Plotting HSV channel histograms from a BGR image Opencv

I have B,G,R histograms that look like the following:
Image Histogram for B channel of an image
Description: On the X axis, I have the values from 0-255, that each pixel ranges from, and on Y axis, I have the number of pixels that have that particular X value.
My code for the same is:
hist1 = cv2.calcHist([image],[0],None,[256],[0,256])
plt.plot(hist1, color='b')
plt.xlabel("Value (blue)")
plt.ylabel("Number of pixels")
plt.title('Image Histogram For Blue Channel')
plt.show()
My question is, that I need to get the same plot - X axis with values, and Y axis with number of pixels, for HSV channels. Basically, instead of B, G, and R plots, I need the same histogram, but one that gets H, S, I.
I got the following code:
img2 = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h, s, v = img2[:,:,0], img2[:,:,1], img2[:,:,2]
hist_h = cv2.calcHist([h],[0],None,[256],[0,256])
#hist_s = cv2.calcHist([s],[0],None,[256],[0,256])
#hist_v = cv2.calcHist([v],[0],None,[256],[0,256])
plt.plot(hist_h, color='r', label="hue")
Which gives me the following plot: Hue plot for an image
But from what I've read so far, BGR and HSV are different color spaces. So, I want to know, that when I'm using the calcHist function after converting to HSV, and then splitting into three channels, those channels by default are H,S and V? It's not that they're actually only BGR, but just simply mislabelled H, S and V? I just want to verify how both the methods are practically the same, but BGR and HSV are different color spaces.
Edit: Here's the source image
Image
Most likely you have a synthetic image with nothing in the red and green channels and some random data centred on 128 in the blue channel.
When you go to HSV colourspace, all the hues are centred on 110 which corresponds to 220 degrees which is blue in the regular 0..360 HSV range. Remember OpenCV uses a range of 0..180 for Hue when using uint8 so that it fits in uint8's range of 0..255. So you effectively need to multiply the 110 you see in your Hue histogram by 2... making 220 which is blue.
See bottom part of this figure.
As you seem so uncertain of your plotting, I made histograms of the HSV channels for you below. I used a different tool to generate them, but don't let that bother you - in fact confirmation from a different tool is always a good sign.
First, here are the Hue (left), Saturation (centre) and Value (right) channels side-by-side:
Now the Hue histogram:
This tells you all the hues in the image are similar - i.e. various shades of blue.
Now the Saturation histogram:
This tells you that the colours in the image are generally low-to-medium saturated with no really vivid colours.
And finally, the Value histogram:
This tells you the image is generally mid-brightness, with no dark shadows and a small peak of brighter areas on the right of the histogram corresponding to where the white parts are in the original.

Get pixel location of binary image with intensity 255 in python opencv

I want to get the pixel coordinates of the blue dots in an image.
To get it, I first converted it to gray scale and use threshold function.
import numpy as np
import cv2
img = cv2.imread("dot.jpg")
img_g = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret1,th1 = cv2.threshold(img_g,127,255,cv2.THRESH_BINARY_INV)
What to do next if I want to get the pixel location with intensity 255? Please tell if there is some simpler method to do the same.
I don't think this is going to work as you would expect.
Usually, in order to get a stable tracking over a shape with a specific color, you do that in RGB/HSV/HSL plane, you could start with HSV which is more robust in terms of lighting.
1-Convert to HSV using cv2.cvtColor()
2-Use cv2.inRagne(blue_lower, blue_upper) to "filter" all un-wanted colors.
Now you have a good-looking binary image with only blue color in it (assuming you have a static background or more filters should be added).
3-Now if you want to detect dots (which is usually more than one pixel) you could try cv2.findContours
4- You can get x,y pixel of contours using many methods(depends on the shape of what you want to detect) like this cv2.boundingRect()

How to plot centroids on image after kmeans clustering?

I have a color image and wanted to do k-means clustering on it using OpenCV.
This is the image on which I wanted to do k-means clustering.
This is my code:
import numpy as np
import cv2
import matplotlib.pyplot as plt
image1 = cv2.imread("./triangle.jpg", 0)
Z1 = image1.reshape((-1))
Z1 = np.float32(Z1)
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
K1 = 2
ret, mask, center =cv2.kmeans(Z1,K1,None,criteria,10,cv2.KMEANS_RANDOM_CENTERS)
center = np.uint8(center)
print(center)
res_image1 = center[mask.flatten()]
clustered_image1 = res_image1.reshape((image1.shape))
for c in center:
plt.hlines(c, xmin=0, xmax=max(clustered_image1.shape[0], clustered_image1.shape[1]), lw=1.)
plt.imshow(clustered_image1)
plt.show()
This is what I get from the center variable.
[[112]
[255]]
This is the output image
My problem is that I'm unable to understand the output. I have two lists in the center variable because I wanted two classes. But why do they have only one value?
Shouldn't it be something like this (which makes sense because centroids should be points):
[[x1, y1]
[x2, y2]]
instead of this:
[[x]
[y]]
and if I read the image as a color image like this:
image1 = cv2.imread("./triangle.jpg")
Z1 = image1.reshape((-1, 3))
I get this output:
[[255 255 255]
[ 89 173 1]]
Color image output
Can someone explain to me how I can get 2d points instead of lines? Also, how do I interpret the output I got from the center variable when using the color image?
Please let me know if I'm unclear anywhere. Thanks!!
K-Means-clustering finds clusters of similar values. Your input is an array of color values, hence you find the colors that describe the 2 clusters. [255 255 255] is the white color, [ 89 173 1] is the green color. Similar for [112] and [255] in the grayscale version. What you're doing is color quantization
They are correctly the centroids, but their dimension is color, not location. Therefor you cannot plot it anywhere. Well you can, but I looks like this:
See how the 'color location' determines to which class each pixel belongs?
This is not something you can locate in your image. What you can do is find the pixels that belong to the different clusters, and use the locations of the found pixels to determine their centroid or 'average' position.
To get the 'average' position of each color, you have to separate out the pixel coordinates according to the class/color to which they belong. In the code below I used np.where( img <= 240) where 240 is the threshold. I used 240 out of ease, but you could use K-Means to determine where the threshold should be. (inRange() might be useful at some point)) If you sum the coordinates and divide that by the number of pixels found, you'll have what I think you are looking for:
Result:
Code:
import cv2
# load image as grayscale
img = cv2.imread('D21VU.jpg',0)
# get the positions of all pixels that are not full white (= triangle)
triangle_px = np.where( img <= 240)
# dividing the sum of the values by the number of pixels
# to get the average location
ty = int(sum(triangle_px[0])/len(triangle_px[0]))
tx = int(sum(triangle_px[1])/len(triangle_px[1]))
# print location and draw filled black circle
print("Triangle ({},{})".format(tx,ty))
cv2.circle(img, (tx,ty), 10,(0), -1)
# the same process, but now with only white pixels
white_px = np.where( img > 240)
wy = int(sum(white_px[0])/len(white_px[0]))
wx = int(sum(white_px[1])/len(white_px[1]))
# print location and draw white filled circle
print("White: ({},{})".format(wx,wy))
cv2.circle(img, (wx,wy), 10,(255), -1)
# display result
cv2.imshow('Result',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Here is an Imagemagick solution, since I am not proficient with OpenCV.
Basically, I convert your actual image (from your link in the comments) to binary, then use image moments to extract the centroid and other statistics.
I suspect you can do something similar in OpenCV, Skimage, or Python Wand, which is based upon Imagemagick. (See for example:
https://docs.opencv.org/3.4/d3/dc0/group__imgproc__shape.html#ga556a180f43cab22649c23ada36a8a139
https://scikit-image.org/docs/dev/api/skimage.measure.html#skimage.measure.moments_coords_central
https://en.wikipedia.org/wiki/Image_moment)
Input:
Your image does not have just two colors. Perhaps this image did not have kmeans clustering applied with 2 colors only. So I will do that with an Imagemagick script that I have built.
kmeans -n 2 -m 5 img.png img2.png
final colors:
count,hexcolor
99234,#65345DFF
36926,#27AD0EFF
Then I convert the two colors to black and white by simply thresholding and stretching the dynamic range to full black and white.
convert img2.png -threshold 50% -auto-level img3.png
Then I get all the image moment statistics for the white pixels, which includes the x,y centroid in pixels relative to the top left corner of the image. It also includes the equivalent ellipse major and minor axes, angle of major axis, eccentricity of the ellipse, and equivalent brightness of the ellipse, plus the 8 Hu image moments.
identify -verbose -moments img3.png
Channel moments:
Gray:
--> Centroid: 208.523,196.302 <--
Ellipse Semi-Major/Minor axis: 170.99,164.34
Ellipse angle: 140.853
Ellipse eccentricity: 0.197209
Ellipse intensity: 106.661 (0.41828)
I1: 0.00149333 (0.380798)
I2: 3.50537e-09 (0.000227937)
I3: 2.10942e-10 (0.00349771)
I4: 7.75424e-13 (1.28576e-05)
I5: 9.78445e-24 (2.69016e-09)
I6: -4.20164e-17 (-1.77656e-07)
I7: 1.61745e-24 (4.44704e-10)
I8: 9.25127e-18 (3.91167e-08)

How to get the red channel color space of an image?

I'm trying to get the red channel color space of an image, currently the way I'm doing it, I get a grayscale image:
img = img[:,:,2]
But I want an image like this:
Above, the top image is the red channel color space image, and the bottom one is the original image. What exactly is being done to achieve this image?
I've also tried
img[:,:,0] = 0
img[:,:,1] = 0
But the result obtained is not as desired. Here's an article on red channel color space: https://en.wikipedia.org/wiki/RG_color_space
Actually, your expected output image is not the red channel color space of the original one. It's sort of a COLORMAP that has been applied on input image. The good news is that OpenCV come up with multiple built-in colormaps. The bad news is that your expected output cant be generate by OpenCV's built-in colormaps. But don't give up, you can map the colors using a custom lookup table using cv2.LUT() function.
For better demonstration here are some examples with your image:
img = cv2.imread('origin.png')
im_color = cv2.applyColorMap(img, cv2.COLORMAP_HSV)
cv2.imshow('mapped_image', im_color)
# cv2.imwrite('result.png', im_color)
cv2.waitKey(0)
Here are all the OpenCV's COLORMAP's:
print [sub for sub in dir(cv2) if sub.startswith('COLORMAP_')]
['COLORMAP_AUTUMN', 'COLORMAP_BONE', 'COLORMAP_COOL', 'COLORMAP_HOT', 'COLORMAP_HSV', 'COLORMAP_JET', 'COLORMAP_OCEAN', 'COLORMAP_PINK', 'COLORMAP_RAINBOW', 'COLORMAP_SPRING', 'COLORMAP_SUMMER', 'COLORMAP_WINTER']
An example for mapping the colors using a custom lookup table using cv2.LUT():
table = np.array([( i * invert_value) for i in np.arange(256)]).astype("uint8")
cv2.LUT(image, table)
Your second suggestion should throw away the blue and green colors and give you a "red channel image". If you want a RG (red-green) color space image, throw away only the blue channel:
img[:,:,0] = 0
But the example image you posted doesn't illustrate that, as the resulting image has information left in all three channels. My guess is that it was produced with a "colormap", where different colors represent different values of red in the original image. Such a mapping can look any way you like, so it's not easy to reconstruct it from your example images

Categories

Resources