// +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 <scott@ausocean.org> 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+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 }