How to use python and nodejs to build a remote monitoring system without saying anything.
1. Platform and Environment
- Python 3.6, opencv3.0 or above (or some functionality in cv2 is unavailable)
- 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