// +build !circleci /* DESCRIPTION A filter that detects motion and discards frames without motion. The filter uses a Mixture of Gaussians method (MoG) to determine what is background and what is foreground. AUTHORS Ella Pietraroia LICENSE mog.go is Copyright (C) 2019 the Australian Ocean Lab (AusOcean) It is free software: you can redistribute it and/or modify them under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License in gpl.txt. If not, see http://www.gnu.org/licenses. */ package filter import ( "bytes" "fmt" "image" "image/color" "image/jpeg" "io" "os" "strconv" "sync" "time" "golang.org/x/image/font" "golang.org/x/image/font/basicfont" "golang.org/x/image/math/fixed" ) func absDiff(a, b uint32) int { c := int(a) - int(b) if c < 0 { return -c } else { return c } } // MOGFilter is a filter that provides basic motion detection. MoG is short for // Mixture of Gaussians method. type BasicFilter struct { dst io.WriteCloser img image.Image bg [][][3]uint32 bwImg *image.RGBA w int h int file io.WriteCloser motion int debug bool } // NewMOGFilter returns a pointer to a new MOGFilter struct. func NewBasicFilter(dst io.WriteCloser, debug bool) *BasicFilter { bwImg := image.NewRGBA(image.Rect(0, 0, 0, 0)) var file io.WriteCloser var err error if debug { file, err = os.Create("motion.mjpeg") if err != nil { panic("debug file didn't create") } } else { file = nil } return &BasicFilter{dst, nil, nil, bwImg, 0, 0, file, 0, debug} } // Implements io.Closer. // Close frees resources used by gocv, because it has to be done manually, due to // it using c-go. func (b *BasicFilter) Close() error { return nil } // Go routine for one row of the image to be processed func (b *BasicFilter) Process(j int, wg *sync.WaitGroup) { defer wg.Done() for i := 0; i < b.w; i++ { // ti0 := time.Now() n := b.img.At(i, j) // ti1 := time.Now() R, G, B, _ := n.RGBA() // ti2 := time.Now() diffB := absDiff(B, b.bg[j][i][2]) diffR := absDiff(R, b.bg[j][i][0]) diffG := absDiff(G, b.bg[j][i][1]) // ti3 := time.Now() diff := diffR + diffG + diffB if diff > 45000 { b.motion++ } if b.debug { if diff > 45000 { b.bwImg.SetRGBA(i, j, color.RGBA{0xff, 0xff, 0xff, 0xff}) } else { b.bwImg.SetRGBA(i, j, color.RGBA{0x00, 0x00, 0x00, 0xff}) } } // ti4 := time.Now() // Update backgound image. copy(b.bg[j][i][:], []uint32{R, G, B}) // ti5 := time.Now() // fmt.Print("At: ", ti1.Sub(ti0).Nanoseconds(), "\n") // fmt.Print("RGBA: ", ti2.Sub(ti1).Nanoseconds(), "\n") // fmt.Print("3x absDiff: ", ti3.Sub(ti2).Nanoseconds(), "\n") // fmt.Print("total diff + threshold + debug: ", ti4.Sub(ti3).Nanoseconds(), "\n") // fmt.Print("update bg: ", ti5.Sub(ti4).Nanoseconds(), "\n") // fmt.Print("Total: ", time.Now().Sub(ti0).Nanoseconds(), "\n\n") } } // 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 (b *BasicFilter) Write(f []byte) (int, error) { fmt.Print("in basic write\n") t0 := time.Now() //decode MJPEG r := bytes.NewReader(f) var err error b.img, err = jpeg.Decode(r) if err != nil { return 0, fmt.Errorf("image can't be decoded: %w", err) } t1 := time.Now() //get background image and save a new background image if needed //first frame must always be sent if b.bg == nil { bounds := b.img.Bounds() b.w = bounds.Max.X b.h = bounds.Max.Y b.bwImg = image.NewRGBA(image.Rect(0, 0, b.w, b.h)) b.bg = make([][][3]uint32, b.h) for i, _ := range b.bg { b.bg[i] = make([][3]uint32, b.w) } for j := 0; j < b.h; j++ { for i := 0; i < b.w; i++ { n := b.img.At(i, j) R, G, B, _ := n.RGBA() copy(b.bg[j][i][:], []uint32{R, G, B}) } } return len(f), nil } //for all pixels get the difference from the background image // c := make(chan int) // fmt.Print("channel made \n") var m sync.Mutex j := 0 m.Lock() var wg sync.WaitGroup for j < b.h { wg.Add(4) go b.Process(j, &wg) go b.Process(j+1, &wg) go b.Process(j+2, &wg) go b.Process(j+3, &wg) j = j + 4 wg.Wait() } if j >= b.h { m.Unlock() } t2 := time.Now() //visualise if b.debug { col := color.RGBA{200, 100, 0, 255} d := &font.Drawer{ Dst: b.bwImg, Src: image.NewUniform(col), Face: basicfont.Face7x13, Dot: fixed.P(20, 30), } var s string if b.motion > 1000 { s = strconv.Itoa(b.motion) + " Motion" } else { s = strconv.Itoa(b.motion) } d.DrawString(s) err = jpeg.Encode(b.file, b.bwImg, nil) if err != nil { return len(f), err } } t3 := time.Now() fmt.Print("Encode: ", t1.Sub(t0).Milliseconds(), "\n") fmt.Print("Calc loop: ", t2.Sub(t1).Milliseconds(), "\n") fmt.Print("VLC: ", t3.Sub(t2).Milliseconds(), "\n") fmt.Print("Total: ", time.Now().Sub(t0).Milliseconds(), "\n\n") //choose a threshold that motion is detected for if greater if b.motion < 1000 { return len(f), nil } //discard non motion frames //write motion frames return b.dst.Write(f) }