pr changes made commenting and [][][3] to [][]pixel, pixel is a new struct

This commit is contained in:
Ella Pietraroia 2020-01-31 12:55:49 +10:30
parent d8c611de52
commit 5c68510209
1 changed files with 150 additions and 142 deletions

View File

@ -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)
} }