Skip to main content

VideoStream

usbwebcamera.py

import osimportos
sysimportimport timeimportsys
mathimportimport getoptimporttime
import math
import getopt
import numpy as npimportnp
cv2importimport threadingimportcv2
subprocessfromimport threading
import subprocess
from collections import dequefromdeque

from lock_manager import Lock_ManagerfromLock_Manager
from util import UtilclassUtil

class 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 NoneifNone

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()

              

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")