# Software AI person detector using MobileNetSSD # # 8JUL2018wbk # This version has a crude work-around for the problem of net.foreward() returning the previous detection # for one or more frames with not detections, especially prevalent under IR illumination. # # This is the current version to use on Pi3MeyeCV3, NoirOne, etc. dedicated systems # AI_detection.py is the initial version spawned from the Movidius version. # 18JUN2018wbk # The basic code I started with is here: # https://www.pyimagesearch.com/2018/05/14/a-gentle-guide-to-deep-learning-object-detection/ # # A few changes for the Raspberry PiCamera, less than one frame per second, but its usable! # # USAGE # with defaults # python PiCam_detection.py # specify a different model: # python PiCam_detection.py --prototxt MobileNetSSD_deploy.prototxt.txt --model MobileNetSSD_deploy.caffemodel --confidence 0.6 # # if starting over ssh -X can display the images for camera setup and demo: # python PiCam_detection.py -d 1 # import the necessary packages from imutils.video import VideoStream import numpy as np import argparse import cv2 import time #import imutils import paho.mqtt.client as mqtt import os import datetime import signal # setup signal handler for graceful shutdown QUIT=False def sigint_handler(signal, frame): global QUIT #print 'caught SIGINT!' QUIT=True def sighup_handler(signal, frame): global QUIT print 'caught SIGHUP!' QUIT=True def sigquit_handler(signal, frame): global QUIT print 'caught SIGQUIT!' QUIT=True def sigterm_handler(signal, frame): global QUIT print 'caught SIGTERM!' QUIT=True signal.signal(signal.SIGINT, sigint_handler) signal.signal(signal.SIGHUP, sighup_handler) signal.signal(signal.SIGQUIT, sigquit_handler) signal.signal(signal.SIGTERM, sigterm_handler) #signal.signal(signal.SIGSEGV, signal_handler) # python docs say it makes little sense to capture this one # this is a self-contained system, node-red can integrate it with others if needed MQTTserver="localhost" detectPath="/media/pi/AI/detect" subscribeTopic="AI/Control/#" # files will be saved /detectPath/yyyy-mm-dd/hh-mm-ss.jpg # a root crontab should run to delete them after N-days to keep the SD card from filling up # only detected images get written. if os.path.exists(detectPath) == False: os.mkdir(detectPath) # The callback for when the client receives a CONNACK response from the server. def on_connect(client, userdata, flags, rc): #print("Connected with result code "+str(rc)) # Subscribing in on_connect() means that if we lose the connection and # reconnect then subscriptions will be renewed. client.subscribe(subscribeTopic) # The callback for when a PUBLISH message is received from the server, aka message from SUBSCRIBE topic. # typical filepath from Motioneye: "/home/pi/Meye/2018-05-20/10-35-12.jpg" # this bit of code will likely be DVR depenent and probably should be parameterized AImode="Idle" # will be Email, Audio, or Idle via MQTT from alarmboneServer def on_message(client, userdata, msg): global AImode # would be Notify, Audio, or Idle if str(msg.topic) == "AI/Control/Mode": AImode = str(msg.payload) print(str(msg.topic)+": "+AImode) else: print(str(msg.topic)+": "+str(msg.payload)) # see what is happening def on_publish(client, userdata, mid): #print("mid: " + str(mid)) # don't think I need to care about this for now, print for initial tests pass def on_disconnect(client, userdata, rc): if rc != 0: print("Unexpected disconnection!") pass # construct the argument parse and parse the arguments ap = argparse.ArgumentParser() #ap.add_argument("-i", "--image", required=True, # help="path to input image") ap.add_argument("-p", "--prototxt", default="MobileNetSSD_deploy.prototxt.txt", help="path to Caffe 'deploy' prototxt file") ap.add_argument("-m", "--model", default="MobileNetSSD_deploy.caffemodel", help="path to Caffe pre-trained model") ap.add_argument("-c", "--confidence", type=float, default=0.6, help="minimum probability to filter weak detections") # running headless (AlarmPi)want display default=0 ap.add_argument("-d", "--display", type=int, default=0, help="1 to display detected image on screen") args = vars(ap.parse_args()) # initialize the list of class labels MobileNet SSD was trained to # detect, then generate a set of bounding box colors for each class CLASSES = ["background", "aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair", "cow", "diningtable", "dog", "horse", "motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor"] COLORS = np.random.uniform(0, 255, size=(len(CLASSES), 3)) COLORS[15] = (0,255,0) # force person box to be green IGNORE = set(["aeroplane", "boat", "bottle", "bus", "chair", "diningtable","pottedplant", "sofa", "train", "tvmonitor"]) # frame dimensions should be sqaure PREPROCESS_DIMS = (300, 300) # this is set by the model training #DISPLAY_DIMS = (960, 544) # set picamera capture to this resolution, 544 instead of 540 loses warning DISPLAY_DIMS = (1296, 976) # 1296x972 is suposed to be "more efficient" in picamara docs but imutils needs divisible by 8 values # load our serialized model from disk print("[INFO] loading model...") net = cv2.dnn.readNetFromCaffe(args["prototxt"], args["model"]) if args["display"] > 0: cv2.namedWindow("Detector Output") cv2.moveWindow("Detector Output", 25, 40) print("[INFO] starting the video stream ...") vs = VideoStream(usePiCamera=True, resolution=DISPLAY_DIMS).start() time.sleep(2) prevDetections=0 # this is the crude work-around, drop duplicate detections, seems real detections always vary a bit #count=-1 # connect to MQTT broker print("[INFO] connecting to MQTT broker...") client = mqtt.Client() client.on_connect = on_connect client.on_message = on_message client.on_publish = on_publish client.on_disconnect = on_disconnect client.will_set("AI/Status", "AI has died!", 2, True) # let everyone know we have died, perhaps node-red can restart it client.connect(MQTTserver, 1883, 60) client.loop() #client.publish("AI/Status", "", 2, True) # clear previous status #client.loop() # send message client.publish("AI/Status", "AI is running.", 2, True) client.loop() # send message # loop over frames from the video file stream idlePath="/media/pi/AI/idleImage" # name for temp file so UI can view "live" camera when in Idle mode idleName=idlePath while QUIT == False: #pump MQTT client.loop(0.2) #count=count+1 try: # grab the frame from the threaded video stream image = vs.read() # show the input image (h, w) = image.shape[:2] blob = cv2.dnn.blobFromImage(cv2.resize(image, PREPROCESS_DIMS), 0.007843, (300, 300), 127.5) # pass the blob through the network and obtain the detections and predictions net.setInput(blob) detections = net.forward() # loop over the detections person_found = False if np.array_equal(prevDetections, detections): if args["display"] > 0: cv2.imshow("Detector Output", image) key = cv2.waitKey(10) & 0xFF if key == ord("q"): break #print " duplicate detections frame: " + str(count) continue prevDetections=detections for i in np.arange(0, detections.shape[2]): # extract the confidence (i.e., probability) associated with the prediction confidence = detections[0, 0, i, 2] # filter out weak detections by ensuring the `confidence` is greater than the minimum confidence if confidence > args["confidence"]: # extract the index of the class label from the `detections`, # then compute the (x, y)-coordinates of the bounding box for the object idx = int(detections[0, 0, i, 1]) # if the predicted class label is in the set of classes we want to ignore then skip the detection if CLASSES[idx] in IGNORE: continue box = detections[0, 0, i, 3:7] * np.array([w, h, w, h]) (startX, startY, endX, endY) = box.astype("int") # display the prediction label = "{}: {:.0f}%".format(CLASSES[idx], confidence * 100) #print("[INFO] {}".format(label)) cv2.rectangle(image, (startX, startY), (endX, endY), COLORS[idx], 2) y = startY - 15 if startY - 15 > 15 else startY + 15 cv2.putText(image, label, (startX, y), cv2.FONT_HERSHEY_SIMPLEX, 0.5, COLORS[idx], 2) if idx == 15: xlen=endX-startX ylen=endY-startY if xlen/ylen < 1.0: # filter out some cars & background detected as person when looking into the setting/rising sun person_found = True currentDT = datetime.datetime.now() if person_found == True and AImode != "Idle": #create an output path and filename folder=currentDT.strftime("%Y-%m-%d") file=currentDT.strftime("%H:%M:%S") folder=str(detectPath + "/" + folder) if os.path.exists(folder) == False: os.mkdir(folder) outName=str(folder + "/" + file +".jpg") print outName cv2.imwrite(outName, image) # pass the result on to anyone that cares via MQTT client.publish("AI/Detection", outName) # the topic higherarchy perhaps needs more though but keep it simple to start elif AImode == "Idle": #delete last idle file if os.path.exists(idleName): os.remove(idleName) idleName=str(idlePath + "_" + currentDT.strftime("%H:%M:%S") + ".jpg") cv2.imwrite(idleName, image) client.publish("IdleImage", idleName) if args["display"] > 0: cv2.imshow("Detector Output", image) key = cv2.waitKey(10) & 0xFF # if the `q` key was pressed, break from the loop if key == ord("q"): break # if "ctrl+c" is pressed in the terminal, break from the loop # this doesn't work if not attached to terminal, added signal handler except KeyboardInterrupt: break # stop the video stream vs.stop() if args["display"] > 0: cv2.destroyAllWindows() # clean up MQTT client.publish("AI/Status", "AI is stopped.", 2, True) client.loop(0.2) client.loop(0.5) #client.wait_for_publish() # make sure last messagses are sent don't exist anymore?? client.disconnect() # normal exit, Will message should not be sent. # remove temp file if os.path.exists(idleName): os.remove(idleName)