mirror of https://bitbucket.org/ausocean/av.git
185 lines
4.6 KiB
Go
185 lines
4.6 KiB
Go
// +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 <ella@ausocean.org>
|
|
|
|
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"
|
|
"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
|
|
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, 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.
|
|
// Close frees resources used by gocv, because it has to be done manually, due to
|
|
// it using c-go.
|
|
func (m *BasicFilter) Close() error {
|
|
return nil
|
|
}
|
|
|
|
// 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) {
|
|
//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()
|
|
|
|
//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
|
|
|
|
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)
|
|
}
|