How to create python list at every new object detected - python

I am working on python opencv project where I am detecting and tracking person's movement in the frame. I am drawing a line from where the person enters in the frame and keeps drawing where ever the person moves in the frame and then finally stops when the person moves out of the frame. Below is the code:
centroid_dict = dict()
centroid_list = []
object_id_list = []
"""
SOME CODE
"""
objects = tracker.update(rects)
for (objectID, bbox) in objects.items():
x1, y1, x2, y2 = bbox
x1 = int(x1)
y1 = int(y1)
x2 = int(x2)
y2 = int(y2)
cX = int((x1 + x2) / 2.0)
cY = int((y1 + y2) / 2.0)
cv2.circle(frame, (cX, cY), 4, (0, 255, 0), -1)
centroid_list.append((cX, cY))
centroid_dict[objectID] = centroid_list
if objectID not in object_id_list:
# This will run only once for the first time for every objectID
start_pt = (cX, cY)
end_pt = (cX, cY)
cv2.line(frame, start_pt, end_pt, (0, 255, 0), 2)
object_id_list.append(objectID)
first_time = False
else:
l = len(centroid_list)
for pt in range(len(centroid_dict[objectID])):
if not pt + 1 == l:
start_pt = (centroid_dict[objectID][pt][0], centroid_dict[objectID][pt][1])
end_pt = (centroid_dict[objectID][pt+1][0], centroid_dict[objectID][pt+1][1])
cv2.line(frame, start_pt, end_pt, (0, 255, 0), 2)
In the above code, I am getting objectID and the bounding box coordinates of the person from tracker.update(rects). Using the coordinates I am calculating the cX and cY which I use later to draw a line. I am appending all the cX, cY in centroid_list which I am later adding it in centroid_dict[objectID]. This is working fine when the person is tracked. After the first person, when 2nd person comes in, it draws line from where the first person moved out of the frame and the main reason is that in the code, I am not clearing the centroid_list anywhere due to which it keeps holding the first person cX and cY and the 2nd person's cX and cY also due to which the line is drawn in wrong way.
The only solution is that I need to create separate list for every objectID. So that cX and cY are not mixed. I am not able to find a suitable solution for this. Can anyone please help me here. Thanks
EDIT
I am inferencing over a test video file. Here are few of the images to understand the scenario better
In the above image, we can see from where the lady enters and where she moved out of the frame. I have also marked the start and end point in the image. Now look at the below image where the 2nd lady comes in:
In above image, you can see that the 2nd lady has just entered but still for her the starting point is where the 1st lady entered previously and all the coordinates and the line drawn is from the 1st lady's coordinates. This happened because in our code centroid_list still contains the coordinates of the first lady so it actually starting drawing the line from where the 1st lady enters and thus makes no sense. Hope this scenario is much clear now.

you do not need centroid_list you can just use centroid_dict
you have to define centroid_dict as collections.defaultdict before your for loop:
from collections import defaultdict
centroid_dict = defaultdict(list)
in your for loop:
centroid_dict[objectID].append((cX, cY))
to draw all the points just iterate on your centroid_dict.values()

Related

How to compare 2 list for object detection

I have made a function that detects a object on screen using opencv matchTemplate and returns its center locations as (x, y).
I want to compare the results of running the same function on 2 different objects to detect the location of one object in reference of the other. In my case the two objects are a player avatar and some bushes I want to know is the player currently standing near a bush or not.There is only one user avatar on screen therefore it only returns single (x, y) value for it but there are multiple bushes therefore multiple (x, y) values. I want a way to compare those two matrices. Sorry about the code format.
def center(base_img, needle_img, th, color):
result = cv.matchTemplate(BASE_IMG, needle_img, cv.TM_CCOEFF_NORMED)
threshold = th
yloc, xloc = np.where(result >= threshold)
w = needle_img.shape[1]
h = needle_img.shape[0]
rectangles = []
for (x, y) in zip(xloc, yloc):
rectangles.append([int(x), int(y), int(w), int(h)])
rectangles.append([int(x), int(y), int(w), int(h)])
rectangles, weights = cv.groupRectangles(rectangles, 1, 0.4)
points = []
for (x, y, w, h) in rectangles:
certre_x = x + int(w / 2)
certre_y = y + int(h / 2)
cv.drawMarker(base_img, (certre_x, certre_y), color, cv.MARKER_DIAMOND)
points.append((certre_x, certre_y))
# cv.imshow("result", base_img)
# print(points)
# return points
You can loop through the center of the bushes and get the distance to each bush sqrt(( x2-x1)**2 + ( y2-y1)**2) or you could use nearest neighbor and numpy.
Post a code snippet if it worked.

Unable to draw lines using OpenCV

I have created a per_frame function that is fed into ImageAI's detector. I want to draw a line between centroid that meet the distance criteria (if distance >= find_dist_ratio(x1, y1)). Lines should be drawn between the centroids of all objects that meet the criteria, I tried changing it and finally got it without errors but the line does not show up in the output video. Thanks for the help!
def dist_func(counting, output_objects_array,output_objects_count):
a =[]
ret, frame = camera.read()
for d in output_objects_array:
x1 = d['box_points'][0]
y1 = d['box_points'][1]
x2 = d['box_points'][2]
y2 = d['box_points'][3]
centroid = (int((x1 + x2) / 2), int((y1 + y2) / 2))
a.append(centroid)
for i in range(len(a)):
for j in range(i+1, len(a)):
distance = euc_dist(a[i],a[j])
if distance >= find_dist_ratio(x1, y1):
print('close enough')
x, y = a[i]
X, Y = a[j]
cv2.line(frame, (x, y), (X, Y), (255, 0, 0), 5)
It may sound silly, but in your piece of code I can't see if you are really showing the frame. And if the x and y variables are correct (from lower/upper case)
See this example from the docs:
# Create a black image
img = np.zeros((512,512,3), np.uint8)
# Draw a diagonal blue line with thickness of 5 px
cv.line(img,(0,0),(511,511),(255,0,0),5)
For displaying the line drawn here you should also place (after drawing)
cv2.imshow("Line draw", img)
Drawing functions in the docs

How to make objects move in pygame, without making the rendering slow?

I am currently designing an app, using pygame in which I have a number of circles connected through lines, with numerical text written in them. These circles are green, blue and red in color, while the other things are black. Background is white. (Imagine it like a network graph)
My Objective: I am trying to get an animation running, in which the user selects the two circles (let us call them nodes) and I find out the shortest path between the sender node (green) to the receiver node (red). So in this animation, I am making another moving circle on top of the line (or edge) that connects the two adjacent nodes (these may be the intermediate nodes).
So far all good, here's the code of what I am doing:
def runPathAnimation(path, colortype):
for i in range(len(path)-1):
#Calculation of the center of the nodes
x1, y1 = (gmd[path[i]].getNodePosition())[0], (gmd[path[i]].getNodePosition())[1]
x2, y2 = (gmd[path[i+1]].getNodePosition())[0], (gmd[path[i+1]].getNodePosition())[1]
#Get the slope
m = (y1-y2)/(x1-x2) if x1 != x2 else 'undefined'
if str(m) != 'undefined':
c = y2-(m*x2)
if m > 0.5 or (m <= -1 and m >= -1.5):
for y in range(min(y1,y2),max(y1,y2)):
#using the equation of the line
x = int((y-c)/m)
#redrawEverything(path) #OPTION 1
#TRY REDRAW LINE #TODO
pyg.draw.rect(screen, (255, 255, 255), (x-10,y-10,20,20)) #OPTION 2
pyg.draw.circle(screen, colortype, (x,y), 10) #Moving circle
pyg.display.update() #Update Display
#NEED: Redraw!
#The logic repeats....
else:
for x in range(min(x1,x2),max(x1,x2)):
y = int(m*x+c)
#redrawEverything(path)
#TRY REDRAW LINE
pyg.draw.rect(screen, (255, 255, 255), (x-10,y-10,20,20))
pyg.draw.circle(screen, colortype, (x,y), 10)
pyg.display.update()
#NEED: Redraw!
else:
cy = range(min(y1,y2),max(y1,y2))
if y1 > y2:
cy = reversed(cy)
for y in cy:
#redrawEverything(path)
#TRY REDRAW LINE
pyg.draw.rect(screen, (255, 255, 255), (x1-10,y-10,20,20))
pyg.draw.circle(screen, colortype, (x1,y), 10)
pyg.display.update()
#NEED: Redraw!
My Problem: There is a lot of lag with my method of simply updating a circle with another position, without disturbing anything that it covers. I had 2 options in my mind:
OPTION 1: Update everything on the screen (of course it did not give me a good performance)
OPTION 2: Update only the portion of the screen, which is what actually used. However, even with this method, I am not able to achieve a good performance for screen updation. I would like to later add a feature to control the speed of the animation, which may have a speed faster than the maximum performance of my code right now!
As you can see, I do not have any time.sleep() as of now. I would like to increase the performance of my code and then be able to add time.sleep() for a more controlled animation. My current pygame application is already running in parallel to another process, which I implemented using multiprocessing library.
Question: How do I make it faster?
My python version: 3.7.0, pygame version: 1.9.6
PS: Sorry for the length of the question
Try using
pygame.time.Clock().tick(**)
This is a command that allows you to choose the FPS you want to run your program with, allowing you to increase your rendering speed. If you decide to use this, put an integer that represents the FPS where I wrote the asterisks.
So, I found a workaround! Basically, I am unable a make the code o any faster due to pygame's own rendering abilities, even HW mode isn't improving the speed much.
Solution (more of a workaround):
I have added a layer of waiting period in which pygame takes snapshots of the rendered screen and stores the image in a self created cache, without updating the screen. Later, I just have a smooth operable screen which can be used to see the animation.
Here's the code:
def runPathAnimation(path, colortype):
index = 0
images = []
for i in range(len(path)-1):
x1, y1 = (gmd[path[i]].getNodePosition())[0], (gmd[path[i]].getNodePosition())[1]
x2, y2 = (gmd[path[i+1]].getNodePosition())[0], (gmd[path[i+1]].getNodePosition())[1]
m = (y1-y2)/(x1-x2) if x1 != x2 else 'undefined'
cx, cy = range(min(x1,x2),max(x1,x2)), range(min(y1,y2),max(y1,y2))
if y1 > y2:
cy = reversed(cy)
if x1 > x2:
cx = reversed(cx)
if str(m) != 'undefined':
con = y2-(m*x2)
if m > 0.5 or (m <= -1 and m >= -1.5):
for y in cy:
ev = pyg.event.get()
x = int((y-con)/m)
images.append(loadpath(x,y,path,colortype,index))
index += 1
r = pyg.draw.rect(screen, colortype, (md.WIDTH_NETWORKPLOT-250,md.PLOT_AREA[1]+30,index/5,20), 2)
pyg.display.update(r)
else:
for x in cx:
ev = pyg.event.get()
y = int(m*x+con)
images.append(loadpath(x,y,path,colortype,index))
index += 1
r = pyg.draw.rect(screen, colortype, (md.WIDTH_NETWORKPLOT-250,md.PLOT_AREA[1]+30,index/5,20), 2)
pyg.display.update(r)
else:
for y in cy:
ev = pyg.event.get()
images.append(loadpath(x1,y,path,colortype,index))
index += 1
r = pyg.draw.rect(screen, colortype, (md.WIDTH_NETWORKPLOT-250,md.PLOT_AREA[1]+30,index/5,20), 2)
pyg.display.update(r)
print('Loading...'+str((i+1)/len(path)*100)+'%')
runAnimation(images)
def runAnimation(images):
animate = True
img = 0
print('Start!')
while animate:
ev = pyg.event.get()
pyg.event.pump()
keys = pyg.key.get_pressed()
if keys[pyg.K_LEFT]:
img -= 1
if img < 0:
img = 0
if keys[pyg.K_RIGHT]:
img += 1
if img >= len(images) - 2:
img = len(images) - 2
if keys[pyg.K_q]:
animate = False
screen.blit(images[img],(0,0))
pyg.display.update((0, 0, md.WIDTH_NETWORKPLOT, md.PLOT_AREA[1]))
PS: In my code, md.xxx are the dimensions for my matplotlib and pygame screen.
IMPORTANT: This is just a workaround, not a solution!!

Does anybody know how to get the X and Y of a drawn circle?

I can't figure out how to get the X and Y coordinates of a drawn circle
(example: pygame.draw.circle(Surface, color, pos(x,y), radius, width=0))
How could I get the X and y and use it in possibly making another circle go towards those codinates? if anybody knows how it would help a lot...
I am a bit baffled tbh.
import pygame
x, y = 100, 100
r = 50
black = (0, 0, 0)
red = (255, 0, 0)
(... screen stuff ...)
pygame.draw.circle(screen, black, (x,y), r)
pygame.draw.circle(screen, red, (x,y), r / 2)
You should already have the coordinates somewhere in your program. If you want to re-use them, you're going to have to store the coordinates into variables, then you can mess around with the coordinates. So just store the coordinates and draw a second one from there. Hope this helps.

Python - Find center of object in an image

I have an image file that's has a white background with a non-white object.
I want to find the center of the object using python (Pillow).
I have found a similar question in c++ but no acceptable answer - How can I find center of object?
Similar question, but with broken links in answer - What is the fastest way to find the center of an irregularly shaped polygon? (broken links in answer)
I also read this page but it doesn't give me a useful recipe - https://en.wikipedia.org/wiki/Smallest-circle_problem
Here's an example image:
Edit:
The current solution I'm using is this:
def find_center(image_file):
img = Image.open(image_file)
img_mtx = img.load()
top = bottom = 0
first_row = True
# First we find the top and bottom border of the object
for row in range(img.size[0]):
for col in range(img.size[1]):
if img_mtx[row, col][0:3] != (255, 255, 255):
bottom = row
if first_row:
top = row
first_row = False
middle_row = (top + bottom) / 2 # Calculate the middle row of the object
left = right = 0
first_col = True
# Scan through the middle row and find the left and right border
for col in range(img.size[1]):
if img_mtx[middle_row, col][0:3] != (255, 255, 255):
left = col
if first_col:
right = col
first_col = False
middle_col = (left + right) / 2 # Calculate the middle col of the object
return (middle_row, middle_col)
If you define center as Center of Mass, then it is not difficult, although the CoM can be outside of your shape. You can interpret your image as a 2D distribution, and you can find its expected value (CoM) using integration (summation).
If you have numpy it is quite simple. First create a numpy array containing 1 where your image is non-white, then to make it a probability distribution divide it by the total number of ones.
from PIL import Image
import numpy as np
im = Image.open('image.bmp')
immat = im.load()
(X, Y) = im.size
m = np.zeros((X, Y))
for x in range(X):
for y in range(Y):
m[x, y] = immat[(x, y)] != (255, 255, 255)
m = m / np.sum(np.sum(m))
From this point on it turns into basic probability theory. You find the marginal distributions, then you calculate the expected values as if it was a discrete probability distribution.
# marginal distributions
dx = np.sum(m, 1)
dy = np.sum(m, 0)
# expected values
cx = np.sum(dx * np.arange(X))
cy = np.sum(dy * np.arange(Y))
(cx, cy) is the CoM you are looking for.
Remarks:
If you do not have numpy, you can still do it. It is just a bit more tedious as you have to do the summations by loops / comprehensions.
This method can easily be extended if you want to assign a 'mass' based on color. You just have to change m[x, y] = immat[(x, y)] != (255, 255, 255) to m[x, y] = f(immat[(x, y)]) where f is an arbitary (non-negative valued) function.
If you want to avoid the double loop, you can us np.asarray(im), but then be careful with the indices
No loops:
m = np.sum(np.asarray(im), -1) < 255*3
m = m / np.sum(np.sum(m))
dx = np.sum(m, 0) # there is a 0 here instead of the 1
dy = np.sum(m, 1) # as np.asarray switches the axes, because
# in matrices the vertical axis is the main
# one, while in images the horizontal one is
# the first
I would try and find a way to draw a triangle around it, with one point of the triangle at the farthest "points" on the object, and then find the center of that triangle.

Categories