PyImageSearch / imutils

A series of convenience functions to make basic image processing operations such as translation, rotation, resizing, skeletonization, and displaying Matplotlib images easier with OpenCV and Python.
MIT License
4.54k stars 1.03k forks source link

Camera Read and Write FPS not syncced - Python #204

Closed weihan08 closed 4 years ago

weihan08 commented 4 years ago

Hi I am using Raspberry Pi 4b+ (4GB RAM) to implement a driver drowsiness detection system. The problem is apparently the processing of image and drowsiness determination is very taxing, so when the camera points at me the FPS reduces to 13 (Normally it is able to handle 30 FPS @ Full HD), whereas when the camera points to anywhere without a face, the FPS rises to 30. I have used threads to optimize the I/O read, but it doesn't seem to improve the FPS.

In the end, I don't mind if the whole video is 13 FPS, but the problem is when writing the video using openCV VideoWriter, I can only initialize the FPS once. So, when the camera FPS is variable but the video write is fixed, the video written will be fluctuating in speed all the time (slow with no face; normal with face).

As you can see from the code, for some reason, initializing the camera framerate to match the Video Write doesn't help. Writing is still not in sync for some reason.

Hope someone can help! Thanks in advance.

#python drowniness_yawn.py --webcam webcam_index

from scipy.spatial import distance as dist
# from imutils.video import VideoStream
from imutils.video.pivideostream import PiVideoStream
from imutils.video import FPS
from imutils import face_utils
from threading import Thread
import numpy as np
import argparse
import imutils
import datetime as dt
import time
import dlib
import cv2
import os

def alarm(msg):
    global alarm_status
    global alarm_status2
    global saying

    while alarm_status:
        print('Eyes Closed')
        saying = True
#         s = 'espeak "'+msg+'"'
#         os.system(s)
        saying = False

    if alarm_status2:
        print('Yawn')
        saying = True
#         s = 'espeak "' + msg + '"'
#         os.system(s)
        saying = False

def eye_aspect_ratio(eye):
    A = dist.euclidean(eye[1], eye[5])
    B = dist.euclidean(eye[2], eye[4])

    C = dist.euclidean(eye[0], eye[3])

    ear = (A + B) / (2.0 * C)

    return ear

def final_ear(shape):
    (lStart, lEnd) = face_utils.FACIAL_LANDMARKS_IDXS["left_eye"]
    (rStart, rEnd) = face_utils.FACIAL_LANDMARKS_IDXS["right_eye"]

    leftEye = shape[lStart:lEnd]
    rightEye = shape[rStart:rEnd]

    leftEAR = eye_aspect_ratio(leftEye)
    rightEAR = eye_aspect_ratio(rightEye)

    ear = (leftEAR + rightEAR) / 2.0
    return (ear, leftEye, rightEye)

def lip_distance(shape):
    top_lip = shape[50:53]
    top_lip = np.concatenate((top_lip, shape[61:64]))

    low_lip = shape[56:59]
    low_lip = np.concatenate((low_lip, shape[65:68]))

    top_mean = np.mean(top_lip, axis=0)
    low_mean = np.mean(low_lip, axis=0)

    distance = abs(top_mean[1] - low_mean[1])
    return distance

ap = argparse.ArgumentParser()
ap.add_argument("-w", "--webcam", type=int, default=0,
                help="index of webcam on system")
args = vars(ap.parse_args())

EYE_AR_THRESH = 0.3
EYE_AR_CONSEC_FRAMES = 45
YAWN_THRESH = 40
alarm_status = False
alarm_status2 = False
saying = False
COUNTER = 0

print("-> Loading the predictor and detector...")
#detector = dlib.get_frontal_face_detector()
detector = cv2.CascadeClassifier("haarcascade_frontalface_default.xml")    #Faster but less accurate
predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat')

print("-> Starting Video Stream")
#vs = VideoStream(src=args["webcam"]).start()
vs= PiVideoStream().start()
vs.camera.framerate = 13

# vs= VideoStream(usePiCamera=True, framerate = 15).start()      #For Raspberry Pi
time.sleep(1.0)

fps = FPS().start()  

fourcc = cv2.VideoWriter_fourcc(*"mp4v")
writer = None
(h,w) = (None, None)

while True:
    timestamp = dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    frame = vs.read()
    frame = imutils.resize(frame, width=400)
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    fps.update()
    fps.stop()

    NEW_FPS = round(fps.fps(),2)
    print(NEW_FPS)
    if writer is None:
        (height,width,layers)= frame.shape
        writer = cv2.VideoWriter('/home/pi/Desktop/video.mp4', fourcc, 13 , (width,height), True)
    cv2.putText(frame, timestamp,(130, 280), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 0, 255), 2)
    #rects = detector(gray, 0)
    rects = detector.detectMultiScale(gray, scaleFactor=1.1, 
        minNeighbors=5, minSize=(30, 30),
        flags=cv2.CASCADE_SCALE_IMAGE)

    #for rect in rects:
    for (x, y, w, h) in rects:
        rect = dlib.rectangle(int(x), int(y), int(x + w),int(y + h))

        shape = predictor(gray, rect)
        shape = face_utils.shape_to_np(shape)

        eye = final_ear(shape)
        ear = eye[0]
        leftEye = eye[1]
        rightEye = eye[2]

        distance = lip_distance(shape)

        leftEyeHull = cv2.convexHull(leftEye)
        rightEyeHull = cv2.convexHull(rightEye)
        cv2.drawContours(frame, [leftEyeHull], -1, (0, 255, 0), 1)
        cv2.drawContours(frame, [rightEyeHull], -1, (0, 255, 0), 1)

        lip = shape[48:60]
        cv2.drawContours(frame, [lip], -1, (0, 255, 0), 1)

        if ear < EYE_AR_THRESH:
            COUNTER += 1

            if COUNTER >= EYE_AR_CONSEC_FRAMES:
                if alarm_status == False and saying == False:
                    alarm_status = True
                    t = Thread(target=alarm, args=('wake up sir',))
                    t.deamon = True
                    t.start()

                cv2.putText(frame, "DROWSINESS ALERT!", (10, 30),
                            cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)

        else:
            COUNTER = 0
            alarm_status = False

        if (distance > YAWN_THRESH):
                cv2.putText(frame, "Yawn Alert", (10, 30),
                            cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
                if alarm_status2 == False and saying == False:
                    alarm_status2 = True
                    t = Thread(target=alarm, args=('take some fresh air sir',))
                    t.deamon = True
                    t.start()
        else:
            alarm_status2 = False

        cv2.putText(frame, "EAR: {:.2f}".format(ear), (250, 30),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
        cv2.putText(frame, "YAWN: {:.2f}".format(distance), (250, 60),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)

    writer.write(frame)
    cv2.imshow("Frame", frame)
    key = cv2.waitKey(1) & 0xFF

    if key == ord("q"):
        fps.stop()
        print("Elapsed Time: {:.2f}".format(fps.elapsed()))
        print("approx FPS: {:.2f}".format(fps.fps()))
        break

cv2.destroyAllWindows()
vs.stop()
writer.release()