// +build !circleci /* DESCRIPTION A filter that detects motion and discards frames without motion. This filter can use different algorithms for motion detection. AUTHORS Scott Barnard LICENSE motion.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 ( "fmt" "image" "io" "bitbucket.org/ausocean/av/revid/config" "gocv.io/x/gocv" ) const ( defaultMotionDownscaling = 1 defaultMotionInterval = 5 defaultMotionPadding = 10 ) // MotionAlgorithm is the interface the motion filter expects for // motion detection algorithms. type MotionAlgorithm interface { Detect(img *gocv.Mat) bool Close() error } // Motion is a filter that performs motion detection using a supplied // motion detection algorithm. type Motion struct { dst io.WriteCloser // Destination to which motion containing frames go. algorithm MotionAlgorithm // Algorithm to use for motion detection. scale float64 // The factor that frames will be downscaled by for motion detection. sample uint // Interval that motion detection is performed at. padding uint // The amount of frames before and after motion that will be kept. t uint // Frame counter. send uint // Amount of frames to send. frames chan []byte // Used for storing frames. } // NewMotion returns a pointer to a new Motion filter struct. func NewMotion(dst io.WriteCloser, alg MotionAlgorithm, c config.Config) *Motion { // Validate parameters. if c.MotionPadding == 0 { c.LogInvalidField("MotionPadding", defaultMotionPadding) c.MotionPadding = defaultMotionPadding } if c.MotionDownscaling <= 0 { c.LogInvalidField("MotionDownscaling", defaultMotionDownscaling) c.MotionDownscaling = defaultMotionDownscaling } if c.MotionInterval <= 0 { c.LogInvalidField("MotionInterval", defaultMotionInterval) c.MotionInterval = defaultMotionInterval } return &Motion{ dst: dst, algorithm: alg, scale: 1 / float64(c.MotionDownscaling), sample: uint(c.MotionInterval), padding: c.MotionPadding, frames: make(chan []byte, c.MotionInterval+int(c.MotionPadding)), } } // Implements io.Closer. // Close frees resources used by gocv, because it has to be done manually, due to // it using c-go. func (m *Motion) Close() error { return m.algorithm.Close() } // 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 *Motion) Write(f []byte) (int, error) { // Decode image into Mat. img, err := gocv.IMDecode(f, gocv.IMReadColor) if err != nil { return 0, fmt.Errorf("image can't be decoded: %w", err) } defer img.Close() // Downsize image to speed up calculations. gocv.Resize(img, &img, image.Point{}, m.scale, m.scale, gocv.InterpolationNearestNeighbor) // Filter on an interval. if m.t == m.sample/2 { if m.algorithm.Detect(&img) { m.send = m.sample + 2*m.padding - 1 } } m.t = (m.t + 1) % m.sample // Increment counter. // Send frames. m.frames <- f // Put current frame into buffer. toSend := <-m.frames // Get oldest frame out of circular buffer. if m.send > 0 { m.send-- return m.dst.Write(toSend) } return len(f), nil }