Today, I’m going to discuss another Computer Vision installment. I’ll use Open CV & Kalman filter to predict a live ball movement of Cricket, one of the most popular sports in the Indian sub-continent, along with the UK & Australia. But before we start a deep dive, why don’t we first watch the demo?
Isn’t it exciting? Let’s explore it in detail.
Architecture:
Let us understand the flow of events –

The above diagram shows that the application, which uses Open CV, analyzes individual frames. It detects the cricket ball & finally, it tracks every movement by analyzing each frame & then it predicts (pink line) based on the supplied data points.
Python Packages:
Following are the python packages that are necessary to develop this brilliant use case –
pip install opencv-python
pip install numpy
pip install cvzone
CODE:
Let us now understand the code. For this use case, we will only discuss three python scripts. However, we need more than these three. However, we have already discussed them in some of the early posts. Hence, we will skip them here.
- clsPredictBodyLine.py (The main class that will handle the prediction of Cricket balls in the real-time video feed.)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
##################################################### | |
#### Written By: SATYAKI DE #### | |
#### Written On: 20-Nov-2022 #### | |
#### Modified On 30-Nov-2022 #### | |
#### #### | |
#### Objective: This is the main calling #### | |
#### python script that will invoke the #### | |
#### clsPredictBodyLine class to initiate #### | |
#### the prediction capability in real-time #### | |
#### & display the result from a live sports. #### | |
##################################################### | |
import cv2 | |
import cvzone | |
from cvzone.ColorModule import ColorFinder | |
from clsKalmanFilter import clsKalmanFilter | |
from clsConfigClient import clsConfigClient as cf | |
import numpy as np | |
import math | |
import ssl | |
import time | |
# Bypassing SSL Authentication | |
try: | |
_create_unverified_https_context = ssl._create_unverified_context | |
except AttributeError: | |
# Legacy python that doesn't verify HTTPS certificates by default | |
pass | |
else: | |
# Handle target environment that doesn't support HTTPS verification | |
ssl._create_default_https_context = _create_unverified_https_context | |
# Disbling Warning | |
def warn(*args, **kwargs): | |
pass | |
import warnings | |
warnings.warn = warn | |
############################################### | |
### Global Section ### | |
############################################### | |
# Load Kalman filter to predict the trajectory | |
kf = clsKalmanFilter() | |
# Create the color ColorFinder | |
myColorFinder = ColorFinder(False) | |
posListX = [] | |
posListY = [] | |
xList = [item for item in range(0, 1300)] | |
prediction=False | |
############################################### | |
### End of Global Section ### | |
############################################### | |
class clsPredictBodyLine(object): | |
def __init__(self): | |
self.inputFile_1 = str(cf.conf['BASE_FILE']) | |
self.inputFile_2 = str(cf.conf['BASE_IMAGE_FILE']) | |
self.src_path = str(cf.conf['SRC_PATH']) | |
self.hsvVals = cf.conf['HSV'] | |
self.pauseTime = cf.conf['PAUSE'] | |
self.pT1 = int(cf.conf['POINT_1']) | |
self.pT2 = int(cf.conf['POINT_2']) | |
self.pT3 = int(cf.conf['POINT_3']) | |
self.pT4 = int(cf.conf['POINT_4']) | |
def predStream(self, img, hsvVals, FrNo): | |
try: | |
pT1 = self.pT1 | |
pT2 = self.pT2 | |
pT3 = self.pT3 | |
pT4 = self.pT4 | |
#Find the color ball | |
imgColor, mask = myColorFinder.update(img, hsvVals) | |
#Find location of the red_ball | |
imgContours, contours = cvzone.findContours(img, mask, minArea=500) | |
if contours: | |
posListX.append(contours[0]['center'][0]) | |
posListY.append(contours[0]['center'][1]) | |
if posListX: | |
# Find the Coefficients | |
A, B, C = np.polyfit(posListX, posListY, 2) | |
for i, (posX, posY) in enumerate(zip(posListX, posListY)): | |
pos = (posX, posY) | |
cv2.circle(imgContours, pos, 10, (0,255,0), cv2.FILLED) | |
# Using Karman Filter Prediction | |
predicted = kf.predict(posX, posY) | |
cv2.circle(imgContours, (predicted[0], predicted[1]), 12, (255,0,255), cv2.FILLED) | |
ballDetectFlag = True | |
if ballDetectFlag: | |
print('Balls Detected!') | |
if i == 0: | |
cv2.line(imgContours, pos, pos, (0,255,0), 5) | |
cv2.line(imgContours, predicted, predicted, (255,0,255), 5) | |
else: | |
predictedM = kf.predict(posListX[i–1], posListY[i–1]) | |
cv2.line(imgContours, pos, (posListX[i–1], posListY[i–1]), (0,255,0), 5) | |
cv2.line(imgContours, predicted, predictedM, (255,0,255), 5) | |
if len(posListX) < 10: | |
# Calculation for best place to ball | |
a1 = A | |
b1 = B | |
c1 = C – pT1 | |
X1 = int((– b1 – math.sqrt(b1**2 – (4*a1*c1)))/(2*a1)) | |
prediction1 = pT2 < X1 < pT3 | |
a2 = A | |
b2 = B | |
c2 = C – pT4 | |
X2 = int((– b2 – math.sqrt(b2**2 – (4*a2*c2)))/(2*a2)) | |
prediction2 = pT2 < X2 < pT3 | |
prediction = prediction1 | prediction2 | |
if prediction: | |
print('Good Length Ball!') | |
sMsg = "Good Length Ball – (" + str(FrNo) + ")" | |
cvzone.putTextRect(imgContours, sMsg, (50,150), scale=5, thickness=5, colorR=(0,200,0), offset=20) | |
else: | |
print('Loose Ball!') | |
sMsg = "Loose Ball – (" + str(FrNo) + ")" | |
cvzone.putTextRect(imgContours, sMsg, (50,150), scale=5, thickness=5, colorR=(0,0,200), offset=20) | |
return imgContours | |
except Exception as e: | |
x = str(e) | |
print('Error predStream:', x) | |
return img | |
def processVideo(self, debugInd, var): | |
try: | |
cnt = 0 | |
lastRowFlag=True | |
breakFlag = False | |
pauseTime = self.pauseTime | |
src_path = self.src_path | |
inputFile_1 = self.inputFile_1 | |
inputFile_2 = self.inputFile_2 | |
hsvVals = self.hsvVals | |
FileName_1 = src_path + inputFile_1 | |
FileName_2 = src_path + inputFile_2 | |
# Initialize the video | |
cap = cv2.VideoCapture(FileName_1) | |
while True: | |
try: | |
if breakFlag: | |
break | |
# Grab the frames | |
success, img = cap.read() | |
time.sleep(pauseTime) | |
cnt+=1 | |
print('*'*60) | |
print('Frame Number:', str(cnt)) | |
if (cv2.waitKey(1) & 0xFF) == ord("q"): | |
break | |
if success: | |
imgContours = self.predStream(img, hsvVals, cnt) | |
if imgContours is None: | |
imgContours = img | |
imgColor = cv2.resize(imgContours, (0,0), None, 0.7, 0.7) | |
# Display | |
cv2.imshow("ImageColor", imgColor) | |
print('*'*60) | |
else: | |
breakFlag=True | |
except Exception as e: | |
x = str(e) | |
print('Error Main:', x) | |
cv2.destroyAllWindows() | |
return 0 | |
except Exception as e: | |
x = str(e) | |
print('Error:', x) | |
cv2.destroyAllWindows() | |
return 1 |
Please find the key snippet from the above script –
kf = clsKalmanFilter()
The application is instantiating the modified Kalman filter.
myColorFinder = ColorFinder(False)
This command has more purpose than creating a proper mask in debug mode if you want to isolate the color of any object you want to track. To debug this property, one needs to set the flag to True. And you will see the following screen. Click the next video to get the process to generate the accurate HSV.
In the end, you will get a similar entry to the below one –

And you can see the entry that is available in the config for the following parameter –
'HSV': {'hmin': 173, 'smin':177, 'vmin':57, 'hmax':178, 'smax':255, 'vmax':255},
The next important block is –
def predStream(self, img, hsvVals, FrNo): try: pT1 = self.pT1 pT2 = self.pT2 pT3 = self.pT3 pT4 = self.pT4
The four points mentioned above will help us determine the best region for the ball, forcing the batsman to play the shots & a 90% chance of getting caught behind.

The snippets below will apply the mask & identify the contour of the objects which the program intends to track. In this case, we are talking about the pink cricket ball.
#Find the color ball imgColor, mask = myColorFinder.update(img, hsvVals) #Find location of the red_ball imgContours, contours = cvzone.findContours(img, mask, minArea=500) if contours: posListX.append(contours[0]['center'][0]) posListY.append(contours[0]['center'][1])
The next key snippets are as follows –
if posListX: # Find the Coefficients A, B, C = np.polyfit(posListX, posListY, 2) for i, (posX, posY) in enumerate(zip(posListX, posListY)): pos = (posX, posY) cv2.circle(imgContours, pos, 10, (0,255,0), cv2.FILLED) # Using Karman Filter Prediction predicted = kf.predict(posX, posY) cv2.circle(imgContours, (predicted[0], predicted[1]), 12, (255,0,255), cv2.FILLED) ballDetectFlag = True if ballDetectFlag: print('Balls Detected!') if i == 0: cv2.line(imgContours, pos, pos, (0,255,0), 5) cv2.line(imgContours, predicted, predicted, (255,0,255), 5) else: predictedM = kf.predict(posListX[i-1], posListY[i-1]) cv2.line(imgContours, pos, (posListX[i-1], posListY[i-1]), (0,255,0), 5) cv2.line(imgContours, predicted, predictedM, (255,0,255), 5)
The above lines will track the original & predicted lines & then it will plot on top of the frame in real time.
The next line will be as follows –
if len(posListX) < 10: # Calculation for best place to ball a1 = A b1 = B c1 = C - pT1 X1 = int((- b1 - math.sqrt(b1**2 - (4*a1*c1)))/(2*a1)) prediction1 = pT2 < X1 < pT3 a2 = A b2 = B c2 = C - pT4 X2 = int((- b2 - math.sqrt(b2**2 - (4*a2*c2)))/(2*a2)) prediction2 = pT2 < X2 < pT3 prediction = prediction1 | prediction2 if prediction: print('Good Length Ball!') sMsg = "Good Length Ball - (" + str(FrNo) + ")" cvzone.putTextRect(imgContours, sMsg, (50,150), scale=5, thickness=5, colorR=(0,200,0), offset=20) else: print('Loose Ball!') sMsg = "Loose Ball - (" + str(FrNo) + ")" cvzone.putTextRect(imgContours, sMsg, (50,150), scale=5, thickness=5, colorR=(0,0,200), offset=20)
- predictBodyLine.py (The main python script that will invoke the class to predict Cricket balls in the real-time video feed.)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
##################################################### | |
#### Written By: SATYAKI DE #### | |
#### Written On: 25-Nov-2022 #### | |
#### Modified On 30-Nov-2022 #### | |
#### #### | |
#### Objective: This is the main calling #### | |
#### python script that will invoke the #### | |
#### clsPredictBodyLine class to initiate #### | |
#### the predict capability in real-time #### | |
#### from a cricket (Sports) streaming. #### | |
##################################################### | |
# We keep the setup code in a different class as shown below. | |
import clsPredictBodyLine as pbdl | |
from clsConfigClient import clsConfigClient as cf | |
import datetime | |
import logging | |
def main(): | |
try: | |
# Other useful variables | |
debugInd = 'Y' | |
var = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S") | |
var1 = datetime.datetime.now() | |
print('Start Time: ', str(var)) | |
# End of useful variables | |
# Initiating Log Class | |
general_log_path = str(cf.conf['LOG_PATH']) | |
# Enabling Logging Info | |
logging.basicConfig(filename=general_log_path + 'predBodyLine.log', level=logging.INFO) | |
print('Started predicting best bodyline deliveries from the Cricket Streaming!') | |
# Passing source data csv file | |
x1 = pbdl.clsPredictBodyLine() | |
# Execute all the pass | |
r1 = x1.processVideo(debugInd, var) | |
if (r1 == 0): | |
print('Successfully predicted body-line deliveries!') | |
else: | |
print('Failed to predict body-line deliveries!') | |
var2 = datetime.datetime.now() | |
c = var2 – var1 | |
minutes = c.total_seconds() / 60 | |
print('Total difference in minutes: ', str(minutes)) | |
print('End Time: ', str(var1)) | |
except Exception as e: | |
x = str(e) | |
print('Error: ', x) | |
if __name__ == "__main__": | |
main() |
Here is the final key snippet –
# Passing source data csv file x1 = pbdl.clsPredictBodyLine() # Execute all the pass r1 = x1.processVideo(debugInd, var) if (r1 == 0): print('Successfully predicted body-line deliveries!') else: print('Failed to predict body-line deliveries!')
The above lines will first instantiate the main class & then invoke it.
You can find it here if you want to know more about the Kalman filter.
So, finally, we’ve done it.
FOLDER STRUCTURE:

You will get the complete codebase in the following GitHub link.
I’ll bring some more exciting topics in the coming days from the Python verse. Please share & subscribe to my post & let me know your feedback.
Till then, Happy Avenging! 🙂
Note: All the data & scenarios posted here are representational data & scenarios & available over the internet & for educational purposes only. Some of the images (except my photo) we’ve used are available over the net. We don’t claim ownership of these images. There is always room for improvement & especially in the prediction quality.
You must be logged in to post a comment.