Detecting an object and get the mean pixel value (BGR) - python

I have a picture of a leaf with a white paper as the background and I need to remove the noise (yellow dot) and get the pixel value (bgr) of the leaf.
I used green threshold to detect the leaf and masked it with the original image. I used cv2.mean to get the pixel value, but it counts all the pixel include the black area/background.
How to get the pixel value only for the leaf?
Here is the code I used:
import cv2
import numpy as np
img=cv2.imread('crop21.jpg')
blur=cv2.GaussianBlur(img,(5,5),0)
hsv=cv2.cvtColor(blur,cv2.COLOR_BGR2HSV)
#threshold green
low_g=np.array([35,100,60],np.uint8)
up_g=np.array([85,255,190],np.uint8)
mask=cv2.inRange(hsv,low_g,up_g)
mask_upstate=cv2.bitwise_and(blur, blur, mask=mask)
#get the bgr value
mean=cv2.mean(mask_upstate)
print (mean)
cv2.imshow('image',mask_upstate)
cv2.waitKey(0)
cv2.destroyAllWindows()

So basically you have a masked image with a leaf and a black background. The problem now is that it is dividing the sum of the colours by the amount of all pixels, instead of just dividing it by the amount of pixels that has the leaf. An easy quick way to solve this is by multiplying the result from the mean = cv2.mean(mask_upstate) by Total pixels / Non-black pixels, which can be done as follows:
# Get the BGR value
mean = cv2.mean(mask_upstate)
multiplier = float(mask.size)/cv2.countNonZero(mask)
mean = tuple([multiplier * x for x in mean])
Thus, you have the mean of just the non-black pixels, ergo the leaf without the black background.
Hope this helped!

Related

Measuring Average Brightness of a Black and White Image?

I currently have a code for measuring the average brightness of an RGB image.
When i run this with a black and white image, the RGB values are the same, e.g. 37, 37, 37
I have no idea about colours etc but i dont think this is correct
Here is my code at the moment:
from PIL import Image
from math import sqrt
imag = Image.open("../Images/pexels-photo-57905.jpeg")
imag = imag.convert ('RGB')
imag.show()
X,Y = 0,0
pixelRGB = imag.getpixel((X,Y))
R,G,B = pixelRGB
brightness = sum([R,G,B])/3 ##0 is dark (black) and 255 is bright (white)
print(brightness)
print(R,G,B)
In a nutshell, i must convert an RGB image into grayscale, which im using .convert('LA') for, i must THEN measure the brightness of the image by adding the black and white values then dividing them by 2
Are these codes correct to measure the average brightness of a greyscale image? What do the three lines below mean? Does it return the average brightness of the whole picture, or just (0,0)?
X,Y = 0,0
pixelRGB = imag.getpixel((X,Y))
R,G,B = pixelRGB

Rectangular bounding boxes around objects in monochrome images in python?

I have a set of two monochrome images [attached] where I want to put rectangular bounding boxes for both the persons in each image. I understand that cv2.dilate may help, but most of the examples I see are focusing on detecting one rectangle containing the maximum pixel intensities, so essentially they put one big rectangle in the image. I would like to have two separate rectangles.
UPDATE:
This is my attempt:
import numpy as np
import cv2
im = cv2.imread('splinet.png',0)
print im.shape
kernel = np.ones((50,50),np.uint8)
dilate = cv2.dilate(im,kernel,iterations = 10)
ret,thresh = cv2.threshold(im,127,255,0)
im3,contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
plt.imshow(im,cmap='Greys_r')
#plt.imshow(im3,cmap='Greys_r')
for i in range(0, len(contours)):
if (i % 2 == 0):
cnt = contours[i]
#mask = np.zeros(im2.shape,np.uint8)
#cv2.drawContours(mask,[cnt],0,255,-1)
x,y,w,h = cv2.boundingRect(cnt)
cv2.rectangle(im,(x,y),(x+w,y+h),(255,255,0),5)
plt.imshow(im,cmap='Greys_r')
cv2.imwrite(str(i)+'.png', im)
cv2.destroyAllWindows()
And the output is attached below: As you see, small boxes are being made and its not super clear too.
The real problem in your question lies in selection of the optimal threshold from the monochrome image.
In order to do that, calculate the median of the gray scale image (the second image in your post). The threshold level will be set 33% above this median value. Any value below this threshold will be binarized.
This is what I got:
Now performing morphological dilation followed by contour operations you can highlight your region of interest with a rectangle.
Note:
Never set a manual threshold as you did. Threshold can vary for different images. Hence always opt for a threshold based on the median of the image.

Detect white background on images using python

Is there a way to tell whether an image as a white background using python and what could be a good strategy to get a "percentage of confidence" about this question? Seems like the literature on internet doesn't cover exactly this case and I can't find anything strictly related.
The images I want to analyze are typical e-commerce website product pictures, so they should have a single focused object in the middle and white background only at the borders.
Another information that could be available is the max percentage of image space the object should occupy.
I would go with something like this.
Reduce the contrast of the image by making the brightest, whitest pixel something like 240 instead of 255 so that the whites generally found within the image and within parts of the product are no longer pure white.
Put a 1 pixel wide white border around your image - that will allow the floodfill in the next step to "flow" all the way around the edge (even if the "product" touches the edges of the frame) and "seep" into the image from all borders/edges.
Floofdill your image starting at the top-left corner (which is necessarily pure white after step 2) and allow a tolerance of 10-20% when matching the white in case the background is off-white or slightly shadowed, and the white will flow into your image all around the edges until it reaches the product in the centre.
See how many pure white pixels you have now - these are the background ones. The percentage of pure white pixels will give you an indicator of confidence in the image being a product on a whitish background.
I would use ImageMagick from the command line like this:
convert product.jpg +level 5% -bordercolor white -border 1 \
-fill white -fuzz 25% -draw "color 0,0 floodfill" result.jpg
I will put a red border around the following 2 pictures just so you can see the edges on StackOverflow's white background, and show you the before and after images - look at the amount of white in the resulting images (there is none in the second one because it didn't have a white background) and also at the shadow under the router to see the effect of the -fuzz.
Before
After
If you want that as a percentage, you can make all non-white pixels black and then calculate the percentage of white pixels like this:
convert product.jpg -level 5% \
-bordercolor white -border 1 \
-fill white -fuzz 25% -draw "color 0,0 floodfill" -shave 1 \
-fuzz 0 -fill black +opaque white -format "%[fx:int(mean*100)]" info:
62
Before
After
ImageMagick has Python bindings so you could do the above in Python - or you could use OpenCV and Python to implement the same algorithm.
This question may be years ago but I just had a similar task recently. Sharing my answer here might help others that will encounter the same task too and I might also improve my answer by having the community look at it.
import cv2 as cv
import numpy as np
THRESHOLD_INTENSITY = 230
def has_white_background(img):
# Read image into org_img variable
org_img = cv.imread(img, cv.IMREAD_GRAYSCALE)
# cv.imshow('Original Image', org_img)
# Create a black blank image for the mask
mask = np.zeros_like(org_img)
# Create a thresholded image, I set my threshold to 200 as this is the value
# I found most effective in identifying light colored object
_, thres_img = cv.threshold(org_img, 200, 255, cv.THRESH_BINARY_INV)
# Find the most significant contours
contours, hierarchy = cv.findContours(thres_img, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_NONE)
# Get the outermost contours
outer_contours_img = max(contours, key=cv.contourArea)
# Get the bounding rectangle of the contours
x,y,w,h = cv.boundingRect(outer_contours_img)
# Draw a rectangle base on the bounding rectangle of the contours to our mask
cv.rectangle(mask,(x,y),(x+w,y+h),(255,255,255),-1)
# Invert the mask so that we create a hole for the detected object in our mask
mask = cv.bitwise_not(mask)
# Apply mask to the original image to subtract it and retain only the bg
img_bg = cv.bitwise_and(org_img, org_img, mask=mask)
# If the size of the mask is similar to the size of the image then the bg is not white
if h == org_img.shape[0] and w == org_img.shape[1]:
return False
# Create a np array of the
np_array = np.array(img_bg)
# Remove the zeroes from the "remaining bg image" so that we dont consider the black part,
# and find the average intensity of the remaining pixels
ave_intensity = np_array[np.nonzero(np_array)].mean()
if ave_intensity > THRESHOLD_INTENSITY:
return True
else:
return False
These are the images of the steps from the code above:
Here is the Original Image. No copyright infringement intended.
(Cant find the url of the actual imagem from unsplash)
First step is to convert the image to grayscale.
Apply thresholding to the image.
Get the contours of the "thresholded" image and get the contours. Drawing the contours is optional only.
From the contours, get the values of the outer contour and find its bounding rectangle. Optionally draw the rectangle to the image so that you'll see if your assumed thresholding value fits the object in the rectangle.
Create a mask out of the bounding rectangle.
Lastly, subtract the mask to the greyscale image. What will remain is the background image minus the mask.
To Finally identify if the background is white, find the average intensity values of the background image excluding the 0 values of the image array. And base on a certain threshold value, categorize it if its white or not.
Hope this helps. If you think it can still be improve, or if there are flaws with my solution pls comment below.
The most popular image format is .png. PNG image can have a transparent color (alpha). Often match with the white background page. With pillow is easy to find out which pixels are transparent.
A good starting point:
from PIL import Image
img = Image.open('image.png')
img = img.convert("RGBA")
pixdata = img.load()
for y in xrange(img.size[1]):
for x in xrange(img.size[0]):
pixel = pixdata[x, y]
if pixel[3] == 255:
# tranparent....
Or maybe it's enough if you check if top-left pixel it's white:
pixel = pixdata[0, 0]
if item[0] == 255 and item[1] == 255 and item[2] == 255:
# it's white

How to detect if an image is partially occluded?

I have a large number of aerial images. Some of them have the lens partially occluded. For example:
and
I'm trying to automatically detect which of the images have this using OpenCV. My initial though was to check how much of the image is black across multiple images. But hopefully there is a smarted way to do it for images in isolation.
An idea is to determine how many black pixels are on the image. We can do this by creating a blank mask and then coloring all detected black pixels white on the mask using np.where. From here we can count the number of white pixels on the mask with cv2.countNonZero then calculate the pixel percentage. If the calculated percentage is greater than some threshold (say 2%) then the image is partially occluded. Here's the results:
Input image -> mask
Pixel Percentage: 3.33%
Occluded: True
Pixel Percentage: 2.54%
Occluded: True
Code
import cv2
import numpy as np
def detect_occluded(image, threshold=2):
"""Determines occlusion percentage and returns
True for occluded or False for not occluded"""
# Create mask and find black pixels on image
# Color all found pixels to white on the mask
mask = np.zeros(image.shape, dtype=np.uint8)
mask[np.where((image <= [15,15,15]).all(axis=2))] = [255,255,255]
# Count number of white pixels on mask and calculate percentage
mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
h, w = image.shape[:2]
percentage = (cv2.countNonZero(mask)/ (w * h)) * 100
if percentage < threshold:
return (percentage, False)
else:
return (percentage, True)
image = cv2.imread('2.jpg')
percentage, occluded = detect_occluded(image)
print('Pixel Percentage: {:.2f}%'.format(percentage))
print('Occluded:', occluded)
I'd recommend using some sort of floodfill algorithm with black pixels. By checking large (connected) black area's, you could indentify these. This approach has the advantage that you can tweak the parameters for aggressiveness (eg; When is a pixel labled as black, how large must the connected area be, etc).

Replacing a segmented part of an image with it's unsegmented part

I am trying to replace a segmented part of an image with it's unsegmented part with OpenCV in Python. The pictures will make you understand what I mean.
The following picture is the first one, before segmentation :
This is the picture after segmentation :
This is the third picture, after doing what I'm talking about :
How can I do this ? Thanks in advance for your help !
This is actually pretty easy. All you have to do is take your picture after segmentation, and multiply it by a mask where any pixel in the mask that is 0 becomes 1, and anything else becomes 0.
This will essentially blacken all of the pixels with the exception of the pixels within the mask that are 1. By multiplying each of the pixels in your image by the mask, you would effectively produce what you have shown in the figure, but the background is black. All you would have to do now is figure out which locations in your mask are white and set the corresponding locations in your output image to white. In other words:
import cv2
# Load in your original image
originalImg = cv2.imread('Inu8B.jpg',0)
# Load in your mask
mask = cv2.imread('2XAwj.jpg', 0)
# Get rid of quantization artifacts
mask[mask < 128] = 0
mask[mask > 128] = 1
# Create output image
outputImg = originalImg * (mask == 0)
outputImg[mask == 1] = 255
# Display image
cv2.imshow('Output Image', outputImg)
cv2.waitKey(0)
cv2.destroyAllWindows()
Take note that I downloaded the images from your post and loaded them from my computer. Also, your mask has some quantization artifacts due to JPEG, and so I thresholded at intensity 128 to ensure that your image consists of either 0s or 1s.
This is the output I get:
Hope this helps!
Basically, you have a segmentation mask and an image. All you need to do is copy the pixels in the image corresponding to the pixels in the label mask. Generally, the mask dimensions and the image dimensions are the same (if not, you need to resize your mask to the image dimensions). Also, the segmentation pixels corresponding to a particular mask would have the same integer value (1,2,3 etc and background pixels would have a value of 0). So, find out which pixel co-ordinates have a value corresponding to the mask value and use those co-ordinates to find out the intensity values in the image. If you know the syntax of how to access a pixel co-ordinate, read an image in the programming environment you are using and follow the aforementioned procedure, you should be able to do it.

Categories