1+ """Adapted from the OpenCV optical flow documentation code: https://docs.opencv.org/4.5.3/d4/dee/tutorial_optical_flow.html"""
2+
3+ """ ABOUT-----------------------------------------------------------------------
4+ Lucas Kanade Optical Flow calculates the optical flow (motion) of specific features in a video clip.
5+ The Shi-Tomasi corner detection is used to pick points in the video that are easy for to track.
6+ The optical flow algorithm will track where those features move.
7+ The visualization will draw a point over the tracked features and a trail of where the feature has been.
8+ The program currently outputs the result video to the same location as your input video, with the name <input_vid_filename>_LK_FLOW.mp4
9+
10+ The idea is that perhaps the data about how certain pixels/features are moving across the screen could be used to figure out how the player camera / aim was changing.
11+ """
12+
13+ from tkinter import *
14+ from tkinter import filedialog
15+ import cv2 as cv
16+ import numpy as np
17+ from tkinter .messagebox import showinfo
18+
19+ # GUI FILE BROWSER------------------------------------------------------------
20+
21+ window = Tk ()
22+ window .geometry ('300x150' ) # sets the size of the GUI window
23+ window .title ('Select a Video File' ) # creates a title for the window
24+
25+ # function allowing you to find/select video in GUI
26+ def get_file_path ():
27+ global file_path
28+ # Open and return file path
29+ file_path = filedialog .askopenfilename (title = "Select a Video File" , filetypes = (("mp4" , "*.mp4" ), ("mov files" , "*.mov" ) ,("wmv" , "*.wmv" ), ("avi" , "*.avi" )))
30+ showinfo (title = 'Selected File' , message = file_path )
31+
32+ # function allowing you to select the output path in the GUI
33+ def output ():
34+ global outpath
35+ outpath = filedialog .asksaveasfilename (filetypes = [("mp4" , '*.mp4' )])
36+ window .destroy ()
37+
38+ # Creating a button to search for the input file and to select the output destinatio and file name
39+ b1 = Button (window , text = 'Open a File' , command = get_file_path ).pack ()
40+ b2 = Button (window , text = 'Save File Name' , command = output ).pack ()
41+ window .mainloop ()
42+
43+
44+ # PARAMETERS------------------------------------------------------------------
45+
46+ # path to input videofile
47+ vidpath = file_path
48+
49+ # do you want to save the video?
50+ savevid = True
51+
52+ # do you want to preview the output?
53+ previewWindow = True
54+
55+ # output video params
56+ fps = 20 # fps of output video, should match input video
57+
58+ # visualization parameters
59+ numPts = 5 # max number of points to track
60+ trailLength = 60 # how many frames to keep a fading trail behind a tracked point to show motion
61+ trailThickness = 8 # thickness of the trail to draw behind the target
62+ trailFade = 4 # the intensity at which the trail fades
63+ pointSize = 15 # pixel radius of the circle to draw over tracked points
64+
65+ # params for Shi-Tomasi corner detection
66+ shitomasi_params = {
67+ "qualityLevel" : 0.3 ,
68+ "minDistance" : 7 ,
69+ "blockSize" : 7
70+ }
71+
72+ # params for Lucas-Kanade optical flow
73+ LK_params = {
74+ "winSize" : (15 ,15 ),
75+ "maxLevel" : 2 ,
76+ "criteria" : (cv .TERM_CRITERIA_EPS | cv .TERM_CRITERIA_COUNT , 10 , 0.03 )
77+ }
78+
79+
80+ # SETUP -----------------------------------------------------------------------
81+
82+ # generate random colors
83+ color = np .random .randint (0 ,255 ,(100 ,3 ))
84+
85+ # read the video file into memory
86+ cap = cv .VideoCapture (vidpath )
87+
88+ # get the first frame
89+ _ , old_frame = cap .read ()
90+ old_gray = cv .cvtColor (old_frame , cv .COLOR_BGR2GRAY )
91+
92+ # get resolution of video
93+ res_x = len (old_frame [0 ])
94+ res_y = len (old_frame )
95+
96+ # create crosshair mask
97+ crosshair_bottom = int (0.7 * res_y )
98+ crosshair_top = int (0.3 * res_y )
99+ crosshair_left = int (0.3 * res_x )
100+ crosshair_right = int (0.7 * res_x )
101+ crosshairmask = np .zeros (old_frame .shape [:2 ], dtype = "uint8" )
102+ cv .rectangle (crosshairmask , (crosshair_left , crosshair_top ), (crosshair_right , crosshair_bottom ), 255 , - 1 )
103+
104+ # create masks for drawing purposes
105+ trail_history = [[[(0 ,0 ), (0 ,0 )] for i in range (trailLength )] for i in range (numPts )]
106+
107+ # get features from first frame
108+ print (f"\n Running Optical Flow on: { vidpath } " )
109+ old_points = cv .goodFeaturesToTrack (old_gray , maxCorners = numPts , mask = crosshairmask , ** shitomasi_params )
110+
111+ # if saving video
112+ if savevid :
113+ # path to save output video
114+ filename = outpath
115+ savepath = filename + '_LK_FLOW' + '.mp4'
116+ print (f"Saving Output video to: { savepath } " )
117+
118+ # get shape of video frames
119+ height , width , channels = old_frame .shape
120+
121+ # setup videowriter object
122+ fourcc = cv .VideoWriter_fourcc (* 'mp4v' )
123+ videoOut = cv .VideoWriter (savepath , fourcc , fps , (width , height ))
124+
125+ # PROCESS VIDEO ---------------------------------------------------------------
126+ while (True ):
127+ # get next frame and convert to grayscale
128+ stillGoing , new_frame = cap .read ()
129+
130+ # if video is over, quit
131+ if not stillGoing :
132+ break
133+
134+ # convert to grayscale
135+ new_frame_gray = cv .cvtColor (new_frame , cv .COLOR_BGR2GRAY )
136+
137+ # calculate optical flow
138+ new_points , st , err = cv .calcOpticalFlowPyrLK (old_gray , new_frame_gray , old_points , None , ** LK_params )
139+
140+ # select good points
141+ if old_points is not None :
142+ good_new = new_points [st == 1 ]
143+ good_old = old_points [st == 1 ]
144+
145+ # create trail mask to add to image
146+ trailMask = np .zeros_like (old_frame )
147+
148+ # calculate motion lines and points
149+ for i ,(new ,old ) in enumerate (zip (good_new , good_old )):
150+ # flatten coords
151+ a ,b = new .ravel ()
152+ c ,d = old .ravel ()
153+
154+ # list of the prev and current points converted to int
155+ linepts = [(int (a ),int (b )), (int (c ),int (d ))]
156+
157+ # add points to the trail history
158+ trail_history [i ].insert (0 , linepts )
159+
160+ # get color for this point
161+ pointColor = color [i ].tolist ()
162+
163+ # add trail lines
164+ for j in range (len (trail_history [i ])):
165+ trailColor = [int ( pointColor [0 ] - (trailFade * j ) ), int ( pointColor [1 ] - (trailFade * j ) ), int ( pointColor [2 ] - (trailFade * j ) )] # fading colors
166+ trailMask = cv .line (trailMask , trail_history [i ][j ][0 ], trail_history [i ][j ][1 ], trailColor , thickness = trailThickness , lineType = cv .LINE_AA )
167+
168+ # get rid of the trail segment
169+ trail_history [i ].pop ()
170+
171+ # add circle over the point
172+ new_frame = cv .circle (new_frame , trail_history [i ][0 ][0 ], pointSize , color [i ].tolist (), - 1 )
173+
174+ # add trail to frame
175+ img = cv .add (new_frame , trailMask )
176+
177+ # show the frames
178+ if previewWindow :
179+ cv .imshow ('optical flow' , img )
180+
181+ # write frames to new output video
182+ if savevid :
183+ videoOut .write (img )
184+
185+ # kill window if ESC is pressed
186+ k = cv .waitKey (30 ) & 0xff
187+ if k == 27 :
188+ break
189+
190+ # update previous frame and previous points
191+ old_gray = new_frame_gray .copy ()
192+ old_points = good_new .reshape (- 1 ,1 ,2 )
193+
194+ # if old_points < numPts, get new points
195+ if (numPts - len (old_points )) > 0 :
196+ old_points = cv .goodFeaturesToTrack (old_gray , maxCorners = numPts , mask = crosshairmask , ** shitomasi_params )
197+
198+ # after video is finished
199+ print ('\n Complete!\n ' )
0 commit comments