From ed35bd83cbb8f6ce86a5f938380d51f548f83c18 Mon Sep 17 00:00:00 2001 From: Ella Pietraroia Date: Fri, 24 Jan 2020 17:01:28 +1030 Subject: [PATCH] basic filter that does not use gocv for motion detection --- filter/basic.go | 136 ++++++++++++++++++++++++++++++++++++++--- filter/mog.go | 9 ++- go.mod | 1 + go.sum | 3 + revid/config/config.go | 3 +- revid/revid.go | 5 +- 6 files changed, 145 insertions(+), 12 deletions(-) diff --git a/filter/basic.go b/filter/basic.go index a810b0a0..6175f823 100644 --- a/filter/basic.go +++ b/filter/basic.go @@ -7,7 +7,7 @@ DESCRIPTION background and what is foreground. AUTHORS - Scott Barnard + Ella Pietraroia LICENSE mog.go is Copyright (C) 2019 the Australian Ocean Lab (AusOcean) @@ -29,18 +29,46 @@ LICENSE package filter import ( + "bytes" + "fmt" + "image" + "image/color" + "image/jpeg" "io" + "os" + "time" + + "golang.org/x/image/font" + "golang.org/x/image/font/basicfont" + "golang.org/x/image/math/fixed" ) // MOGFilter is a filter that provides basic motion detection. MoG is short for // Mixture of Gaussians method. type BasicFilter struct { - dst io.WriteCloser + dst io.WriteCloser + bg [][][3]uint32 + current [][][3]uint32 + w int + h int + file io.WriteCloser + debug bool } // NewMOGFilter returns a pointer to a new MOGFilter struct. -func NewBasicFilter(dst io.WriteCloser) *BasicFilter { - return &BasicFilter{dst} +func NewBasicFilter(dst io.WriteCloser, debug bool) *BasicFilter { + var file io.WriteCloser + var err error + if debug { + file, err = os.Create("motion.mjpeg") + if err != nil { + panic("!!! TEST CODE !!!: file didnt work") + } + } else { + file = nil + } + + return &BasicFilter{dst, nil, nil, 0, 0, file, debug} } // Implements io.Closer. @@ -53,10 +81,104 @@ func (m *BasicFilter) Close() error { // Implements io.Writer. // Write applies the motion filter to the video stream. Only frames with motion // are written to the destination encoder, frames without are discarded. -func (m *BasicFilter) Write(f []byte) (int, error) { +func (b *BasicFilter) Write(f []byte) (int, error) { + //t0 := time.Now() //decode MJPEG + r := bytes.NewReader(f) + img, err := jpeg.Decode(r) + if err != nil { + return 0, fmt.Errorf("image can't be decoded: %w", err) + } + t1 := time.Now() - //for all frames find difference in this frame from last frames + //get background image and save a new background image if needed + //first frame must always be sent + if b.bg == nil { + bounds := img.Bounds() + b.w = bounds.Max.X + b.h = bounds.Max.Y - return m.dst.Write(f) + b.bg = make([][][3]uint32, b.h) + for i, _ := range b.bg { + b.bg[i] = make([][3]uint32, b.w) + } + + b.current = make([][][3]uint32, b.h) + for i, _ := range b.current { + b.current[i] = make([][3]uint32, b.w) + } + + for j := 0; j < b.h; j++ { + for i := 0; i < b.w; i++ { + n := img.At(i, j) + R, G, B, _ := n.RGBA() + b.bg[j][i] = [3]uint32{R, G, B} + } + } + t2 := time.Now() + fmt.Print("BG loop: ", t2.Sub(t1).Milliseconds(), "\n") + return len(f), nil + } + + motion := 0 + //for all pixels get the difference from the background image + + bwImg := image.NewRGBA(image.Rect(0, 0, b.w, b.h)) + for j := 0; j < b.h; j++ { + for i := 0; i < b.w; i++ { + n := img.At(i, j) + R, G, B, _ := n.RGBA() + b.current[j][i] = [3]uint32{R, G, B} + + diffR := R - b.bg[j][i][0] + diffG := G - b.bg[j][i][1] + diffB := B - b.bg[j][i][2] + + // diffR := int(math.Abs(float64(R) - float64(b.bg[j][i][0]))) + // diffG := int(math.Abs(float64(G) - float64(b.bg[j][i][1]))) + // diffB := int(math.Abs(float64(B) - float64(b.bg[j][i][2]))) + diff := diffR + diffG + diffB + + if diff < 45000 && b.debug { + bwImg.SetRGBA(i, j, color.RGBA{0x00, 0x00, 0x00, 0xff}) + } else { + bwImg.SetRGBA(i, j, color.RGBA{0xff, 0xff, 0xff, 0xff}) + motion++ + } + } + } + //t3 := time.Now() + //visualise + if b.debug { + col := color.RGBA{200, 100, 0, 255} + d := &font.Drawer{ + Dst: bwImg, + Src: image.NewUniform(col), + Face: basicfont.Face7x13, + Dot: fixed.P(20, 30), + } + if motion > 1000 { + d.DrawString("Motion") + } + err = jpeg.Encode(b.file, bwImg, nil) + if err != nil { + return len(f), err + } + } + //t4 := time.Now() + // //update backgound image + b.bg = b.current + //get mean of this difference + + //choose a threshold that motion is detected for if greater + + //discard non motion frames + + //write motion frames + // fmt.Print("Encode: ", t1.Sub(t0).Milliseconds(), "\n") + // fmt.Print("BG loop: ", t2.Sub(t1).Milliseconds(), "\n") + // fmt.Print("Calc loop: ", t3.Sub(t2).Milliseconds(), "\n") + // fmt.Print("VLC: ", t4.Sub(t3).Milliseconds(), "\n") + // fmt.Print("Total: ", time.Now().Sub(t0).Milliseconds(), "\n\n") + return b.dst.Write(f) } diff --git a/filter/mog.go b/filter/mog.go index 20fd5807..e5aa2927 100644 --- a/filter/mog.go +++ b/filter/mog.go @@ -33,6 +33,7 @@ import ( "image" "image/color" "io" + "time" "gocv.io/x/gocv" ) @@ -78,6 +79,7 @@ func (m *MOGFilter) Close() error { // Write applies the motion filter to the video stream. Only frames with motion // are written to the destination encoder, frames without are discarded. func (m *MOGFilter) Write(f []byte) (int, error) { + t0 := time.Now() if m.hfCount < (m.hf - 1) { m.hold[m.hfCount] = f m.hfCount++ @@ -134,9 +136,9 @@ func (m *MOGFilter) Write(f []byte) (int, error) { } // Don't write to destination if there is no motion. - if len(contours) == 0 { - return len(f), nil - } + // if len(contours) == 0 { + // return len(f), nil + // } // Write to destination, past 4 frames then current frame. for i, h := range m.hold { @@ -146,5 +148,6 @@ func (m *MOGFilter) Write(f []byte) (int, error) { return len(f), fmt.Errorf("could not write previous frames: %w", err) } } + fmt.Print(time.Now().Sub(t0).Milliseconds(), "\n") return m.dst.Write(f) } diff --git a/go.mod b/go.mod index dfa6f093..35f29b69 100644 --- a/go.mod +++ b/go.mod @@ -12,5 +12,6 @@ require ( github.com/pkg/errors v0.8.1 github.com/yobert/alsa v0.0.0-20180630182551-d38d89fa843e gocv.io/x/gocv v0.21.0 + golang.org/x/image v0.0.0-20200119044424-58c23975cae1 gopkg.in/natefinch/lumberjack.v2 v2.0.0 ) diff --git a/go.sum b/go.sum index 1b06d678..9a4cad8a 100644 --- a/go.sum +++ b/go.sum @@ -56,7 +56,10 @@ go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= gocv.io/x/gocv v0.21.0 h1:dVjagrupZrfCRY0qPEaYWgoNMRpBel6GYDH4mvQOK8Y= gocv.io/x/gocv v0.21.0/go.mod h1:Rar2PS6DV+T4FL+PM535EImD/h13hGVaHhnCu1xarBs= +golang.org/x/image v0.0.0-20200119044424-58c23975cae1 h1:5h3ngYt7+vXCDZCup/HkCQgW5XwmSvR/nA2JmJ0RErg= +golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/sys v0.0.0-20190913121621-c3b328c6e5a7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= diff --git a/revid/config/config.go b/revid/config/config.go index 1793215e..a7f9e0bd 100644 --- a/revid/config/config.go +++ b/revid/config/config.go @@ -128,6 +128,7 @@ const ( FilterVariableFPS FilterKNN FilterDifference + FilterBasic ) // OS names @@ -323,7 +324,7 @@ var TypeData = map[string]string{ "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", + "Filters": "enums:NoOp,MOG,VariableFPS,KNN,Difference,Basic", "FrameRate": "uint", "Height": "uint", "HorizontalFlip": "bool", diff --git a/revid/revid.go b/revid/revid.go index 4d0866d2..911f615b 100644 --- a/revid/revid.go +++ b/revid/revid.go @@ -348,6 +348,9 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io. r.filters[i] = filter.NewKNNFilter(dst, r.cfg.KNNMinArea, r.cfg.KNNThreshold, int(r.cfg.KNNHistory), int(r.cfg.KNNKernel), r.cfg.ShowWindows, r.cfg.MotionInterval) case config.FilterDifference: r.filters[i] = filter.NewDifference(dst, r.cfg.ShowWindows, r.cfg.DiffThreshold) + r.filters[i] = filter.NewKNNFilter(dst, knnMinArea, knnThreshold, knnHistory, knnKernel, r.cfg.ShowWindows) + case config.FilterBasic: + r.filters[i] = filter.NewBasicFilter(dst, false) default: panic("Undefined Filter") } @@ -669,7 +672,7 @@ func (r *Revid) Update(vars map[string]string) error { } case "Filters": filters := strings.Split(value, ",") - m := map[string]int{"NoOp": config.FilterNoOp, "MOG": config.FilterMOG, "VariableFPS": config.FilterVariableFPS, "KNN": config.FilterKNN, "Difference": config.FilterDifference} + m := map[string]int{"NoOp": config.FilterNoOp, "MOG": config.FilterMOG, "VariableFPS": config.FilterVariableFPS, "KNN": config.FilterKNN, "Difference": config.FilterDifference, "Basic": config.FilterBasic} r.cfg.Filters = make([]int, len(filters)) for i, filter := range filters { v, ok := m[filter]