diff --git a/filter/basic.go b/filter/basic.go index a4f8d5a5..2bc0b6fb 100644 --- a/filter/basic.go +++ b/filter/basic.go @@ -30,6 +30,7 @@ import ( "bytes" "fmt" "image" + "image/color" "image/jpeg" "io" "sync" @@ -40,10 +41,11 @@ type pixel struct{ r, g, b uint32 } // Basic is a filter that provides basic motion detection via a difference // method. type Basic struct { - debugging debugFile + debugging debugWindows dst io.WriteCloser img image.Image bg [][]pixel + bwImg *image.Gray thresh int pix int w int @@ -57,7 +59,7 @@ func NewBasic(dst io.WriteCloser, t, p int) *Basic { dst: dst, thresh: t, pix: p, - debugging: newDebugFile(), + debugging: newWindows("BASIC"), } } @@ -83,7 +85,7 @@ func (bf *Basic) Write(f []byte) (int, error) { bf.w = bounds.Max.X bf.h = bounds.Max.Y - bf.debugging.resetFrame(bf.w, bf.h) + bf.bwImg = image.NewGray(image.Rect(0, 0, bf.w, bf.h)) bf.bg = make([][]pixel, bf.h) for j, _ := range bf.bg { @@ -114,11 +116,8 @@ func (bf *Basic) Write(f []byte) (int, error) { wg.Wait() } - // Will save a video of where motion is detected in motion.mjpeg (in the current folder). - err = bf.debugging.saveFrame(bf.motion, bf.pix) - if err != nil { - return len(f), fmt.Errorf("image can't be encoded for debug video: %w", err) - } + // Draw debug information. + bf.debugging.show(bf.img, bf.bwImg, bf.motion > bf.pix, nil, fmt.Sprintf("Motion: %d", bf.motion), fmt.Sprintf("Pix: %d", bf.pix)) // If there are not enough motion pixels then discard the frame. if bf.motion < bf.pix { @@ -144,9 +143,9 @@ func (bf *Basic) process(j int, wg *sync.WaitGroup) { if diff > bf.thresh { bf.motion++ - bf.debugging.draw(i, j, 0xff) + bf.bwImg.SetGray(i, j, color.Gray{0xff}) } else { - bf.debugging.draw(i, j, 0x00) + bf.bwImg.SetGray(i, j, color.Gray{0x00}) } // Update backgound image. @@ -165,5 +164,4 @@ func absDiff(a, b uint32) int { } else { return c } - } diff --git a/filter/debug.go b/filter/debug.go index 66bbff11..4acd0dce 100644 --- a/filter/debug.go +++ b/filter/debug.go @@ -1,4 +1,5 @@ // +build debug +// +build !circleci /* DESCRIPTION @@ -27,76 +28,81 @@ LICENSE package filter import ( - "fmt" "image" "image/color" - "image/jpeg" - "io" - "os" - "strconv" - "golang.org/x/image/font" - "golang.org/x/image/font/basicfont" - "golang.org/x/image/math/fixed" + "gocv.io/x/gocv" ) -// debugFile is used for sending debug information to a file. -type debugFile struct { - bwImg *image.RGBA - file io.WriteCloser +// debugWindows is used for displaying debug information for the motion filters. +type debugWindows struct { + windows []*gocv.Window } -// newDebugFile creates a file for saving debugging frames to. -func newDebugFile() debugFile { - const debugfile = "motion.mjpeg" - - file, err := os.Create(debugfile) - if err != nil { - panic(fmt.Sprintf("could not create debug file: %w", err)) - } - - return debugFile{ - bwImg: image.NewRGBA(image.Rect(0, 0, 0, 0)), - file: file, - } -} - -// close closes files used for debugging purposes. -func (d *debugFile) close() error { - err := d.file.Close() - if err != nil { - return fmt.Errorf("file cannot be closed: %w", err) +// close frees resources used by gocv. +func (d *debugWindows) close() error { + for _, window := range d.windows { + err := window.Close() + if err != nil { + return err + } } return nil } -// draw draws debugging information to a frame. -func (d *debugFile) draw(i, j int, val uint8) { - d.bwImg.SetRGBA(i, j, color.RGBA{val, val, val, 0xff}) +// newWindows creates debugging windows for the motion filter. +func newWindows(name string) debugWindows { + return debugWindows{ + windows: []*gocv.Window{ + gocv.NewWindow(name + ": Video"), + gocv.NewWindow(name + ": Motion Detection"), + }, + } } -// 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. +// show displays debug information for the motion filters. +func (d *debugWindows) show(img, imgDelta interface{}, motion bool, contours *[][]image.Point, text ...string) { + var im, imD gocv.Mat + const errMsg = "cannot show frame in window: wrong type" + var drkRed = color.RGBA{191, 0, 0, 0} + var lhtRed = color.RGBA{191, 31, 31, 0} - var s string - if motion > pix { - s = strconv.Itoa(motion) + " Motion" - } else { - s = strconv.Itoa(motion) + // Type conversions. + switch img.(type) { + case image.Image: + im, _ = gocv.ImageToMatRGB(img.(image.Image)) + case gocv.Mat: + im = img.(gocv.Mat) + default: + panic(errMsg) + } + switch imgDelta.(type) { + case image.Image: + imD, _ = gocv.ImageToMatRGB(imgDelta.(image.Image)) + case gocv.Mat: + imD = imgDelta.(gocv.Mat) + default: + panic(errMsg) } - (&font.Drawer{ - Dst: d.bwImg, - Src: image.NewUniform(col), - Face: basicfont.Face7x13, - Dot: fixed.P(20, 30), - }).DrawString(s) + // Draw contours. + if contours != nil { + for _, c := range *contours { + rect := gocv.BoundingRect(c) + gocv.Rectangle(&im, rect, lhtRed, 1) + } + } - return jpeg.Encode(d.file, d.bwImg, nil) -} + // Draw debugging text. + if motion { + text = append(text, "Motion Detected") + } + for i, str := range text { + gocv.PutText(&im, str, image.Pt(32, 32*(i+1)), gocv.FontHersheyPlain, 2.0, drkRed, 2) + } -// resetFrame makes an image of given dimensions. -func (d *debugFile) resetFrame(w, h int) { - d.bwImg = image.NewRGBA(image.Rect(0, 0, w, h)) + // Display windows. + d.windows[0].IMShow(im) + d.windows[1].IMShow(imD) + d.windows[0].WaitKey(1) } diff --git a/filter/debug_nocircleci.go b/filter/debug_nocircleci.go deleted file mode 100644 index 87337ad4..00000000 --- a/filter/debug_nocircleci.go +++ /dev/null @@ -1,75 +0,0 @@ -// +build debug -// +build !circleci - -/* -DESCRIPTION - Displays debug information for the motion filters. - -AUTHORS - Scott Barnard - -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 -} - -// close frees resources used by gocv. -func (d *debugWindows) close() { - for _, window := range d.windows { - window.Close() - } -} - -// newWindows creates debugging windows for the motion filter. -func newWindows(name string) debugWindows { - return debugWindows{ - windows: []*gocv.Window{ - gocv.NewWindow(name + ": Bounding boxes"), - gocv.NewWindow(name + ": Motion"), - }, - } -} - -// show displays debug information for the motion filters. -func (d *debugWindows) show(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) -} diff --git a/filter/difference.go b/filter/difference.go index dfc3fcde..2593407f 100644 --- a/filter/difference.go +++ b/filter/difference.go @@ -30,6 +30,7 @@ LICENSE package filter import ( + "fmt" "io" "gocv.io/x/gocv" @@ -96,7 +97,7 @@ func (d *Difference) Write(f []byte) (int, error) { d.prev = img.Clone() // Draw debug information. - d.debugging.show(img, imgDelta, mean > d.thresh) + d.debugging.show(img, imgDelta, mean > d.thresh, nil, fmt.Sprintf("Mean: %f", mean), fmt.Sprintf("Threshold: %f", d.thresh)) // Don't write to destination if there is no motion. if mean < d.thresh { diff --git a/filter/knn.go b/filter/knn.go index 03c7be55..72873b4a 100644 --- a/filter/knn.go +++ b/filter/knn.go @@ -123,7 +123,7 @@ func (m *KNN) Write(f []byte) (int, error) { } // Draw debug information. - m.debugging.show(img, imgDelta, len(contours) > 0, contours) + m.debugging.show(img, imgDelta, len(contours) > 0, &contours) // Don't write to destination if there is no motion. if len(contours) == 0 { diff --git a/filter/mog.go b/filter/mog.go index b096e7b3..701cf0ff 100644 --- a/filter/mog.go +++ b/filter/mog.go @@ -123,7 +123,7 @@ func (m *MOG) Write(f []byte) (int, error) { } // Draw debug information. - m.debugging.show(img, imgDelta, len(contours) > 0, contours) + m.debugging.show(img, imgDelta, len(contours) > 0, &contours) // Don't write to destination if there is no motion. if len(contours) == 0 { diff --git a/filter/release.go b/filter/release.go index 079ecee5..fa1fc249 100644 --- a/filter/release.go +++ b/filter/release.go @@ -1,8 +1,9 @@ // +build !debug +// +build !circleci /* DESCRIPTION - Doesn't display debug information for the motion filters. + Displays debug information for the motion filters. AUTHORS Scott Barnard @@ -26,20 +27,19 @@ LICENSE package filter -// debugWindows is used for sending debug information to a file. -type debugFile struct{} +import ( + "image" +) -// newDebugFile creates a file for saving debugging frames to. -func newDebugFile() debugFile { return debugFile{} } +// debugWindows is used for displaying debug information for the motion filters. +type debugWindows struct{} -// close closes files used for debugging purposes. -func (d *debugFile) close() error { return nil } +// close frees resources used by gocv. +func (d *debugWindows) close() error { return nil } -// draw draws debugging information to a frame. -func (d *debugFile) draw(i, j int, val uint8) {} +// newWindows creates debugging windows for the motion filter. +func newWindows(name string) debugWindows { return debugWindows{} } -// saveFrame writes a frame showing motion to a file. -func (d *debugFile) saveFrame(motion, pix int) error { return nil } - -// resetFrame makes an image of given dimensions. -func (d *debugFile) resetFrame(w, h int) {} +// show displays debug information for the motion filters. +func (d *debugWindows) show(img, imgDelta interface{}, motion bool, contours *[][]image.Point, text ...string) { +} diff --git a/filter/release_nocircleci.go b/filter/release_nocircleci.go deleted file mode 100644 index 0276b457..00000000 --- a/filter/release_nocircleci.go +++ /dev/null @@ -1,46 +0,0 @@ -// +build !debug -// +build !circleci - -/* -DESCRIPTION - Doesn't display debug information for the motion filters. - -AUTHORS - Scott Barnard - -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{} - -// close frees resources used by gocv. -func (d *debugWindows) close() {} - -// newWindows creates debugging windows for the motion filter. -func newWindows(name string) debugWindows { return debugWindows{} } - -// show displays debug information for the motion filters. -func (d *debugWindows) show(img, imgDelta gocv.Mat, motion bool, contours ...[][]image.Point) {}