Building a remote monitoring system using nodejs and python 1. Video capture module

Keywords: encoding Python socket OpenCV

How to use python and nodejs to build a remote monitoring system without saying anything.

1. Platform and Environment

  1. Python 3.6, opencv3.0 or above (or some functionality in cv2 is unavailable)
  2. nodejs8.9.4 relies on the following
{
  "name": "video",
  "version": "1.0.0",
  "description": "Simple live video program",
  "main": "config.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js"
  },
  "keywords": [
    "Live broadcast"
  ],
  "author": "fengziyu",
  "license": "ISC",
  "dependencies": {
    "ejs": "^2.6.1",
    "express": "^4.16.3",
    "socket.io": "^2.1.1"
  }
}

2. Main ideas

Collect videos using python's opencv framework, compress each frame into jpg format, send the server program written by nodejs through udp protocol, and forward the server program to the client accessed by browser through websocket protocol

3. Specific implementation

1. Tool class writing (encapsulation of image compression, transcoding, file manipulation, log manipulation, etc.)

#encoding=utf-8
import time
import numpy
import base64
import os
import logging
import sys
from settings import *
from PIL import Image
from io import BytesIO

#Tool class
class IOUtil(object):
    #Stream Operations Tool Class
    @staticmethod
    def array_to_bytes(pic,formatter="jpeg",quality=70):
        '''
        //Static method for converting numpy arrays to binary streams
        :param pic: numpy array
        :param format: Picture format
        :param quality:Compression ratio,Higher compression ratio,The shorter the binary data generated
        :return: 
        '''
        stream = BytesIO()
        picture = Image.fromarray(pic)
        picture.save(stream,format=formatter,quality=quality)
        jepg = stream.getvalue()
        stream.close()
        return jepg
    @staticmethod
    def bytes_to_base64(byte):
        '''
        //Static method, bytes to base64 encoding
        :param byte: 
        :return: 
        '''
        return base64.b64encode(byte)
    @staticmethod
    def transport_rgb(frame):
        '''
        //Converting bgr image to rgb image or rgb image to bgr image
        '''
        return frame[...,::-1]
    @staticmethod
    def byte_to_package(bytes,cmd,var=1):
        '''
        //Subpack the binary data of the picture stream for each frame
        :param byte: Binary file
        :param cmd:command
        :return: 
        '''
        head = [ver,len(byte),cmd]
        headPack = struct.pack("!3I", *head)
        senddata = headPack+byte
        return senddata
    @staticmethod
    def mkdir(filePath):
        '''
        //create folder
        '''
        if not os.path.exists(filePath):
            os.mkdir(filePath)
    @staticmethod
    def countCenter(box):
        '''
        //Calculate the center of a rectangle
        '''
        return (int(abs(box[0][0] - box[1][0])*0.5) + box[0][0],int(abs(box[0][1] - box[1][1])*0.5) +box[0][1])
    @staticmethod
    def countBox(center):
        '''
        //Calculated from two points, x,y,c,r
        '''
        return (center[0][0],center[0][1],center[1][0]-center[0][0],center[1][1]-center[0][1])
    @staticmethod
    def getImageFileName():
        return time.strftime("%Y_%m_%d_%H_%M_%S", time.localtime())+'.png'

#Construction log
logger = logging.getLogger(LOG_NAME)
formatter = logging.Formatter(LOG_FORMATTER)
IOUtil.mkdir(LOG_DIR);
file_handler = logging.FileHandler(LOG_DIR + LOG_FILE,encoding='utf-8')
file_handler.setFormatter(formatter)
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setFormatter(formatter)
logger.addHandler(file_handler)
logger.addHandler(console_handler)
logger.setLevel(logging.INFO)

This part of the note is that a method is provided to convert images between rgb-bgr color modes because opencv uses BGR as the image mode and RGB as the color mode we are accustomed to

2. Profile

#Photo Save Folder
SCREENSHOT_DIR = "screenshots/"
#Video Save Folder
VIDEO_DIR = "videos/"
#Alert Picture Folder
WARN_DIR = "warn/"
#Log Folder
LOG_DIR = "log/"
#log file
LOG_FILE = "camera.log"
#Image Sending IP
IMAGE_IP = "127.0.0.1"
#Image Send Port
IMAGE_PORT = 9999
#Log Output Format
LOG_FORMATTER = "%(asctime)s %(levelname)-8s: %(message)s"
#Log Output Name
LOG_NAME = 'cameraLogger'
#Whether to open a local window
IS_WINDOW_ON = True

3. Video collection class (responsible for collecting camera information, taking photos, recording videos, etc.)

#encoding=utf-8
import cv2
import numpy
import socket
import json
import threading
import os
import time
import copy
from settings import *
from threading import Thread
from utils import IOUtil,logger

'''
//Manager module, responsible for controlling information acquisition, command acquisition
'''
class CameraManager(object):
    #Camera management class, responsible for controlling information acquisition
    def __init__(self,capture):
        '''
        :param capture: Camera object
        :param windowManager: Hook Classes,Window Management,Key
        '''
        #Read the screenshot and video directories from the configuration file to create folders
        self.screenshot_dir = SCREENSHOT_DIR
        self.video_dir = VIDEO_DIR
        self._capture = capture
        #Current picture
        self._frame = None
        #Video Coding
        self._videoEncoding = None
        #Video Writing Tool
        self._videoWriter = None
        #Whether to turn on display
        self._isShow = False
        #Is it working
        self._isWorking = True
        #fps
        self._fps = 0
        #Is Video Writing in Progress
        self._videoFilename = None
        IOUtil.mkdir(self.screenshot_dir)
        IOUtil.mkdir(self.video_dir)
        logger.info("Video Collector Initialization Completed!")
    def getFrame(self):
        #Returns the current frame
        return copy.copy(self._frame)
    def getFps(self):
        #Get current fps
        return self._fps
    def isWritingVideo(self):
        #Is Video Writing in Progress
        return self._videoFilename is not None
    def start(self):
        #Start-up
        logger.info("Turn on video capture")
        frameThread = Thread(target = self._update,args=())
        frameThread.start()
        return self
    def stop(self):
        #Stop working
        self._isWorking = False
        self._videoFilename = None
        logger.info("Turn off video capture")
    def _update(self):
        #Update Camera Picture
        logger.info("Video Acquisition Thread Start...")
        while self._isWorking:
            startTime = time.time()
            if self._capture is not None:
                _,self._frame = self._capture.read()
            try:
                self._fps = 1/(time.time() - startTime)
            except:
                self._fps = 30
        logger.info("Video capture thread shutdown...")
    def getImageFileName(self,filename=None,imageformat=".png"):
        #Get picture file name
        if not filename:
            filename = time.strftime("%Y_%m_%d_%H_%M_%S", time.localtime())
        filename += imageformat
        return os.path.join(self.screenshot_dir,filename)
    def writeImage(self):
        #Write Picture File
        logger.info("Start writing a picture")
        imageFileName = self.getImageFileName()
        try:
            cv2.imwrite(imageFileName,self._frame)
        except Exception as e:
            logger.error("Failed to write picture:"+str(e))
        logger.info("Write Picture Successfully"+imageFileName)
    def getVideoFileName(self,filename=None,videoformat=".avi"):
        #Get the video file name
        if not filename:
            filename = time.strftime("%Y_%m_%d_%H_%M_%S", time.localtime())
        filename += videoformat
        return os.path.join(self.video_dir,filename)
    def startWritingVideo(self,filename=None,encoding=cv2.VideoWriter_fourcc("I","4","2","0"),videoformat=".avi"):
        #Initialize write to video file
        logger.info("Turn on video recording")
        self._videoFilename = self.getVideoFileName()
        self._videoEncoding = encoding
        Thread(target = self._writingVideo,args=()).start()
    def stopWritingVideo(self):
        #Turn off video writing
        self._videoFilename = None
        self._videoWriter = None
        self._videoEncoding = None
    def _writingVideo(self):
        #Write Video File
        logger.info("Video Acquisition Thread Start,Target file is:"+self._videoFilename)
        if self._videoWriter is None:
                size = (int(self._capture.get(cv2.CAP_PROP_FRAME_WIDTH)),int(self._capture.get(cv2.CAP_PROP_FRAME_HEIGHT)))
                self._videoWriter = cv2.VideoWriter(self._videoFilename,self._videoEncoding,self._fps,size)
        while self._videoFilename is not None:
            self._videoWriter.write(self._frame)
        logger.info("Finished writing video,File name:"+self._videoFilename)
    def isWorking(self):
        #Work Control
        return self._isWorking
if __name__ == "__main__":
    video = cv2.VideoCapture(0)
    camera = CameraManager(video)
    camera.start()
    while True:
        frame = camera.getFrame()
        if frame is not None:
            cv2.imshow("test",frame)
            k = cv2.waitKey(1) & 0xff
            if k == 27:
                camera.stop()
                break
            elif k == 9:
            #tab key to turn on video recording
              if not camera.isWritingVideo():
                  camera.startWritingVideo()
              else:
                  camera.stopWritingVideo()
            elif k == 32:
                #Spacebar Screenshot
                camera.writeImage()

Run the script directly, and a test window appears, which will be captured in the directory specified in the configuration file by pressing the space

Posted by mgmoses on Mon, 13 Jan 2020 08:07:10 -0800