mirror of https://bitbucket.org/ausocean/av.git
134 lines
3.8 KiB
Go
134 lines
3.8 KiB
Go
|
// +build !nocv
|
||
|
|
||
|
/*
|
||
|
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"
|
||
|
|
||
|
"gocv.io/x/gocv"
|
||
|
|
||
|
"bitbucket.org/ausocean/av/revid/config"
|
||
|
)
|
||
|
|
||
|
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
|
||
|
}
|