Skip to main content

VideoStream

usbwebcamera.py

import osimport sysimport timeimport mathimport getoptimport numpy as npimport cv2import threadingimport subprocessfrom collections import dequefrom lock_manager import Lock_Managerfrom util import Utilclass UsbWebCamera(threading.Thread):        def __init__(self, video_source=1, source=None, do_record=True, do_display=True, do_add_contours=True, do_add_target=False):                threading.Thread.__init__(self)                self.name = self.__class__.__name__                self.archive = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'archive')                self.writer = None                self.current_frame = None                self.codec = cv2.VideoWriter_fourcc('M','J', 'P', 'G')                self.OBSERVER_LENGTH = 5 # Time in seconds to be observed for motion                self.threshold = 15                self.CAMERA_SOURCE = video_source                self.REMAIN_RECORDING_FILES = 3 # 10이상 부터 삭제 후 저장                self.do_display = do_display                self.do_record = do_record                self.do_add_contours = do_add_contours                self.do_add_target = do_add_target                self.current_file = None                self.source = cv2.VideoCapture(source) if source is not None else self.init_camera()                self.fps = self.find_fps(self.source)                self.height, self.width = self.get_dimensions(self.source)                Util.log(self.name, "Initializing usb camera class with video_source=" + str(self.CAMERA_SOURCE))                Util.log(self.name,"width: {"+str(self.width)+"}, height : {"+str(self.height)+"}")                self.lock_manager = Lock_Manager("motion")        def __del__(self):                # Release camera                self.source.release()                # Close all windows                cv2.destroyAllWindows()                # Remove lock if exists                self.lock_manager.remove()        def get_frame(self):                """                Return the current frame                @return bytes                """                return self.frame_to_jpg(self.current_frame) if self.current_frame is not None else None        def frame_to_jpg(self, frame):                """                Convert video frame to jpg                @param array frame                @return bytes                """                ret, jpeg = cv2.imencode('.jpg', self.current_frame)                return jpeg.tobytes()        def get_dimensions(self, source):                """                Determine height and width of the video source                @return tuple(int, int)                """                frame = cv2.cvtColor(source.read()[1],cv2.COLOR_RGB2GRAY)                return frame.shape[0: 2]        def find_fps(self, source):                """                Determine frames per second of the video source                @param video source                @return int                """                Util.log(self.name, "Determining FPS...")                # How many frames to capture                num_frames = 120                # Start time                start = time.time()                # Grab a few frames                for i in range(0, num_frames):                        ret, frame = source.read()                # End time                end = time.time()                # Calculate frames per second                fps = int(math.floor(num_frames / (end - start)))                Util.log(self.name, "Setting FPS to " + str(fps))                return fps        def init_camera(self):                """                Start the camera                @return cv2.VideoCapture                """                # Init camera                camera = cv2.VideoCapture(self.CAMERA_SOURCE)                #camera.set(3, 320)                #camera.set(4, 240)                # Wait half a second for light adjustment                time.sleep(0.5)                return camera        def start_recording(self):                """                Setup the recorder                """                self.current_file = self.archive + "/" + self.detected_at + "-usb.avi"                Util.log(self.name, "Motion detected! Recording...")                # Set path and FPS                self.writer = cv2.VideoWriter(self.current_file, self.codec, self.fps, (self.width, self.height))        def stop_recording(self):                """                Reset values to default                """                self.writer = None                self.current_file = None                self.detected_at = None        def convert_to_mp4(self, path):                """                Convert video file to mp4 using ffmpeg                @param string path                """                try:                        Util.log(self.name, "Converting video...")                        destination = os.path.splitext(path)[0] + '.mp4'                        cmd = 'ffmpeg -i "{}" "{}" 2> /dev/null && rm "{}"'.format(path, destination, path)                        #cmd = 'for i in ' + self.archive + '/*.avi; do ffmpeg -i "$i" "${i%.*}.mp4" 2> /dev/null && rm "$i"; done'                        p = subprocess.Popen(cmd, shell=True)                        (output, err) = p.communicate()                except subprocess.CalledProcessError:                        Util.log(self.name, "Error converting video")        def run(self):                """                Main worker                """                observer = deque(maxlen=self.fps * self.OBSERVER_LENGTH)                previous_frame = None                while True:                        # Grab a frame                        (grabbed, self.current_frame) = self.source.read()                        # End of feed                        if not grabbed:                                break                        # Gray frame                        frame_gray = cv2.cvtColor(self.current_frame, cv2.COLOR_BGR2GRAY)                        # Blur frame                        frame_blur = cv2.GaussianBlur(frame_gray, (21, 21), 0)                        # If there's no previous frame, us the current one                        if previous_frame is None:                                previous_frame = frame_blur                                continue                        # Delta frame                        delta_frame = cv2.absdiff(previous_frame, frame_blur)                        # Threshold frame                        threshold_frame = cv2.threshold(delta_frame, 15, 255, cv2.THRESH_BINARY)[1]                        # Dilate the thresholded image to fill in holes                        kernel = np.ones((5, 5), np.uint8)                        dilated_frame = cv2.dilate(threshold_frame, kernel, iterations=4)                        # Find difference in percent                        res = dilated_frame.astype(np.uint8)                        movement = (np.count_nonzero(res) * 100) / res.size                        # Add movement percentage to observer                        observer.append(movement)                        if self.do_add_contours or self.do_add_target:                                self.current_frame, targets = self.add_contours(self.current_frame, dilated_frame)                                if self.do_add_target:                                        self.current_frame = self.add_target(self.current_frame, targets)                        if self.do_record and self.detected(sum([x > self.threshold for x in observer]) > 0):                                if not self.recording():                                        self.start_recording()                                self.writer.write(self.current_frame)                        elif self.recording():                                # Delete Old files                                self.delete()                                                                # Convert                                self.convert_to_mp4(self.current_file)                                # Reset all                                self.stop_recording()                                Util.log(self.name, "Observing...")                        # Set blurred frame as new previous frame                        previous_frame = frame_blur                        # Display                        if self.do_display:                                cv2.imshow("Current frame:", self.current_frame)                        # Exit on 'q'                        key = cv2.waitKey(1) & 0xFF                        if key == ord('q'):                                break        def delete(self):                """                delete mic data to a mp4 file.                @param list data                """                count = 0                Util.log(self.name, "Delete USB Cam video...")                                file_list = sorted(os.listdir(self.archive), reverse=True)                for filename in [file for file in file_list if file.endswith("usb.mp4")]:                        if not filename.startswith('.'):                                type = self.get_type(filename)                                if type == "video":                                        count = count + 1                                        if self.REMAIN_RECORDING_FILES < count:                                                Util.log(self.name, "Delete USB video filename=" + filename + ", type=" + type + ", count=" + str(count))                                                os.remove(self.archive + "/" + filename)        def get_type(self, filename):                name, extension = os.path.splitext(filename)                return 'video' if extension == '.mp4' else 'video' if extension == '.avi' else 'audio' if extension == '.wav' else 'audio' if extension == '.mp3' else 'photo'        def add_contours(self, raw_frame, dilated_frame):                """                Add contours to frame                @param array raw_frame                @param array dilated_frame                @return tuple(array, list)                """                # Find contours on thresholded image                _, contours, nada = cv2.findContours(dilated_frame.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)                # Make coutour frame                contour_frame = raw_frame.copy()                # Target contours                targets = []                # Loop over the contour                for c in contours:                        # If the contour is too small, ignore it                        if cv2.contourArea(c) < 500:                                # Make sure this has a less than sign, not an html escape                                continue                        # Contour data                        M = cv2.moments(c)                        cx = int(M['m10']/M['m00'])                        cy = int(M['m01']/M['m00'])                        x, y, w, h = cv2.boundingRect(c)                        rx = x + int(w / 2)                        ry = y + int(h / 2)                        ca = cv2.contourArea(c)                        # plot contours                        cv2.drawContours(contour_frame,[c],0,(0,0,255),2)                        cv2.rectangle(contour_frame,(x,y),(x+w,y+h),(0,255,0),2)                        cv2.circle(contour_frame,(cx,cy),2,(0,0,255),2)                        cv2.circle(contour_frame,(rx,ry),2,(0,255,0),2)                        # save target contours                        targets.append((rx,ry,ca))                return contour_frame, targets        def add_target(self, raw_frame, targets):                """                Add crosshairs to frame                @param array raw_frame                @param list targets                @return array                """                # Make target                area = sum([x[2] for x in targets])                mx = 0                my = 0                if targets:                        for x, y, a in targets:                                mx += x                                my += y                        mx = int(round(mx / len(targets), 0))                        my = int(round(my / len(targets), 0))                # Plot target                tr = 50                target_frame = raw_frame.copy()                if targets:                        cv2.circle(target_frame, (mx, my), tr, (0, 0, 255, 0), 2)                        cv2.line(target_frame, (mx - tr, my), (mx + tr, my), (0, 0, 255, 0), 2)                        cv2.line(target_frame, (mx, my - tr), (mx, my + tr), (0, 0, 255, 0), 2)                return target_frame        def detected(self, has_motion):                """                Check if this or another detector detected something                @param boolean has_motion                @return boolean                """                if has_motion:                        self.lock_manager.set()                else:                        self.lock_manager.remove()                self.detected_at = self.lock_manager.get_lock_time()                return self.detected_at is not None        def recording(self):                """                Check if currently recording                @return boolean                """                return self.writer is not Noneif __name__ == "__main__":        args = sys.argv[1:]        source = None        do_display = False        try:                opts, args = getopt.getopt(args, "hs:d",["source=", "display"])        except getopt.GetoptError:                print('python3 motion_detector.py -s <source> [-d]')                sys.exit(2)        for opt, arg in opts:                if opt == '-h':                        print('python3 motion_detector.py -s <source> [-d]')                        sys.exit()                elif opt in ("-s", "--source"):                        source = arg.strip()                elif opt in ("-d", "--display"):                        do_display = True        if source is not None:                print('Input: ', source)        else:                print('Input: Camera')        if source is not None and not os.path.isfile(source):                print(str(source) + " does not exist")        else:                md = Motion_Detector(source=source, do_display=do_display, do_add_contours=True)                md.start()

piwebcamera.py

import os
import sys
import time
import math
import getopt
import numpy as np
import cv2
import threading
import subprocess
from collections import deque

from lock_manager import Lock_Manager
from util import Util

class PiWebCamera(threading.Thread):
        def __init__(self, video_source=0, source=None, do_record=True, do_display=True, do_add_contours=True, do_add_target=False):
                threading.Thread.__init__(self)

                self.name = self.__class__.__name__
                self.archive = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'archive')

                self.writer = None
                self.current_frame = None

                self.codec = cv2.VideoWriter_fourcc('M','J', 'P', 'G')
                self.OBSERVER_LENGTH = 5 # Time in seconds to be observed for motion
                self.threshold = 15

                self.CAMERA_SOURCE = video_source
                self.REMAIN_RECORDING_FILES = 3 # 10이상 부터 삭제 후 저장
                self.do_display = do_display
                self.do_record = do_record
                self.do_add_contours = do_add_contours
                self.do_add_target = do_add_target
                self.current_file = None

                self.source = cv2.VideoCapture(source) if source is not None else self.init_camera()

                self.fps = self.find_fps(self.source)
                self.height, self.width = self.get_dimensions(self.source)
                Util.log(self.name, "Initializing pi camera class with video_source=" + str(self.CAMERA_SOURCE))
                Util.log(self.name,"width: {"+str(self.width)+"}, height : {"+str(self.height)+"}")

                self.lock_manager = Lock_Manager("motion")

        def __del__(self):
                # Release camera
                self.source.release()

                # Close all windows
                cv2.destroyAllWindows()

                # Remove lock if exists
                self.lock_manager.remove()

        def get_frame(self):
                """
                Return the current frame

                @return bytes
                """
                return self.frame_to_jpg(self.current_frame) if self.current_frame is not None else None

        def frame_to_jpg(self, frame):
                """
                Convert video frame to jpg

                @param array frame
                @return bytes
                """
                ret, jpeg = cv2.imencode('.jpg', self.current_frame)
                return jpeg.tobytes()

        def get_dimensions(self, source):
                """
                Determine height and width of the video source

                @return tuple(int, int)
                """
                frame = cv2.cvtColor(source.read()[1],cv2.COLOR_RGB2GRAY)
                return frame.shape[0: 2]

        def find_fps(self, source):
                """
                Determine frames per second of the video source

                @param video source
                @return int
                """
                Util.log(self.name, "Determining FPS...")

                # How many frames to capture
                num_frames = 120

                # Start time
                start = time.time()

                # Grab a few frames
                for i in range(0, num_frames):
                        ret, frame = source.read()

                # End time
                end = time.time()

                # Calculate frames per second
                fps = int(math.floor(num_frames / (end - start)))
                Util.log(self.name, "Setting FPS to " + str(fps))

                return fps

        def init_camera(self):
                """
                Start the camera

                @return cv2.VideoCapture
                """
                # Init camera
                camera = cv2.VideoCapture(self.CAMERA_SOURCE)
                #camera.set(3, 320)
                #camera.set(4, 240)

                # Wait half a second for light adjustment
                time.sleep(0.5)

                return camera

        def start_recording(self):
                """
                Setup the recorder
                """

                self.current_file = self.archive + "/" + self.detected_at + "-pic.avi"

                Util.log(self.name, "Motion detected! Recording...")

                # Set path and FPS
                self.writer = cv2.VideoWriter(self.current_file, self.codec, self.fps, (self.width, self.height))

        def stop_recording(self):
                """
                Reset values to default
                """
                self.writer = None
                self.current_file = None
                self.detected_at = None

        def convert_to_mp4(self, path):
                """
                Convert video file to mp4 using ffmpeg

                @param string path
                """
                try:
                        Util.log(self.name, "Converting video...")
                        destination = os.path.splitext(path)[0] + '.mp4'
                        cmd = 'ffmpeg -i "{}" "{}" 2> /dev/null && rm "{}"'.format(path, destination, path)
                        #cmd = 'for i in ' + self.archive + '/*.avi; do ffmpeg -i "$i" "${i%.*}.mp4" 2> /dev/null && rm "$i"; done'
                        p = subprocess.Popen(cmd, shell=True)
                        (output, err) = p.communicate()

                except subprocess.CalledProcessError:
                        Util.log(self.name, "Error converting video")

        def run(self):
                """
                Main worker
                """
                observer = deque(maxlen=self.fps * self.OBSERVER_LENGTH)
                previous_frame = None

                while True:
                        # Grab a frame
                        (grabbed, self.current_frame) = self.source.read()

                        # End of feed
                        if not grabbed:
                                break

                        # Gray frame
                        frame_gray = cv2.cvtColor(self.current_frame, cv2.COLOR_BGR2GRAY)

                        # Blur frame
                        frame_blur = cv2.GaussianBlur(frame_gray, (21, 21), 0)

                        # If there's no previous frame, us the current one
                        if previous_frame is None:
                                previous_frame = frame_blur
                                continue

                        # Delta frame
                        delta_frame = cv2.absdiff(previous_frame, frame_blur)

                        # Threshold frame
                        threshold_frame = cv2.threshold(delta_frame, 15, 255, cv2.THRESH_BINARY)[1]

                        # Dilate the thresholded image to fill in holes
                        kernel = np.ones((5, 5), np.uint8)
                        dilated_frame = cv2.dilate(threshold_frame, kernel, iterations=4)

                        # Find difference in percent
                        res = dilated_frame.astype(np.uint8)
                        movement = (np.count_nonzero(res) * 100) / res.size

                        # Add movement percentage to observer
                        observer.append(movement)

                        if self.do_add_contours or self.do_add_target:
                                self.current_frame, targets = self.add_contours(self.current_frame, dilated_frame)

                                if self.do_add_target:
                                        self.current_frame = self.add_target(self.current_frame, targets)

                        if self.do_record and self.detected(sum([x > self.threshold for x in observer]) > 0):
                                if not self.recording():
                                        self.start_recording()

                                self.writer.write(self.current_frame)
                        elif self.recording():
                                # Delete Old files
                                self.delete()
                                
                                # Convert
                                self.convert_to_mp4(self.current_file)

                                # Reset all
                                self.stop_recording()

                                Util.log(self.name, "Observing...")

                        # Set blurred frame as new previous frame
                        previous_frame = frame_blur

                        # Display
                        if self.do_display:
                                cv2.imshow("Current frame:", self.current_frame)

                        # Exit on 'q'
                        key = cv2.waitKey(1) & 0xFF

                        if key == ord('q'):
                                break

        def delete(self):
                """
                delete mic data to a mp4 file.
                @param list data
                """
                count = 0
                Util.log(self.name, "Delete PI Cam video...")
                
                file_list = sorted(os.listdir(self.archive), reverse=True)
                for filename in [file for file in file_list if file.endswith("pic.mp4")]:
                        if not filename.startswith('.'):
                                type = self.get_type(filename)
                                if type == "video":
                                        count = count + 1
                                        if self.REMAIN_RECORDING_FILES < count:
                                                Util.log(self.name, "Delete PIC video filename=" + filename + ", type=" + type + ", count=" + str(count))
                                                os.remove(self.archive + "/" + filename)

        def get_type(self, filename):
                name, extension = os.path.splitext(filename)
                return 'video' if extension == '.mp4' else 'video' if extension == '.avi' else 'audio' if extension == '.wav' else 'audio' if extension == '.mp3' else 'photo'

        def add_contours(self, raw_frame, dilated_frame):
                """
                Add contours to frame

                @param array raw_frame
                @param array dilated_frame
                @return tuple(array, list)
                """
                # Find contours on thresholded image
                _, contours, nada = cv2.findContours(dilated_frame.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)

                # Make coutour frame
                contour_frame = raw_frame.copy()

                # Target contours
                targets = []

                # Loop over the contour
                for c in contours:
                        # If the contour is too small, ignore it
                        if cv2.contourArea(c) < 500:
                                # Make sure this has a less than sign, not an html escape
                                continue

                        # Contour data
                        M = cv2.moments(c)
                        cx = int(M['m10']/M['m00'])
                        cy = int(M['m01']/M['m00'])
                        x, y, w, h = cv2.boundingRect(c)
                        rx = x + int(w / 2)
                        ry = y + int(h / 2)
                        ca = cv2.contourArea(c)

                        # plot contours
                        cv2.drawContours(contour_frame,[c],0,(0,0,255),2)
                        cv2.rectangle(contour_frame,(x,y),(x+w,y+h),(0,255,0),2)
                        cv2.circle(contour_frame,(cx,cy),2,(0,0,255),2)
                        cv2.circle(contour_frame,(rx,ry),2,(0,255,0),2)

                        # save target contours
                        targets.append((rx,ry,ca))

                return contour_frame, targets

        def add_target(self, raw_frame, targets):
                """
                Add crosshairs to frame

                @param array raw_frame
                @param list targets
                @return array
                """
                # Make target
                area = sum([x[2] for x in targets])
                mx = 0
                my = 0

                if targets:
                        for x, y, a in targets:
                                mx += x
                                my += y
                        mx = int(round(mx / len(targets), 0))
                        my = int(round(my / len(targets), 0))

                # Plot target
                tr = 50
                target_frame = raw_frame.copy()

                if targets:
                        cv2.circle(target_frame, (mx, my), tr, (0, 0, 255, 0), 2)
                        cv2.line(target_frame, (mx - tr, my), (mx + tr, my), (0, 0, 255, 0), 2)
                        cv2.line(target_frame, (mx, my - tr), (mx, my + tr), (0, 0, 255, 0), 2)

                return target_frame

        def detected(self, has_motion):
                """
                Check if this or another detector detected something

                @param boolean has_motion
                @return boolean
                """
                if has_motion:
                        self.lock_manager.set()
                else:
                        self.lock_manager.remove()

                self.detected_at = self.lock_manager.get_lock_time()

                return self.detected_at is not None

        def recording(self):
                """
                Check if currently recording

                @return boolean
                """
                return self.writer is not None

if __name__ == "__main__":
        args = sys.argv[1:]
        source = None
        do_display = False

        try:
                opts, args = getopt.getopt(args, "hs:d",["source=", "display"])
        except getopt.GetoptError:
                print('python3 motion_detector.py -s <source> [-d]')
                sys.exit(2)

        for opt, arg in opts:
                if opt == '-h':
                        print('python3 motion_detector.py -s <source> [-d]')
                        sys.exit()
                elif opt in ("-s", "--source"):
                        source = arg.strip()
                elif opt in ("-d", "--display"):
                        do_display = True

        if source is not None:
                print('Input: ', source)
        else:
                print('Input: Camera')

        if source is not None and not os.path.isfile(source):
                print(str(source) + " does not exist")
        else:
                md = Motion_Detector(source=source, do_display=do_display, do_add_contours=True)
                md.start()

capture.py

import os
import cv2
import datetime, time
from pathlib import Path

archive_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'archive')

def capture_and_save(im):
    s = im.shape
    # Add a timestamp
    font = cv2.FONT_HERSHEY_SIMPLEX
    bottomLeftCornerOfText = (10,s[0]-10)
    fontScale = 1
    fontColor = (20,20,20)
    lineType = 2

    cv2.putText(im,datetime.datetime.now().isoformat().split(".")[0],bottomLeftCornerOfText,font,fontScale,fontColor, lineType)

    m = 0
    p = Path(archive_path)
    for imp in p.iterdir():
        if imp.suffix == ".png" and imp.stem != "last":
            num = imp.stem.split("_")[1]
            try:
                num = int(num)
                if num>m:
                    m = num
            except:
                print("Error reading image number for",str(imp))
    m +=1
    lp = Path(archive_path + "/last.png")
    if lp.exists() and lp.is_file():
        np = Path(archive_path + "/img_{}.png".format(m))
        np.write_bytes(lp.read_bytes())
    cv2.imwrite(archive_path + "/last.png",im)

if __name__=="__main__":
    capture_and_save()
    print("done")