OpenCV: Track co-ordinates of expanding rectangle - python

I have a calibration process in which I want to get the maximum height and width of the screen. So I made a growing rectangle of which I want co-ordinates by image processing. This rectangle will always be viewed at an angle.
I have used a coloured rectangle to detect it in HSV range but it doesn't seems to be working. First I detect the green coloured rectangle then threshold it then detect canny edges then find contours and filter the largest contour. Final contour with approximation is shown in 'img' tab in screenshot.
The problem here is the further edge of the green rectangle appears black and doesn't get detected in HSV range. So the maximum width and height is not obtained. Even if I have the top-left and bottom-right corner of the rectangle in edge detection.
Is there a way other to track scaling rectangle other than detecting HSV range of coloured rectangle. Since the rectangle is moving there should be.
CODE:
video = cv2.VideoCapture('rectCalibration2.mp4')
img = np.zeros((360,640,3))
prevImg = np.zeros((360,640,3))
finalContour = []
end_time = time.time() + 15
largestArea = 5000
while(video.isOpened()):
_, frame = video.read()
if frame is None:
print('ended')
break
frame = cv2.resize(frame, (640, 360))
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
greenLower = (55, 100, 6)
greenUpper = (70, 255, 255)
mask = cv2.inRange(hsv, greenLower, greenUpper)
th = cv2.adaptiveThreshold(mask,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY,11,2)
th = cv2.erode(th, None, iterations=3)
th = cv2.dilate(th, None, iterations=3)
edges = cv2.Canny(th,200,400)
m, contours, hierarchy =
cv2.findContours(edges,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
largeContours = []
for contour in contours:
area = cv2.contourArea(contour)
if area > largestArea:
largestArea = area
largeContours.append(contour)
finalContour = contour
img = np.zeros((360,640,3))
cv2.drawContours(img, largeContours, -1, (255, 255, 255))
prevImg = img
if (prevImg is not img):
end_time = time.time() + 15
if time.time() > end_time:
epsilon = 0.1 * cv2.arcLength(finalContour, True)
approx = cv2.approxPolyDP(finalContour, epsilon, True)
print(approx)
prevImg = np.zeros((360,640,3))
cv2.drawContours(prevImg,[approx],0,(255,255,255),2)
cv2.imshow('final', prevImg)
video.release()
break
cv2.imshow('frame',frame)
cv2.imshow('img', img)
cv2.imshow('edges', edges)
if cv2.waitKey(33) == ord('q'):
video.release()
break
Small Rectangle
Frame, Edges, Contours
The 'img' tab shows the finalContour which is not of the maximum width and height

Related

Using mask of image to find contours openCV

I have the task of find the contours of a red boundary drawn on a site location map. From the contours detected, I need to find the coordinates and save these to an array. I am able to filter for the red boundary and draw the contours, however I don't know how use this new image in my coordinate extraction. As a temporary solution I have screenshotted the mask generated, saved this, then in a new program have used this screenshot to find the coordinates. Is there a way to join all this code together?
This is the code for drawing the contours:
import cv2
img = cv2.imread(r'C:\Users\abbys\OneDrive\Pictures\agileapp\cornwall_cropped.png')
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# Gen lower mask (0-5) and upper mask (175-180) of RED
mask1 = cv2.inRange(img_hsv, (0,50,20), (5,255,255))
mask2 = cv2.inRange(img_hsv, (175,50,20), (180,255,255))
# Merge the mask and crop the red regions
mask = cv2.bitwise_or(mask1, mask2 )
cropped = cv2.bitwise_and(img, img, mask=mask)
## Display
cv2.imshow("mask", mask)
cv2.imshow("cropped", cropped)
cv2.waitKey()
This is the code used to extract the coordinates - the image I read in is a screen shot of the 'cropped' image from the above code
# Reading image
font = cv2.FONT_HERSHEY_COMPLEX
img2 = cv2.imread(r'C:\Users\abbys\OneDrive\Pictures\agileapp\cropped.png', cv2.IMREAD_COLOR)
# Reading same image in another
# variable and converting to gray scale.
img = cv2.imread(r'C:\Users\abbys\OneDrive\Pictures\agileapp\cropped.png', cv2.IMREAD_GRAYSCALE)
# Converting image to a binary image
# ( black and white only image).
_, threshold = cv2.threshold(img, 110, 255, cv2.THRESH_BINARY)
# Detecting contours in image.
contours, _= cv2.findContours(threshold, cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)
# Going through every contour found in the image.
for cnt in contours :
approx = cv2.approxPolyDP(cnt, 0.009 * cv2.arcLength(cnt, True), True)
# draws boundary of contours.
cv2.drawContours(img2, [approx], 0, (0, 0, 255), 5)
# Used to flatted the array containing
# the co-ordinates of the vertices.
n = approx.ravel()
i = 0
for j in n :
if(i % 2 == 0):
x = n[i]
y = n[i + 1]
# String containing the co-ordinates.
string = str(x) + " " + str(y)
if(i == 0):
# text on topmost co-ordinate.
cv2.putText(img2, "Arrow tip", (x, y),
font, 0.5, (255, 0, 0))
else:
# text on remaining co-ordinates.
cv2.putText(img2, string, (x, y),
font, 0.5, (0, 255, 0))
i = i + 1
# Showing the final image.
cv2.imshow('image2', img2)
# Exiting the window if 'q' is pressed on the keyboard.
if cv2.waitKey(0) & 0xFF == ord('q'):
cv2.destroyAllWindows()

Finding the coordinates of the edges on a rectangluar object

I am trying to build a document scanner application from scratch using OpenCV and python. Till now i have done the following:
re-scaled the image
preprocessed the image, that is converted to greyscale, applied the Gaussian blur, applied adaptive threshold and finally used canny edge detection.
I then found the largest contour and drew it
Detected the edges of the contour and drew them
step 4 is where the problem is, I'm getting two of the points in the correct location however two seem to be slightly offset.
I can't seem to understand what I'm doing wrong, additionally could this problem potentially be due to the way i have preprocessed the image?
import cv2
import numpy as np
# Function to resize the image
def Re_scaleImg(img):
scale_percent = 50
width = int(img.shape[1] * scale_percent / 100)
height = int(img.shape[0] * scale_percent / 100)
dim = (width, height)
# resize the image
resized = cv2.resize(img, dim, interpolation = cv2.INTER_AREA)
return resized
# Function to process the image
def process(img):
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (3,3), 0)
thresh = cv2.adaptiveThreshold(blur, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 2)
edged = cv2.Canny(thresh, 75, 200)
#cv2.imshow("blur", blur)
#cv2.imshow("edged", thresh)
return edged
# Function to find the areas of contours
def find_contourArea(contours):
areas = []
for cnt in contours:
cont_area = cv2.contourArea(cnt)
areas.append(cont_area)
return areas
image = cv2.imread("receipt.jpeg")
resized = Re_scaleImg(image)
processed_img = process(resized)
# finding the contours
contours, hierarchy = cv2.findContours(processed_img.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
resized_copy1 = resized.copy()
# sorting the contours
sorted_contours = sorted(contours, key=cv2.contourArea, reverse=True)
largest_contour = sorted_contours[0]
epsilon = 0.01*cv2.arcLength(largest_contour, True)
approximation = cv2.approxPolyDP(largest_contour, epsilon, True)
cv2.drawContours(resized_copy1, [approximation], -1, (0, 255, 0), 3)
# Obtaining the corners of the rectangle
rot_rect = cv2.minAreaRect(largest_contour)
box = cv2.boxPoints(rot_rect)
box = np.int0(box)
for p in box:
pt = (p[0], p[1])
cv2.circle(resized_copy1, pt, 10, (255, 0, 0), -1)
print(pt)
cv2.imshow("contours", resized_copy1)
cv2.waitKey(0)
Both the images are shown below:
the original image:
output image:
Instead of finding the full contour are you could try to find the lines on the edge of the document instead.
With the Hough Line Transform you can find the four most prominent lines (with the most votes).
From these lines you can then calculate the intersection points and use the four points closes to the center of the full shape as corner points.

How to get only water level contour with OpenCV python on raspberry pi

I am using raspberry pi4 (8GB) with pi camera to detect water level . I have defined a line from 0,375 to 800,375 . If top most point of water level contour goes above this line then I want to call a function. Here is my code and attached image of setup. How do I get water level contour only. Does it require canny edge detection over contours to get clear water level ? first I am getting largest contour and then defining its top most point.
import numpy as np
import cv2
import time
from datetime import datetime
#color=(255,0,0)
color=(0,255,0)
thickness=2
kernel = np.ones((2,2),np.uint8) # added 01/07/2021
picflag = 0 # set value to 1 once picture is taken
# function to take still picture when water level goes beyond threshold
def takepicture(frame):
currentTime = datetime.now()
picTime = currentTime.strftime("%d.%m.%Y-%H%M%S") # Create file name for our picture
text = currentTime.strftime("%d.%m.%Y-%H:%M:%S")
font = cv2.FONT_HERSHEY_SIMPLEX # font
org = (05, 20) # org
fontScale = 0.5 # fontScale
color = (0, 0, 255) # Red color in BGR
thickness = 1 # Line thickness of 2 px
picName = picTime + '.png'
image = cv2.putText(frame, text, org, font, fontScale, color, thickness, cv2.LINE_AA, False)
cv2.imwrite(picName , image)
picflag = 1
return
cap = cv2.VideoCapture(0)
while(True):
# Capture frame-by-frame
ret, frame = cap.read() # ret = 1 if the video is captured; frame is the image
# Our operations on the frame come here
gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
#blur = cv2.GaussianBlur(gray,(21,21),0)
gray= cv2.medianBlur(gray, 3) #to remove salt and paper noise
#ret,thresh = cv2.threshold(gray,10,20,cv2.THRESH_BINARY_INV)
ret,thresh = cv2.threshold(gray,127,127,cv2.THRESH_BINARY_INV)
thresh = cv2.morphologyEx(thresh, cv2.MORPH_GRADIENT, kernel) # get outer boundaries only added 01/07/2021
thresh = cv2.dilate(thresh,kernel,iterations = 5) # strengthen weak pixels added 01/07/2021
img1, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
#img1,contours,hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) #added 01/07/2021
cv2.line(frame, pt1=(0,375), pt2=(800,375), color=(0,0,255), thickness=2) # added 01/07/2021
if len(contours) != 0:
c = max(contours, key = cv2.contourArea) # find the largest contour
#x,y,w,h = cv2.boundingRect(c) # get bounding box of largest contour
img2=cv2.drawContours(frame, c, -1, color, thickness) # draw largest contour
#img2=cv2.drawContours(frame, contours, -1, color, thickness) # draw all contours
#img3 = cv2.rectangle(img2,(x,y),(x+w,y+h),(0,0,255),2) # draw red bounding box in img
#center = (x, y)
#print(center)
left = tuple(c[c[:, :, 0].argmin()][0])
right = tuple(c[c[:, :, 0].argmax()][0])
top = tuple(c[c[:, :, 1].argmin()][0])
bottom = tuple(c[c[:, :, 1].argmax()][0])
# Draw dots onto frame
cv2.drawContours(frame, [c], -1, (36, 255, 12), 2)
cv2.circle(frame, left, 8, (0, 50, 255), -1)
cv2.circle(frame, right, 8, (0, 255, 255), -1)
cv2.circle(frame, top, 8, (255, 50, 0), -1)
cv2.circle(frame, bottom, 8, (255, 255, 0), -1)
#print('left: {}'.format(left))
#print('right: {}'.format(right))
#print(format(top))
top_countour_point = top[1]
print(top_countour_point)
#print('bottom: {}'.format(bottom))
#if ((top_countour_point <= 375) and (picflag == 0)): #checking if contour top point is above line
#takepicture(frame)
#continue
#if ((top_countour_point > 375) and (picflag == 0)) :
#picflag = 0
#continue
# Display the resulting image
# cv2.line(frame, pt1=(0,375), pt2=(800,375), color=(0,0,255), thickness=2) # added 01/07/2021
#cv2.imshow('Contour',img3)
#cv2.imshow('thresh' ,thresh)
cv2.imshow('Contour',frame)
if cv2.waitKey(1) & 0xFF == ord('q'): # press q to quit
break
# When everything done, release the capture
cap.release()
cv2.destroyAllWindows()
Caveats
As it was pointed out in the comments, there is very little to work with based on your post. In general, I agree with s0mbre that you'd be better of with a water level sensor, and with kavko, that if you do want to use a camera, you need to better control your environment, with lighting, camera angles, background, etc.
However, that is not to say that it is not possible with your current setup, assuming, that it is a static setup except for the water level. As such, there are some assumptions that we can make.
Here are the steps that I took to get an approximate approach:
Gather image data
You have only provided one image with lines already on it, so that's not a lot to go on. What I did is that I removed the line that you added:
Fortunately there wasn't too much of a line left afterwards.
Image processing:
I have loaded the image (this would come from the code you have posted).
Using the assumptions above, I have decided to focus on a narrow slice of the image. I selected only the middle 60 pixels (1)
slc = frame[:, 300:360]
Next, I have converted it to greyscale (2)
gray_slc = cv2.cvtColor(slc, cv2.COLOR_BGR2GRAY)
I have used Canny edge detection (see docs here) to find the edges in the image (3)
edges = cv2.Canny(gray_slc, 50, 90)
After that, I have applied a Hough Transform, to find all the edges (related Stack Overflow answer) (4)
rho = 1
theta = np.pi / 180
threshold = 15
min_line_length = 50
max_line_gap = 20
lines = cv2.HoughLinesP(edges, rho, theta, threshold, np.array([]), min_line_length, max_line_gap)
Given that I can assume that all of the top lines are the edge of the container, I averaged the y coordinates of the lines, and picked the lower most one as the water level:
y_avgs = [(line[0, 1] + line[0, 3]) / 2 for line in lines]
water_level = max(y_avgs)
Having this, I just checked, if it is over the threshold you have selected:
trigger_threshold = 375
if water_level > trigger_threshold:
print("Water level is under the selected line")
Now, keep in mind, I only had the one image to go on. Considering lighting conditions, yours results may vary.

Detect different color blob opencv

I'm new to opencv and for a school project i need to detect a red and a green circle with a camera, so i've use blobdetection, but it detect me the two colors, i think that my mask is bad, each color is linked to a specific action.
At the moment my code detect well red and green circle on the same page but i want it to detect only red circle on a white page.
Thank for your help
# Standard imports
import cv2
import numpy as np;
# Read image
im = cv2.VideoCapture(0)
# Setup SimpleBlobDetector parameters.
params = cv2.SimpleBlobDetector_Params()
# Change thresholds
params.minThreshold = 100;
params.maxThreshold = 200;
# Filter by Area.
params.filterByArea = True
params.minArea = 200
params.maxArea = 20000
# Filter by Circularity
params.filterByCircularity = True
params.minCircularity = 0.1
# Filter by Convexity
params.filterByConvexity = True
params.minConvexity = 0.1
# Filter by Inertia
params.filterByInertia = True
params.minInertiaRatio = 0.1
blueLower = (0,85,170) #100,130,50
blueUpper = (140,110,255) #200,200,130
while(1):
ret, frame=im.read()
mask = cv2.inRange(frame, blueLower, blueUpper)
mask = cv2.erode(mask, None, iterations=0)
mask = cv2.dilate(mask, None, iterations=0)
frame = cv2.bitwise_and(frame,frame,mask = mask)
# Set up the detector with default parameters.
detector = cv2.SimpleBlobDetector_create(params)
# Detect blobs.
keypoints = detector.detect(mask)
# Draw detected blobs as red circles.
# cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS ensures the size of the circle corresponds to the size of blob
im_with_keypoints = cv2.drawKeypoints(mask, keypoints, np.array([]), (0,0,255), cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
# Display the resulting frame
frame = cv2.bitwise_and(frame,im_with_keypoints,mask = mask)
cv2.imshow('frame',frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# When everything done, release the capture
im.release()
cv2.destroyAllWindows()
EDIT 1: Code update
Now i got a issue where my full circle isn't detected.
No Blob Detection
Second Version
# Standard imports
import cv2
import numpy as np;
# Read image
im = cv2.VideoCapture(0)
while(1):
ret, frame=im.read()
lower = (130,150,80) #130,150,80
upper = (250,250,120) #250,250,120
mask = cv2.inRange(frame, lower, upper)
lower, contours, upper = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
blob = max(contours, key=lambda el: cv2.contourArea(el))
M = cv2.moments(blob)
center = (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"]))
canvas = im.copy()
cv2.circle(canvas, center, 2, (0,0,255), -1)
cv2.imshow('frame',frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
im.release()
cv2.destroyAllWindows()
You need to work out what the BGR numbers for your green are (let's say for arguments sake [0, 255, 0]), then create a mask that ignores any colours outside a tolerance around your green:
mask = cv2.inRange(image, lower, upper)
Take a look at this tutorial for a step by step.
Play around with lower and upper to get the right behaviour. Then you can find the contours in the mask:
_, contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_NONE)
Then go through the contours list to find the biggest one (filter out any possible noise):
blob = max(contours, key=lambda el: cv2.contourArea(el))
And that's your final 'blob'. You can find the center by doing:
M = cv2.moments(blob)
center = (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"]))
You can draw this center onto a copy of your image, for checking:
canvas = im.copy()
cv2.circle(canvas, center, 2, (0,0,255), -1)
Obviously, this makes the assumption that there's only one green ball and nothing else green in the image. But it's a start.
EDIT - RESPONSE TO SECOND POST
I think the following should work. I haven't tested it, but you should be able to at least do a bit more debugging with the canvas and mask displayed:
# Standard imports
import cv2
import numpy as np;
# Read image
cam = cv2.VideoCapture(0)
while(1):
ret, frame = cam.read()
if not ret:
break
canvas = frame.copy()
lower = (130,150,80) #130,150,80
upper = (250,250,120) #250,250,120
mask = cv2.inRange(frame, lower, upper)
try:
# NB: using _ as the variable name for two of the outputs, as they're not used
_, contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
blob = max(contours, key=lambda el: cv2.contourArea(el))
M = cv2.moments(blob)
center = (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"]))
cv2.circle(canvas, center, 2, (0,0,255), -1)
except (ValueError, ZeroDivisionError):
pass
cv2.imshow('frame',frame)
cv2.imshow('canvas',canvas)
cv2.imshow('mask',mask)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
im.release()
cv2.destroyAllWindows()
You should use HSV color space for better results if you wanna make filter by color.
ret, frame=im.read()
frame= cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) # Add this to your code
mask = cv2.inRange(frame, blueLower, blueUpper)

Trying to draw circle on Laser pointer, OpenCV

So i have taken the code from Github of #bradmontgomer and trying to modify it. The code first converts the frame into HSV color space, split the video frame into color channels and then Performs an AND on HSV components to identify the laser. I am having trouble in finding the contours of the detected laser point. heres my code;
def threshold_image(self, channel):
if channel == "hue":
minimum = self.hue_min
maximum = self.hue_max
elif channel == "saturation":
minimum = self.sat_min
maximum = self.sat_max
elif channel == "value":
minimum = self.val_min
maximum = self.val_max
(t, tmp) = cv2.threshold(
self.channels[channel], # src
maximum, # threshold value
0, # we dont care because of the selected type
cv2.THRESH_TOZERO_INV #t type
)
(t, self.channels[channel]) = cv2.threshold(
tmp, # src
minimum, # threshold value
255, # maxvalue
cv2.THRESH_BINARY # type
)
if channel == 'hue':
# only works for filtering red color because the range for the hue is split
self.channels['hue'] = cv2.bitwise_not(self.channels['hue'])
def detect(self, frame):
# resize the frame, blur it, and convert it to the HSV
# color space
frame = imutils.resize(frame, width=600)
hsv_img = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
# split the video frame into color channels
h, s, v = cv2.split(hsv_img)
self.channels['hue'] = h
self.channels['saturation'] = s
self.channels['value'] = v
# Threshold ranges of HSV components; storing the results in place
self.threshold_image("hue")
self.threshold_image("saturation")
self.threshold_image("value")
# Perform an AND on HSV components to identify the laser!
self.channels['laser'] = cv2.bitwise_and(
self.channels['hue'],
self.channels['value']
)
self.channels['laser'] = cv2.bitwise_and(
self.channels['saturation'],
self.channels['laser']
)
# Merge the HSV components back together.
hsv_image = cv2.merge([
self.channels['hue'],
self.channels['saturation'],
self.channels['value'],
])
thresh = cv2.threshold(self.channels['laser'], 25, 255, cv2.THRESH_BINARY)[1]
#find contours in the mask and initialize the current
#(x, y) center of the ball
#cnts = cv2.findContours(self.channels['laser'].copy(), cv2.RETR_EXTERNAL,
#cv2.CHAIN_APPROX_SIMPLE)
(_, cnts, _) = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
center = None
# only proceed if at least one contour was found
if len(cnts) > 0:
# find the largest contour in the mask, then use
# it to compute the minimum enclosing circle and
# centroid
c = max(cnts, key=cv2.contourArea)
((x, y), radius) = cv2.minEnclosingCircle(c)
M = cv2.moments(c)
center = (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"]))
# only proceed if the radius meets a minimum size
if radius > 10:
# draw the circle and centroid on the frame,
# then update the list of tracked points
cv2.circle(frame, (int(x), int(y)), int(radius),
(0, 255, 255), 2)
cv2.circle(frame, center, 5, (0, 0, 255), -1)
cv2.imshow('LaserPointer', self.channels['laser'])
################################################
return hsv_image
I am getting the cnts greater then 0 in line "if len(cnts) > 0:", but can't see a circle drawn in the laser pointer.
There was another function (display()) that was displaying laser frame (self.channel['laser']),
def display(self, img, frame):
"""Display the combined image and (optionally) all other image channels
NOTE: default color space in OpenCV is BGR.
"""
cv2.imshow('RGB_VideoFrame', frame)
cv2.imshow('LaserPointer', self.channels['laser'])
I commented out these cv2.iamshow lines from this function and then I was able to see circle around the laser pointer. This is because now frame from cv2.iamshow line inside function "detect(self, frame):" was executed. I then applied further codings on the pointer to detect its location.

Categories