Multi-object tracking initialization in Opencv using multiTracker object - python-3.x

I am using multiTracker in cv2 to track multiple objects. My code is built based on this link and this one. I want to initialize all the bounding boxes at once at any point during the video. However, I have trouble doing this. Here is the code:
import imutils
import cv2
from random import randint
trackerName = 'csrt'
videoPath = "C:/Users/Art/testVideo.mp4"
OPENCV_OBJECT_TRACKERS = {
"csrt": cv2.TrackerCSRT_create,
"kcf": cv2.TrackerKCF_create,
"boosting": cv2.TrackerBoosting_create,
"mil": cv2.TrackerMIL_create,
"tld": cv2.TrackerTLD_create,
"medianflow": cv2.TrackerMedianFlow_create,
"mosse": cv2.TrackerMOSSE_create
}
trackers = cv2.MultiTracker_create()
cap = cv2.VideoCapture(videoPath)
while cap.isOpened():
ret, frame = cap.read()
if frame is None:
break
# for fast processing resize the frame
frame = imutils.resize(frame, width=600)
(success, boxes) = trackers.update(frame)
for box in boxes:
(x, y, w, h) = [int(v) for v in box]
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv2.imshow("Frame", frame)
key = cv2.waitKey(1) & 0xFF
# if the 'p' key is selected, we select a bounding box to track
if key == ord("p"):
boxes = []
colors = []
boxFlag = True
while boxFlag:
box = cv2.selectROI('MultiTracker', frame, fromCenter=False,
showCrosshair=True)
boxes.append(box)
colors.append((randint(64, 255), randint(64, 255), randint(64,
255)))
print("Press q to quit selecting boxes and start tracking")
print("Press any other key to select next object")
if key == ord("q"): # q is pressed
boxFlag = False
# Initialize MultiTracker
for box in boxes:
tracker = OPENCV_OBJECT_TRACKERS[trackerName]()
trackers.add(tracker, frame, box)
cap.release()
cv2.destroyAllWindows()
However, there are some problems. First, when I click on 'p' key to select bounding boxes, the video pauses and another window opens that shows the frame at which video was paused and I can select bounding boxes on the new window only. Also, when I press 'q' key, nothing will happen and basically it stays there forever. My question is how I can fix this problem, and be able to initialize tracking after I select all of the bounding boxes.

I figured it out and thought this might be useful for someone else. I made some changes to the previous code. I also realized that cv2 has selectROIs method which can be useful if someone wants to select multiple bounding boxes at once. Here is the updated code:
import imutils
import cv2
from random import randint
trackerName = 'csrt'
videoPath = "C:/Users/Art/testVideo.mp4"
OPENCV_OBJECT_TRACKERS = {
"csrt": cv2.TrackerCSRT_create,
"kcf": cv2.TrackerKCF_create,
"boosting": cv2.TrackerBoosting_create,
"mil": cv2.TrackerMIL_create,
"tld": cv2.TrackerTLD_create,
"medianflow": cv2.TrackerMedianFlow_create,
"mosse": cv2.TrackerMOSSE_create
}
# initialize OpenCV's special multi-object tracker
trackers = cv2.MultiTracker_create()
cap = cv2.VideoCapture(videoPath)
while cap.isOpened():
ret, frame = cap.read()
if frame is None:
break
frame = imutils.resize(frame, width=600)
(success, boxes) = trackers.update(frame)
# loop over the bounding boxes and draw them on the frame
for box in boxes:
(x, y, w, h) = [int(v) for v in box]
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv2.imshow("Frame", frame)
key = cv2.waitKey(1) & 0xFF
# if the 's' key is selected, we are going to "select" a bounding
# box to track
if key == ord("s"):
colors = []
# select the bounding box of the object we want to track (make
# sure you press ENTER or SPACE after selecting the ROI)
box = cv2.selectROIs("Frame", frame, fromCenter=False,
showCrosshair=True)
box = tuple(map(tuple, box))
for bb in box:
tracker = OPENCV_OBJECT_TRACKERS[trackerName]()
trackers.add(tracker, frame, bb)
# if you want to reset bounding box, select the 'r' key
elif key == ord("r"):
trackers.clear()
trackers = cv2.MultiTracker_create()
box = cv2.selectROIs("Frame", frame, fromCenter=False,
showCrosshair=True)
box = tuple(map(tuple, box))
for bb in box:
tracker = OPENCV_OBJECT_TRACKERS[trackerName]()
trackers.add(tracker, frame, bb)
elif key == ord("q"):
break
cap.release()
cv2.destroyAllWindows()
After selecting each bounding box, you need to click on "Enter" or "Space" button to finalize it and start selecting a new bounding box. Once you are done with bounding box selection, press "Esc" key to end ROI selection and start tracking. Also, if you need to reset bounding boxes for any reason, press "r" button.

Maybe You should try the "cv2.selectROIs" function before the "while cap.isOpened():" loop, without loop, because it looks like "cv2.selectROIs" function works as loop itself, while you are selecting ROIs.

Related

How to resize a Graph element after window resize in PySimpleGUI

I have a GUI in Python using PySimpleGUI with a Graph element (drawing canvas) on it. I want the user to be able to resize the window. The drawing (Graph element) should resize along with the window (as well as all the drawing elements on the Graph element).
I tried the following code, but the resizing doesn't work. Either the circle remains the same size in pixels, regardless of the window size (current code) or the circle does resize, but resizing the Graph element triggers a Window resize event by itself (uncomment line 13 with the graph.set_size() method)
Question: how to resize the Graph element together with the Window, and also resize the elements on the Graph canvas. In the example code: I want the circle to resize so that it fits inside the window.
import PySimpleGUI as sg
INIT_SIZE_PX = (100, 100)
COORD_SYSTEM = [(0, 0), (2, 2)]
CIRCLE_CENTER = (1, 1)
CIRCLE_RADIUS = 1
CIRCLE_COLOR = 'red'
def draw_graph(graph, size):
graph.erase()
# graph.set_size(size) # NOT the way, triggers resize events on its own
# graph.change_coordinates(*COORD_SYSTEM) # Not necessary
graph.draw_circle(CIRCLE_CENTER, CIRCLE_RADIUS, fill_color=CIRCLE_COLOR)
layout = [[
sg.Graph(INIT_SIZE_PX, *COORD_SYSTEM, key='graph',
expand_x=True, expand_y=True)
]]
window = sg.Window('Window title', layout=layout,
finalize=True, resizable=True)
window.bind('<Configure>', "Resize_Event")
draw_graph(window['graph'], window.size)
while True:
event, values = window.read()
if event == sg.WIN_CLOSED or event == 'Exit':
break
elif event == "Resize_Event":
new_size = (int(window.size[0] * 0.9), int(window.size[1] * 0.9))
draw_graph(window['graph'], new_size)
print(f"Resized to: {window.size=}; {window['graph'].Size=}")
window.close()
Here, only the coordinate changed for all the items on Graph element, all other features kept the same, like line width, image, text.
If more detail update for items on Graph element required, maybe you need to write the code for graph.widget.scale method.
from random import randint
import PySimpleGUI as sg
def random_color():
return '#%02X%02X%02X' % (randint(0,255), randint(0,255), randint(0,255))
size = (640, 480)
layout = [
[sg.Graph(size, (0, 0), size, background_color='green', expand_x=True, expand_y=True, pad=(0, 0), key='Graph')],
[sg.Push(), sg.Button('Plot')],
]
window = sg.Window('Title', layout, resizable=True, margins=(0, 0), finalize=True)
graph, plot = window['Graph'], window['Plot']
## Change the pack order of Graph (row frame) to laste one.
graph_row_frame_pack_info = graph.widget.master.pack_info()
plot_row_frame_pack_info = plot.widget.master.pack_info()
plot_row_frame_pack_info['side'] = 'bottom'
plot.widget.master.pack(**plot_row_frame_pack_info)
graph.widget.master.pack(**graph_row_frame_pack_info)
graph.bind('<Configure>', ' Configure')
while True:
event, values = window.read()
if event == sg.WIN_CLOSED:
break
elif event == 'Graph Configure':
e = graph.user_bind_event
w0, h0 = graph.CanvasSize
# Update the canvas size for coordinate conversion
w1, h1 = graph.CanvasSize = e.width, e.height
w_scale, h_scale = w1/w0, h1/h0
graph.widget.scale("all", 0, 0, w_scale, h_scale)
elif event == 'Plot':
graph.erase()
graph.draw_rectangle((100, 100), (540, 380), fill_color=random_color())
graph.draw_arc((100, 380), (540, 430), 180, 180, fill_color=random_color())
graph.draw_circle((320, 240), 140, fill_color=random_color())
graph.draw_image(data=sg.EMOJI_BASE64_HAPPY_LAUGH, location=(100, 380))
graph.draw_text('Hello World', location=(320, 240))
window.close()

How can I display multiple windows concatenated with cv2 in array form?

I am capturing the frames in real time from various cameras. The output of each camera is displayed in a window with cv2.imshow. What I want to achieve now is that all the windows are concatenated forming a grid/matrix, for example, if I have 4 cameras, that the windows show me concatenated in 2x2.
Below I attach the code of what I have. Which allows me to capture the frames of the different cameras that I have, but I am not being able to do the above.
cap = []
ret = []
frame = []
final = ""
i = 0
cap.append(cv2.VideoCapture(0))
cap.append(cv2.VideoCapture(1))
cap.append(cv2.VideoCapture(2))
number_cameras = len(cap)
# I initialize values
for x in range(number_cameras):
ret.append(x)
frame.append(x)
while(True):
# I capture frame by frame from each camera
ret[i], frame[i] = cap[i].read()
if i == number_cameras-1:
final = cv2.hconcat([cv2.resize(frame[x], (400, 400)) for x in range(number_cameras)])
cv2.namedWindow('frame')
cv2.moveWindow('frame', 0, 0)
# I show the concatenated outputs
cv2.imshow('frame', final)
i = 0
else:
i = i + 1
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# I release the cameras
for x in range(number_cameras):
cap[x].release()
cv2.destroyAllWindows()
You can construct a frame out of 4 frames by resizing the frames to half their width and height. Then create an empty frame and fill it according to the resized width and height.
Since I don't currently have 4 cameras I simulating this using a single camera.
import numpy as np
import cv2
cap = cv2.VideoCapture(0)
while(True):
ret, frame = cap.read()
#find half of the width
w = int(frame.shape[1] * 50 / 100)
#find half of the height
h = int(frame.shape[0] * 50 / 100)
dim = (w, h)
#resize the frame to half it size
frame =cv2.resize(frame, dim, interpolation = cv2.INTER_AREA)
#create an empty frame
output = np.zeros((h*2,w*2,3), np.uint8)
#place a frame at the top left
output[0:h, 0:w] = frame
#place a frame at the top right
output[0:h, w:w * 2] = frame
#place a frame at the bottom left
output[h:h * 2, w:w * 2] = frame
#place a frame at the bottom right
output[h:h * 2, 0:w] = frame
cv2.imshow("Output", output)
key = cv2.waitKey(1) & 0xFF
# if the `q` key was pressed, break from the loop
if key == ord("q"):
break
cv2.destroyAllWindows()

How to count objects in video using openCV and python?

I want to count vehicles when the rectangle touches(intersects) the line. I don't know more about counting objects in video frame. Based on my pre-search, I have found out some useful information such as centroid, euclidean distance and etc. But, i don't have much knowledge about the geometry math behind it. Here is an example video. https://www.youtube.com/watch?v=z1Cvn3_4yGo. I think they used the same method.
I have got the center of the rectangle and drawn a red line across the road. Now I want to count vehicles when it touches the line. Bellow, I have uploaded my current output of the system in a image format as well as my current code.
# import the necessary packages
from imutils.video import VideoStream
from imutils.video import FPS
import argparse
import imutils
import time
import cv2
tracker = cv2.TrackerCSRT_create()
vs = cv2.VideoCapture("Video.mp4")
initBB = None
# loop over frames from the video stream
while vs.isOpened():
ret,frame = vs.read()
cv2.line(frame, (933 , 462), (1170 , 462), (0,0,255), 3)
# check to see if we are currently tracking an object
if initBB is not None:
# grab the new bounding box coordinates of the object
(success, box) = tracker.update(frame)
# check to see if the tracking was a success
if success:
(x, y, w, h) = [int(v) for v in box]
cv2.rectangle(frame, (x, y), (x + w, y + h),
(0, 255, 0), 2)
cX = int((x + x+w) / 2.0)
cY = int((y + y+h) / 2.0)
cv2.circle(frame, (cX, cY), 4, (255, 0, 0), -1)
# show the output frame
cv2.imshow("Frame", frame)
key = cv2.waitKey(1) & 0xFF
if key == ord("s"):
# select the bounding box of the object we want to track (make
# sure you press ENTER or SPACE after selecting the ROI)
initBB = cv2.selectROI("Frame", frame, fromCenter=False,
showCrosshair=True)
# start OpenCV object tracker using the supplied bounding box
# coordinates, then start the FPS throughput estimator as well
tracker.init(frame, initBB)
fps = FPS().start()
# if the `q` key was pressed, break from the loop
elif key == ord("q"):
break
else:
vs.release()
cv2.destroyAllWindows()
You should just check whether the lower two x axis points of your bonding box are greater than or equal to the x axis points of the line

Opencv3 - How to give ID to the people I count based on contours and rectrangles?

I want to give ID to the contourareas that I draw rectangle on them. Now my code tracks moving object on the screen and put a rectangle around them. I want to give an ID to the each of rectangles. I know how to count how many rectangles on the screen but I don't know how to give rectangles an exact ID that doesn't change when another rectangle joins the screen.
The code I use to draw rectangles:
video_path = 'C:\\Users\\MONSTER\\Desktop\\video.avi'
cv2.ocl.setUseOpenCL(False)
version = cv2.__version__.split('.')[0]
print(version)
#read video file
cap = cv2.VideoCapture(video_path)
#check opencv version
if version == '2' :
fgbg = cv2.BackgroundSubtractorMOG2()
if version == '3':
fgbg = cv2.createBackgroundSubtractorMOG2()
while (cap.isOpened):
#if ret is true than no error with cap.isOpened
ret, frame = cap.read()
if ret==True:
#apply background substraction
fgmask = fgbg.apply(frame)
ret1,th1 = cv2.threshold(fgmask,150,200,cv2.THRESH_BINARY)
#check opencv version
if version == '2' :
(contours, hierarchy) = cv2.findContours(th1.copy(),
cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
if version == '3' :
(im2, contours, hierarchy) = cv2.findContours(th1.copy(),
cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
for c in contours:
if cv2.contourArea(c) < 200:
continue
#get bounding box from countour
(x, y, w, h) = cv2.boundingRect(c)
#draw bounding box
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv2.imshow('foreground and background',th1)
cv2.imshow('rgb',frame)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cap.release()
cv2.destroyAllWindows()
And I changed the code above to the code below to put text on rectangles but the text changes when another rectangle joins.
i = 1
for c in contours:
if cv2.contourArea(c) < 200:
continue
#get bounding box from countour
(x, y, w, h) = cv2.boundingRect(c)
#draw bounding box
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
i = i + 1
font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(frame,str(i),(x,y), font, 2,(255,255,255),2,cv2.LINE_AA)
Do you have any idea to give rectangles exact ID's.
Your code does not do the tracking. only the detection from the background only. To make your code natively do the tracking, I am afraid it's not that simple. tracking will only happen if you have a perfect connected object and there is only 1 object. It usually rarely happens as fail detection happens all the time. Thus multiple RECT are created.
The ID will keep changes for different frames when you enter the scene and there are multiple detections like the image below. I tried it before. Each time the bounding rect will change from object to object. Especially when you use a simple method like bgfg, this lost track or lost id happens almost every frame.
The proper way is to use real tracking algorithm to constantly update the object. e.g
https://www.youtube.com/watch?v=qvcyK4ZMKbM
The input to TLD tracker is from the Rect obtained by
(x, y, w, h) = cv2.boundingRect(c)
The source code is in the github. feel free to test it
https://github.com/gnebehay/TLD
Follow the installation to get it up and integrate into your current detection routing.
https://github.com/zk00006/OpenTLD/wiki/Installation
You need to track multiple objects.
You need to check their intersections and stop tracking when they collide.
https://www.pyimagesearch.com/2018/08/06/tracking-multiple-objects-with-opencv/

Pixel Co-Ordinate Logging for an imported Image (Python)

Okay so, I want to be able to record the co-ordinates of pixels within a given image. The image is used to calibrate and determine the lens distortion and iFoV (Field of View) of a camera at varying distances.
I am trying to code (in Python) a program that will allow me to open up an initial image within a directory and allow me to pinpoint the pixel co-ordinates of the following areas;
TOP LEFT, TOP RIGHT, TOP MIDDLE, MID RIGHT, MID LEFT, BOT LEFT, BOT MID and BOT RIGHT
So basically, i need to open the image click those 8 points (recording the points in a csv file) then press a key e.g. ''SPACEBAR'' or ''ESC'' etc. which brings up the next image in the directory (Tool at a new length). Being able to repeat for many images.
Example Calibration Tool - Red Dots are the Wanted Co-Ordinates:
I have written a mock tool that addresses your purpose.
Pass the appropriate path conatining image in filepath.
Press and release left mouse button to see the marking.
Hit spacebar to clear markings on current image (in case you make a mistake).
Press lower case 'q' to move to next image.
Code:
import cv2
import numpy as np
import glob
ix, iy = -1, -1
# mouse callback function
def draw_circle(event, x, y, flags, param):
global ix, iy, mode
if event == cv2.EVENT_LBUTTONDOWN:
ix, iy = x, y
elif event == cv2.EVENT_LBUTTONUP:
cv2.circle(img, (x, y), 5, (0, 0, 255), -1)
print(x, y)
cv2.namedWindow('image')
cv2.setMouseCallback('image', draw_circle)
filepath = r'C:\Users\Jackson\Desktop\*.png'
for file in glob.glob(filepath):
img = cv2.imread(file)
img2 = img.copy()
while(1):
cv2.imshow('image', img)
k = cv2.waitKey(1) & 0xFF
if k == 32: #--- Press spacebar to clear markings on current image ---
img = img2.copy()
elif k == ord('q'): #--- Press lowercase q to move to next image ---
break
cv2.destroyAllWindows()

Resources