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.
Related
im using the following code to color screen a photo. We are trying to locate the orange circle within the image. Is there a way to eliminate some of the background noise shown in the second photo? Tweaking the color range some may help but its never enough to fully eliminate the background noise. I've also considered trying to locate circle shapes within the image but i am unsure how to do that. Any help would be amazing!
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
Lower_bound = np.array([0, 80, 165]) # COLORS NEEDED
Upper_bound = np.array([75, 255, 255])
mask = cv2.inRange(hsv, Lower_bound, Upper_bound)
Option 1 (HSV color space):
If you want to continue using HSV color space robustly, you must check out this post. There you can control the variations across the three channels using trackbars.
Option 2 (LAB color space):
Here I will be using the LAB color space where dominant colors can be segmented pretty easily. LAB space stores image across three channels (1 brightness channel) and (2 color channels):
L-channel: amount of lightness in the image
A-channel: amount of red/green in the image
B-channel: amount of blue/yellow in the image
Since orange is a close neighbor of color red, using the A-channel can help segment it.
Code:
img = cv2.imread('image_path')
# Convert to LAB color space
lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
cv2.imshow('A-channel', lab[:,:,1])
The above image is the A-channel, where the object of interest is pretty visible.
# Apply threshold
th = cv2.threshold(lab[:,:,1],150,255,cv2.THRESH_BINARY)[1]
Why does this work?
Looking at the LAB color plot, the red color is on one end of A-axis (a+) while green color is on the opposite end of the same axis (a-). This means, higher values in this channel represents colors close to red, while the lower values values represents colors close to green.
The same can be done along the b-channel while trying to segment yellow/blue color in the image.
Post-processing:
From here onwards for every frame:
Identify the largest contour in the binary threshold image th.
Mask it over the frame using cv2.bitwise_and()
Note: this LAB color space can help segment dominant colors easily. You need to test them before using it for other colors.
My goal is to draw the text bounding boxes for the following image. Since the two regions are colored differently, so this should be easy. I just need to select the pixels that match a certain color values to filter out the other text region and run a convex hull detection.
However, when I zoom in the image, I notice that the text regions has the zig-zag effect on the edges, so I'm not able to easily find the two color values (for the blue and green) from the above image.
I wonder is there a way to remove the zig-zag effect to make sure each phrase is colored consistently? Or is there a way to determine the dominant color for each text region?
The anti-aliasing causes the color to become lighter (or darker if against a black background) so you can think of the color as being affected by light. In that case, we can use light-invariant color spaces to extract the colors.
So first convert to hsv since it is a light invariant colorspace. Since the background can be either black or white, we will filter out them out (if the bg is always white and the text can be black you would need to change the filtering to allow for that).
I took the saturation as less than 80 as that will encompass white black and gray since they are the only colors with low saturation. (your image is not perfectly white, its 238 instead of 255 maybe due to jpg compression)
Since we found all the black, white and gray, the rest of the image are our main colors, so i took the inverse mask of the filter, then to make the colors uniform and unaffected by light, set the Saturation and Value of the colors to 255, that way the only difference between all the colors will be the hue. I also set bg pixels to 0 to make it easier for finding contours but thats not necissary
After this you can use whatever method you want to get the different groups of colors, I just did a quick histogram for the hue values and got 3 peaks but 2 were close together so they can be bundled together as 1. You can maybe use peak finding to try to find the peaks. There might be better methods of finding the color groups but this is what i just thought of quickly.
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
mask = hsv[:,:,1] < 80 # for white, gray & black
hsv[mask] = 0 # set bg pixels to 0
hsv[~mask,1:] = 255 # set fg pixels saturation and value to 255 for uniformity
colors = hsv[~mask]
z = np.bincount(colors[:,0])
print(z)
bgr = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
cv2.imshow('bgr', bgr)
I got data that is made based on a 3 channels, which are red in as RGB, this means that the color bar would have to circle between all 3 colors to show all possible shades Is there a simple way of doing this?
Here is an example. Red is left enhanced, blue right enhanced and green centrally enhanced. (it is looking at spectral features.) This means that Red+Blue (= Purple) would be right and central enhanced and weak in left. etc.
I need a way to show that properly with a colorbar of sorts.
I'm not sure I understood what is your expected result. I'm providing a temporary answer anyway so that you can eventually point me to the right direction.
This is an example colorbar made with numpy arrays:
The code I used to generate it is the following:
import numpy as np
import cv2
# Initialize an empty array that matches opencv ranges for hsv images:
# hue (cylinder 180°) 0-179 (multiplied by 10 to "stretch" horizontally)
# saturation is fixed at 254
# value (0-254)
bar = np.ndarray([255,1800,3], dtype="uint8")
for x in range(1800):
for y in range(255):
bar[y,x,0] = int(x/10)
bar[y,x,1] = 254
bar[y,x,2] = y
#Convert to BGR (opencv standard instead of rgb)
bgr = cv2.cvtColor(bar, cv2.COLOR_HSV2BGR)
cv2.imshow('Colorbar', bgr)
cv2.waitKey()
I've built a function that takes an image, and builds a Laplacian Pyramid from it. I want to take, say, the first image of the Laplacian Pyramid and place it onto a black canvas(using np.zeros to build it).
I've done this, but what I get is that the black canvas takes on a color similar to the Laplacian Image, instead of remaining black.
The code basically replaces an NxM spot on the canvas with the laplacian image:
canvas[0:768, 0:1024] = laplace_image
I was wondering what exactly I'm missing here, as trying this with a grayscale image yields the correct canvas.
And the plotting code which is probably the issue:
plt.figure()
plt.imshow(canvas, cmap='gray')
plt.show()
Here is an example of the values in a Laplacian Image
[[0.00206756 0.00217308 0.00229568 0.00241833 0.00253975 0.0026407
0.0027411 0.00283026 0.00289416 0.00295967 0.00302006 0.003061
0.00308811 0.00310638 0.00311357 0.00311655 0.00312005 0.00312285
0.00311985 0.00311802 0.003109 0.00308746 0.00304459 0.00298541
0.00291537 0.00283966 0.00276133 0.00267244 0.00255839 0.00242822
0.002288 0.002139 ]
[0.00066538 0.00070738 0.00075546 0.00080446 0.00084945 0.00087207
0.00088813 0.00091252 0.0009471 0.00099087 0.00103915 0.00107427
0.00109442 0.00109901 0.00110466 0.00110936 0.0011094 0.0011042
0.0010959 0.00109445 0.00109941 0.00108648 0.00105162 0.00103264
0.00101328 0.00098499 0.00094468 0.00089966 0.00084997 0.00079252
0.00072701 0.00066181]]
Setting vmin=0 will ensure that all zeros in canvas get interpreted as black by imshow:
plt.figure()
plt.imshow(canvas, cmap='gray', vmin=0)
plt.show()
Before it's fed into your colormap, the data in canvas is first normalized so that the smallest value corresponds to black and the largest value corresponds to white. You can control the normalization by passing in the vmin and vmax arguments to imshow. For cmap=gray, any values x <= vmin will get displayed as black, and any values x >= vmax will get displayed as white.
You reproduce a similar problem to the one you describe if there's any negative values in the image data:
img = np.zeros((500,1000))
img[:, :250] = -2
img[:, 250:500] = 2
plt.imshow(img, cmap='gray')
Passing in vmin=0 will cause the zeros in the second half of img to be displayed as black instead of gray:
plt.imshow(img, cmap='gray', vmin=0)
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)