2019-01-26 17:02:59 +03:00
import os
import cv2
2019-02-09 17:51:11 +03:00
import imutils
2019-01-26 17:02:59 +03:00
import time
import datetime
import ctypes
import logging
import multiprocessing as mp
2019-03-25 14:24:36 +03:00
import queue
2019-02-04 15:18:49 +03:00
import threading
2019-02-10 21:00:52 +03:00
import json
2019-01-26 17:02:59 +03:00
from contextlib import closing
import numpy as np
from object_detection . utils import visualization_utils as vis_util
2019-02-28 05:55:07 +03:00
from flask import Flask , Response , make_response , send_file
2019-02-10 21:00:52 +03:00
import paho . mqtt . client as mqtt
2019-01-26 17:02:59 +03:00
2019-02-26 05:27:02 +03:00
from frigate . util import tonumpyarray
from frigate . mqtt import MqttMotionPublisher , MqttObjectPublisher
2019-02-28 05:55:07 +03:00
from frigate . objects import ObjectParser , ObjectCleaner , BestPersonFrame
2019-02-26 05:27:02 +03:00
from frigate . motion import detect_motion
2019-02-27 05:29:52 +03:00
from frigate . video import fetch_frames , FrameTracker
2019-03-25 14:24:36 +03:00
from frigate . object_detection import FramePrepper , PreppedQueueProcessor , detect_objects
2019-01-26 17:02:59 +03:00
2019-02-26 05:27:02 +03:00
RTSP_URL = os . getenv ( ' RTSP_URL ' )
2019-01-26 17:02:59 +03:00
2019-02-10 21:00:52 +03:00
MQTT_HOST = os . getenv ( ' MQTT_HOST ' )
2019-03-09 22:41:35 +03:00
MQTT_USER = os . getenv ( ' MQTT_USER ' )
MQTT_PASS = os . getenv ( ' MQTT_PASS ' )
2019-02-10 23:43:21 +03:00
MQTT_TOPIC_PREFIX = os . getenv ( ' MQTT_TOPIC_PREFIX ' )
2019-02-10 21:00:52 +03:00
2019-03-27 14:55:32 +03:00
# REGIONS = "300,0,0,2000,200,no-mask-300.bmp:300,300,0,2000,200,no-mask-300.bmp:300,600,0,2000,200,no-mask-300.bmp:300,900,0,2000,200,no-mask-300.bmp:300,0,300,2000,200,no-mask-300.bmp:300,300,300,2000,200,no-mask-300.bmp:300,600,300,2000,200,no-mask-300.bmp:300,900,300,2000,200,no-mask-300.bmp"
2019-02-10 05:38:11 +03:00
# REGIONS = "400,350,250,50"
2019-03-27 14:55:32 +03:00
REGIONS = os . getenv ( ' REGIONS ' )
2019-02-02 06:38:13 +03:00
2019-02-24 17:41:03 +03:00
DEBUG = ( os . getenv ( ' DEBUG ' ) == ' 1 ' )
2019-01-26 17:02:59 +03:00
def main ( ) :
2019-02-26 05:27:02 +03:00
DETECTED_OBJECTS = [ ]
2019-03-27 14:17:00 +03:00
recent_frames = { }
2019-02-04 16:07:13 +03:00
# Parse selected regions
regions = [ ]
for region_string in REGIONS . split ( ' : ' ) :
region_parts = region_string . split ( ' , ' )
regions . append ( {
' size ' : int ( region_parts [ 0 ] ) ,
' x_offset ' : int ( region_parts [ 1 ] ) ,
2019-02-10 05:38:11 +03:00
' y_offset ' : int ( region_parts [ 2 ] ) ,
2019-02-25 15:48:01 +03:00
' min_person_area ' : int ( region_parts [ 3 ] ) ,
2019-03-20 15:11:38 +03:00
# array for prepped frame with shape (1, 300, 300, 3)
' prepped_frame_array ' : mp . Array ( ctypes . c_uint8 , 300 * 300 * 3 ) ,
# shared value for storing the prepped_frame_time
' prepped_frame_time ' : mp . Value ( ' d ' , 0.0 ) ,
# Lock to control access to the prepped frame
' prepped_frame_lock ' : mp . Lock ( )
2019-02-04 16:07:13 +03:00
} )
2019-02-10 23:25:17 +03:00
# capture a single frame and check the frame shape so the correct array
# size can be allocated in memory
video = cv2 . VideoCapture ( RTSP_URL )
ret , frame = video . read ( )
if ret :
frame_shape = frame . shape
else :
print ( " Unable to capture video stream " )
exit ( 1 )
video . release ( )
2019-03-26 04:35:44 +03:00
2019-01-26 17:02:59 +03:00
# compute the flattened array length from the array shape
flat_array_length = frame_shape [ 0 ] * frame_shape [ 1 ] * frame_shape [ 2 ]
2019-02-01 15:35:48 +03:00
# create shared array for storing the full frame image data
2019-03-17 17:03:52 +03:00
shared_arr = mp . Array ( ctypes . c_uint8 , flat_array_length )
2019-02-09 19:17:07 +03:00
# create shared value for storing the frame_time
shared_frame_time = mp . Value ( ' d ' , 0.0 )
2019-02-26 05:27:02 +03:00
# Lock to control access to the frame
2019-02-17 20:52:56 +03:00
frame_lock = mp . Lock ( )
# Condition for notifying that a new frame is ready
frame_ready = mp . Condition ( )
2019-02-17 22:12:27 +03:00
# Condition for notifying that objects were parsed
objects_parsed = mp . Condition ( )
2019-02-24 17:13:36 +03:00
# Queue for detected objects
2019-03-28 04:44:57 +03:00
object_queue = queue . Queue ( )
2019-03-25 14:24:36 +03:00
# Queue for prepped frames
2019-03-26 04:35:44 +03:00
prepped_frame_queue = queue . Queue ( len ( regions ) * 2 )
2019-02-26 05:27:02 +03:00
2019-02-02 06:38:13 +03:00
# shape current frame so it can be treated as an image
frame_arr = tonumpyarray ( shared_arr ) . reshape ( frame_shape )
2019-01-26 17:02:59 +03:00
2019-02-26 05:27:02 +03:00
# start the process to capture frames from the RTSP stream and store in a shared array
2019-02-10 23:25:17 +03:00
capture_process = mp . Process ( target = fetch_frames , args = ( shared_arr ,
2019-02-26 05:27:02 +03:00
shared_frame_time , frame_lock , frame_ready , frame_shape , RTSP_URL ) )
2019-01-26 17:02:59 +03:00
capture_process . daemon = True
2019-03-27 14:17:00 +03:00
# for each region, start a separate thread to resize the region and prep for detection
2019-03-25 14:24:36 +03:00
detection_prep_threads = [ ]
2019-02-17 21:12:48 +03:00
for region in regions :
2019-03-25 14:24:36 +03:00
detection_prep_threads . append ( FramePrepper (
frame_arr ,
2019-02-09 19:17:07 +03:00
shared_frame_time ,
2019-03-25 14:24:36 +03:00
frame_ready ,
frame_lock ,
2019-02-19 15:47:00 +03:00
region [ ' size ' ] , region [ ' x_offset ' ] , region [ ' y_offset ' ] ,
2019-03-25 14:24:36 +03:00
prepped_frame_queue
) )
2019-01-26 17:02:59 +03:00
2019-03-25 14:24:36 +03:00
prepped_queue_processor = PreppedQueueProcessor (
2019-03-28 04:44:57 +03:00
prepped_frame_queue ,
object_queue
2019-03-25 14:24:36 +03:00
)
prepped_queue_processor . start ( )
2019-02-27 05:29:52 +03:00
# start a thread to store recent motion frames for processing
frame_tracker = FrameTracker ( frame_arr , shared_frame_time , frame_ready , frame_lock ,
2019-03-27 14:17:00 +03:00
recent_frames )
2019-02-27 05:29:52 +03:00
frame_tracker . start ( )
2019-02-28 05:55:07 +03:00
# start a thread to store the highest scoring recent person frame
2019-03-27 14:17:00 +03:00
best_person_frame = BestPersonFrame ( objects_parsed , recent_frames , DETECTED_OBJECTS )
2019-02-28 05:55:07 +03:00
best_person_frame . start ( )
2019-02-26 05:27:02 +03:00
# start a thread to parse objects from the queue
2019-03-27 14:55:32 +03:00
object_parser = ObjectParser ( object_queue , objects_parsed , DETECTED_OBJECTS , regions )
2019-02-09 18:15:55 +03:00
object_parser . start ( )
2019-02-26 05:27:02 +03:00
# start a thread to expire objects from the detected objects list
2019-03-27 14:17:00 +03:00
object_cleaner = ObjectCleaner ( objects_parsed , DETECTED_OBJECTS )
2019-02-24 17:13:36 +03:00
object_cleaner . start ( )
2019-02-04 15:18:49 +03:00
2019-02-26 05:27:02 +03:00
# connect to mqtt and setup last will
2019-03-27 14:17:00 +03:00
def on_connect ( client , userdata , flags , rc ) :
2019-02-28 15:30:34 +03:00
print ( " On connect called " )
# publish a message to signal that the service is running
client . publish ( MQTT_TOPIC_PREFIX + ' /available ' , ' online ' , retain = True )
2019-02-17 21:59:51 +03:00
client = mqtt . Client ( )
2019-02-28 15:30:34 +03:00
client . on_connect = on_connect
2019-02-17 21:59:51 +03:00
client . will_set ( MQTT_TOPIC_PREFIX + ' /available ' , payload = ' offline ' , qos = 1 , retain = True )
2019-03-09 22:41:35 +03:00
if not MQTT_USER is None :
client . username_pw_set ( MQTT_USER , password = MQTT_PASS )
2019-02-17 21:59:51 +03:00
client . connect ( MQTT_HOST , 1883 , 60 )
client . loop_start ( )
2019-02-26 05:27:02 +03:00
# start a thread to publish object scores (currently only person)
2019-02-28 15:49:27 +03:00
mqtt_publisher = MqttObjectPublisher ( client , MQTT_TOPIC_PREFIX , objects_parsed , DETECTED_OBJECTS )
2019-02-10 21:00:52 +03:00
mqtt_publisher . start ( )
2019-02-26 05:27:02 +03:00
# start the process of capturing frames
2019-01-26 17:02:59 +03:00
capture_process . start ( )
print ( " capture_process pid " , capture_process . pid )
2019-02-26 05:27:02 +03:00
2019-03-27 14:17:00 +03:00
# start the object detection prep threads
2019-03-25 14:24:36 +03:00
for detection_prep_thread in detection_prep_threads :
detection_prep_thread . start ( )
2019-02-09 17:51:11 +03:00
2019-02-26 05:27:02 +03:00
# create a flask app that encodes frames a mjpeg on demand
2019-02-09 18:15:55 +03:00
app = Flask ( __name__ )
2019-02-28 05:55:07 +03:00
@app.route ( ' /best_person.jpg ' )
def best_person ( ) :
frame = np . zeros ( frame_shape , np . uint8 ) if best_person_frame . best_frame is None else best_person_frame . best_frame
ret , jpg = cv2 . imencode ( ' .jpg ' , frame )
response = make_response ( jpg . tobytes ( ) )
response . headers [ ' Content-Type ' ] = ' image/jpg '
return response
2019-02-09 18:15:55 +03:00
@app.route ( ' / ' )
def index ( ) :
# return a multipart response
return Response ( imagestream ( ) ,
mimetype = ' multipart/x-mixed-replace; boundary=frame ' )
def imagestream ( ) :
while True :
# max out at 5 FPS
time . sleep ( 0.2 )
# make a copy of the current detected objects
detected_objects = DETECTED_OBJECTS . copy ( )
2019-02-17 20:54:40 +03:00
# lock and make a copy of the current frame
2019-02-17 21:12:48 +03:00
with frame_lock :
frame = frame_arr . copy ( )
2019-02-09 18:15:55 +03:00
# convert to RGB for drawing
frame = cv2 . cvtColor ( frame , cv2 . COLOR_BGR2RGB )
# draw the bounding boxes on the screen
2019-02-09 19:17:07 +03:00
for obj in detected_objects :
2019-02-09 18:15:55 +03:00
vis_util . draw_bounding_box_on_image_array ( frame ,
obj [ ' ymin ' ] ,
obj [ ' xmin ' ] ,
obj [ ' ymax ' ] ,
obj [ ' xmax ' ] ,
color = ' red ' ,
thickness = 2 ,
display_str_list = [ " {} : {} % " . format ( obj [ ' name ' ] , int ( obj [ ' score ' ] * 100 ) ) ] ,
use_normalized_coordinates = False )
for region in regions :
2019-02-10 23:25:17 +03:00
color = ( 255 , 255 , 255 )
2019-02-09 18:15:55 +03:00
cv2 . rectangle ( frame , ( region [ ' x_offset ' ] , region [ ' y_offset ' ] ) ,
( region [ ' x_offset ' ] + region [ ' size ' ] , region [ ' y_offset ' ] + region [ ' size ' ] ) ,
2019-02-10 23:25:17 +03:00
color , 2 )
2019-02-09 19:17:07 +03:00
2019-02-09 18:15:55 +03:00
# convert back to BGR
frame = cv2 . cvtColor ( frame , cv2 . COLOR_RGB2BGR )
# encode the image into a jpg
ret , jpg = cv2 . imencode ( ' .jpg ' , frame )
yield ( b ' --frame \r \n '
b ' Content-Type: image/jpeg \r \n \r \n ' + jpg . tobytes ( ) + b ' \r \n \r \n ' )
app . run ( host = ' 0.0.0.0 ' , debug = False )
2019-01-26 17:02:59 +03:00
capture_process . join ( )
2019-03-25 14:24:36 +03:00
for detection_prep_thread in detection_prep_threads :
detection_prep_thread . join ( )
2019-02-27 05:29:52 +03:00
frame_tracker . join ( )
2019-02-28 05:55:07 +03:00
best_person_frame . join ( )
2019-02-09 18:15:55 +03:00
object_parser . join ( )
2019-02-24 17:13:36 +03:00
object_cleaner . join ( )
2019-02-10 21:00:52 +03:00
mqtt_publisher . join ( )
2019-01-26 17:02:59 +03:00
if __name__ == ' __main__ ' :
main ( )