mirror of https://bitbucket.org/ausocean/av.git
pr changes made commenting and [][][3] to [][]pixel, pixel is a new struct
This commit is contained in:
parent
d8c611de52
commit
5c68510209
292
filter/basic.go
292
filter/basic.go
|
@ -3,14 +3,14 @@
|
||||||
/*
|
/*
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
A filter that detects motion and discards frames without motion. The
|
A filter that detects motion and discards frames without motion. The
|
||||||
filter uses a Mixture of Gaussians method (MoG) to determine what is
|
filter uses a difference method looking at each individual pixel to
|
||||||
background and what is foreground.
|
determine what is background and what is foreground.
|
||||||
|
|
||||||
AUTHORS
|
AUTHORS
|
||||||
Ella Pietraroia <ella@ausocean.org>
|
Ella Pietraroia <ella@ausocean.org>
|
||||||
|
|
||||||
LICENSE
|
LICENSE
|
||||||
mog.go is Copyright (C) 2019 the Australian Ocean Lab (AusOcean)
|
basic.go is Copyright (C) 2020 the Australian Ocean Lab (AusOcean)
|
||||||
|
|
||||||
It is free software: you can redistribute it and/or modify them
|
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
|
under the terms of the GNU General Public License as published by the
|
||||||
|
@ -38,13 +38,127 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/image/font"
|
"golang.org/x/image/font"
|
||||||
"golang.org/x/image/font/basicfont"
|
"golang.org/x/image/font/basicfont"
|
||||||
"golang.org/x/image/math/fixed"
|
"golang.org/x/image/math/fixed"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const filename = "motion.mjpeg"
|
||||||
|
|
||||||
|
const (
|
||||||
|
threshold = 45000
|
||||||
|
pixels = 1000
|
||||||
|
)
|
||||||
|
|
||||||
|
type pixel struct {
|
||||||
|
r uint32
|
||||||
|
g uint32
|
||||||
|
b uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// BasicFilter is a filter that provides basic motion detection via a difference
|
||||||
|
// method.
|
||||||
|
type Basic struct {
|
||||||
|
dst io.WriteCloser
|
||||||
|
img image.Image
|
||||||
|
bg [][]pixel
|
||||||
|
bwImg *image.RGBA
|
||||||
|
w int
|
||||||
|
h int
|
||||||
|
file io.WriteCloser
|
||||||
|
motion int
|
||||||
|
debug bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBasicFilter returns a pointer to a new Basic filter struct.
|
||||||
|
func NewBasicFilter(dst io.WriteCloser, debug bool) *Basic {
|
||||||
|
bwImg := image.NewRGBA(image.Rect(0, 0, 0, 0))
|
||||||
|
var file io.WriteCloser
|
||||||
|
var err error
|
||||||
|
file = nil
|
||||||
|
if debug {
|
||||||
|
file, err = os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("could not create debug file: %v", err))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
file = nil
|
||||||
|
}
|
||||||
|
return &Basic{dst, nil, nil, bwImg, 0, 0, file, 0, debug}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements io.Closer.
|
||||||
|
// Close frees resources used by gocv, because it has to be done manually, due to
|
||||||
|
// it using c-go.
|
||||||
|
func (bf *Basic) 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 (bf *Basic) Write(f []byte) (int, error) {
|
||||||
|
// Decode MJPEG.
|
||||||
|
var err error
|
||||||
|
bf.img, err = jpeg.Decode(bytes.NewReader(f))
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("image can't be decoded: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// First frame must be set as the first background image
|
||||||
|
if bf.bg == nil {
|
||||||
|
bounds := bf.img.Bounds()
|
||||||
|
bf.w = bounds.Max.X
|
||||||
|
bf.h = bounds.Max.Y
|
||||||
|
|
||||||
|
bf.bwImg = image.NewRGBA(image.Rect(0, 0, bf.w, bf.h))
|
||||||
|
|
||||||
|
bf.bg = make([][]pixel, bf.h)
|
||||||
|
for i, _ := range bf.bg {
|
||||||
|
bf.bg[i] = make([]pixel, bf.w)
|
||||||
|
for j, _ := range bf.bg[i] {
|
||||||
|
p := bf.img.At(i, j)
|
||||||
|
r, g, b, _ := p.RGBA()
|
||||||
|
bf.bg[i][j].r = r
|
||||||
|
bf.bg[i][j].b = b
|
||||||
|
bf.bg[i][j].g = g
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(f), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use 4x goroutines to each process one row of pixels.
|
||||||
|
j := 0
|
||||||
|
var wg *sync.WaitGroup
|
||||||
|
|
||||||
|
for j < bf.h {
|
||||||
|
wg.Add(4)
|
||||||
|
go bf.process(j, wg)
|
||||||
|
go bf.process(j+1, wg)
|
||||||
|
go bf.process(j+2, wg)
|
||||||
|
go bf.process(j+3, wg)
|
||||||
|
j = j + 4
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Will save a video of where motion is detected in motion.mjpeg (in the current folder).
|
||||||
|
if bf.debug {
|
||||||
|
err := bf.saveFrame()
|
||||||
|
if err != nil {
|
||||||
|
return len(f), err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are not enough motion pixels then discard the frame.
|
||||||
|
if bf.motion < pixels {
|
||||||
|
return len(f), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write all motion frames.
|
||||||
|
return bf.dst.Write(f)
|
||||||
|
}
|
||||||
|
|
||||||
func absDiff(a, b uint32) int {
|
func absDiff(a, b uint32) int {
|
||||||
c := int(a) - int(b)
|
c := int(a) - int(b)
|
||||||
if c < 0 {
|
if c < 0 {
|
||||||
|
@ -55,160 +169,54 @@ func absDiff(a, b uint32) int {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MOGFilter is a filter that provides basic motion detection. MoG is short for
|
|
||||||
// Mixture of Gaussians method.
|
|
||||||
type BasicFilter struct {
|
|
||||||
dst io.WriteCloser
|
|
||||||
img image.Image
|
|
||||||
bg [][][3]uint32
|
|
||||||
bwImg *image.RGBA
|
|
||||||
w int
|
|
||||||
h int
|
|
||||||
file io.WriteCloser
|
|
||||||
motion int
|
|
||||||
debug bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMOGFilter returns a pointer to a new MOGFilter struct.
|
|
||||||
func NewBasicFilter(dst io.WriteCloser, debug bool) *BasicFilter {
|
|
||||||
bwImg := image.NewRGBA(image.Rect(0, 0, 0, 0))
|
|
||||||
var file io.WriteCloser
|
|
||||||
var err error
|
|
||||||
if debug {
|
|
||||||
file, err = os.Create("motion.mjpeg")
|
|
||||||
if err != nil {
|
|
||||||
panic("debug file didn't create")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
file = nil
|
|
||||||
}
|
|
||||||
return &BasicFilter{dst, nil, nil, bwImg, 0, 0, file, 0, debug}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implements io.Closer.
|
|
||||||
// Close frees resources used by gocv, because it has to be done manually, due to
|
|
||||||
// it using c-go.
|
|
||||||
func (b *BasicFilter) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Go routine for one row of the image to be processed
|
// Go routine for one row of the image to be processed
|
||||||
func (b *BasicFilter) Process(j int, wg *sync.WaitGroup) {
|
func (bf *Basic) process(j int, wg *sync.WaitGroup) {
|
||||||
defer wg.Done()
|
for i := 0; i < bf.w; i++ {
|
||||||
for i := 0; i < b.w; i++ {
|
n := bf.img.At(i, j)
|
||||||
n := b.img.At(i, j)
|
r, b, g, _ := n.RGBA()
|
||||||
R, G, B, _ := n.RGBA()
|
|
||||||
|
|
||||||
// Compare the difference of the RGB values of each pixel to the background image.
|
// Compare the difference of the RGB values of each pixel to the background image.
|
||||||
diffB := absDiff(B, b.bg[j][i][2])
|
diffR := absDiff(r, bf.bg[j][i].r)
|
||||||
diffR := absDiff(R, b.bg[j][i][0])
|
diffG := absDiff(g, bf.bg[j][i].g)
|
||||||
diffG := absDiff(G, b.bg[j][i][1])
|
diffB := absDiff(g, bf.bg[j][i].b)
|
||||||
|
|
||||||
diff := diffR + diffG + diffB
|
diff := diffR + diffG + diffB
|
||||||
|
|
||||||
if diff > 45000 {
|
if diff > threshold {
|
||||||
b.motion++
|
bf.motion++
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.debug {
|
if bf.debug {
|
||||||
if diff > 45000 {
|
if diff > threshold {
|
||||||
b.bwImg.SetRGBA(i, j, color.RGBA{0xff, 0xff, 0xff, 0xff})
|
bf.bwImg.SetRGBA(i, j, color.RGBA{0xff, 0xff, 0xff, 0xff})
|
||||||
} else {
|
} else {
|
||||||
b.bwImg.SetRGBA(i, j, color.RGBA{0x00, 0x00, 0x00, 0xff})
|
bf.bwImg.SetRGBA(i, j, color.RGBA{0x00, 0x00, 0x00, 0xff})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update backgound image.
|
// Update backgound image.
|
||||||
copy(b.bg[j][i][:], []uint32{R, G, B})
|
bf.bg[i][j].r = r
|
||||||
|
bf.bg[i][j].b = b
|
||||||
|
bf.bg[i][j].g = g
|
||||||
}
|
}
|
||||||
|
wg.Done()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements io.Writer.
|
func (bf *Basic) saveFrame() error {
|
||||||
// Write applies the motion filter to the video stream. Only frames with motion
|
col := color.RGBA{200, 100, 0, 255}
|
||||||
// are written to the destination encoder, frames without are discarded.
|
d := &font.Drawer{
|
||||||
func (b *BasicFilter) Write(f []byte) (int, error) {
|
Dst: bf.bwImg,
|
||||||
t0 := time.Now()
|
Src: image.NewUniform(col),
|
||||||
// Decode MJPEG.
|
Face: basicfont.Face7x13,
|
||||||
r := bytes.NewReader(f)
|
Dot: fixed.P(20, 30),
|
||||||
var err error
|
|
||||||
b.img, err = jpeg.Decode(r)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("image can't be decoded: %w", err)
|
|
||||||
}
|
}
|
||||||
|
var s string
|
||||||
t1 := time.Now()
|
if bf.motion > 1000 {
|
||||||
|
s = strconv.Itoa(bf.motion) + " Motion"
|
||||||
// Get background image and save a new background image if needed
|
} else {
|
||||||
// first frame must always be sent.
|
s = strconv.Itoa(bf.motion)
|
||||||
if b.bg == nil {
|
|
||||||
bounds := b.img.Bounds()
|
|
||||||
b.w = bounds.Max.X
|
|
||||||
b.h = bounds.Max.Y
|
|
||||||
|
|
||||||
b.bwImg = image.NewRGBA(image.Rect(0, 0, b.w, b.h))
|
|
||||||
|
|
||||||
b.bg = make([][][3]uint32, b.h)
|
|
||||||
for i, _ := range b.bg {
|
|
||||||
b.bg[i] = make([][3]uint32, b.w)
|
|
||||||
}
|
|
||||||
|
|
||||||
for j := 0; j < b.h; j++ {
|
|
||||||
for i := 0; i < b.w; i++ {
|
|
||||||
n := b.img.At(i, j)
|
|
||||||
R, G, B, _ := n.RGBA()
|
|
||||||
copy(b.bg[j][i][:], []uint32{R, G, B})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return len(f), nil
|
|
||||||
}
|
}
|
||||||
|
d.DrawString(s)
|
||||||
// Use 4x goroutines to each process one row of pixels
|
err := jpeg.Encode(bf.file, bf.bwImg, nil)
|
||||||
j := 0
|
return err
|
||||||
var m sync.Mutex
|
|
||||||
m.Lock()
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
|
|
||||||
for j < b.h {
|
|
||||||
wg.Add(4)
|
|
||||||
go b.Process(j, &wg)
|
|
||||||
go b.Process(j+1, &wg)
|
|
||||||
go b.Process(j+2, &wg)
|
|
||||||
go b.Process(j+3, &wg)
|
|
||||||
j = j + 4
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
if j >= b.h {
|
|
||||||
m.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Will save a video of where motion is detected in motion.mjpeg (in the current folder).
|
|
||||||
if b.debug {
|
|
||||||
col := color.RGBA{200, 100, 0, 255}
|
|
||||||
d := &font.Drawer{
|
|
||||||
Dst: b.bwImg,
|
|
||||||
Src: image.NewUniform(col),
|
|
||||||
Face: basicfont.Face7x13,
|
|
||||||
Dot: fixed.P(20, 30),
|
|
||||||
}
|
|
||||||
var s string
|
|
||||||
if b.motion > 1000 {
|
|
||||||
s = strconv.Itoa(b.motion) + " Motion"
|
|
||||||
} else {
|
|
||||||
s = strconv.Itoa(b.motion)
|
|
||||||
}
|
|
||||||
d.DrawString(s)
|
|
||||||
err = jpeg.Encode(b.file, b.bwImg, nil)
|
|
||||||
if err != nil {
|
|
||||||
return len(f), err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there are not enough motion pixels then discard the frame.
|
|
||||||
if b.motion < 1000 {
|
|
||||||
return len(f), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write all motion frames.
|
|
||||||
return b.dst.Write(f)
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue