{ "cells": [ { "cell_type": "code", "execution_count": 1, "id": "7b423cf2-b549-4aa3-9b8a-11d016aace3b", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/var/folders/9r/fngx7sv11bl1k4rvtyflv1pw0000gn/T/ipykernel_52043/172184081.py:3: DeprecationWarning: \n", "Pyarrow will become a required dependency of pandas in the next major release of pandas (pandas 3.0),\n", "(to allow more performant data types, such as the Arrow string type, and better interoperability with other libraries)\n", "but was not found to be installed on your system.\n", "If this would cause problems for you,\n", "please provide us feedback at https://github.com/pandas-dev/pandas/issues/54466\n", " \n", " import pandas as pd\n" ] } ], "source": [ "import numpy as np\n", "import cv2\n", "import pandas as pd\n", "import matplotlib.pyplot as plt" ] }, { "cell_type": "code", "execution_count": 2, "id": "387b1580-2cff-48b0-9854-29428ef033b8", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "372.0 25.0 2560 1440\n" ] } ], "source": [ "cap = cv2.VideoCapture('video3.mp4')\n", "frames_count, fps, width, height = cap.get(cv2.CAP_PROP_FRAME_COUNT), cap.get(cv2.CAP_PROP_FPS), cap.get(\n", " cv2.CAP_PROP_FRAME_WIDTH), cap.get(cv2.CAP_PROP_FRAME_HEIGHT)\n", "width = int(width)\n", "height = int(height)\n", "print(frames_count, fps, width, height)" ] }, { "cell_type": "code", "execution_count": 3, "id": "0589d5a1-1fec-4e1e-ac2f-e64396d0d539", "metadata": {}, "outputs": [], "source": [ "# creates a pandas data frame with the number of rows the same length as frame count\n", "df = pd.DataFrame(index=range(int(frames_count)))\n", "df.index.name = \"Frames\"\n" ] }, { "cell_type": "code", "execution_count": 4, "id": "1943e0e6-7175-4e1a-a795-d3f2b64b559a", "metadata": {}, "outputs": [], "source": [ "framenumber = 0 # keeps track of current frame\n", "carscrossedup = 0 # keeps track of cars that crossed up\n", "carscrosseddown = 0 # keeps track of cars that crossed down\n", "carids = [] # blank list to add car ids\n", "caridscrossed = [] # blank list to add car ids that have crossed\n", "totalcars = 0 # keeps track of total cars\n", "fgbg = cv2.createBackgroundSubtractorMOG2() # create background subtractor" ] }, { "cell_type": "code", "execution_count": 5, "id": "0fb78ee3-ae0f-47d4-9adb-ca7dc0871a3f", "metadata": {}, "outputs": [], "source": [ "# information to start saving a video file\n", "ret, frame = cap.read() # import image\n", "ratio = .5 # resize ratio\n", "image = cv2.resize(frame, (0, 0), None, ratio, ratio) # resize image\n", "width2, height2, channels = image.shape\n", "video = cv2.VideoWriter('traffic_counter.avi', cv2.VideoWriter_fourcc('M', 'J', 'P', 'G'), fps, (height2, width2), 1)" ] }, { "cell_type": "code", "execution_count": 6, "id": "17b207f1-ca8f-4f15-9da1-ba84e71c7f3d", "metadata": {}, "outputs": [ { "ename": "NameError", "evalue": "name 'fgmask_resized' is not defined", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[6], line 272\u001b[0m\n\u001b[1;32m 266\u001b[0m cv2\u001b[38;5;241m.\u001b[39mmoveWindow(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mcountours\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;241m0\u001b[39m, \u001b[38;5;241m0\u001b[39m)\n\u001b[1;32m 268\u001b[0m \u001b[38;5;66;03m# cv2.imshow(\"fgmask\", fgmask)\u001b[39;00m\n\u001b[1;32m 269\u001b[0m \u001b[38;5;66;03m# cv2.moveWindow(\"fgmask\", int(width * ratio), 0)\u001b[39;00m\n\u001b[1;32m 270\u001b[0m \n\u001b[1;32m 271\u001b[0m \u001b[38;5;66;03m# Concatenate the original frame and the resized fgmask horizontally\u001b[39;00m\n\u001b[0;32m--> 272\u001b[0m concatenated_image \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39mconcatenate((frame, \u001b[43mfgmask_resized\u001b[49m), axis\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m1\u001b[39m)\n\u001b[1;32m 274\u001b[0m \u001b[38;5;66;03m# Display the concatenated image\u001b[39;00m\n\u001b[1;32m 275\u001b[0m plt\u001b[38;5;241m.\u001b[39mimshow(cv2\u001b[38;5;241m.\u001b[39mcvtColor(concatenated_image, cv2\u001b[38;5;241m.\u001b[39mCOLOR_BGR2RGB))\n", "\u001b[0;31mNameError\u001b[0m: name 'fgmask_resized' is not defined" ] } ], "source": [ "while True:\n", "\n", " ret, frame = cap.read() # import image\n", "\n", " if ret: # if there is a frame continue with code\n", "\n", " image = cv2.resize(frame, (0, 0), None, ratio, ratio) # resize image\n", "\n", " gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # converts image to gray\n", "\n", " fgmask = fgbg.apply(gray) # uses the background subtraction\n", "\n", " # applies different thresholds to fgmask to try and isolate cars\n", " # just have to keep playing around with settings until cars are easily identifiable\n", " kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) # kernel to apply to the morphology\n", " closing = cv2.morphologyEx(fgmask, cv2.MORPH_CLOSE, kernel)\n", " opening = cv2.morphologyEx(closing, cv2.MORPH_OPEN, kernel)\n", " dilation = cv2.dilate(opening, kernel)\n", " retvalbin, bins = cv2.threshold(dilation, 220, 255, cv2.THRESH_BINARY) # removes the shadows\n", "\n", " # creates contours\n", " contours, hierarchy = cv2.findContours(bins, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)\n", "\n", " # use convex hull to create polygon around contours\n", " hull = [cv2.convexHull(c) for c in contours]\n", "\n", " # draw contours\n", " cv2.drawContours(image, hull, -1, (0, 255, 0), 3)\n", "\n", " # line created to stop counting contours, needed as cars in distance become one big contour\n", " lineypos = 250\n", " cv2.line(image, (0, lineypos), (width, lineypos), (255, 0, 0), 5)\n", "\n", " # line y position created to count contours\n", " lineypos2 = 150\n", " cv2.line(image, (0, lineypos2), (width, lineypos2), (0, 255, 0), 5)\n", "\n", " # min area for contours in case a bunch of small noise contours are created\n", " minarea = 300\n", "\n", " # max area for contours, can be quite large for buses\n", " maxarea = 50000\n", "\n", " # vectors for the x and y locations of contour centroids in current frame\n", " cxx = np.zeros(len(contours))\n", " cyy = np.zeros(len(contours))\n", "\n", " for i in range(len(contours)): # cycles through all contours in current frame\n", "\n", " if hierarchy[0, i, 3] == -1: # using hierarchy to only count parent contours (contours not within others)\n", "\n", " area = cv2.contourArea(contours[i]) # area of contour\n", "\n", " if minarea < area < maxarea: # area threshold for contour\n", "\n", " # calculating centroids of contours\n", " cnt = contours[i]\n", " M = cv2.moments(cnt)\n", " cx = int(M['m10'] / M['m00'])\n", " cy = int(M['m01'] / M['m00'])\n", "\n", " if cy > lineypos: # filters out contours that are above line (y starts at top)\n", "\n", " # gets bounding points of contour to create rectangle\n", " # x,y is top left corner and w,h is width and height\n", " x, y, w, h = cv2.boundingRect(cnt)\n", "\n", " # creates a rectangle around contour\n", " cv2.rectangle(image, (x, y), (x + w, y + h), (255, 0, 0), 2)\n", "\n", " # Prints centroid text in order to double check later on\n", " cv2.putText(image, str(cx) + \",\" + str(cy), (cx + 10, cy + 10), cv2.FONT_HERSHEY_SIMPLEX,\n", " .3, (0, 0, 255), 1)\n", "\n", " cv2.drawMarker(image, (cx, cy), (0, 0, 255), cv2.MARKER_STAR, markerSize=5, thickness=1,\n", " line_type=cv2.LINE_AA)\n", "\n", " # adds centroids that passed previous criteria to centroid list\n", " cxx[i] = cx\n", " cyy[i] = cy\n", "\n", " # eliminates zero entries (centroids that were not added)\n", " cxx = cxx[cxx != 0]\n", " cyy = cyy[cyy != 0]\n", "\n", " # empty list to later check which centroid indices were added to dataframe\n", " minx_index2 = []\n", " miny_index2 = []\n", "\n", " # maximum allowable radius for current frame centroid to be considered the same centroid from previous frame\n", " maxrad = 25\n", "\n", " # The section below keeps track of the centroids and assigns them to old carids or new carids\n", "\n", " if len(cxx): # if there are centroids in the specified area\n", "\n", " if not carids: # if carids is empty\n", "\n", " for i in range(len(cxx)): # loops through all centroids\n", "\n", " carids.append(i) # adds a car id to the empty list carids\n", " df[str(carids[i])] = \"\" # adds a column to the dataframe corresponding to a carid\n", "\n", " # assigns the centroid values to the current frame (row) and carid (column)\n", " df.at[int(framenumber), str(carids[i])] = [cxx[i], cyy[i]]\n", "\n", " totalcars = carids[i] + 1 # adds one count to total cars\n", "\n", " else: # if there are already car ids\n", "\n", " dx = np.zeros((len(cxx), len(carids))) # new arrays to calculate deltas\n", " dy = np.zeros((len(cyy), len(carids))) # new arrays to calculate deltas\n", "\n", " for i in range(len(cxx)): # loops through all centroids\n", "\n", " for j in range(len(carids)): # loops through all recorded car ids\n", "\n", " # acquires centroid from previous frame for specific carid\n", " oldcxcy = df.iloc[int(framenumber - 1)][str(carids[j])]\n", "\n", " # acquires current frame centroid that doesn't necessarily line up with previous frame centroid\n", " curcxcy = np.array([cxx[i], cyy[i]])\n", "\n", " if not oldcxcy: # checks if old centroid is empty in case car leaves screen and new car shows\n", "\n", " continue # continue to next carid\n", "\n", " else: # calculate centroid deltas to compare to current frame position later\n", "\n", " dx[i, j] = oldcxcy[0] - curcxcy[0]\n", " dy[i, j] = oldcxcy[1] - curcxcy[1]\n", "\n", " for j in range(len(carids)): # loops through all current car ids\n", "\n", " sumsum = np.abs(dx[:, j]) + np.abs(dy[:, j]) # sums the deltas wrt to car ids\n", "\n", " # finds which index carid had the min difference and this is true index\n", " correctindextrue = np.argmin(np.abs(sumsum))\n", " minx_index = correctindextrue\n", " miny_index = correctindextrue\n", "\n", " # acquires delta values of the minimum deltas in order to check if it is within radius later on\n", " mindx = dx[minx_index, j]\n", " mindy = dy[miny_index, j]\n", "\n", " if mindx == 0 and mindy == 0 and np.all(dx[:, j] == 0) and np.all(dy[:, j] == 0):\n", " # checks if minimum value is 0 and checks if all deltas are zero since this is empty set\n", " # delta could be zero if centroid didn't move\n", "\n", " continue # continue to next carid\n", "\n", " else:\n", "\n", " # if delta values are less than maximum radius then add that centroid to that specific carid\n", " if np.abs(mindx) < maxrad and np.abs(mindy) < maxrad:\n", "\n", " # adds centroid to corresponding previously existing carid\n", " df.at[int(framenumber), str(carids[j])] = [cxx[minx_index], cyy[miny_index]]\n", " minx_index2.append(minx_index) # appends all the indices that were added to previous carids\n", " miny_index2.append(miny_index)\n", "\n", " for i in range(len(cxx)): # loops through all centroids\n", "\n", " # if centroid is not in the minindex list then another car needs to be added\n", " if i not in minx_index2 and miny_index2:\n", "\n", " df[str(totalcars)] = \"\" # create another column with total cars\n", " totalcars = totalcars + 1 # adds another total car the count\n", " t = totalcars - 1 # t is a placeholder to total cars\n", " carids.append(t) # append to list of car ids\n", " df.at[int(framenumber), str(t)] = [cxx[i], cyy[i]] # add centroid to the new car id\n", "\n", " elif curcxcy[0] and not oldcxcy and not minx_index2 and not miny_index2:\n", " # checks if current centroid exists but previous centroid does not\n", " # new car to be added in case minx_index2 is empty\n", "\n", " df[str(totalcars)] = \"\" # create another column with total cars\n", " totalcars = totalcars + 1 # adds another total car the count\n", " t = totalcars - 1 # t is a placeholder to total cars\n", " carids.append(t) # append to list of car ids\n", " df.at[int(framenumber), str(t)] = [cxx[i], cyy[i]] # add centroid to the new car id\n", "\n", " # The section below labels the centroids on screen\n", "\n", " currentcars = 0 # current cars on screen\n", " currentcarsindex = [] # current cars on screen carid index\n", "\n", " for i in range(len(carids)): # loops through all carids\n", "\n", " if df.at[int(framenumber), str(carids[i])] != '':\n", " # checks the current frame to see which car ids are active\n", " # by checking in centroid exists on current frame for certain car id\n", "\n", " currentcars = currentcars + 1 # adds another to current cars on screen\n", " currentcarsindex.append(i) # adds car ids to current cars on screen\n", "\n", " for i in range(currentcars): # loops through all current car ids on screen\n", "\n", " # grabs centroid of certain carid for current frame\n", " curcent = df.iloc[int(framenumber)][str(carids[currentcarsindex[i]])]\n", "\n", " # grabs centroid of certain carid for previous frame\n", " oldcent = df.iloc[int(framenumber - 1)][str(carids[currentcarsindex[i]])]\n", "\n", " if curcent: # if there is a current centroid\n", "\n", " # On-screen text for current centroid\n", " cv2.putText(image, \"Centroid\" + str(curcent[0]) + \",\" + str(curcent[1]),\n", " (int(curcent[0]), int(curcent[1])), cv2.FONT_HERSHEY_SIMPLEX, .5, (0, 255, 255), 2)\n", "\n", " cv2.putText(image, \"ID:\" + str(carids[currentcarsindex[i]]), (int(curcent[0]), int(curcent[1] - 15)),\n", " cv2.FONT_HERSHEY_SIMPLEX, .5, (0, 255, 255), 2)\n", "\n", " cv2.drawMarker(image, (int(curcent[0]), int(curcent[1])), (0, 0, 255), cv2.MARKER_STAR, markerSize=5,\n", " thickness=1, line_type=cv2.LINE_AA)\n", "\n", " if oldcent: # checks if old centroid exists\n", " # adds radius box from previous centroid to current centroid for visualization\n", " xstart = oldcent[0] - maxrad\n", " ystart = oldcent[1] - maxrad\n", " xwidth = oldcent[0] + maxrad\n", " yheight = oldcent[1] + maxrad\n", " cv2.rectangle(image, (int(xstart), int(ystart)), (int(xwidth), int(yheight)), (0, 125, 0), 1)\n", "\n", " # checks if old centroid is on or below line and curcent is on or above line\n", " # to count cars and that car hasn't been counted yet\n", " if oldcent[1] >= lineypos2 and curcent[1] <= lineypos2 and carids[\n", " currentcarsindex[i]] not in caridscrossed:\n", "\n", " carscrossedup = carscrossedup + 1\n", " cv2.line(image, (0, lineypos2), (width, lineypos2), (0, 0, 255), 5)\n", " caridscrossed.append(\n", " currentcarsindex[i]) # adds car id to list of count cars to prevent double counting\n", "\n", " # checks if old centroid is on or above line and curcent is on or below line\n", " # to count cars and that car hasn't been counted yet\n", " elif oldcent[1] <= lineypos2 and curcent[1] >= lineypos2 and carids[\n", " currentcarsindex[i]] not in caridscrossed:\n", "\n", " carscrosseddown = carscrosseddown + 1\n", " cv2.line(image, (0, lineypos2), (width, lineypos2), (0, 0, 125), 5)\n", " caridscrossed.append(currentcarsindex[i])\n", "\n", " # Top left hand corner on-screen text\n", " cv2.rectangle(image, (0, 0), (250, 100), (255, 0, 0), -1) # background rectangle for on-screen text\n", "\n", " cv2.putText(image, \"Cars in Area: \" + str(currentcars), (0, 15), cv2.FONT_HERSHEY_SIMPLEX, .5, (0, 170, 0), 1)\n", "\n", " cv2.putText(image, \"Cars Crossed Up: \" + str(carscrossedup), (0, 30), cv2.FONT_HERSHEY_SIMPLEX, .5, (0, 170, 0),\n", " 1)\n", "\n", " cv2.putText(image, \"Cars Crossed Down: \" + str(carscrosseddown), (0, 45), cv2.FONT_HERSHEY_SIMPLEX, .5,\n", " (0, 170, 0), 1)\n", "\n", " cv2.putText(image, \"Total Cars Detected: \" + str(len(carids)), (0, 60), cv2.FONT_HERSHEY_SIMPLEX, .5,\n", " (0, 170, 0), 1)\n", "\n", " cv2.putText(image, \"Frame: \" + str(framenumber) + ' of ' + str(frames_count), (0, 75), cv2.FONT_HERSHEY_SIMPLEX,\n", " .5, (0, 170, 0), 1)\n", "\n", " cv2.putText(image, 'Time: ' + str(round(framenumber / fps, 2)) + ' sec of ' + str(round(frames_count / fps, 2))\n", " + ' sec', (0, 90), cv2.FONT_HERSHEY_SIMPLEX, .5, (0, 170, 0), 1)\n", "\n", " # displays images and transformations\n", " cv2.imshow(\"countours\", image)\n", " cv2.moveWindow(\"countours\", 0, 0)\n", "\n", " # cv2.imshow(\"fgmask\", fgmask)\n", " # cv2.moveWindow(\"fgmask\", int(width * ratio), 0)\n", "\n", " # Resize fgmask to match the dimensions of the original frame\n", " fgmask_resized = cv2.resize(fgmask, (width, height)) \n", "\n", " # Concatenate the original frame and the resized fgmask horizontally\n", " concatenated_image = np.concatenate((frame, fgmask_resized), axis=1)\n", " \n", " # Display the concatenated image\n", " plt.imshow(cv2.cvtColor(concatenated_image, cv2.COLOR_BGR2RGB))\n", " plt.axis('off') # Turn off axis\n", " plt.show()\n", "\n", " cv2.imshow(\"closing\", closing)\n", " cv2.moveWindow(\"closing\", width * 2, 0)\n", "\n", " cv2.imshow(\"opening\", opening)\n", " cv2.moveWindow(\"opening\", 0, int(height * ratio))\n", "\n", " cv2.imshow(\"dilation\", dilation)\n", " cv2.moveWindow(\"dilation\", int(width * ratio), int(height * ratio))\n", "\n", " cv2.imshow(\"binary\", bins)\n", " cv2.moveWindow(\"binary\", width * 2, int(height * ratio))\n", "\n", " # adds to dataframe frame number\n", " df.at[int(framenumber), \"framenumber\"] = framenumber\n", "\n", " k = cv2.waitKey(1)\n", " if k == ord('q'):\n", " break\n", " if framenumber == frames_count - 1: # if this is the last frame, exit\n", " break\n", " else:\n", " framenumber += 1 # increase the frame number\n", "\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "id": "43926339-1ac7-46f6-9b1b-7f01b523226e", "metadata": {}, "outputs": [], "source": [ "# Done processing the video, release resources\n", "cap.release()\n", "video.release()\n", "cv2.destroyAllWindows()" ] }, { "cell_type": "code", "execution_count": null, "id": "db30c949-f81f-4cdc-ac3c-1a24d64fcfb9", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "id": "a9be421d-8df7-4655-bd01-783b4e39e7a9", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.10" } }, "nbformat": 4, "nbformat_minor": 5 }