mirror of https://bitbucket.org/ausocean/av.git
basic filter that does not use gocv for motion detection
This commit is contained in:
parent
021f77c392
commit
ed35bd83cb
136
filter/basic.go
136
filter/basic.go
|
@ -7,7 +7,7 @@ DESCRIPTION
|
||||||
background and what is foreground.
|
background and what is foreground.
|
||||||
|
|
||||||
AUTHORS
|
AUTHORS
|
||||||
Scott Barnard <scott@ausocean.org>
|
Ella Pietraroia <ella@ausocean.org>
|
||||||
|
|
||||||
LICENSE
|
LICENSE
|
||||||
mog.go is Copyright (C) 2019 the Australian Ocean Lab (AusOcean)
|
mog.go is Copyright (C) 2019 the Australian Ocean Lab (AusOcean)
|
||||||
|
@ -29,18 +29,46 @@ LICENSE
|
||||||
package filter
|
package filter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"image/jpeg"
|
||||||
"io"
|
"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
|
// MOGFilter is a filter that provides basic motion detection. MoG is short for
|
||||||
// Mixture of Gaussians method.
|
// Mixture of Gaussians method.
|
||||||
type BasicFilter struct {
|
type BasicFilter struct {
|
||||||
dst io.WriteCloser
|
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.
|
// NewMOGFilter returns a pointer to a new MOGFilter struct.
|
||||||
func NewBasicFilter(dst io.WriteCloser) *BasicFilter {
|
func NewBasicFilter(dst io.WriteCloser, debug bool) *BasicFilter {
|
||||||
return &BasicFilter{dst}
|
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.
|
// Implements io.Closer.
|
||||||
|
@ -53,10 +81,104 @@ func (m *BasicFilter) Close() error {
|
||||||
// Implements io.Writer.
|
// Implements io.Writer.
|
||||||
// Write applies the motion filter to the video stream. Only frames with motion
|
// Write applies the motion filter to the video stream. Only frames with motion
|
||||||
// are written to the destination encoder, frames without are discarded.
|
// are written to the destination encoder, frames without are discarded.
|
||||||
func (m *BasicFilter) Write(f []byte) (int, error) {
|
func (b *BasicFilter) Write(f []byte) (int, error) {
|
||||||
|
//t0 := time.Now()
|
||||||
//decode MJPEG
|
//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()
|
||||||
|
|
||||||
//for all frames find difference in this frame from last frames
|
//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
|
||||||
|
|
||||||
return m.dst.Write(f)
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ import (
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
"io"
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
"gocv.io/x/gocv"
|
"gocv.io/x/gocv"
|
||||||
)
|
)
|
||||||
|
@ -78,6 +79,7 @@ func (m *MOGFilter) Close() error {
|
||||||
// Write applies the motion filter to the video stream. Only frames with motion
|
// Write applies the motion filter to the video stream. Only frames with motion
|
||||||
// are written to the destination encoder, frames without are discarded.
|
// are written to the destination encoder, frames without are discarded.
|
||||||
func (m *MOGFilter) Write(f []byte) (int, error) {
|
func (m *MOGFilter) Write(f []byte) (int, error) {
|
||||||
|
t0 := time.Now()
|
||||||
if m.hfCount < (m.hf - 1) {
|
if m.hfCount < (m.hf - 1) {
|
||||||
m.hold[m.hfCount] = f
|
m.hold[m.hfCount] = f
|
||||||
m.hfCount++
|
m.hfCount++
|
||||||
|
@ -134,9 +136,9 @@ func (m *MOGFilter) Write(f []byte) (int, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't write to destination if there is no motion.
|
// Don't write to destination if there is no motion.
|
||||||
if len(contours) == 0 {
|
// if len(contours) == 0 {
|
||||||
return len(f), nil
|
// return len(f), nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Write to destination, past 4 frames then current frame.
|
// Write to destination, past 4 frames then current frame.
|
||||||
for i, h := range m.hold {
|
for i, h := range m.hold {
|
||||||
|
@ -146,5 +148,6 @@ func (m *MOGFilter) Write(f []byte) (int, error) {
|
||||||
return len(f), fmt.Errorf("could not write previous frames: %w", err)
|
return len(f), fmt.Errorf("could not write previous frames: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fmt.Print(time.Now().Sub(t0).Milliseconds(), "\n")
|
||||||
return m.dst.Write(f)
|
return m.dst.Write(f)
|
||||||
}
|
}
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -12,5 +12,6 @@ require (
|
||||||
github.com/pkg/errors v0.8.1
|
github.com/pkg/errors v0.8.1
|
||||||
github.com/yobert/alsa v0.0.0-20180630182551-d38d89fa843e
|
github.com/yobert/alsa v0.0.0-20180630182551-d38d89fa843e
|
||||||
gocv.io/x/gocv v0.21.0
|
gocv.io/x/gocv v0.21.0
|
||||||
|
golang.org/x/image v0.0.0-20200119044424-58c23975cae1
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||||
)
|
)
|
||||||
|
|
3
go.sum
3
go.sum
|
@ -56,7 +56,10 @@ go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
|
||||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
gocv.io/x/gocv v0.21.0 h1:dVjagrupZrfCRY0qPEaYWgoNMRpBel6GYDH4mvQOK8Y=
|
gocv.io/x/gocv v0.21.0 h1:dVjagrupZrfCRY0qPEaYWgoNMRpBel6GYDH4mvQOK8Y=
|
||||||
gocv.io/x/gocv v0.21.0/go.mod h1:Rar2PS6DV+T4FL+PM535EImD/h13hGVaHhnCu1xarBs=
|
gocv.io/x/gocv v0.21.0/go.mod h1:Rar2PS6DV+T4FL+PM535EImD/h13hGVaHhnCu1xarBs=
|
||||||
|
golang.org/x/image v0.0.0-20200119044424-58c23975cae1 h1:5h3ngYt7+vXCDZCup/HkCQgW5XwmSvR/nA2JmJ0RErg=
|
||||||
|
golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/sys v0.0.0-20190913121621-c3b328c6e5a7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190913121621-c3b328c6e5a7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
|
|
|
@ -128,6 +128,7 @@ const (
|
||||||
FilterVariableFPS
|
FilterVariableFPS
|
||||||
FilterKNN
|
FilterKNN
|
||||||
FilterDifference
|
FilterDifference
|
||||||
|
FilterBasic
|
||||||
)
|
)
|
||||||
|
|
||||||
// OS names
|
// OS names
|
||||||
|
@ -323,7 +324,7 @@ var TypeData = map[string]string{
|
||||||
"DiffThreshold": "float",
|
"DiffThreshold": "float",
|
||||||
"Exposure": "enum:auto,night,nightpreview,backlight,spotlight,sports,snow,beach,verylong,fixedfps,antishake,fireworks",
|
"Exposure": "enum:auto,night,nightpreview,backlight,spotlight,sports,snow,beach,verylong,fixedfps,antishake,fireworks",
|
||||||
"FileFPS": "int",
|
"FileFPS": "int",
|
||||||
"Filters": "enums:NoOp,MOG,VariableFPS,KNN,Difference",
|
"Filters": "enums:NoOp,MOG,VariableFPS,KNN,Difference,Basic",
|
||||||
"FrameRate": "uint",
|
"FrameRate": "uint",
|
||||||
"Height": "uint",
|
"Height": "uint",
|
||||||
"HorizontalFlip": "bool",
|
"HorizontalFlip": "bool",
|
||||||
|
|
|
@ -348,6 +348,9 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io.
|
||||||
r.filters[i] = filter.NewKNNFilter(dst, r.cfg.KNNMinArea, r.cfg.KNNThreshold, int(r.cfg.KNNHistory), int(r.cfg.KNNKernel), r.cfg.ShowWindows, r.cfg.MotionInterval)
|
r.filters[i] = filter.NewKNNFilter(dst, r.cfg.KNNMinArea, r.cfg.KNNThreshold, int(r.cfg.KNNHistory), int(r.cfg.KNNKernel), r.cfg.ShowWindows, r.cfg.MotionInterval)
|
||||||
case config.FilterDifference:
|
case config.FilterDifference:
|
||||||
r.filters[i] = filter.NewDifference(dst, r.cfg.ShowWindows, r.cfg.DiffThreshold)
|
r.filters[i] = filter.NewDifference(dst, r.cfg.ShowWindows, r.cfg.DiffThreshold)
|
||||||
|
r.filters[i] = filter.NewKNNFilter(dst, knnMinArea, knnThreshold, knnHistory, knnKernel, r.cfg.ShowWindows)
|
||||||
|
case config.FilterBasic:
|
||||||
|
r.filters[i] = filter.NewBasicFilter(dst, false)
|
||||||
default:
|
default:
|
||||||
panic("Undefined Filter")
|
panic("Undefined Filter")
|
||||||
}
|
}
|
||||||
|
@ -669,7 +672,7 @@ func (r *Revid) Update(vars map[string]string) error {
|
||||||
}
|
}
|
||||||
case "Filters":
|
case "Filters":
|
||||||
filters := strings.Split(value, ",")
|
filters := strings.Split(value, ",")
|
||||||
m := map[string]int{"NoOp": config.FilterNoOp, "MOG": config.FilterMOG, "VariableFPS": config.FilterVariableFPS, "KNN": config.FilterKNN, "Difference": config.FilterDifference}
|
m := map[string]int{"NoOp": config.FilterNoOp, "MOG": config.FilterMOG, "VariableFPS": config.FilterVariableFPS, "KNN": config.FilterKNN, "Difference": config.FilterDifference, "Basic": config.FilterBasic}
|
||||||
r.cfg.Filters = make([]int, len(filters))
|
r.cfg.Filters = make([]int, len(filters))
|
||||||
for i, filter := range filters {
|
for i, filter := range filters {
|
||||||
v, ok := m[filter]
|
v, ok := m[filter]
|
||||||
|
|
Loading…
Reference in New Issue