diff --git a/filter/knn.go b/filter/knn.go index 4ce51d9e..be733ec0 100644 --- a/filter/knn.go +++ b/filter/knn.go @@ -54,7 +54,7 @@ func NewKNNFilter(dst io.WriteCloser, area, threshold float64, history, kernelSi k := gocv.GetStructuringElement(gocv.MorphRect, image.Pt(kernelSize, kernelSize)) var windows []*gocv.Window if debug { - windows = []*gocv.Window{gocv.NewWindow("Debug: Bounding boxes"), gocv.NewWindow("Debug: Motion")} + windows = []*gocv.Window{gocv.NewWindow("KNN: Bounding boxes"), gocv.NewWindow("KNN: Motion")} } return &KNNFilter{dst, area, &bs, k, debug, windows} } diff --git a/filter/mog.go b/filter/mog.go index e8671274..9c647bd2 100644 --- a/filter/mog.go +++ b/filter/mog.go @@ -54,7 +54,7 @@ func NewMOGFilter(dst io.WriteCloser, area, threshold float64, history int, debu k := gocv.GetStructuringElement(gocv.MorphRect, image.Pt(3, 3)) var windows []*gocv.Window if debug { - windows = []*gocv.Window{gocv.NewWindow("Debug: Bounding boxes"), gocv.NewWindow("Debug: Motion")} + windows = []*gocv.Window{gocv.NewWindow("MOG: Bounding boxes"), gocv.NewWindow("MOG: Motion")} } return &MOGFilter{dst, area, &bs, k, debug, windows} } diff --git a/revid/config/config.go b/revid/config/config.go index 92b7943b..b766c648 100644 --- a/revid/config/config.go +++ b/revid/config/config.go @@ -263,10 +263,10 @@ type Config struct { Width uint // Width defines the input video width Raspivid input. Bitrate uint // Bitrate specifies the bitrate for constant bitrate in kbps. - HorizontalFlip bool // HorizontalFlip flips video horizontally for Raspivid input. - VerticalFlip bool // VerticalFlip flips video vertically for Raspivid input. - Filter int // Defines the method of filtering to be used in between lexing and encoding. - PSITime int // Sets the time between a packet being sent + 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. + PSITime int // Sets the time between a packet being sent. // RTMP ring buffer parameters. RTMPRBSize int // The number of elements in the RTMP sender ringbuffer. @@ -290,6 +290,7 @@ var TypeData = map[string]string{ "CBR": "bool", "ClipDuration": "uint", "Exposure": "enum:auto,night,nightpreview,backlight,spotlight,sports,snow,beach,verylong,fixedfps,antishake,fireworks", + "Filters": "enums:NoOp,MOG,VariableFPS,KNN", "FrameRate": "uint", "Height": "uint", "HorizontalFlip": "bool", diff --git a/revid/revid.go b/revid/revid.go index cc68e02a..756a51b0 100644 --- a/revid/revid.go +++ b/revid/revid.go @@ -129,8 +129,8 @@ type Revid struct { // lexTo, encoder and packer handle transcoding the input stream. lexTo func(dest io.Writer, src io.Reader, delay time.Duration) error - // filter will hold the filter interface that will write to the chosen filter from the lexer. - filter filter.Filter + // filters will hold the filter interface that will write to the chosen filter from the lexer. + filters []filter.Filter // encoders will hold the multiWriteCloser that writes to encoders from the filter. encoders io.WriteCloser @@ -346,18 +346,28 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io. r.encoders = multiWriter(encoders...) - switch r.cfg.Filter { - case config.FilterNoOp: - r.filter = filter.NewNoOp(r.encoders) - case config.FilterMOG: - r.filter = filter.NewMOGFilter(r.encoders, mogMinArea, mogThreshold, mogHistory, r.cfg.ShowWindows) - case config.FilterVariableFPS: - r.filter = filter.NewVariableFPSFilter(r.encoders, minFPS, filter.NewMOGFilter(r.encoders, mogMinArea, mogThreshold, mogHistory, r.cfg.ShowWindows)) - case config.FilterKNN: - r.filter = filter.NewKNNFilter(r.encoders, knnMinArea, knnThreshold, knnHistory, knnKernel, r.cfg.ShowWindows) + l := len(r.cfg.Filters) + r.filters = []filter.Filter{filter.NewNoOp(r.encoders)} + if l != 0 { + r.filters = make([]filter.Filter, l) + dst := r.encoders + + for i := l - 1; i >= 0; i-- { + switch r.cfg.Filters[i] { + case config.FilterNoOp: + r.filters[i] = filter.NewNoOp(dst) + case config.FilterMOG: + r.filters[i] = filter.NewMOGFilter(dst, mogMinArea, mogThreshold, mogHistory, r.cfg.ShowWindows) + case config.FilterVariableFPS: + r.filters[i] = filter.NewVariableFPSFilter(dst, minFPS, filter.NewMOGFilter(dst, mogMinArea, mogThreshold, mogHistory, r.cfg.ShowWindows)) + case config.FilterKNN: + r.filters[i] = filter.NewKNNFilter(dst, knnMinArea, knnThreshold, knnHistory, knnKernel, r.cfg.ShowWindows) + default: + panic("Undefined Filter") + } + dst = r.filters[i] + } - default: - panic("Undefined Filter") } switch r.cfg.Input { @@ -474,10 +484,13 @@ func (r *Revid) Stop() { r.cfg.Logger.Log(logger.Error, pkg+"failed to close pipeline", "error", err.Error()) } - err = r.filter.Close() - if err != nil { - r.cfg.Logger.Log(logger.Error, pkg+"failed to close pipeline", "error", err.Error()) + for _, filter := range r.filters { + err = filter.Close() + if err != nil { + r.cfg.Logger.Log(logger.Error, pkg+"failed to close pipeline", "error", err.Error()) + } } + r.cfg.Logger.Log(logger.Info, pkg+"closed pipeline") if r.cmd != nil && r.cmd.Process != nil { @@ -667,13 +680,17 @@ func (r *Revid) Update(vars map[string]string) error { default: r.cfg.Logger.Log(logger.Warning, pkg+"invalid VerticalFlip param", "value", value) } - case "Filter": + case "Filters": + filters := strings.Split(value, ",") m := map[string]int{"NoOp": config.FilterNoOp, "MOG": config.FilterMOG, "VariableFPS": config.FilterVariableFPS, "KNN": config.FilterKNN} - v, ok := m[value] - if !ok { - r.cfg.Logger.Log(logger.Warning, pkg+"invalid FilterMethod param", "value", value) + r.cfg.Filters = make([]int, len(filters)) + for i, filter := range filters { + v, ok := m[filter] + if !ok { + r.cfg.Logger.Log(logger.Warning, pkg+"invalid Filters param", "value", value) + } + r.cfg.Filters[i] = v } - r.cfg.Filter = v case "PSITime": v, err := strconv.Atoi(value) if err != nil || v < 0 { @@ -792,7 +809,7 @@ func (r *Revid) Update(vars map[string]string) error { // processFrom is run as a routine to read from a input data source, lex and // then send individual access units to revid's encoders. func (r *Revid) processFrom(read io.Reader, delay time.Duration) { - r.err <- r.lexTo(r.filter, read, delay) + r.err <- r.lexTo(r.filters[0], read, delay) r.cfg.Logger.Log(logger.Info, pkg+"finished lexing") r.wg.Done() }