├── video ├── SolutionVideoLink ├── ball_bounce_output.csv ├── README.md └── Ball_Bounce_Detection_Final-Copy.ipynb /video: -------------------------------------------------------------------------------- 1 | https://drive.google.com/file/d/16smC_RuoFHRlaTnUcjZmPF-SYugXwORz/view 2 | -------------------------------------------------------------------------------- /SolutionVideoLink: -------------------------------------------------------------------------------- 1 | https://drive.google.com/file/d/1g79CMvOCCYTAIqnjS4ipebn2pYBwEs4G/view?usp=share_link 2 | -------------------------------------------------------------------------------- /ball_bounce_output.csv: -------------------------------------------------------------------------------- 1 | bounce_number,time_of_bounce,quadrant_of_bounce,frame_number 2 | 1,0.81,4,49 3 | 2,1.603,out_of_bound,97 4 | 3,2.958,1,179 5 | 4,5.353,1,324 6 | 5,6.378,2,386 7 | 6,8.823,2,534 8 | 7,12.012,3,727 9 | 8,14.804,3,896 10 | 9,15.333,out_of_bound,928 11 | 10,16.919,out_of_bound,1024 12 | 11,18.009,2,1090 13 | 12,20.124,2,1218 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Computer Vision to detect the movement of the ball in the video : 2 | 3 | 4 | Identified 5 | - Number of times the ball bounces from the floor 6 | - The timestamp of each bounce 7 | - Quadrant in which the ball bounces 8 | - Out of bounds in case the ball bounces outside the quadrant 9 | - Frame number at which it bounces/ makes contact with the floor 10 | using OpenCV 11 | -------------------------------------------------------------------------------- /Ball_Bounce_Detection_Final-Copy.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 4, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdout", 10 | "output_type": "stream", 11 | "text": [ 12 | "Bounce= 1\n", 13 | "Quadrant 4\n", 14 | "bounce_time 0.8095987411487018\n", 15 | "frame 49\n", 16 | "Bounce= 2\n", 17 | "Outside\n", 18 | "bounce_time 1.6026750590086547\n", 19 | "frame 97\n", 20 | "Bounce= 3\n", 21 | "Quadrant 1\n", 22 | "frame 179\n", 23 | "bounce_time 2.957513768686074\n", 24 | "Bounce= 4\n", 25 | "Quadrant 1\n", 26 | "frame 324\n", 27 | "bounce_time 5.353265145554682\n", 28 | "Bounce= 5\n", 29 | "Quadrant 2\n", 30 | "bounce_time 6.377655389457121\n", 31 | "frame 386\n", 32 | "Bounce= 6\n", 33 | "Quadrant 2\n", 34 | "bounce_time 8.822974036191976\n", 35 | "frame 534\n", 36 | "Bounce= 7\n", 37 | "Quadrant 3\n", 38 | "bounce_time 12.011801730920535\n", 39 | "frame 727\n", 40 | "Bounce= 8\n", 41 | "Quadrant 3\n", 42 | "bounce_time 14.80409126671912\n", 43 | "frame 896\n", 44 | "Bounce= 9\n", 45 | "Outside\n", 46 | "bounce_time 15.332808811959087\n", 47 | "frame 928\n", 48 | "Bounce= 10\n", 49 | "Outside\n", 50 | "bounce_time 16.918961447678992\n", 51 | "frame 1024\n", 52 | "Bounce= 11\n", 53 | "Quadrant 2\n", 54 | "bounce_time 18.009441384736427\n", 55 | "frame 1090\n", 56 | "Bounce= 12\n", 57 | "Quadrant 2\n", 58 | "bounce_time 20.124311565696303\n", 59 | "frame 1218\n" 60 | ] 61 | }, 62 | { 63 | "ename": "error", 64 | "evalue": "OpenCV(4.5.5) D:\\a\\opencv-python\\opencv-python\\opencv\\modules\\imgproc\\src\\resize.cpp:4052: error: (-215:Assertion failed) !ssize.empty() in function 'cv::resize'\n", 65 | "output_type": "error", 66 | "traceback": [ 67 | "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", 68 | "\u001b[1;31merror\u001b[0m Traceback (most recent call last)", 69 | "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 40\u001b[0m \u001b[1;32mwhile\u001b[0m \u001b[1;32mTrue\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 41\u001b[0m \u001b[0msuccess\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mimg\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mcap\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mread\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 42\u001b[1;33m \u001b[0mimg\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mcv2\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mresize\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mimg\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m(\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m0\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;32mNone\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m0.35\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m0.35\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 43\u001b[0m \u001b[0mimgcolor\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mmask\u001b[0m\u001b[1;33m=\u001b[0m \u001b[0mmyColorFinder\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mupdate\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mimg\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mhsvVals\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 44\u001b[0m \u001b[0mimgContours\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mcontours\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mcvzone\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mfindContours\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mimg\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mmask\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mminArea\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m500\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", 70 | "\u001b[1;31merror\u001b[0m: OpenCV(4.5.5) D:\\a\\opencv-python\\opencv-python\\opencv\\modules\\imgproc\\src\\resize.cpp:4052: error: (-215:Assertion failed) !ssize.empty() in function 'cv::resize'\n" 71 | ] 72 | } 73 | ], 74 | "source": [ 75 | "import cv2\n", 76 | "import cvzone\n", 77 | "import numpy as np\n", 78 | "import math\n", 79 | "import datetime\n", 80 | "from cvzone.ColorModule import ColorFinder\n", 81 | "cap = cv2.VideoCapture('Hackathon video.mov')\n", 82 | "myColorFinder = ColorFinder(False)\n", 83 | "hsvVals={'hmin': 21, 'smin': 0, 'vmin': 0, 'hmax': 89, 'smax': 255, 'vmax': 255}\n", 84 | "poslist=[]\n", 85 | "pointsList = []\n", 86 | "totalframecount= int(cap.get(cv2.CAP_PROP_FRAME_COUNT))\n", 87 | "\n", 88 | "\n", 89 | "def gradient(pt1,pt2):\n", 90 | " if (pt1[0]-pt2[0])==0:\n", 91 | " return (pt1[1]-pt2[1])\n", 92 | " else:\n", 93 | " return (pt1[1]-pt2[1])/(pt1[0]-pt2[0])\n", 94 | "def getAngle(pt1,pt2,pt3):\n", 95 | " m1 = gradient(pt1,pt2)\n", 96 | " m2 = gradient(pt2,pt3)\n", 97 | " if 1+(m2*m1)!=0:\n", 98 | " angR = math.atan((m2-m1)/(1+(m2*m1)))\n", 99 | " else:\n", 100 | " angR = math.atan(m2-m1)\n", 101 | " angD = round(math.degrees(angR))\n", 102 | " return angD\n", 103 | "Angle=[]\n", 104 | "pitch=[]\n", 105 | "bounce_count=1\n", 106 | "start_time=datetime.datetime.now()\n", 107 | "start_time=start_time.timestamp()\n", 108 | "time_list=[]\n", 109 | "quadrant=[]\n", 110 | "bounce_number=[]\n", 111 | "time_bounce=[]\n", 112 | "frame_number=[]\n", 113 | "\n", 114 | "while True:\n", 115 | " success, img=cap.read()\n", 116 | " img = cv2.resize(img, (0, 0), None, 0.35, 0.35)\n", 117 | " imgcolor, mask= myColorFinder.update(img,hsvVals)\n", 118 | " imgContours,contours=cvzone.findContours(img,mask,minArea=500)\n", 119 | " area=[(15,250),(430,20),(900,155),((540,550))]\n", 120 | " quad1=[(15,250),(250,115),(470,200),(230,370)]\n", 121 | " quad2=[(250,115),(430,20),(630,75),(470,200)]\n", 122 | " quad3=[(230,370),(470,200),(750,310),(540,550)]\n", 123 | " quad4=[(470,200),(630,75),(900,155),(750,310)]\n", 124 | " if contours:\n", 125 | " current_time=datetime.datetime.now()\n", 126 | " current_time=current_time.timestamp()\n", 127 | " lapsed_time=(current_time-start_time)\n", 128 | " \n", 129 | " time_list.append(round(lapsed_time,3))\n", 130 | " poslist.append(contours[0]['center'])\n", 131 | " cv2.circle(imgContours,(contours[0]['center']),5,(0,255,0),cv2.FILLED)\n", 132 | " if len(poslist)>2:\n", 133 | " a=getAngle(tuple(poslist[-3]),tuple(poslist[-2]),tuple(poslist[-1]))\n", 134 | " Angle.append(a)\n", 135 | " cv2.circle(imgContours,(poslist[-3]),5,(255,0,255),cv2.FILLED)\n", 136 | " cv2.circle(imgContours,(poslist[-2]),5,(255,0,255),cv2.FILLED)\n", 137 | " cv2.circle(imgContours,(poslist[-1]),5,(255,0,255),cv2.FILLED) \n", 138 | " else:\n", 139 | " val=0\n", 140 | " if len(Angle)>1:\n", 141 | " maxi=max(Angle)\n", 142 | " i=Angle.index(maxi)\n", 143 | " if Angle[i]==Angle[i+1]:\n", 144 | " i=i+1\n", 145 | " bounce_number.append(bounce_count)\n", 146 | " print(\"Bounce=\",bounce_count)\n", 147 | " if poslist[i+1]:\n", 148 | " pitch=poslist[i+1]\n", 149 | " pitch_time=time_list[i+1]\n", 150 | " else:\n", 151 | " pitch=poslist[i]\n", 152 | " pitch_time=time_list[i]\n", 153 | " \n", 154 | " \n", 155 | " #print(\"Time=\",pitch_time)\n", 156 | " #print(\"Pitch point\",pitch)\n", 157 | " (cx,cy)=pitch\n", 158 | " res=cv2.pointPolygonTest(np.array(quad1,np.int32),(int(cx),int(cy)),False)\n", 159 | " if res>=0:\n", 160 | " print(\"Quadrant 1\")\n", 161 | " val=1\n", 162 | " cv2.polylines(imgContours,[np.array(quad1,np.int32)],True,(15,220,10),6)\n", 163 | " cv2.circle(imgContours,(cx,cy),5,(0,255,0),cv2.FILLED)\n", 164 | " a=int(cap.get(cv2.CAP_PROP_POS_FRAMES))\n", 165 | " print(\"frame\",a)\n", 166 | " a1=(a/totalframecount)*21\n", 167 | " print(\"bounce_time\",a1)\n", 168 | " frame_number.append(a)\n", 169 | " time_bounce.append(round(a1,3))\n", 170 | " quadrant.append(\"1\")\n", 171 | " res=cv2.pointPolygonTest(np.array(quad2,np.int32),(int(cx),int(cy)),False)\n", 172 | " if res>=0:\n", 173 | " print(\"Quadrant 2\")\n", 174 | " val=1\n", 175 | " cv2.polylines(imgContours,[np.array(quad2,np.int32)],True,(15,220,10),6)\n", 176 | " cv2.circle(imgContours,(cx,cy),5,(0,255,0),cv2.FILLED)\n", 177 | " b=int(cap.get(cv2.CAP_PROP_POS_FRAMES))\n", 178 | " b1=(b/totalframecount)*21\n", 179 | " print(\"bounce_time\",b1)\n", 180 | " print(\"frame\",b)\n", 181 | " frame_number.append(b)\n", 182 | " time_bounce.append(round(b1,3))\n", 183 | " quadrant.append(\"2\")\n", 184 | " \n", 185 | " res=cv2.pointPolygonTest(np.array(quad3,np.int32),(int(cx),int(cy)),False)\n", 186 | " if res>=0:\n", 187 | " print(\"Quadrant 3\")\n", 188 | " val=1\n", 189 | " cv2.polylines(imgContours,[np.array(quad3,np.int32)],True,(15,220,10),6)\n", 190 | " cv2.circle(imgContours,(cx,cy),5,(0,255,0),cv2.FILLED)\n", 191 | " c=int(cap.get(cv2.CAP_PROP_POS_FRAMES))\n", 192 | " c1=(c/totalframecount)*21\n", 193 | " print(\"bounce_time\",c1)\n", 194 | " frame_number.append(c)\n", 195 | " quadrant.append(\"3\")\n", 196 | " time_bounce.append(round(c1,3))\n", 197 | " print(\"frame\",c)\n", 198 | " res=cv2.pointPolygonTest(np.array(quad4,np.int32),(int(cx),int(cy)),False)\n", 199 | " if res>=0:\n", 200 | " print(\"Quadrant 4\")\n", 201 | " val=1\n", 202 | " cv2.polylines(imgContours,[np.array(quad4,np.int32)],True,(15,220,10),6)\n", 203 | " cv2.circle(imgContours,(cx,cy),5,(0,255,0),cv2.FILLED)\n", 204 | " d=int(cap.get(cv2.CAP_PROP_POS_FRAMES))\n", 205 | " d1=(d/totalframecount)*21\n", 206 | " print(\"bounce_time\",d1)\n", 207 | " frame_number.append(d)\n", 208 | " time_bounce.append(round(d1,3))\n", 209 | " quadrant.append(\"4\")\n", 210 | " print(\"frame\",d)\n", 211 | " if val==0:\n", 212 | " print(\"Outside\")\n", 213 | " cv2.polylines(imgContours,[np.array(area,np.int32)],True,(15,220,10),6)\n", 214 | " e=int(cap.get(cv2.CAP_PROP_POS_FRAMES))\n", 215 | " e1=(e/totalframecount)*21\n", 216 | " \n", 217 | " print(\"bounce_time\",e1)\n", 218 | " time_bounce.append(round(e1,3))\n", 219 | " frame_number.append(e)\n", 220 | " quadrant.append(\"out_of_bound\")\n", 221 | " print(\"frame\",e)\n", 222 | " bounce_count+=1\n", 223 | " cv2.circle(imgContours,(pitch),5,(0,0,255),cv2.FILLED)\n", 224 | " poslist=[]\n", 225 | " Angle=[]\n", 226 | " time_list=[]\n", 227 | " \n", 228 | " cv2.imshow(\"Image\", imgContours)\n", 229 | " cv2.waitKey(1)\n", 230 | "\n", 231 | "cap.release()\n", 232 | "cv2.destroyAllWindows()" 233 | ] 234 | }, 235 | { 236 | "cell_type": "code", 237 | "execution_count": 5, 238 | "metadata": {}, 239 | "outputs": [ 240 | { 241 | "name": "stdout", 242 | "output_type": "stream", 243 | "text": [ 244 | "['4', 'out_of_bound', '1', '1', '2', '2', '3', '3', 'out_of_bound', 'out_of_bound', '2', '2']\n" 245 | ] 246 | } 247 | ], 248 | "source": [ 249 | "print(quadrant)" 250 | ] 251 | }, 252 | { 253 | "cell_type": "code", 254 | "execution_count": 6, 255 | "metadata": {}, 256 | "outputs": [ 257 | { 258 | "name": "stdout", 259 | "output_type": "stream", 260 | "text": [ 261 | "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]\n" 262 | ] 263 | } 264 | ], 265 | "source": [ 266 | "print(bounce_number)" 267 | ] 268 | }, 269 | { 270 | "cell_type": "code", 271 | "execution_count": 7, 272 | "metadata": {}, 273 | "outputs": [ 274 | { 275 | "name": "stdout", 276 | "output_type": "stream", 277 | "text": [ 278 | "[0.81, 1.603, 2.958, 5.353, 6.378, 8.823, 12.012, 14.804, 15.333, 16.919, 18.009, 20.124]\n" 279 | ] 280 | } 281 | ], 282 | "source": [ 283 | "print(time_bounce)" 284 | ] 285 | }, 286 | { 287 | "cell_type": "code", 288 | "execution_count": 8, 289 | "metadata": {}, 290 | "outputs": [ 291 | { 292 | "name": "stdout", 293 | "output_type": "stream", 294 | "text": [ 295 | "[49, 97, 179, 324, 386, 534, 727, 896, 928, 1024, 1090, 1218]\n" 296 | ] 297 | } 298 | ], 299 | "source": [ 300 | "print(frame_number)" 301 | ] 302 | }, 303 | { 304 | "cell_type": "code", 305 | "execution_count": 9, 306 | "metadata": {}, 307 | "outputs": [], 308 | "source": [ 309 | "import pandas as pd" 310 | ] 311 | }, 312 | { 313 | "cell_type": "code", 314 | "execution_count": 10, 315 | "metadata": {}, 316 | "outputs": [], 317 | "source": [ 318 | "df=pd.DataFrame({\n", 319 | " \"bounce_number\":bounce_number,\n", 320 | " \"time_of_bounce\":time_bounce,\n", 321 | " \"quadrant_of_bounce\":quadrant,\n", 322 | " \"frame_number\":frame_number\n", 323 | "})\n", 324 | "\n", 325 | "df.to_csv(\"output.csv\",index=False)" 326 | ] 327 | }, 328 | { 329 | "cell_type": "code", 330 | "execution_count": 11, 331 | "metadata": {}, 332 | "outputs": [ 333 | { 334 | "data": { 335 | "text/html": [ 336 | "
\n", 337 | "\n", 350 | "\n", 351 | " \n", 352 | " \n", 353 | " \n", 354 | " \n", 355 | " \n", 356 | " \n", 357 | " \n", 358 | " \n", 359 | " \n", 360 | " \n", 361 | " \n", 362 | " \n", 363 | " \n", 364 | " \n", 365 | " \n", 366 | " \n", 367 | " \n", 368 | " \n", 369 | " \n", 370 | " \n", 371 | " \n", 372 | " \n", 373 | " \n", 374 | " \n", 375 | " \n", 376 | " \n", 377 | " \n", 378 | " \n", 379 | " \n", 380 | " \n", 381 | " \n", 382 | " \n", 383 | " \n", 384 | " \n", 385 | " \n", 386 | " \n", 387 | " \n", 388 | " \n", 389 | " \n", 390 | " \n", 391 | " \n", 392 | " \n", 393 | " \n", 394 | " \n", 395 | " \n", 396 | " \n", 397 | "
bounce_numbertime_of_bouncequadrant_of_bounceframe_number
010.810449
121.603out_of_bound97
232.9581179
345.3531324
456.3782386
\n", 398 | "
" 399 | ], 400 | "text/plain": [ 401 | " bounce_number time_of_bounce quadrant_of_bounce frame_number\n", 402 | "0 1 0.810 4 49\n", 403 | "1 2 1.603 out_of_bound 97\n", 404 | "2 3 2.958 1 179\n", 405 | "3 4 5.353 1 324\n", 406 | "4 5 6.378 2 386" 407 | ] 408 | }, 409 | "execution_count": 11, 410 | "metadata": {}, 411 | "output_type": "execute_result" 412 | } 413 | ], 414 | "source": [ 415 | "df.head()" 416 | ] 417 | }, 418 | { 419 | "cell_type": "code", 420 | "execution_count": null, 421 | "metadata": {}, 422 | "outputs": [], 423 | "source": [] 424 | } 425 | ], 426 | "metadata": { 427 | "kernelspec": { 428 | "display_name": "Python 3", 429 | "language": "python", 430 | "name": "python3" 431 | }, 432 | "language_info": { 433 | "codemirror_mode": { 434 | "name": "ipython", 435 | "version": 3 436 | }, 437 | "file_extension": ".py", 438 | "mimetype": "text/x-python", 439 | "name": "python", 440 | "nbconvert_exporter": "python", 441 | "pygments_lexer": "ipython3", 442 | "version": "3.8.5" 443 | } 444 | }, 445 | "nbformat": 4, 446 | "nbformat_minor": 4 447 | } 448 | --------------------------------------------------------------------------------