From 10bfc296be3f84a978434ff3677646b988c68d0b Mon Sep 17 00:00:00 2001 From: Scott Date: Fri, 31 Jan 2020 10:21:23 +1030 Subject: [PATCH 1/3] 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)) From 72a5c3158852539a87049da60ce925317935ddfd Mon Sep 17 00:00:00 2001 From: Scott Date: Fri, 31 Jan 2020 15:56:38 +1030 Subject: [PATCH 2/3] filter: add option to reduce resolution of frames for KNN motion filter This makes the same changes as for the MOG but for the KNN filter. --- filter/filters_circleci.go | 2 +- filter/knn.go | 8 ++++++-- revid/revid.go | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/filter/filters_circleci.go b/filter/filters_circleci.go index e9b4b8df..bb49d712 100644 --- a/filter/filters_circleci.go +++ b/filter/filters_circleci.go @@ -37,7 +37,7 @@ func NewMOG(dst io.WriteCloser, area, threshold float64, history int, debug bool } // NewKNN returns a pointer to a new NoOp struct for testing purposes only. -func NewKNN(dst io.WriteCloser, area, threshold float64, history, kernelSize int, debug bool, hf int) *NoOp { +func NewKNN(dst io.WriteCloser, area, threshold float64, history, kernelSize int, debug bool, hf int, downscalingFactor int) *NoOp { return &NoOp{dst: dst} } diff --git a/filter/knn.go b/filter/knn.go index b9b1c3f5..0009ce7d 100644 --- a/filter/knn.go +++ b/filter/knn.go @@ -49,17 +49,18 @@ type KNN 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. } // NewKNN returns a pointer to a new KNN filter struct. -func NewKNN(dst io.WriteCloser, area, threshold float64, history, kernelSize int, debug bool, hf int) *KNN { +func NewKNN(dst io.WriteCloser, area, threshold float64, history, kernelSize int, debug bool, hf int, downscalingFactor int) *KNN { bs := gocv.NewBackgroundSubtractorKNNWithParams(history, threshold, false) k := gocv.GetStructuringElement(gocv.MorphRect, image.Pt(kernelSize, kernelSize)) var windows []*gocv.Window if debug { windows = []*gocv.Window{gocv.NewWindow("KNN: Bounding boxes"), gocv.NewWindow("KNN: Motion")} } - return &KNN{dst, area, &bs, k, debug, windows, make([][]byte, hf-1), hf, 0} + return &KNN{dst, area, &bs, k, debug, windows, make([][]byte, hf-1), hf, 0, 1 / float64(downscalingFactor)} } // Implements io.Closer. @@ -94,6 +95,9 @@ func (m *KNN) 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/revid.go b/revid/revid.go index c562799c..faac33a9 100644 --- a/revid/revid.go +++ b/revid/revid.go @@ -345,7 +345,7 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io. 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.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) + 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, r.cfg.MotionDownscaling) case config.FilterDifference: r.filters[i] = filter.NewDifference(dst, r.cfg.ShowWindows, r.cfg.DiffThreshold) case config.FilterBasic: From 12febf18f4b2ac974f9957fe9b9654ad89a7e9bb Mon Sep 17 00:00:00 2001 From: Scott Date: Mon, 3 Feb 2020 10:10:01 +1030 Subject: [PATCH 3/3] =?UTF-8?q?filter:=20downscalingFactor=20=E2=86=92=20s?= =?UTF-8?q?caleFactor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- filter/filters_circleci.go | 4 ++-- filter/knn.go | 4 ++-- filter/mog.go | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/filter/filters_circleci.go b/filter/filters_circleci.go index bb49d712..2e119fe5 100644 --- a/filter/filters_circleci.go +++ b/filter/filters_circleci.go @@ -32,12 +32,12 @@ 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, downscalingFactor int) *NoOp { +func NewMOG(dst io.WriteCloser, area, threshold float64, history int, debug bool, hf int, scaleFactor int) *NoOp { return &NoOp{dst: dst} } // NewKNN returns a pointer to a new NoOp struct for testing purposes only. -func NewKNN(dst io.WriteCloser, area, threshold float64, history, kernelSize int, debug bool, hf int, downscalingFactor int) *NoOp { +func NewKNN(dst io.WriteCloser, area, threshold float64, history, kernelSize int, debug bool, hf int, scaleFactor int) *NoOp { return &NoOp{dst: dst} } diff --git a/filter/knn.go b/filter/knn.go index 0009ce7d..fdb01937 100644 --- a/filter/knn.go +++ b/filter/knn.go @@ -53,14 +53,14 @@ type KNN struct { } // NewKNN returns a pointer to a new KNN filter struct. -func NewKNN(dst io.WriteCloser, area, threshold float64, history, kernelSize int, debug bool, hf int, downscalingFactor int) *KNN { +func NewKNN(dst io.WriteCloser, area, threshold float64, history, kernelSize int, debug bool, hf int, scaleFactor int) *KNN { bs := gocv.NewBackgroundSubtractorKNNWithParams(history, threshold, false) k := gocv.GetStructuringElement(gocv.MorphRect, image.Pt(kernelSize, kernelSize)) var windows []*gocv.Window if debug { windows = []*gocv.Window{gocv.NewWindow("KNN: Bounding boxes"), gocv.NewWindow("KNN: Motion")} } - return &KNN{dst, area, &bs, k, debug, windows, make([][]byte, hf-1), hf, 0, 1 / float64(downscalingFactor)} + return &KNN{dst, area, &bs, k, debug, windows, make([][]byte, hf-1), hf, 0, 1 / float64(scaleFactor)} } // Implements io.Closer. diff --git a/filter/mog.go b/filter/mog.go index d951823a..7971452c 100644 --- a/filter/mog.go +++ b/filter/mog.go @@ -53,14 +53,14 @@ type MOG struct { } // NewMOG returns a pointer to a new MOG filter struct. -func NewMOG(dst io.WriteCloser, area, threshold float64, history int, debug bool, hf int, downscalingFactor int) *MOG { +func NewMOG(dst io.WriteCloser, area, threshold float64, history int, debug bool, hf int, scaleFactor 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, 1 / float64(downscalingFactor)} + return &MOG{dst, area, &bs, k, debug, windows, make([][]byte, hf-1), hf, 0, 1 / float64(scaleFactor)} } // Implements io.Closer.