From 10bfc296be3f84a978434ff3677646b988c68d0b Mon Sep 17 00:00:00 2001 From: Scott Date: Fri, 31 Jan 2020 10:21:23 +1030 Subject: [PATCH] filter: add option to reduce resolution of frames for MOG motion filter A new netsender variable, FilterDownscaling is used for reducing the resolution of frames within the MOG motion filter. This does not affect the resolution of the video output, it is only used to speed up calculations. A FilterDownscaling factor of 2 is equivalent to skipping every 2nd pixel in the image. --- filter/filters_circleci.go | 2 +- filter/mog.go | 8 ++- revid/config/config.go | 124 ++++++++++++++++++++----------------- revid/revid.go | 11 +++- 4 files changed, 83 insertions(+), 62 deletions(-) diff --git a/filter/filters_circleci.go b/filter/filters_circleci.go index eba06743..e9b4b8df 100644 --- a/filter/filters_circleci.go +++ b/filter/filters_circleci.go @@ -32,7 +32,7 @@ import ( ) // NewMOG returns a pointer to a new NoOp struct for testing purposes only. -func NewMOG(dst io.WriteCloser, area, threshold float64, history int, debug bool, hf int) *NoOp { +func NewMOG(dst io.WriteCloser, area, threshold float64, history int, debug bool, hf int, downscalingFactor int) *NoOp { return &NoOp{dst: dst} } diff --git a/filter/mog.go b/filter/mog.go index 80706ecf..d951823a 100644 --- a/filter/mog.go +++ b/filter/mog.go @@ -49,17 +49,18 @@ type MOG struct { hold [][]byte // Will hold all frames up to hf (so only every hf frame is motion detected). hf int // The number of frames to be held. hfCount int // Counter for the hold array. + scale float64 // The factor that frames will be downscaled by for motion detection. } // NewMOG returns a pointer to a new MOG filter struct. -func NewMOG(dst io.WriteCloser, area, threshold float64, history int, debug bool, hf int) *MOG { +func NewMOG(dst io.WriteCloser, area, threshold float64, history int, debug bool, hf int, downscalingFactor int) *MOG { bs := gocv.NewBackgroundSubtractorMOG2WithParams(history, threshold, false) k := gocv.GetStructuringElement(gocv.MorphRect, image.Pt(3, 3)) var windows []*gocv.Window if debug { windows = []*gocv.Window{gocv.NewWindow("MOG: Bounding boxes"), gocv.NewWindow("MOG: Motion")} } - return &MOG{dst, area, &bs, k, debug, windows, make([][]byte, hf-1), hf, 0} + return &MOG{dst, area, &bs, k, debug, windows, make([][]byte, hf-1), hf, 0, 1 / float64(downscalingFactor)} } // Implements io.Closer. @@ -94,6 +95,9 @@ func (m *MOG) Write(f []byte) (int, error) { imgDelta := gocv.NewMat() defer imgDelta.Close() + // Downsize image to speed up calculations. + gocv.Resize(img, &img, image.Point{}, m.scale, m.scale, gocv.InterpolationNearestNeighbor) + // Seperate foreground and background. m.bs.Apply(img, &imgDelta) diff --git a/revid/config/config.go b/revid/config/config.go index cc94c082..70be5115 100644 --- a/revid/config/config.go +++ b/revid/config/config.go @@ -85,7 +85,6 @@ const ( defaultClipDuration = 0 defaultAudioInputCodec = codecutil.ADPCM defaultPSITime = 2 - defaultMotionInterval = 5 defaultFileFPS = 0 // Ring buffer defaults. @@ -94,7 +93,9 @@ const ( defaultRBWriteTimeout = 5 // Motion filter parameter defaults. - defaultMinFPS = 1.0 + defaultMinFPS = 1.0 + defaultMotionDownscaling = 1 + defaultMotionInterval = 1 // KNN filter parameter defaults. defaultKNNMinArea = 25.0 @@ -283,7 +284,6 @@ type Config struct { HorizontalFlip bool // HorizontalFlip flips video horizontally for Raspivid input. VerticalFlip bool // VerticalFlip flips video vertically for Raspivid input. Filters []int // Defines the methods of filtering to be used in between lexing and encoding. - MotionInterval int // Sets the number of frames that are held before the filter is used (on the nth frame) PSITime int // Sets the time between a packet being sent. // Ring buffer parameters. @@ -292,7 +292,9 @@ type Config struct { RBWriteTimeout int // The ringbuffer write timeout in seconds. // Motion filter parameters. - MinFPS float64 // The reduced framerate of the video when there is no motion. + MinFPS float64 // The reduced framerate of the video when there is no motion. + MotionInterval int // Sets the number of frames that are held before the filter is used (on the nth frame) + MotionDownscaling int // Downscaling factor of frames used for motion detection. // KNN filter parameters. KNNMinArea float64 // Used to ignore small areas of motion detection. @@ -303,8 +305,12 @@ type Config struct { // MOG filter parameters. MOGMinArea float64 // Used to ignore small areas of motion detection. MOGThreshold float64 // Intensity value from the KNN motion detection algorithm that is considered motion. - MOGHistory uint // Length of MOG filter's history + MOGHistory uint // Length of MOG filter's history. + // Difference filter parameters. + DiffThreshold float64 // Intensity value from the Difference motion detection algorithm that is considered motion. + + // Basic filter parameters. BasicThreshold int BasicPixels int @@ -313,63 +319,62 @@ type Config struct { // Defines the rate at which frames from a file source are processed. FileFPS int - // Difference filter parameters. - DiffThreshold float64 // Intensity value from the Difference motion detection algorithm that is considered motion. } // TypeData contains information about all of the variables that // can be set over the web. It is a psuedo const. var TypeData = map[string]string{ - "AutoWhiteBalance": "enum:off,auto,sun,cloud,shade,tungsten,fluorescent,incandescent,flash,horizon", - "BasicPixels": "int", - "BasicThreshold": "int", - "BitDepth": "int", - "Brightness": "uint", - "BurstPeriod": "uint", - "CameraChan": "int", - "CameraIP": "string", - "CBR": "bool", - "ClipDuration": "uint", - "DiffThreshold": "float", - "Exposure": "enum:auto,night,nightpreview,backlight,spotlight,sports,snow,beach,verylong,fixedfps,antishake,fireworks", - "FileFPS": "int", - "Filters": "enums:NoOp,MOG,VariableFPS,KNN,Difference,Basic", - "FrameRate": "uint", - "Height": "uint", - "HorizontalFlip": "bool", - "HTTPAddress": "string", - "Input": "enum:raspivid,rtsp,v4l,file", - "InputCodec": "enum:H264,MJPEG", - "InputPath": "string", - "KNNHistory": "uint", - "KNNKernel": "float", - "KNNMinArea": "float", - "KNNThreshold": "float", - "logging": "enum:Debug,Info,Warning,Error,Fatal", - "Loop": "bool", - "MinFPS": "float", - "MinFrames": "uint", - "mode": "enum:Normal,Paused,Burst,Loop", - "MOGHistory": "uint", - "MOGMinArea": "float", - "MOGThreshold": "float", - "MotionInterval": "int", - "Output": "enum:File,Http,Rtmp,Rtp", - "OutputPath": "string", - "Outputs": "enums:File,Http,Rtmp,Rtp", - "Quantization": "uint", - "RBCapacity": "uint", - "RBMaxElements": "uint", - "RBWriteTimeout": "uint", - "Rotation": "uint", - "RTMPURL": "string", - "RTPAddress": "string", - "Saturation": "int", - "ShowWindows": "bool", - "VBRBitrate": "int", - "VBRQuality": "enum:standard,fair,good,great,excellent", - "VerticalFlip": "bool", - "Width": "uint", + "AutoWhiteBalance": "enum:off,auto,sun,cloud,shade,tungsten,fluorescent,incandescent,flash,horizon", + "BasicPixels": "int", + "BasicThreshold": "int", + "BitDepth": "int", + "Brightness": "uint", + "BurstPeriod": "uint", + "CameraChan": "int", + "CameraIP": "string", + "CBR": "bool", + "ClipDuration": "uint", + "DiffThreshold": "float", + "Exposure": "enum:auto,night,nightpreview,backlight,spotlight,sports,snow,beach,verylong,fixedfps,antishake,fireworks", + "FileFPS": "int", + "Filters": "enums:NoOp,MOG,VariableFPS,KNN,Difference,Basic", + "FrameRate": "uint", + "Height": "uint", + "HorizontalFlip": "bool", + "HTTPAddress": "string", + "Input": "enum:raspivid,rtsp,v4l,file", + "InputCodec": "enum:H264,MJPEG", + "InputPath": "string", + "KNNHistory": "uint", + "KNNKernel": "float", + "KNNMinArea": "float", + "KNNThreshold": "float", + "logging": "enum:Debug,Info,Warning,Error,Fatal", + "Loop": "bool", + "MinFPS": "float", + "MinFrames": "uint", + "mode": "enum:Normal,Paused,Burst,Loop", + "MOGHistory": "uint", + "MOGMinArea": "float", + "MOGThreshold": "float", + "MotionDownscaling": "uint", + "MotionInterval": "int", + "Output": "enum:File,Http,Rtmp,Rtp", + "OutputPath": "string", + "Outputs": "enums:File,Http,Rtmp,Rtp", + "Quantization": "uint", + "RBCapacity": "uint", + "RBMaxElements": "uint", + "RBWriteTimeout": "uint", + "Rotation": "uint", + "RTMPURL": "string", + "RTPAddress": "string", + "Saturation": "int", + "ShowWindows": "bool", + "VBRBitrate": "int", + "VBRQuality": "enum:standard,fair,good,great,excellent", + "VerticalFlip": "bool", + "Width": "uint", } // Validation errors. @@ -544,6 +549,11 @@ func (c *Config) Validate() error { c.BasicPixels = defaultBasicPixels } + if c.MotionDownscaling <= 0 { + c.logInvalidField("MotionDownscaling", defaultMotionDownscaling) + c.MotionDownscaling = defaultMotionDownscaling + } + if c.ShowWindows { os, err := osName() if err != nil { diff --git a/revid/revid.go b/revid/revid.go index 9dd04862..c562799c 100644 --- a/revid/revid.go +++ b/revid/revid.go @@ -341,9 +341,9 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io. case config.FilterNoOp: r.filters[i] = filter.NewNoOp(dst) case config.FilterMOG: - r.filters[i] = filter.NewMOG(dst, r.cfg.MOGMinArea, r.cfg.MOGThreshold, int(r.cfg.MOGHistory), r.cfg.ShowWindows, r.cfg.MotionInterval) + r.filters[i] = filter.NewMOG(dst, r.cfg.MOGMinArea, r.cfg.MOGThreshold, int(r.cfg.MOGHistory), r.cfg.ShowWindows, r.cfg.MotionInterval, r.cfg.MotionDownscaling) case config.FilterVariableFPS: - r.filters[i] = filter.NewVariableFPS(dst, r.cfg.MinFPS, filter.NewMOG(dst, r.cfg.MOGMinArea, r.cfg.MOGThreshold, int(r.cfg.MOGHistory), r.cfg.ShowWindows, r.cfg.MotionInterval)) + r.filters[i] = filter.NewVariableFPS(dst, r.cfg.MinFPS, filter.NewMOG(dst, r.cfg.MOGMinArea, r.cfg.MOGThreshold, int(r.cfg.MOGHistory), r.cfg.ShowWindows, r.cfg.MotionInterval, r.cfg.MotionDownscaling)) case config.FilterKNN: r.filters[i] = filter.NewKNN(dst, r.cfg.KNNMinArea, r.cfg.KNNThreshold, int(r.cfg.KNNHistory), int(r.cfg.KNNKernel), r.cfg.ShowWindows, r.cfg.MotionInterval) case config.FilterDifference: @@ -904,6 +904,13 @@ func (r *Revid) Update(vars map[string]string) error { if value == "Loop" { r.cfg.Loop = true } + case "MotionDownscaling": + v, err := strconv.Atoi(value) + if err != nil { + r.cfg.Logger.Log(logger.Warning, pkg+"invalid MotionDownscaling var", "value", value) + break + } + r.cfg.MotionDownscaling = v } } r.cfg.Logger.Log(logger.Info, pkg+"revid config changed", "config", fmt.Sprintf("%+v", r.cfg))