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).
Related
I am new to opencv.
My Idea is: I have a picture, and defined 4 points (pixels?) e.g. 0x0,0x100,100x0,100x00
What would be best approach to probe each of those BUT, creating square around them.
so e.g. for 0x0 (well not the best example as it can't go around), so let's say 50x50 point and create some kind of mask around that pixel let's say 10x10 pixels square width and height, and then get average RGB of that square, and then do it for all points.
So far I can only probe single points for RGB, but don't have an idea how to approach masking.
I have a feeling like openCV could have some easy solution for that, but all I am finding is super overcomplicated (imho) code that I don't really understand.
If you have an irregular region, then make a mask for it. You can compute the mean of region corresponding to the mask in Python/OpenCV as follows:
Input:
Mask:
import cv2
# load image
img = cv2.imread('zelda1.jpg')
# load mask as grayscale
mask = cv2.imread('zelda1_mask.png', 0)
# get mean of pixels corresponding to mask
mean = cv2.mean(img, mask=mask)
# print mean of each channel including alpha; alpha=0 is opaque
print(mean)
# mask region on input
region = img.copy()
img_masked = cv2.bitwise_and(img, img, mask=mask)
# Save result
cv2.imwrite('zelda1_region2.jpg', img_masked)
# Display input
cv2.imshow('input', img)
cv2.imshow('mask', mask)
cv2.imshow('input masked', img_masked)
cv2.waitKey(0)
cv2.destroyAllWindows()
Region of image where mean is computed:
Mean:
(50.23702664796634, 32.84151472650771, 198.3702664796634, 0.0)
Here is one way to do that in Python/OpenCV using Numpy slicing to get a square region about any give point.
Input:
import cv2
# load image
img = cv2.imread('zelda1.jpg')
# Define point
x = 90
y = 200
# Define region size
rr = 10
# crop image +-20 pixels
crop = img[y-rr:y+rr, x-rr:x+rr]
# compute mean
mean = cv2.mean(crop)
# print mean of each channel including alpha; alpha=0 is opaque
print(mean)
# draw region on input
region = img.copy()
cv2.rectangle(region, (x-rr,y-rr), (x+rr,y+rr), (255,255,255), 1)
# Save result
cv2.imwrite('zelda1_region.jpg', region)
# Display input
cv2.imshow('input', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Region:
Mean of region for each channel:
(53.6175, 35.9, 205.2375, 0.0)
I am trying to write an algorithm to systematically determine how many different "curves" are in an image. Example Image. I'm specifically interested in the white lines here, so I've used a color threshold to mask the rest of the image and only get the white pixels. These lines represent a path run by a player (wide receivers in the NFL), so I'm interested in the x and y coordinates that the path represents - and each "curve" represents a different path that the player took (or "route"). All curves should start on or behind the blue line.
However, while I can get just the white pixels, I can't figure out how to systematically identify the separate curves. In this example image, there are 8 white curves (or routes) present. I've identified those curves in this image. I tried edge detection, and then using scipy ndimage to get the number of connected components, but because the curves overlap it counts them as connected and only gives me 3 labeled components for this image as opposed to eight. Here's what the edge detection output looks like. Is there a better way to go about this? Here is my sample code.
import cv2
from skimage.morphology import skeletonize
import numpy as np
from scipy import ndimage
#Read in image
image = cv2.imread('example_image.jpeg')
#Color boundary to get white pixels
lower_white = np.array([230, 230, 230])
upper_white = np.array([255, 255, 255])
#mask image for white pixels
mask = cv2.inRange(image, lower_white, upper_white)
c_pixels = cv2.bitwise_and(image, image, mask=mask)
#make pixels from 0 to 1 form to use in skeletonize
c_pixels = c_pixels.clip(0,1)
ske_c = skeletonize(c_pixels[:,:,1]).astype(np.uint8)
#Edge Detection
inputImage =ske_c*255
edges = cv2.Canny(inputImage,100,200,apertureSize = 7)
#Show edges
cv2.imshow('edges', edges)
cv2.waitKey(0)
cv2.destroyAllWindows()
#Find number of components
# smooth the image (to remove small objects); set the threshold
edgesf = ndimage.gaussian_filter(edges, 1)
T = 50 # set threshold by hand to avoid installing `mahotas` or
# `scipy.stsci.image` dependencies that have threshold() functions
# find connected components
labeled, nr_objects = ndimage.label(edgesf > T) # `dna[:,:,0]>T` for red-dot case
print("Number of objects is %d " % nr_objects)
I want to analyse a specific part of an image, as an example I'd like to focus on the bottom right 200x200 section and count all the black pixels, so far I have:
im1 = Image.open(path)
rgb_im1 = im1.convert('RGB')
for pixel in rgb_im1.getdata():
Whilst you could do this with cropping and a pair of for loops, that is really slow and not ideal.
I would suggest you use Numpy as it is very commonly available, very powerful and very fast.
Here's a 400x300 black rectangle with a 1-pixel red border:
#!/usr/bin/env python3
import numpy as np
from PIL import Image
# Open the image and make into Numpy array
im = Image.open('image.png')
ni = np.array(im)
# Declare an ROI - Region of Interest as the bottom-right 200x200 pixels
# This is called "Numpy slicing" and is near-instantaneous https://www.tutorialspoint.com/numpy/numpy_indexing_and_slicing.htm
ROI = ni[-200:,-200:]
# Calculate total area of ROI and subtract non-zero pixels to get number of zero pixels
# Numpy.count_nonzero() is highly optimised and extremely fast
black = 200*200 - np.count_nonzero(ROI)
print(f'Black pixel total: {black}')
Sample Output
Black pixel total: 39601
Yes, you can make it shorter, for example:
h, w = 200,200
im = np.array(Image.open('image.png'))
black = h*w - np.count_nonzero(ni[-h:,-w:])
If you want to debug it, you can take the ROI and make it into a PIL Image which you can then display. So just use this line anywhere after you make the ROI:
# Display image to check
Image.fromarray(ROI).show()
You can try cropping the Image to the specific part that you want:-
img = Image.open(r"Image_location")
x,y = img.size
img = img.crop((x-200, y-200, x, y))
The above code takes an input image, and crops it to its bottom right 200x200 pixels. (make sure the image dimensions are more then 200x200, otherwise an error will occur)
Original Image:-
Image after Cropping:-
You can then use this cropped image, to count the number of black pixels, where it depends on your use case what you consider as a BLACK pixel (a discrete value like (0, 0, 0) or a range/threshold (0-15, 0-15, 0-15)).
P.S.:- The final Image will always have a dimension of 200x200 pixels.
from PIL import Image
img = Image.open("ImageName.jpg")
crop_area = (a,b,c,d)
cropped_img = img.crop(crop_area)
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!
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