diff --git a/.gitignore b/.gitignore index b661a1e..a28cacc 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ *.pt *.avi *.mp4 +*.jpg # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/README.md b/README.md index 5608df9..1d696f7 100644 --- a/README.md +++ b/README.md @@ -20,11 +20,11 @@ In addition to the detections, **it has been possible to track these objects and ### Information flow -![Information flow](readme-img/Untitled.png) +![Information flow](readme-img/graph_1.png) ### Count algorithm -![Count Algorithm](readme-img/Untitled%201.png) +![Count Algorithm](readme-img/graph_2.png) ## Results @@ -105,7 +105,7 @@ Do this following steps *in order.* nvtop # To run the nvtop ``` - ![nvidia-smi](/readme-img/Untitled%202.png) + ![nvidia-smi](/readme-img/nvidia-smi.png) **Pull nvidia-gpu image** @@ -117,26 +117,14 @@ Follow this **[installation guide.](https://docs.nvidia.com/datacenter/cloud-na git pull https://github.com/aagustinconti/yolov7_counting ``` -**Change permissions of ./start.sh** - -```bash -sudo chmod +x ./start.sh -``` **Download YOLOv7x pretrained weights** 1. Download the pretrained weights of YOLOv7x [here.](https://github.com/WongKinYiu/yolov7/blob/main/README.md#performance) 2. Save them in **./pretrained_weights** directory. -**RUN ./start.sh** - -To build the docker image if is not available in your PC and run the container with the propp - -```bash -./start.sh -``` -### Configuring the test +**Configuring the test** - Into the **test_oop.py** file you can modify some characteristics of the instance of the class *YoloSortCount()* before to execute the *run()* method. - By default: @@ -145,15 +133,18 @@ To build the docker image if is not available in your PC and run the container w - Classes: person (ID: 0) - Save: False -**RUN the test** -On the docker terminal run +**RUN docker_control.py** + +To build the docker image if it's not available in your PC and run the container with the propp ```bash - python test_oop.py +python3 docker_control.py ``` + + ## Thanks to [YOLOv7](https://github.com/WongKinYiu/yolov7/) diff --git a/dependencies/Dockerfile b/dependencies/Dockerfile index 88f24d1..dc636ef 100644 --- a/dependencies/Dockerfile +++ b/dependencies/Dockerfile @@ -1,5 +1,5 @@ # Specify the parent image from which we build -FROM pytorch/pytorch:latest +FROM pytorch/pytorch:1.13.1-cuda11.6-cudnn8-runtime # Copy the dependencies directory COPY . /dependencies_files_docker @@ -11,7 +11,8 @@ WORKDIR /dependencies_files_docker RUN pip install -r requirements.txt RUN chmod +x dependencies.sh RUN bash dependencies.sh -RUN cp -f ./upsampling.py /opt/conda/lib/python3.7/site-packages/torch/nn/modules/upsampling.py +RUN mv /opt/conda/lib/python3.10/site-packages/torch/nn/modules/upsampling.py /opt/conda/lib/python3.10/site-packages/torch/nn/modules/upsampling_wrong.py +RUN cp -f ./upsampling.py /opt/conda/lib/python3.10/site-packages/torch/nn/modules/upsampling.py # To go to the working directory WORKDIR /workspace diff --git a/dependencies/requirements.txt b/dependencies/requirements.txt index 89dbc03..f2cd1d1 100644 --- a/dependencies/requirements.txt +++ b/dependencies/requirements.txt @@ -1,17 +1,25 @@ +opencv-python==4.6.0.66 +scipy==1.7.3 +gdown==4.5.3 +tensorboard==2.10.1 +tensorboard-data-server==0.6.1 +tensorboard-plugin-wit==1.8.1 +pafy==0.5.5 +youtube-dl==2020.12.2 +pandas==1.3.5 + + absl-py==1.3.0 brotlipy==0.7.0 cachetools==5.2.0 -conda==4.13.0 -conda-build==3.21.9 cycler==0.11.0 Cython==0.29.32 deep-sort-realtime==1.3.1 dnspython==2.2.1 -easydict==1.10 flake8==5.0.4 fonttools==4.37.4 future==0.18.2 -gdown==4.5.3 + google-auth==2.13.0 google-auth-oauthlib==0.4.6 grpcio==1.50.0 @@ -32,16 +40,15 @@ nvidia-cuda-nvrtc-cu11==11.7.99 nvidia-cuda-runtime-cu11==11.7.99 nvidia-cudnn-cu11==8.5.0.96 oauthlib==3.2.2 -opencv-python==4.6.0.66 + packaging==21.3 -pafy==0.5.5 -pandas==1.3.5 + + Pillow==9.0.1 protobuf==3.19.6 pyasn1==0.4.8 pyasn1-modules==0.2.8 pycodestyle==2.9.1 -pycosat==0.6.3 pyflakes==2.5.0 pyparsing==3.0.9 python-dateutil==2.8.2 @@ -49,19 +56,12 @@ python-etcd==0.4.5 PyYAML==6.0 requests-oauthlib==1.3.1 rsa==4.9 -scipy==1.7.3 + seaborn==0.12.0 tb-nightly==2.11.0a20221105 -tensorboard==2.10.1 -tensorboard-data-server==0.6.1 -tensorboard-plugin-wit==1.8.1 -thop==0.1.1.post2209072238 -torch==1.13.0 -torchelastic==0.2.0 -torchtext==0.13.0 -torchvision==0.14.0 + Werkzeug==2.2.2 yacs==0.1.8 yapf==0.32.0 -youtube-dl==2020.12.2 + zipp==3.10.0 diff --git a/dependencies/upsampling.py b/dependencies/upsampling.py index 184f474..ee91ff4 100644 --- a/dependencies/upsampling.py +++ b/dependencies/upsampling.py @@ -153,8 +153,9 @@ def __init__(self, size: Optional[_size_any_t] = None, scale_factor: Optional[_r self.recompute_scale_factor = recompute_scale_factor def forward(self, input: Tensor) -> Tensor: - return F.interpolate(input, self.size, self.scale_factor, self.mode, self.align_corners) - #recompute_scale_factor=self.recompute_scale_factor) + return F.interpolate(input, self.size, self.scale_factor, self.mode, self.align_corners, + #recompute_scale_factor=self.recompute_scale_factor + ) def extra_repr(self) -> str: if self.scale_factor is not None: diff --git a/docker_control.py b/docker_control.py index f3434f6..34dfda4 100644 --- a/docker_control.py +++ b/docker_control.py @@ -23,13 +23,20 @@ logging.info("Checking if the Image already exists...") -if not os.system('[ "$(docker image inspect yolov7_detect_track_count:latest 2> /dev/null)" == [] ]'): +if str(os.popen("docker image inspect yolov7_detect_track_count:latest 2> /dev/null").read())[:2] == "[]": logging.info("The image doesn't exist. Building the Docker Image...") - os.system("cd ./dependencies") + os.chdir("./dependencies") + + cwd = os.getcwd() + logging.info(f"The current directory is --> {cwd}") + os.system("docker build -t yolov7_detect_track_count .") - os.system("cd ..") + os.chdir("..") + + cwd = os.getcwd() + logging.info(f"The current directory is --> {cwd}") else: logging.info("The image has already exists.") @@ -41,9 +48,15 @@ logging.info("Running a container...") + + container = client.containers.run( image='yolov7_detect_track_count:latest', + stdin_open=True, + tty=True, auto_remove=True, + network_mode='host', + name="test", device_requests=[docker.types.DeviceRequest( device_ids=["0"], capabilities=[['gpu']])], devices=["/dev/video0:/dev/video0"], @@ -52,6 +65,5 @@ cwd: {'bind': '/workspace', 'mode': 'rw'} }, environment=[f"DISPLAY={display}"], - tty=True, command='python test_oop.py' ) diff --git a/flask-app.py b/flask-app.py deleted file mode 100644 index 3fd1cbc..0000000 --- a/flask-app.py +++ /dev/null @@ -1,38 +0,0 @@ -# Import necessary libraries -from flask import Flask, render_template, Response -import cv2 -# Initialize the Flask app -app = Flask(__name__) - -#https://towardsdatascience.com/video-streaming-in-web-browsers-with-opencv-flask-93a38846fe00 -camera = cv2.VideoCapture("http://192.168.102.3:4747/video") -''' -for ip camera use - rtsp://username:password@ip_address:554/user=username_password='password'_channel=channel_number_stream=0.sdp' -for local webcam use cv2.VideoCapture(0) -''' - - -def gen_frames(): - while True: - success, frame = camera.read() # read the camera frame - if not success: - break - else: - ret, buffer = cv2.imencode('.jpg', frame) - frame = buffer.tobytes() - yield (b'--frame\r\n' - b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n') # concat - - -@app.route('/') -def index(): - return render_template('index.html') - - -@app.route('/video_feed') -def video_feed(): - return Response(gen_frames(), mimetype='multipart/x-mixed-replace; boundary=frame') - - -if __name__ == "__main__": - app.run(debug=True) diff --git a/readme-img/Untitled.png b/readme-img/graph_1.png similarity index 100% rename from readme-img/Untitled.png rename to readme-img/graph_1.png diff --git a/readme-img/Untitled 1.png b/readme-img/graph_2.png similarity index 100% rename from readme-img/Untitled 1.png rename to readme-img/graph_2.png diff --git a/readme-img/Untitled 2.png b/readme-img/nvidiasmi.png similarity index 100% rename from readme-img/Untitled 2.png rename to readme-img/nvidiasmi.png diff --git a/start.sh b/start.sh deleted file mode 100755 index f39d846..0000000 --- a/start.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash - -# Create the image of torch - -echo "Checking if the Image already exists..." - -if [[ "$(docker image inspect yolov7_detect_track_count:latest 2> /dev/null)" == [] ]]; - -then - echo "The image doesn't exist. Building the Docker Image..." - - cd ./dependencies - docker build -t yolov7_detect_track_count . - cd .. - -else - echo "The image has already exists." -fi - - -# And then run -# 1. To allow the use of screen -echo "Setting up X Server to accept connections. Turning Access control off..." -xhost + - -# 2. To run the image -echo "Runing the Docker Image in the current location -> Display: ON ; Web camera access: ON" -docker run --gpus all --rm -it -e DISPLAY=$DISPLAY -v $PWD:/workspace -v /tmp/.X11-unix:/tmp/.X11-unix:rw --device="/dev/video0:/dev/video0" yolov7_detect_track_count:latest diff --git a/templates/index.html b/templates/index.html deleted file mode 100644 index 44d4d83..0000000 --- a/templates/index.html +++ /dev/null @@ -1,10 +0,0 @@ - -
-
-
-

Live Streaming

- -
-
-
- \ No newline at end of file diff --git a/test_oop.py b/test_oop.py index 48c9c61..66ae002 100644 --- a/test_oop.py +++ b/test_oop.py @@ -12,11 +12,11 @@ WebCamera: 0 ---> DEFAULT Youtube Video or stream: "https://www.youtube.com/watch?v=qP1y7Tdab7Y" Stream URL: "http://IP/hls/stream_src.m3u8" -RSTP Stream: "http://192.168.102.3:4747/video" +RSTP Stream: "http://192.168.1.3:4747/video" Local video: "img_bank/cows_for_sale.mp4" Local image: "img_bank/img.jpg" | "img_bank/img.png" """ -test.video_path = 0 #"http://192.168.102.3:4747/video" +test.video_path = 0#"https://www.youtube.com/watch?v=2wqpy036z24" """ @@ -28,7 +28,7 @@ """ test.max_width = 720 test.max_fps = 25 # Max 1000 -test.inv_h_frame = False +test.inv_h_frame = True """ @@ -51,7 +51,7 @@ - Load the ROI color. """ #test.roi = [0,0,0,0] -test.auto_load_roi = False +test.auto_load_roi = True test.roi_color = (255, 255, 255) @@ -68,7 +68,7 @@ """ test.model_path = 'pretrained_weights/yolov7.pt' test.graphic_card = 0 -test.class_ids = [] +test.class_ids = [0] test.img_sz = 640 test.color = (0, 255, 0) test.conf_thres = 0.5 diff --git a/test_oop_2.py b/test_oop_2.py deleted file mode 100644 index 295df7c..0000000 --- a/test_oop_2.py +++ /dev/null @@ -1,137 +0,0 @@ -from yolov7_sort_count_oop import YoloSortCount - -#################### TEST #################### - -# INSTANCIATE - -test = YoloSortCount() - - -""" -###### AVAILABLE SOURCES ###### -WebCamera: 0 ---> DEFAULT -Youtube Video or stream: "https://www.youtube.com/watch?v=qP1y7Tdab7Y" -Stream URL: "http://IP/hls/stream_src.m3u8" -RSTP Stream: "http://192.168.102.3:4747/video" -Local video: "img_bank/cows_for_sale.mp4" -Local image: "img_bank/img.jpg" | "img_bank/img.png" -""" -test.video_path = "http://192.168.102.3:4747/video" - - -""" -###### FRAME PROPERTIES ###### - -- Set the max size of the frame (width) -- Set the max fps of the video -- Invert the image (In case of your WebCamera is mirrored, IE) -""" -test.max_width = 720 -test.max_fps = 25 # Max 1000 -test.inv_h_frame = False - - -""" -###### SHOWING RESULTS ###### - -- Show the results in your display (Interactive ROI, imshow of the out frame) -- In case of you are not showing the results, set the timer to stop the execution. -- Stop the frame with hold_image method in case you are using image as a source. -""" -test.show_img = True -test.ends_in_sec = 10 -test.hold_img = False - - -""" -###### ROI ###### - -- Load the ROI manually. -- -- Load the ROI color. -""" -#test.roi = [0,0,0,0] -test.auto_load_roi = True -test.roi_color = (255, 255, 255) - - -""" -###### DETECTION MODEL ###### - -- Specify the path of the model. -- Select the ID of your Graphic Card (nvidia-smi) -- Select the classes to detect -- Set the image size (Check if the YOLO model allows that --> IE: yolov7.pt 640, yolov7-w6.pt 1280 or 640) -- Set the bounding box color -- Set the minimum confidence to detect. -- Set the minimum overlap of a predicted versus actual bounding box for an object. -""" -test.model_path = 'pretrained_weights/yolov7.pt' -test.graphic_card = 0 -test.class_ids = [] -test.img_sz = 640 -test.color = (0, 255, 0) -test.conf_thres = 0.5 -test.iou_thres = 0.65 - -""" -###### TRACKING MODEL ###### - -- Specify the path of the model. -- Set the max distance between two points to consider a tracking object. -- Set the max overlap of a predicted versus actual bounding box for an object. -- Set the image size (Check if the YOLO model allows that --> IE: yolov7.pt 640, yolov7-w6.pt 1280 or 640) -- Set max_age to consider a lost of a tracking object that get out of the seen area. -- Set the minimum frames to start to track objects. -- Set the value that indicates how many previous frames of feature vectors should be retained for distance calculation for each track. -- Set the color of the centroid and label of a tracking object. -""" -test.deep_sort_model = "osnet_x1_0" -test.ds_max_dist = 0.1 -test.ds_max_iou_distance = 0.7 -test.ds_max_age = 30 -test.ds_n_init = 3 -test.ds_nn_budget = 100 -test.ds_color = (0, 0, 255) - -""" -###### PLOT RESULTS ###### - -- Specify the min x (left to right) to plot the draws -- Specify the min y (top to bottom) to plot the draws -- Specify padding between rectangles and text -- Specify the text color. -- Specify the rectangles color. -""" -test.plot_xmin = 10 -test.plot_ymin = 10 -test.plot_padding = 2 -test.plot_text_color = (255, 255, 255) -test.plot_bgr_color = (0, 0, 0) - - -""" -###### DEBUG TEXT ###### - -- Show the configs -- Show the detection output variables -- Show the tracking output variables -- Show counting output variables -""" -test.show_configs = False -test.show_detection = False -test.show_tracking = False -test.show_count = False - -""" -###### SAVING RESULTS ###### - -- Select if you want to save the results -- Select a location to save the results -""" -test.save_vid = False -test.save_loc = "results/result" - - -# Run -test.run() diff --git a/yolov7_sort_count_oop.py b/yolov7_sort_count_oop.py index 74ef4cd..bf2f11a 100644 --- a/yolov7_sort_count_oop.py +++ b/yolov7_sort_count_oop.py @@ -26,10 +26,6 @@ from count_oop import Count -""" - - -""" class YoloSortCount(): @@ -67,7 +63,6 @@ def __init__(self): self.ds_nn_budget = 100 self.ds_color = (0, 0, 255) - self.max_fps = 25 self.max_width = 720 # Pre defined @@ -75,6 +70,7 @@ def __init__(self): self.orig_w = None self.orig_h = None + self.orig_ratio = None self.orig_fps = None self.stopped = False @@ -93,7 +89,7 @@ def __init__(self): # Debug logging.basicConfig( - format='%(asctime)s | %(levelname)s: %(message)s', level=logging.NOTSET) + format='%(asctime)s | %(levelname)s: %(message)s', level=logging.INFO) self.show_configs = False self.show_detection = False @@ -154,28 +150,18 @@ def load_video_capture(self, video_path): logging.info(f'Source of the video: {video_path}') cap = cv2.VideoCapture(video_path) + + # To discard delayed frames + cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) + logging.info('The video capture has been loaded.') orig_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) orig_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) - - - if orig_w > self.max_width and orig_w != 0 : - logging.info( - 'Capture has more width than max. width allowed. Rezising...') - - orig_ratio = orig_h / orig_w - - cap = self.change_res(cap, self.max_width, orig_ratio) - - orig_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) - orig_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) - - logging.info(f'Capture has been resized to {(orig_w,orig_h)}') - + orig_ratio = orig_w / orig_h orig_fps = cap.get(cv2.CAP_PROP_FPS) % 100 - return cap, orig_w, orig_h, orig_fps + return cap, orig_w, orig_h, orig_ratio, orig_fps except Exception as err: raise ImportError( @@ -233,6 +219,7 @@ def load_tracking_model(self, deep_sort_model, max_dist, max_iou_distance, max_a """ try: + logging.info('This step may take a while...') deepsort = DeepSort(deep_sort_model, max_dist=max_dist, max_iou_distance=max_iou_distance, @@ -250,10 +237,15 @@ def load_roi(self): - To select the ROI, interactive way. """ - - cap_roi, _, _, _ = self.load_video_capture(self.video_path) + cap_roi, _, _,_,_= self.load_video_capture(self.video_path) + + orig_w_roi = int(cap_roi.get(cv2.CAP_PROP_FRAME_WIDTH)) + orig_h_roi = int(cap_roi.get(cv2.CAP_PROP_FRAME_HEIGHT)) + orig_ratio_roi = orig_w_roi / orig_h_roi + ret, select_roi_frame = cap_roi.read() - + + # To avoid the camera delay if not self.hold_img: frame_count_roi = 0 while frame_count_roi <= 3 and ret: @@ -261,6 +253,12 @@ def load_roi(self): ret, select_roi_frame = cap_roi.read() frame_count_roi += 1 + + # To adjust to the max width + if (self.max_width !=None) and (orig_w_roi != 0) and (orig_w_roi > self.max_width): + select_roi_frame = cv2.resize(select_roi_frame,(int(self.max_width), int(self.max_width/orig_ratio_roi))) + + # To show image correctly (IE: web camera) if self.inv_h_frame: select_roi_frame = cv2.flip(select_roi_frame, 1) @@ -327,17 +325,6 @@ def plot_text(self, frame, frame_w, fps, plot_xmin, plot_ymin, padding, counter_ return frame - def change_res(self, cap, max_width, orig_ratio): - """ - WHAT IT DOES: - - Change te resolution of the frame. - - """ - - cap.set(3, max_width) - cap.set(4, int(max_width * orig_ratio)) - - return cap def run(self): """ @@ -370,7 +357,7 @@ def run(self): self.roi = self.load_roi() logging.info('ROI has been loaded.') - cap, self.orig_w, self.orig_h, self.orig_fps = self.load_video_capture( + cap, self.orig_w, self.orig_h, self.orig_ratio, self.orig_fps = self.load_video_capture( self.video_path) if not self.roi: @@ -389,16 +376,25 @@ def run(self): start_ends_in_sec = time.time() + # Run detection while (cap.isOpened()): - - # Get frame + + # Get frame (The slow streaming video capture + # can be resolved by: + # https://stackoverflow.com/questions/58293187/opencv-real-time-streaming-video-capture-is-slow-how-to-drop-frames-or-get-sync ) + ret, self.frame = cap.read() + # To resize frames + if (self.max_width !=None) and (self.orig_w != 0) and (self.orig_w > self.max_width): + self.frame = cv2.resize(self.frame,(int(self.max_width), int(self.max_width/self.orig_ratio))) + # To show image correctly (IE: web camera) if self.inv_h_frame: self.frame = cv2.flip(self.frame, 1) + # If the video has not finished yet if ret: @@ -463,7 +459,7 @@ def run(self): self.stopped = True break else: - if cv2.waitKey(int(1000/self.max_fps)) & 0xFF == ord('q'): + if cv2.waitKey(1) & 0xFF == ord('q'): logging.info('Exiting by keyboard...') self.stopped = True break