code:
import cv2
from filterpy.kalman import KalmanFilter
from ultralytics import YOLO
import numpy as np
import pandas as pd
from sktime.datatypes._panel._convert import from_2d_array_to_nested
from pickle import load
from sktime.datatypes._panel._convert import from_nested_to_2d_array
from sktime.datatypes import check_raise
#from sktime.datatypes._panel._concat import concat

model = YOLO('/Users/surabhi/Documents/kalman/best.pt')

kf = KalmanFilter(dim_x=4, dim_z=2)
kf.x = np.array([0, 0, 0, 0]) # initial state estimate
kf.P = np.eye(4) * 1000 # initial error covariance matrix
kf.F = np.array([[1, 0, 1, 0],
                 [0, 1, 0, 1],
                 [0, 0, 1, 0],
                 [0, 0, 0, 1]]) # state transition matrix
kf.H = np.array([[1, 0, 0, 0],
                 [0, 1, 0, 0]]) # measurement matrix
kf.R = np.diag([0.1, 0.1]) # measurement noise covariance matrix
kf.Q=  np.diag([0.1, 0.1, 0.1, 0.1])
dt = 1.0
u = np.zeros((4, 1))

cap = cv2.VideoCapture("1_1.mp4")
frame_num = 0
predicted_points = []
bounce_detected = False 
last_bounce_frame = -5
test_df = pd.DataFrame(columns=[ 'x', 'y', 'V'])

while True:
    ret, frame = cap.read()
    if ret is False:
        break
    bbox = model(frame, show=True)
    frame_num += 1
    for boxes_1 in bbox:
        result = boxes_1.boxes.xyxy
        if len(result) == 0:
            print("not detected")
        else:
            cx = int((result[0][0] + result[0][2]) / 2)
            cy = int((result[0][1] + result[0][3]) / 2)
            centroid = np.array([cx, cy])
            kf.predict()
            kf.update(centroid)
            next_point = (kf.x).tolist()
            predicted_points.append((int(next_point[0]), int(next_point[1])))
            if len(predicted_points) > 2:
                p1 = np.array(predicted_points[-2])
                p2 = np.array(predicted_points[-1])
                ball_vector = p2 - p1
                ball_speed = np.linalg.norm(ball_vector)
                if ball_speed > 0:
                    ball_direction = ball_vector / ball_speed
                    frame_boundary = np.array([frame.shape[1], frame.shape[0]])
                    to_boundary = (frame_boundary - p2) / ball_direction
                    bounce_point = p2 + ball_direction * to_boundary.min()
                    if not np.all(frame_boundary > bounce_point) or not np.all(bounce_point > 0):
                        bounce_point = p2
                    print("Bounce Point:", tuple(map(int, bounce_point)))
                    cv2.circle(frame, tuple(map(int, bounce_point)), 5, (0, 0, 0), 10)
            V=np.sqrt(kf.x[2]**2 + kf.x[3]**2)
            test_df = test_df.append({ 'x': next_point[0], 'y': next_point[1], 
                                                    'V': np.sqrt(kf.x[2]**2 + kf.x[3]**2)}, 
                                                   ignore_index=True)
            print(test_df)
            print("ENTER LOOP")
            
            #test_df.drop(['x', 'y', 'V'], 1, inplace=True)
            #print(test_df)

            X = pd.concat([x, y, V],1)
            X = X.apply(pd.to_numeric, errors='coerce')
            #X = X.dropna()
            #X_2d = from_nested_to_2d_array(X)
            check_raise(X, mtype='pd.DataFrame')

            # load the pre-trained classifier  
            clf = load(open('clf.pkl', 'rb'))
            print("X:",X)
            predcted = clf.predict(X)
            idx = list(np.where(predcted == 1)[0])
            print("**************************************")
            print(idx)
            idx = np.array(idx) - 10
            print(idx)
                        

            if len(predicted_points) > 10:
                predicted_points.pop(0)

            if not bounce_detected and frame_num - last_bounce_frame > 10:
                if round(V)==19 or round(V)==22 : # If Y acceleration is less than the negative threshold, say -15
                    bounce_detected = True
                    last_bounce_frame = frame_num
                    print("Bounce detected")
                    
            print("next_point", next_point)
            print("frame_number", frame_num)
            cv2.putText(frame, f'Frame: {frame_num}', (10, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
            cv2.circle(frame, (cx, cy), 5, (0,0,255), 5)
            cv2.circle(frame, (int(next_point[0]), int(next_point[1])), 5, (255, 0, 0), 10)
            for i, p in enumerate(predicted_points):
                color = (255,255,255)
                cv2.circle(frame, p, 5, color, 2)
            if bounce_detected:
                cv2.putText(frame, 'Bounce Detected', (10, 350), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
            if kf.x[3] > 0: # After a bounce is detected, wait until acceleration is above the threshold, say -5, to detect the bounce again
                bounce_detected = False
        
           # test_df_1=pd.DataFrame({'frame': frame_num , 'x': next_point[0], 'y':next_point[1], 'vx':vx,'vy':vy ,'V': V}, index=[0])
            #test_df.concat(test_df_1)
            #test_df=pd.concat([test_df,test_df_1], ignore_index=True)
            #test_df.to_csv('file.csv')
            cv2.imshow('raw', frame)
            #test_df=pd.DataFrame()
           # test_df=pd.concat([test_df,test_df_1], ignore_index=True)
           # print(trajectory_df)
            test_df.to_csv('file.csv')
            #test_df_1=pd.DataFrame({'frame': frame_num , 'x': next_point[0], 'y':next_point[1], 'vx':vx,'vy':vy ,'V': V}, index=[0])
            # Uncomment the following lines to save the output video
            # out.write(frame)
            # if cv2.waitKey(1) & 0xFF == ord('q'):
            #     break
cap.release()
cv2.destroyAllWindows()

how to detect ball bounce