diff --git a/README.md b/README.md index c8107378dada63bf4e092718fbe13b49905a3a2f..db9ebef1c1f1cf0488f3ee7d9a3a6d6bbf625834 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ ========== # Run the server-side FastAPI app ## In development mode +Using the template files, you first need to create backend/.env and backend/cameras.py. You can start a virtual environment with the following instructions. ```sh @@ -34,6 +35,8 @@ Run the server, `docker-compose up -d` # Run the client-side React app in a different terminal window: +Using the template file, you first need to create frontend/.env. + ```sh $ cd frontend $ npm install @@ -44,6 +47,18 @@ Navigate to [http://localhost:3000](http://localhost:3000) <br/> +# Documentation + +## Algorithm + +The crowd-counting AI model used is based on this repository: +https://github.com/ZhengPeng7/W-Net-Keras +The dataset used is ShanghaiTech Part B. +The model is given a 3-channel image and generates a density map of half the size of the input image. +The estimated number of people is obtained by summing on all pixels of the density map. + +<br/> + # TODO ## Coté algo - Accéder à d'autre infos telle que l'API des cours sur demande à la DISI pour intégrer ça aux prédictions (ex: cours en promo complète juste implique plus d'attente) diff --git a/backend/cameras.py.template b/backend/cameras.py.template index 5d197e572134b2e9d63518bcbcd5a227ee42bde0..001fe763574175b9b429e3c475371d012175d57b 100644 --- a/backend/cameras.py.template +++ b/backend/cameras.py.template @@ -1,18 +1,25 @@ restaurants = [ { "restaurant": "local", - "a_factor": 30, - "b_factor": 120, + # the linear function estimating the waiting time t based on the number of people n and the number c of open checkouts is: + # t = a * n / c + b + "a_factor": 30, # slope + "b_factor": 120, # y-intercept + # list of all the cameras that correspond to a same restaurant "cameras": [ - { + { + # the RTSP url will be: rtsp://user:password@IP:stream "IP": "", "user": "", "password": "", "stream": "stream1", + # list of the coordinates of the points which constitue the region that should be masked on the picture "mask_points": [ + # polygon which should be part of the mask [ + # [x, y] coordinates of each vertex of the polygon (on a 1280*720 picture) [70, 370], [420, 720], [1280, 720], @@ -22,8 +29,10 @@ restaurants = [ [130, 350] ] ], - "caisses": + # list of the coordinates of each checkout + "checkouts": [ + # each checkout corresponds to a rectangle, indicated by two x and two y coordinates { "x1": 380, "x2": 435, diff --git a/backend/video_capture.py b/backend/video_capture.py index 08cf8533470937c824551fdf2c7e28441e7858e8..9e9e1cca1e72e91826c536070f36082f6aabd567 100644 --- a/backend/video_capture.py +++ b/backend/video_capture.py @@ -17,6 +17,7 @@ async def handle_cameras(): db = SessionLocal() for restaurant in restaurants: for camera in restaurant["cameras"]: + # each camera masked is constructed based on the given coordiantes and saved mask = np.zeros((720, 1280, 3), dtype=np.float32) cv2.fillPoly(mask, np.array(camera["mask_points"]), (255, 255, 255)) camera["mask"] = mask @@ -28,6 +29,7 @@ async def handle_cameras(): for restaurant in restaurants: + # we check whether or not the restaurant is opened is_open = db.query( models.OpeningHours).filter( models.OpeningHours.place == restaurant["restaurant"], @@ -36,13 +38,15 @@ async def handle_cameras(): models.OpeningHours.close_time >= current_time).first() is not None if is_open: - count_prediction = 0 - open_checkouts = 0 - cams_working = True + count_prediction = 0 # estimated number of people in the restaurant + open_checkouts = 0 # estimated number of open checkouts in the restaurant + cams_working = True # boolean indicating whether or not all cams are working in the restaurant for camera in restaurant["cameras"]: + # connection to the rtsp stream cap = cv2.VideoCapture(f'rtsp://{camera["user"]}:{camera["password"]}@{camera["IP"]}/{camera["stream"]}') if cams_working and cap.isOpened(): + # extraction and preoprocessing of the first frame _, frame = cap.read() masked_img = cv2.bitwise_and( frame.astype(np.float32), camera["mask"]) @@ -53,16 +57,19 @@ async def handle_cameras(): np.array( [treated_img]))), axis=0) + # getting the density map from the model and the number of people pred_map = np.squeeze(model.predict(input_image, verbose=0)) count_prediction += np.sum(pred_map) - for caisse in camera["caisses"]: - if np.sum(pred_map[caisse["x1"] // 2:caisse["x2"] // 2, caisse["y1"] // 2:caisse["y2"] // 2]) > 0.5: + for checkout in camera["checkouts"]: + # we check whether or not the density in the checkout area is high enough to determine if it is open or not + if np.sum(pred_map[checkout["x1"] // 2:checkout["x2"] // 2, checkout["y1"] // 2:checkout["y2"] // 2]) > 0.5: open_checkouts += 1 else: cams_working = False cap.release() if cams_working and open_checkouts: + # the estimated waiting time is calculated and put in the database waiting_time = timedelta( seconds=restaurant['b_factor'] + int(count_prediction * @@ -75,4 +82,5 @@ async def handle_cameras(): db.add(db_record) db.commit() await manager.broadcast(json.dumps({"type": "data"})) + # sleeping enough time so that there is a 60 second delay between the start of two consecutive loops time.sleep(max(0, 60 - (datetime.now() - current_date).total_seconds()))