Skip to content

Commit b444697

Browse files
committed
meanshift, camshift & optical flow
1 parent cd7d6cc commit b444697

File tree

7 files changed

+335
-4
lines changed

7 files changed

+335
-4
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
1+
resources
22
recordings
33
.idea

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# OpenCV2 Object Tracking
2+
3+
## Meanshift Algorithm
4+
5+
[OpenCV Meanshift Algorithm for Object Tracking](https://mpolinowski.github.io/devnotes/2021-12-08--opencv-meanshift-tracking)
6+
7+
## CAMshift Algorithm
8+
9+
[OpenCV CAMshift Algorithm for Object Tracking](https://mpolinowski.github.io/devnotes/2021-12-09--opencv-camshift-tracking)
10+
11+
## Optical Flow Algorithm (Sparse & Dense)
12+
13+
[OpenCV Optical Flow Algorithm for Object Tracking](https://mpolinowski.github.io/devnotes/2021-12-10--opencv-optical-flow-tracking)

scripts/camshift_tracking.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Usage
2+
# python scripts/meanshift_tracking.py -u 'rtsp://admin:instar@192.168.2.19/livestream/13'
3+
import numpy as np
4+
import cv2
5+
#from matplotlib import pyplot as plt
6+
import argparse
7+
# from imutils import resize
8+
from imutils.video import VideoStream
9+
import time
10+
11+
# Parse the arguments
12+
ap = argparse.ArgumentParser()
13+
ap.add_argument("-u", "--url", help="RTSP streaming URL", default="rtsp://admin:instar@192.168.2.19/livestream/13")
14+
args = vars(ap.parse_args())
15+
16+
# get video stream from IP camera
17+
print("[INFO] starting video stream")
18+
vs = VideoStream(args["url"]).start()
19+
20+
# first frame from stream
21+
frame = vs.read()
22+
# optional - resize image if source too high res
23+
# frame = resize(frame, width=1280)
24+
# select region of interest
25+
bbox = cv2.selectROI(frame)
26+
x, y, w, h = bbox
27+
track_window = (x, y, w, h)
28+
# define area of bounding box as area of interest
29+
roi = frame[y:y+h, x:x+w]
30+
# convert frame to HSV colour space
31+
hsv_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)
32+
# get histogram for [0] blue, [1] green, [2] red channel
33+
# https://docs.opencv.org/4.x/d1/db7/tutorial_py_histogram_begins.html
34+
roi_hist = cv2.calcHist([hsv_roi], [0], None, [180], [0, 180])
35+
# convert hist values 0-180 to a range between 0-1
36+
roi_hist = cv2.normalize(roi_hist, roi_hist, 0, 255, cv2.NORM_MINMAX)
37+
# set up the termination criteria, either 10 iteration or move by at least 1 pt
38+
parameter = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1)
39+
40+
# now loop through the rest of avail frames
41+
# and use camshift to track defined roi
42+
while True:
43+
# get next frame
44+
frame = vs.read()
45+
if True:
46+
# convert to hsv
47+
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
48+
# compare blue channel of current with roi histogram
49+
# https://docs.opencv.org/3.4.15/da/d7f/tutorial_back_projection.html
50+
dst = cv2.calcBackProject([hsv], [0], roi_hist, [0, 180], 1)
51+
# call camshift() to find match of histogram in current frame
52+
# and get the new coordinates
53+
ok, track_window = cv2.CamShift(dst, (x, y, w, h), parameter)
54+
if not ok:
55+
print('[WARNING] track lost')
56+
# take the updated coordinates
57+
pts = cv2.boxPoints(ok)
58+
pts = np.int0(pts)
59+
# use coordinates to draw polylines
60+
output = cv2.polylines(frame, [pts], True, 255, 5)
61+
# display track
62+
cv2.imshow("CAMshift Track", output)
63+
if cv2.waitKey(1) & 0xFF == ord('q'):
64+
break
65+
else:
66+
break
67+
68+
69+
cv2.destroyAllWindows()

scripts/meanshift_tracking.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Usage
22
# python scripts/meanshift_tracking.py -u 'rtsp://admin:instar@192.168.2.19/livestream/13'
33
import cv2
4-
from matplotlib import pyplot as plt
4+
# from matplotlib import pyplot as plt
55
import argparse
66
# from imutils import resize
77
from imutils.video import VideoStream
@@ -65,7 +65,7 @@
6565
print('[WARNING] track lost')
6666
# now update the roi coordinates to new values
6767
x, y, w, h = track_window
68-
cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 255), 5)
68+
cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 255), 2)
6969
# display track
7070
cv2.imshow("Meanshift Track", frame)
7171
if cv2.waitKey(1) & 0xFF == ord('q'):
@@ -74,4 +74,4 @@
7474
break
7575

7676

77-
cv2.destroyAllWindows()
77+
cv2.destroyAllWindows()

scripts/optical_flow_dense.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Usage
2+
# python scripts/optical_flow_sparse_manual.py -p 'resources/car_race_01.mp4'
3+
import datetime
4+
import sys
5+
6+
import cv2
7+
import argparse
8+
import numpy as np
9+
10+
# Parse the arguments
11+
ap = argparse.ArgumentParser()
12+
ap.add_argument("-p", "--path", help="Path to video file", default="resources/car_race_02.mp4")
13+
args = vars(ap.parse_args())
14+
15+
cap = cv2.VideoCapture(args["path"])
16+
if not cap.isOpened():
17+
print("[ERROR] opening video file")
18+
sys.exit()
19+
20+
# Optional recording parameter
21+
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
22+
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
23+
fps = int(cap.get(cv2.CAP_PROP_FPS))
24+
video_codec = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
25+
prefix = 'recordings/'+datetime.datetime.now().strftime("%y%m%d_%H%M%S")
26+
basename = "object_track.mp4"
27+
video_output = cv2.VideoWriter("_".join([prefix, basename]), video_codec, fps, (frame_width, frame_height))
28+
29+
ok, first_frame = cap.read()
30+
if not ok:
31+
print("[ERROR] getting frame from video")
32+
sys.exit()
33+
frame_gray_init = cv2.cvtColor(first_frame, cv2.COLOR_BGR2GRAY)
34+
35+
# create canvas to paint on
36+
hsv_canvas = np.zeros_like(first_frame)
37+
# set saturation value (position 2 in HSV space) to 255
38+
hsv_canvas[..., 1] = 255
39+
40+
while True:
41+
# get next frame
42+
ok, frame = cap.read()
43+
if not ok:
44+
print("[ERROR] reached end of file")
45+
break
46+
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
47+
# compare initial frame with current frame
48+
flow = cv2.calcOpticalFlowFarneback(frame_gray_init, frame_gray, None, 0.5, 3, 15, 3, 5, 1.1, 0)
49+
# get x and y coordinates
50+
magnitude, angle = cv2.cartToPolar(flow[..., 0], flow[..., 1])
51+
# set hue of HSV canvas (position 1)
52+
hsv_canvas[..., 0] = angle*(180/(np.pi/2))
53+
# set pixel intensity value (position 3
54+
hsv_canvas[..., 2] = cv2.normalize(magnitude, None, 0, 255, cv2.NORM_MINMAX)
55+
56+
frame_rgb = cv2.cvtColor(hsv_canvas, cv2.COLOR_HSV2BGR)
57+
58+
# optional recording result/mask
59+
video_output.write(frame_rgb)
60+
61+
cv2.imshow('Optical Flow (dense)', frame_rgb)
62+
if cv2.waitKey(1) & 0xFF == ord('q'):
63+
break
64+
65+
# set initial frame to current frame
66+
frame_gray_init = frame_gray
67+
68+
cv2.destroyAllWindows()
69+
cap.release()
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Usage
2+
# python scripts/optical_flow_sparse_manual.py -p 'resources/car_race_01.mp4'
3+
import datetime
4+
import sys
5+
import numpy as np
6+
import cv2
7+
import argparse
8+
9+
# Parse the arguments
10+
ap = argparse.ArgumentParser()
11+
ap.add_argument("-p", "--path", help="Path to video file", default="resources/car_race_02.mp4")
12+
args = vars(ap.parse_args())
13+
14+
cap = cv2.VideoCapture(args["path"])
15+
if not cap.isOpened():
16+
print("[ERROR] cannot open video file")
17+
sys.exit()
18+
19+
# generate initial corners of detected object
20+
# set limit, minimum distance in pixels and quality of object corner to be tracked
21+
parameters_shitomasi = dict(maxCorners=100, qualityLevel=0.3, minDistance=7)
22+
# set min size of tracked object, e.g. 15x15px
23+
parameter_lucas_kanade = dict(winSize=(15, 15), maxLevel=2, criteria=(cv2.TERM_CRITERIA_EPS |
24+
cv2.TERM_CRITERIA_COUNT, 10, 0.03))
25+
# create random colours for visualization for all 100 max corners for RGB channels
26+
colours = np.random.randint(0, 255, (100, 3))
27+
28+
# get first video frame
29+
ok, frame = cap.read()
30+
if not ok:
31+
print("[ERROR] cannot get frame from video")
32+
sys.exit()
33+
# convert to grayscale
34+
frame_gray_init = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
35+
36+
# Use optical flow to detect object corners / edges from initial frame
37+
edges = cv2.goodFeaturesToTrack(frame_gray_init, mask = None, **parameters_shitomasi)
38+
# [Debug] show amount of found edges
39+
# max value = maxCorners see above. Reduce qualityLevel to get more hits
40+
# print(len(edges))
41+
42+
# create a black canvas the size of the initial frame
43+
canvas = np.zeros_like(frame)
44+
45+
# Optional recording parameter
46+
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
47+
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
48+
fps = int(cap.get(cv2.CAP_PROP_FPS))
49+
video_codec = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
50+
prefix = 'recordings/'+datetime.datetime.now().strftime("%y%m%d_%H%M%S")
51+
basename = "object_track.mp4"
52+
video_output = cv2.VideoWriter("_".join([prefix, basename]), video_codec, fps, (frame_width, frame_height))
53+
54+
# loop through the remaining frames of the video
55+
# and apply algorithm to track selected objects
56+
while True:
57+
# get next frame
58+
ok, frame = cap.read()
59+
if not ok:
60+
print("[INFO] end of file reached")
61+
break
62+
# prepare grayscale image
63+
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
64+
# update object corners by comparing with found edges in initial frame
65+
update_edges, status, errors = cv2.calcOpticalFlowPyrLK(frame_gray_init, frame_gray, edges, None,
66+
**parameter_lucas_kanade)
67+
# only update edges if algorithm successfully tracked
68+
new_edges = update_edges[status == 1]
69+
# to calculate directional flow we need to compare with previous position
70+
old_edges = edges[status == 1]
71+
72+
for i, (new, old) in enumerate(zip(new_edges, old_edges)):
73+
a, b = new.ravel()
74+
c, d = old.ravel()
75+
76+
# draw line between old and new corner point with random colour
77+
mask = cv2.line(canvas, (int(a), int(b)), (int(c), int(d)), colours[i].tolist(), 2)
78+
# draw circle around new position
79+
frame = cv2.circle(frame, (int(a), int(b)), 5, colours[i].tolist(), -1)
80+
81+
result = cv2.add(frame, mask)
82+
# optional recording result/mask
83+
# video_output.write(result)
84+
cv2.imshow('Optical Flow (sparse)', result)
85+
if cv2.waitKey(1) & 0xFF == ord('q'):
86+
break
87+
# overwrite initial frame with current before restarting the loop
88+
frame_gray_init = frame_gray.copy()
89+
# update to new edges before restarting the loop
90+
edges = new_edges.reshape(-1, 1, 2)
91+
92+
93+
cv2.destroyAllWindows()
94+
cap.release()
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Usage
2+
# python scripts/optical_flow_sparse_manual.py -u 'rtsp://admin:instar@192.168.2.19/livestream/13'
3+
# Click on video to select point to track
4+
import sys
5+
import numpy as np
6+
import cv2
7+
import argparse
8+
from imutils.video import VideoStream
9+
10+
# Parse the arguments
11+
ap = argparse.ArgumentParser()
12+
ap.add_argument("-u", "--url", help="RTSP streaming URL", default="rtsp://admin:instar@192.168.2.19/livestream/12")
13+
args = vars(ap.parse_args())
14+
15+
# get video stream from IP camera
16+
print("[INFO] starting video stream")
17+
vs = VideoStream(args["url"]).start()
18+
19+
# first frame from stream
20+
frame = vs.read()
21+
22+
# convert to grayscale
23+
frame_gray_init = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
24+
25+
# set min size of tracked object, e.g. 15x15px
26+
parameter_lucas_kanade = dict(winSize=(15, 15), maxLevel=4, criteria=(cv2.TERM_CRITERIA_EPS |
27+
cv2.TERM_CRITERIA_COUNT, 10, 0.03))
28+
29+
30+
# define function to manually select object to track
31+
def select_point(event, x, y, flags, params):
32+
global point, selected_point, old_points
33+
# record coordinates of mouse click
34+
if event == cv2.EVENT_LBUTTONDOWN:
35+
point = (x, y)
36+
selected_point = True
37+
old_points = np.array([[x, y]], dtype=np.float32)
38+
39+
40+
# associate select function with window Selector
41+
cv2.namedWindow('Optical Flow')
42+
cv2.setMouseCallback('Optical Flow', select_point)
43+
44+
# initialize variables updated by function
45+
selected_point = False
46+
point = ()
47+
old_points = ([[]])
48+
49+
# create a black canvas the size of the initial frame
50+
canvas = np.zeros_like(frame)
51+
52+
# loop through the remaining frames of the video
53+
# and apply algorithm to track selected objects
54+
while True:
55+
# get next frame
56+
frame = vs.read()
57+
# covert to grayscale
58+
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
59+
60+
if selected_point is True:
61+
cv2.circle(frame, point, 5, (0, 0, 255), 2)
62+
# update object corners by comparing with found edges in initial frame
63+
new_points, status, errors = cv2.calcOpticalFlowPyrLK(frame_gray_init, frame_gray, old_points, None,
64+
**parameter_lucas_kanade)
65+
66+
# overwrite initial frame with current before restarting the loop
67+
frame_gray_init = frame_gray.copy()
68+
# update to new edges before restarting the loop
69+
old_points = new_points
70+
71+
x, y = new_points.ravel()
72+
j, k = old_points.ravel()
73+
74+
# draw line between old and new corner point with random colour
75+
canvas = cv2.line(canvas, (int(x), int(y)), (int(j), int(k)), (0, 255, 0), 3)
76+
# draw circle around new position
77+
frame = cv2.circle(frame, (int(x), int(y)), 5, (0, 255, 0), -1)
78+
79+
result = cv2.add(frame, canvas)
80+
cv2.imshow('Optical Flow', result)
81+
if cv2.waitKey(1) & 0xFF == ord('q'):
82+
break
83+
84+
85+
cv2.destroyAllWindows()
86+
sys.exit()

0 commit comments

Comments
 (0)