Finding size of the object in python (OpenCV)? - python

How to get height and width of bright part using python (opencv) ? Having 2000 of these picture and end goal is to make table with length and width values

It is primitive method. Convert to grayscale and check which points have value bigger then some "bright" color ie. 21 and it gives array True/False - using .any(axis=0) you can reduce every row to single value, using .any(axis=1) you can reduce every column to single value and then using sum() you can count how many True was in any row or column (because True/False is converted to 1/0)
import cv2
img = cv2.imread('image.png')
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#print(img)
print('height, width, color:', img.shape)
#cv2.imshow('image', img)
#cv2.waitKey(0)
#cv2.destroyAllWindows()
print('width:', sum((img > 21).any(axis=0)))
print('height:', sum((img > 21).any(axis=1)))
For your image it gives me
width: 19
height: 27
For my image (below) it gives me
width: 23
height: 128
EDIT: Version with small change.
I set mask = (img > 21) to
calculate size
create Black&White image which better shows which points are
used to calculate size.
BTW: code ~mask inverts mask (convert True to False and False to True). It can be used also to invert image - ~img - to create negative for RGB, Grayscale or B&W.
Code:
import cv2
for filename in ['image-1.png', 'image-2.png']:
img = cv2.imread(filename)
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
print('height, width, color:', img.shape)
mask = (img > 21)
# display size
print(' width:', sum( mask.any(axis=0) ))
print('height:', sum( mask.any(axis=1) ))
# create Black&White version
img[ mask ] = 255 # set white
img[ ~mask ] = 0 # set black
# display Black&White version
cv2.imshow('image', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
# write Black&White version
cv2.imwrite('BW-' + filename, img)
--->
--->
EDIT: The same result using cv2.boundingRect() instead of sum(mask.any()) - but it still needs img[ mask ] = 255 to create Black&White image.
import cv2
for filename in ['image-1.png', 'image-2.png']:
print('filename:', filename)
img = cv2.imread(filename)
#print('height, width, color:', img.shape)
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#print('height, width, color:', img.shape)
mask = (img > 21)
# create Black&White version
img[ mask ] = 255 # set white
img[ ~mask ] = 0 # set black
x, y, width, height = cv2.boundingRect(img)
# display size
print(' width:', width)
print('height:', height)
# display Black&White version
#cv2.imshow('image', img)
#cv2.waitKey(0)
#cv2.destroyAllWindows()
# write Black&White version
#cv2.imwrite('BW-' + filename, img)
https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_contours/py_contour_features/py_contour_features.html#bounding-rectangle
EDIT: The same result using cv2.boundingRect() and cv2.threshold() - so it doesn't need mask
import cv2
for filename in ['image-1.png', 'image-2.png']:
print('filename:', filename)
img = cv2.imread(filename)
#print('height, width, color:', img.shape)
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#print('height, width, color:', img.shape)
ret, img = cv2.threshold(img, 21, 255, cv2.THRESH_BINARY) # the same 21 as in `mask = (img > 21)`
x, y, width, height = cv2.boundingRect(img)
# display size
print(' width:', width)
print('height:', height)
# display Black&White version
cv2.imshow('image', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
# write Black&White version
#cv2.imwrite('BW-' + filename, img)
https://docs.opencv.org/3.4/d7/d4d/tutorial_py_thresholding.html
https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_thresholding/py_thresholding.html
https://www.learnopencv.com/opencv-threshold-python-cpp/
https://www.geeksforgeeks.org/python-thresholding-techniques-using-opencv-set-1-simple-thresholding/

Related

How to make adaptive Threshold at part on image in Opencv python?

Maybe my question is strange something but I need to make an adaptive Threshold on part of the image that the user selects with his mouse and that's my code
import cv2
img = cv2.imread("test.png")
# img2 = cv2.imread("flower.jpg")
# variables
ix = -1
iy = -1
drawing = False
def draw_reactangle_with_drag(event, x, y, flags, param):
global ix, iy, drawing, img
if event == cv2.EVENT_LBUTTONDOWN:
drawing = True
ix = x
iy = y
elif event == cv2.EVENT_MOUSEMOVE:
if drawing == True:
img2 = cv2.imread("test.png")
cv2.rectangle(img2, pt1=(ix, iy), pt2=(x, y),
color=(0, 255, 255), thickness=1)
img = img2
elif event == cv2.EVENT_LBUTTONUP:
drawing = False
img2 = cv2.imread("test.png")
cv2.rectangle(img2, pt1=(ix, iy), pt2=(x, y),
color=(0, 255, 255), thickness=1)
img = img2
gray = cv2.cvtColor(img2[y: iy, x: ix], cv2.COLOR_BGR2GRAY)
th = cv2.adaptiveThreshold(gray,
255, # maximum value assigned to pixel values exceeding the threshold
cv2.ADAPTIVE_THRESH_GAUSSIAN_C, # gaussian weighted sum of neighborhood
cv2.THRESH_BINARY, # thresholding type
5, # block size (5x5 window)
3) # constant
img = th
cv2.namedWindow(winname="Title of Popup Window")
cv2.setMouseCallback("Title of Popup Window", draw_reactangle_with_drag)
while True:
cv2.imshow("Title of Popup Window", img)
if cv2.waitKey(10) == 27:
break
cv2.destroyAllWindows()
and that's what I got at attached screen
What am I missing?
Here is one solution for the desired region in Python/OpenCV. It is to use division normalization rather than adaptive thresholding. (This may or may not work for other regions.)
Read the input
Specify crop coordinates for rectangle
Crop the image
Blur the cropped image
Divide the input by the blurred image
Save the result
Input:
import cv2
import numpy as np
# read the input
img = cv2.imread('equador.png')
# specify the crop rectangle
# 364 396 359 453 (y iy x ix)
x1 = 359
y1 = 364
x2 = 453
y2 = 396
# crop the input
crop = img[y1:y2, x1:x2]
# blur
blur = cv2.GaussianBlur(crop, (0,0), sigmaX=99, sigmaY=99)
# divide
divide = cv2.divide(crop, blur, scale=255)
# put the divide back into the input
result = img.copy()
result[y1:y2, x1:x2] = divide
# save results
cv2.imwrite('equador_crop.png', crop)
cv2.imwrite('equador_crop_blur.png', blur)
cv2.imwrite('equador_crop_divide.png', divide)
cv2.imwrite('equador_crop_divide_result.png', result)
# show results
cv2.imshow('crop', crop)
cv2.imshow('blur', blur)
cv2.imshow('divide', divide)
cv2.imshow('result', result)
cv2.waitKey(0)
Cropped Image:
Blurred Image:
Division Normalized Crop:
Division Normalized Replace:
Note: you may prefer to convert the cropped image to grayscale before blurring and then divide the grayscale version by the blurred image.

Python OpenCV - How to recognize the specific colour in the image

Hello all I am working on the program that will simply recognize % of violet colour in the image. I am struggling with this rgb values.. Is there any smarter way of predefined static values for colours like 'blue' or 'green' or 'violet' in openCV already?
This is the code and the picture
import cv2
import numpy as np
imagePath = "probki/b.png"
img = cv2.imread(imagePath)
violet = [84, 39, 60]
diff = 100
boundaries = [([violet[2], violet[1]-diff, violet[0]-diff],
[violet[2]+diff, violet[1]+diff, violet[0]+diff])]
scalePercent = 1
width = int(img.shape[1] * scalePercent)
height = int(img.shape[0] * scalePercent)
newSize = (width, height)
img = cv2.resize(img, newSize, None, None, None, cv2.INTER_AREA)
cv2.imshow("img resized", img)
cv2.waitKey(0)
for (lower, upper) in boundaries:
lower = np.array(lower, dtype=np.uint8)
upper = np.array(upper, dtype=np.uint8)
mask = cv2.inRange(img, lower, upper)
cv2.imshow("binary mask", mask)
cv2.waitKey(0)
output = cv2.bitwise_and(img, img, mask=mask)
cv2.imshow("ANDed mask", output)
cv2.waitKey(0)
ratio_violet = cv2.countNonZero(mask)/(img.size/3)
colorPercent = (ratio_violet * 100) / scalePercent
print('violet pixel percentage:', np.round(colorPercent, 2))
cv2.imshow("images", np.hstack([img, output]))
cv2.waitKey(0)

How to make a single image using several images?

I have these images and there is a shadow in all images. I target is making a single image of a car without shadow by using these three images:
Finally, how can I get this kind of image as shown below:
Any kind of help or suggestions are appreciated.
EDITED
According to the comments, I used np.maximum and achieved easily to my target:
import cv2
import numpy as np
img_1 = cv2.imread('1.png', cv2.COLOR_BGR2RGB)
img_2 = cv2.imread('2.png', cv2.COLOR_BGR2RGB)
img = np.maximum(img_1, img_2)
cv2.imshow('img1', img_1)
cv2.imshow('img2', img_2)
cv2.imshow('img', img)
cv2.waitKey(0)
Here's a possible solution. The overall idea is to compute the location of the shadows, produce a binary mask identifying the location of the shadows and use this information to copy pixels from all the cropped sub-images.
Let's see the code. The first problem is to locate the three images. I used the black box to segment and crop each car, like this:
# Imports:
import cv2
import numpy as np
# image path
path = "D://opencvImages//"
fileName = "qRLI7.png"
# Reading an image in default mode:
inputImage = cv2.imread(path + fileName)
# Get the HSV image:
hsvImage = cv2.cvtColor(inputImage, cv2.COLOR_BGR2HSV)
# Get the grayscale image:
grayImage = cv2.cvtColor(inputImage, cv2.COLOR_BGR2GRAY)
showImage("grayImage", grayImage)
# Threshold via Otsu:
_, binaryImage = cv2.threshold(grayImage, 5, 255, cv2.THRESH_BINARY_INV)
cv2.imshow("binaryImage", binaryImage)
cv2.waitKey(0)
The previous bit uses the grayscale version of the image and applies a fixed binarization using a threshold of 5. I also pre-compute the HSV version of the original image. The result of the thresholding is this:
I'm trying to get the black rectangles and use them to crop each car. Let's get the contours and filter them by area, as the black rectangles on the binary image have the biggest area:
for i, c in enumerate(currentContour):
# Get the contour's bounding rectangle:
boundRect = cv2.boundingRect(c)
# Get the dimensions of the bounding rect:
rectX = boundRect[0]
rectY = boundRect[1]
rectWidth = boundRect[2]
rectHeight = boundRect[3]
# Get the area:
blobArea = rectWidth * rectHeight
minArea = 20000
if blobArea > minArea:
# Deep local copies:
hsvImage = hsvImage.copy()
localImage = inputImage.copy()
# Get the S channel from the HSV image:
(H, S, V) = cv2.split(hsvImage)
# Crop image:
croppedImage = V[rectY:rectY + rectHeight, rectX:rectX + rectWidth]
localImage = localImage[rectY:rectY + rectHeight, rectX:rectX + rectWidth]
_, binaryMask = cv2.threshold(croppedImage, 0, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY_INV)
After filtering each contour to get the biggest one, I need to locate the position of the shadow. The shadow is mostly visible in the HSV color space, particularly, in the V channel. I cropped two versions of the image: The original BGR image, now cropped, and the V cropped channel of the HSV image. This is the binary mask that results from applying an automatic thresholding on the S channel :
To locate the shadow I only need the starting x coordinate and its width, because the shadow is uniform across every cropped image. Its height is equal to each cropped image's height. I reduced the V image to a row, using the SUM mode. This will sum each pixel across all columns. The biggest values will correspond to the position of the shadow:
# Image reduction:
reducedImg = cv2.reduce(binaryMask, 0, cv2.REDUCE_SUM, dtype=cv2.CV_32S)
# Normalize image:
max = np.max(reducedImg)
reducedImg = reducedImg / max
# Clip the values to [0,255]
reducedImg = np.clip((255 * reducedImg), 0, 255)
# Convert the mat type from float to uint8:
reducedImg = reducedImg.astype("uint8")
_, shadowMask = cv2.threshold(reducedImg, 250, 255, cv2.THRESH_BINARY)
The reduced image is just a row:
The white pixels denote the largest values. The location of the shadow is drawn like a horizontal line with the largest area, that is, the most contiguous white pixels. I process this row by getting contours and filtering, again, to the largest area:
# Get the biggest rectangle:
subContour, _ = cv2.findContours(shadowMask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for j, s in enumerate(subContour):
# Get the contour's bounding rectangle:
boundRect = cv2.boundingRect(s)
# Get the dimensions of the bounding rect:
rectX = boundRect[0]
rectY = boundRect[1]
rectWidth = boundRect[2]
rectHeight = boundRect[3]
# Get the area:
blobArea = rectWidth * rectHeight
minArea = 30
if blobArea > minArea:
# Get image dimensions:
(imageHeight, imageWidth) = localImage.shape[:2]
# Set an empty array, this will be the binary mask
shadowMask = np.zeros((imageHeight, imageWidth, 3), np.uint8)
color = (255, 255, 255)
cv2.rectangle(shadowMask, (int(rectX), int(0)),
(int(rectX + rectWidth), int(0 + imageHeight)), color, -1)
# Invert mask:
shadowMask = 255 - shadowMask
# Store mask and cropped image:
shadowRois.append((shadowMask.copy(), localImage.copy()))
Alright, with that information I create a mask, where the only thing drawn in white is the location of the mask. I store this mask and the original BGR crop in the shadowRois list.
What follows is a possible method to use this information and create a full image. The idea is that I use the information of each mask to copy all the non-masked pixels. I accumulate this information on a buffer, initially an empty image, like this:
# Prepare image buffer:
buffer = np.zeros((100, 100, 3), np.uint8)
# Loop through cropped images and produce the final image:
for r in range(len(shadowRois)):
# Get data from the list:
(mask, img) = shadowRois[r]
# Get image dimensions:
(imageHeight, imageWidth) = img.shape[:2]
# Resize the buffer:
newSize = (imageWidth, imageHeight)
buffer = cv2.resize(buffer, newSize, interpolation=cv2.INTER_AREA)
# Get the image mask:
temp = cv2.bitwise_and(img, mask)
# Set info in buffer, substitute the black pixels
# for the new data:
buffer = np.where(temp == (0, 0, 0), buffer, temp)
cv2.imshow("Composite Image", buffer)
cv2.waitKey(0)
The result is this:

Grayscaled image has dark lower border

I am using opencv to take images using my webcam.
cam = cv2.VideoCapture(0)
cv2.namedWindow("Handwritten Number Recognition")
img_counter = 0
while True:
ret, frame = cam.read()
if not ret:
print("failed to grab frame")
break
cv2.imshow("Handwritten Number Recognition", frame)
k = cv2.waitKey(1)
if k%256 == 27:
# ESC pressed
print("Prediction is underway...")
break
elif k%256 == 32:
# SPACE pressed
img_name = "opencv_frame_{}.png".format(img_counter)
cv2.imwrite(img_name, frame)
print("Image taken!")
img_counter += 1
cam.release()
cv2.destroyAllWindows()
I then convert the image into grayscale and downsize it:
user_test = img_name
col = Image.open(user_test)
gray = col.convert('L')
bw = gray.point(lambda x: 0 if x<100 else 255, '1')
bw.save("bw_image.jpg")
bw
img_array = cv2.imread("bw_image.jpg", cv2.IMREAD_GRAYSCALE)
img_array = cv2.bitwise_not(img_array)
plt.imshow(img_array, cmap = plt.cm.binary)
plt.show()
img_size = 28
new_array = cv2.resize(img_array, (img_size,img_size))
final_array = new_array.reshape(1,-1)
plt.imshow(new_array, cmap = plt.cm.binary)
plt.show()
But the images have a very dark patch in the bottom which hampers the predictions I want to make with my data:
Original image:
What can I do to get past this problem? Interestingly this only happens if I click the image using opencv. If I use an image clicked through the same webcam but through the camera appliucation the error is not visible (Adding path of the image for preprocessing).
You have two options:
Choosing a better global threshold for the gray values. This is the easier less generic solution. Normally, people would choose the Otsu method to automatically select the optimal threshold. Have a look at: Opencv Thresholding Tutorial
threshold, dst_img = cv2.threshold(img, 0, 255, cv2.THRESH_OTSU)
Using an adaptive threshold. Adaptive simply means using a calculated threshold for each sliding window location based on some criteria. Have a look at: Niblack's Binarization methods
Using option one:
img = cv2.imread("thresh.jpg", cv2.IMREAD_GRAYSCALE)
threshold, img = cv2.threshold(img, 0, 255, cv2.THRESH_OTSU)
cv2.imwrite("thresh_bin.jpg", img)
Output:
this problem because of used thresholding method
bw = gray.point(lambda x: 0 if x<100 else 255, '1')
to solve this you can change the low limit value (100) to 75 or using opencv auto threshold
the, bw = cv2.threshold(gray_img,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
Here is one way to do that in Python/OpenCV using division normalization, thresholding and some morphology.
Input:
import cv2
import numpy as np
# read the image
img = cv2.imread('five.jpg')
# convert to gray
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# blur
smooth = cv2.GaussianBlur(gray, (555,555), 0)
# divide smooth by gray image
division = cv2.divide(smooth, gray, scale=255)
# invert
division = 255 - division
# threshold
thresh = cv2.threshold(division, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]
# add white border to help morphology close
border = cv2.copyMakeBorder(thresh, 60,60,60,60, cv2.BORDER_CONSTANT, value=(255,255,255))
hh, ww = border.shape
# morphology close
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (29,29))
result = cv2.morphologyEx(border, cv2.MORPH_CLOSE, kernel)
# remove border
result = result[60:hh-60, 60:ww-60]
# save results
cv2.imwrite('five_division_threshold.jpg',result)
# show results
cv2.imshow('smooth', smooth)
cv2.imshow('division', division)
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result:

how to fix png image onto another image with opencv with transparent background

I want to add glasses PNG image onto another image, but i get the PNG image with white background which hides the face.
I need to know what i did wrong please.
Original face image
Original glasses image
Expected result
import cv2
import numpy
img = cv2.imread("barack-obama.jpg")
lunette = cv2.imread("lunette.png", cv2.IMREAD_UNCHANGED)
eye_cascade = cv2.CascadeClassifier('haarcascade_eye.xml')
face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
eyes = eye_cascade.detectMultiScale(img, scaleFactor = 1.1, minNeighbors = 5)
r = 500.0 / img.shape[1]
dim = (500, int(img.shape[0] * r))
resized = cv2.resize(img, dim, interpolation = cv2.INTER_AREA)
grey = cv2.cvtColor(resized, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(grey, 1.3, 5)
for (x,y,w,h) in faces:
roi_grey = grey[y:y+h, x:x+w]
roi_color = resized[y:y + h, x:x + w]
eyes = eye_cascade.detectMultiScale(roi_grey)
for (ex,ey,ew,eh) in eyes:
cv2.rectangle(roi_color,(ex,ey),(ex+ew,ey+eh),(0,255,0),2)
lunette = cv2.resize(lunette , (w,h))
w, h, c = lunette.shape
for i in range(0, w):
for j in range(0, h):
if lunette[i, j][0] != 0:
resized[y + i, x + j] = lunette[i, j][1]
#help please
cv2.imshow('img',resized)
cv2.waitKey(0)
Here is one way to do that in Python/OpenCV/Numpy:
Read the background image (Obama)
Read the overlay image (glasses) unchanged
Extract the base BGR channels from the overlay image
Extract the alpha channel from the overlay image as a mask
Insert the BGR channels of the overlay into the background image at the desired location
Insert the mask into a black image the size of the background image at the desired location
Use Numpy where to composite the new background and overlay images using the new mask image
Save the result
import cv2
import numpy as np
# read background image
img = cv2.imread("obama.jpg")
ht, wd = img.shape[:2]
# read overlay image
img2 = cv2.imread("sunglasses.png", cv2.IMREAD_UNCHANGED)
ht2, wd2 = img2.shape[:2]
# extract alpha channel as mask and base bgr images
bgr = img2[:,:,0:3]
mask = img2[:,:,3]
# insert bgr into img at desired location and insert mask into black image
x = 580
y = 390
bgr_new = img.copy()
bgr_new[y:y+ht2, x:x+wd2] = bgr
mask_new = np.zeros((ht,wd), dtype=np.uint8)
mask_new[y:y+ht2, x:x+wd2] = mask
mask_new = cv2.cvtColor(mask_new, cv2.COLOR_GRAY2BGR)
# overlay the base bgr image onto img using mask
result = np.where(mask_new==255, bgr_new, img)
# save results
cv2.imwrite('obama_glasses.jpg', result)
# display results
cv2.imshow('bgr', bgr)
cv2.imshow('mask', mask)
cv2.imshow('bgr_new', bgr_new)
cv2.imshow('mask_new', mask_new)
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result:
You can use cvzone library for solve this problem.
install cvzone library
pip install cvzone
import cvzone in your project
import cvzone
imread your emoji with following format
lunette = cv2.imread("lunette.png", cv2.IMREAD_UNCHANGED)
now replace the your emoji like this:
YOUR BACKGROUND PICTURE = cvzone.overlayPNG(YOUR BACKGROUND PICTURE, lunette , [x, y])

Categories