filter: implement debug builds for basic motion filter

This commit is contained in:
Scott 2020-02-04 11:14:09 +10:30
parent 5917f35ccd
commit b6d6090e25
6 changed files with 199 additions and 102 deletions

View File

@ -30,62 +30,44 @@ import (
"bytes"
"fmt"
"image"
"image/color"
"image/jpeg"
"io"
"os"
"strconv"
"sync"
"golang.org/x/image/font"
"golang.org/x/image/font/basicfont"
"golang.org/x/image/math/fixed"
)
const debugfile = "motion.mjpeg"
type pixel struct{ r, g, b uint32 }
// Basic is a filter that provides basic motion detection via a difference
// method.
type Basic struct {
debugFile
dst io.WriteCloser
img image.Image
bg [][]pixel
bwImg *image.RGBA
thresh int
pix int
w int
h int
file io.WriteCloser
motion int
debug bool
}
// NewBasic returns a pointer to a new Basic filter struct.
func NewBasic(dst io.WriteCloser, debug bool, t, p int) *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(debugfile)
if err != nil {
panic(fmt.Sprintf("could not create debug file: %v", err))
}
func NewBasic(dst io.WriteCloser, t, p int) *Basic {
bf := &Basic{
dst: dst,
thresh: t,
pix: p,
}
return &Basic{dst, nil, nil, bwImg, t, p, 0, 0, file, 0, debug}
err := bf.newDebugFile()
if err != nil {
panic(fmt.Sprintf("could not create debug file: %w", err))
}
return bf
}
// Implements io.Closer.
func (bf *Basic) Close() error {
if bf.debug {
err := bf.file.Close()
if err != nil {
return fmt.Errorf("file cannot be closed: %w", err)
}
}
return nil
return bf.closeDebugFile()
}
// Implements io.Writer.
@ -105,7 +87,7 @@ func (bf *Basic) Write(f []byte) (int, error) {
bf.w = bounds.Max.X
bf.h = bounds.Max.Y
bf.bwImg = image.NewRGBA(image.Rect(0, 0, bf.w, bf.h))
bf.resetDebugFileFrame(bf.w, bf.h)
bf.bg = make([][]pixel, bf.h)
for j, _ := range bf.bg {
@ -137,11 +119,9 @@ func (bf *Basic) Write(f []byte) (int, error) {
}
// 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), fmt.Errorf("image can't be encoded for debug video: %w", err)
}
err = bf.saveFrame(bf.motion, bf.pix)
if err != nil {
return len(f), fmt.Errorf("image can't be encoded for debug video: %w", err)
}
// If there are not enough motion pixels then discard the frame.
@ -168,14 +148,9 @@ func (bf *Basic) process(j int, wg *sync.WaitGroup) {
if diff > bf.thresh {
bf.motion++
}
if bf.debug {
if diff > bf.thresh {
bf.bwImg.SetRGBA(i, j, color.RGBA{0xff, 0xff, 0xff, 0xff})
} else {
bf.bwImg.SetRGBA(i, j, color.RGBA{0x00, 0x00, 0x00, 0xff})
}
bf.drawToDebugFile(i, j, 0xff)
} else {
bf.drawToDebugFile(i, j, 0x00)
}
// Update backgound image.
@ -186,25 +161,6 @@ func (bf *Basic) process(j int, wg *sync.WaitGroup) {
wg.Done()
}
// Writes a visualisation of the motion being detected to file.
func (bf *Basic) saveFrame() error {
col := color.RGBA{200, 100, 0, 255} // Red text.
d := &font.Drawer{
Dst: bf.bwImg,
Src: image.NewUniform(col),
Face: basicfont.Face7x13,
Dot: fixed.P(20, 30),
}
var s string
if bf.motion > bf.pix {
s = strconv.Itoa(bf.motion) + " Motion"
} else {
s = strconv.Itoa(bf.motion)
}
d.DrawString(s)
return jpeg.Encode(bf.file, bf.bwImg, nil)
}
// Returns the absolute value of the difference of two uint32 numbers.
func absDiff(a, b uint32) int {
c := int(a) - int(b)

View File

@ -1,5 +1,4 @@
// +build debug
// +build !circleci
/*
DESCRIPTION
@ -28,43 +27,70 @@ LICENSE
package filter
import (
"fmt"
"image"
"image/color"
"image/jpeg"
"io"
"os"
"strconv"
"gocv.io/x/gocv"
"golang.org/x/image/font"
"golang.org/x/image/font/basicfont"
"golang.org/x/image/math/fixed"
)
// debugWindows is used for displaying debug information for the motion filters.
type debugWindows struct {
windows []*gocv.Window
// debugWindows is used for sending debug information to a file.
type debugFile struct {
bwImg *image.RGBA
file io.WriteCloser
}
// closeWindows frees resources used by gocv.
func (d *debugWindows) closeWindows() {
for _, window := range d.windows {
window.Close()
// newDebugFile creates a file for saving debugging frames to.
func (d *debugFile) newDebugFile() error {
const debugfile = "motion.mjpeg"
d.bwImg = image.NewRGBA(image.Rect(0, 0, 0, 0))
var err error
d.file, err = os.Create(debugfile)
return err
}
// closeDebugFile closes files used for debugging purposes.
func (d *debugFile) closeDebugFile() error {
err := d.file.Close()
if err != nil {
return fmt.Errorf("file cannot be closed: %w", err)
}
return nil
}
// newWindows creates debugging windows for the motion filter.
func (d *debugWindows) newWindows(name string) {
d.windows = []*gocv.Window{gocv.NewWindow(name + ": Bounding boxes"), gocv.NewWindow(name + ": Motion")}
// drawToDebugFile draws debugging information to a frame.
func (d *debugFile) drawToDebugFile(i, j int, val uint8) {
d.bwImg.SetRGBA(i, j, color.RGBA{val, val, val, 0xff})
}
// showDebug displays debug information for the motion filters.
func (d *debugWindows) showDebug(img, imgDelta gocv.Mat, motion bool, contours ...[][]image.Point) {
if len(contours) > 0 {
for _, c := range contours[0] {
rect := gocv.BoundingRect(c)
gocv.Rectangle(&img, rect, color.RGBA{0, 0, 255, 0}, 1)
}
// saveFrame writes a frame showing motion to a file.
func (d *debugFile) saveFrame(motion, pix int) error {
col := color.RGBA{200, 100, 0, 255} // Red text.
var s string
if motion > pix {
s = strconv.Itoa(motion) + " Motion"
} else {
s = strconv.Itoa(motion)
}
if motion {
gocv.PutText(&img, "Motion", image.Pt(32, 32), gocv.FontHersheyPlain, 2.0, color.RGBA{255, 0, 0, 0}, 2)
}
(&font.Drawer{
Dst: d.bwImg,
Src: image.NewUniform(col),
Face: basicfont.Face7x13,
Dot: fixed.P(20, 30),
}).DrawString(s)
d.windows[0].IMShow(img)
d.windows[1].IMShow(imgDelta)
d.windows[0].WaitKey(1)
return jpeg.Encode(d.file, d.bwImg, nil)
}
// resetDebugFileFrame makes an image of given dimensions.
func (d *debugFile) resetDebugFileFrame(w, h int) {
d.bwImg = image.NewRGBA(image.Rect(0, 0, w, h))
}

View File

@ -0,0 +1,70 @@
// +build debug
// +build !circleci
/*
DESCRIPTION
Displays debug information for the motion filters.
AUTHORS
Scott Barnard <scott@ausocean.org>
LICENSE
This file is Copyright (C) 2020 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 (
"image"
"image/color"
"gocv.io/x/gocv"
)
// debugWindows is used for displaying debug information for the motion filters.
type debugWindows struct {
windows []*gocv.Window
}
// closeWindows frees resources used by gocv.
func (d *debugWindows) closeWindows() {
for _, window := range d.windows {
window.Close()
}
}
// newWindows creates debugging windows for the motion filter.
func (d *debugWindows) newWindows(name string) {
d.windows = []*gocv.Window{gocv.NewWindow(name + ": Bounding boxes"), gocv.NewWindow(name + ": Motion")}
}
// showDebug displays debug information for the motion filters.
func (d *debugWindows) showDebug(img, imgDelta gocv.Mat, motion bool, contours ...[][]image.Point) {
if len(contours) > 0 {
for _, c := range contours[0] {
rect := gocv.BoundingRect(c)
gocv.Rectangle(&img, rect, color.RGBA{0, 0, 255, 0}, 1)
}
}
if motion {
gocv.PutText(&img, "Motion", image.Pt(32, 32), gocv.FontHersheyPlain, 2.0, color.RGBA{255, 0, 0, 0}, 2)
}
d.windows[0].IMShow(img)
d.windows[1].IMShow(imgDelta)
d.windows[0].WaitKey(1)
}

View File

@ -1,5 +1,4 @@
// +build !debug
// +build !circleci
/*
DESCRIPTION
@ -27,20 +26,20 @@ LICENSE
package filter
import (
"image"
// debugWindows is used for sending debug information to a file.
type debugFile struct{}
"gocv.io/x/gocv"
)
// newDebugFile creates a file for saving debugging frames to.
func (d *debugFile) newDebugFile() error { return nil }
// debugWindows is used for displaying debug information for the motion filters.
type debugWindows struct{}
// closeDebugFile closes files used for debugging purposes.
func (d *debugFile) closeDebugFile() error { return nil }
// closeWindows frees resources used by gocv.
func (d *debugWindows) closeWindows() {}
// drawToDebugFile draws debugging information to a frame.
func (d *debugFile) drawToDebugFile(i, j int, val uint8) {}
// newWindows creates debugging windows for the motion filter.
func (d *debugWindows) newWindows(name string) {}
// saveFrame writes a frame showing motion to a file.
func (d *debugFile) saveFrame(motion, pix int) error { return nil }
// showDebug displays debug information for the motion filters.
func (d *debugWindows) showDebug(img, imgDelta gocv.Mat, motion bool, contours ...[][]image.Point) {}
// resetDebugFileFrame makes an image of given dimensions.
func (d *debugFile) resetDebugFileFrame(w, h int) {}

View File

@ -0,0 +1,46 @@
// +build !debug
// +build !circleci
/*
DESCRIPTION
Displays debug information for the motion filters.
AUTHORS
Scott Barnard <scott@ausocean.org>
LICENSE
This file is Copyright (C) 2020 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 (
"image"
"gocv.io/x/gocv"
)
// debugWindows is used for displaying debug information for the motion filters.
type debugWindows struct{}
// closeWindows frees resources used by gocv.
func (d *debugWindows) closeWindows() {}
// newWindows creates debugging windows for the motion filter.
func (d *debugWindows) newWindows(name string) {}
// showDebug displays debug information for the motion filters.
func (d *debugWindows) showDebug(img, imgDelta gocv.Mat, motion bool, contours ...[][]image.Point) {}

View File

@ -349,7 +349,7 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io.
case config.FilterDifference:
r.filters[i] = filter.NewDifference(dst, r.cfg.DiffThreshold)
case config.FilterBasic:
r.filters[i] = filter.NewBasic(dst, false, r.cfg.BasicThreshold, r.cfg.BasicPixels)
r.filters[i] = filter.NewBasic(dst, r.cfg.BasicThreshold, r.cfg.BasicPixels)
default:
panic("Undefined Filter")
}