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/revid/config/config.go b/revid/config/config.go index 5f6fa4b3..af2ec6b1 100644 --- a/revid/config/config.go +++ b/revid/config/config.go @@ -126,6 +126,7 @@ const ( FilterMOG FilterVariableFPS FilterKNN + FilterBasic ) // OS names @@ -310,7 +311,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", + "Filters": "enums:NoOp,MOG,VariableFPS,KNN,Basic", "FrameRate": "uint", "Height": "uint", "HorizontalFlip": "bool", diff --git a/revid/revid.go b/revid/revid.go index d717d66f..bdbab24e 100644 --- a/revid/revid.go +++ b/revid/revid.go @@ -360,6 +360,8 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io. r.filters[i] = filter.NewVariableFPSFilter(dst, minFPS, filter.NewMOGFilter(r.encoders, mogMinArea, mogThreshold, mogHistory, r.cfg.ShowWindows, r.cfg.MotionInterval)) case config.FilterKNN: 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") } @@ -680,7 +682,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} + m := map[string]int{"NoOp": config.FilterNoOp, "MOG": config.FilterMOG, "VariableFPS": config.FilterVariableFPS, "KNN": config.FilterKNN, "Basic": config.FilterBasic} r.cfg.Filters = make([]int, len(filters)) for i, filter := range filters { v, ok := m[filter]