2020-10-23 17:55:21 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"image"
|
|
|
|
"image/color"
|
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"gocv.io/x/gocv"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
minimumMotionArea = 3000 // Motion detection minimum area needed to move
|
|
|
|
recordLengthAfterMotion = 30 // Number of seconds to keep recording going after motion was last detected
|
2020-10-29 04:32:45 +00:00
|
|
|
motionDetectInterval = 30 //number of frames between motion detection attempts // TBD: Implement this, currently does nothing
|
2020-10-23 17:55:21 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
lastMotionDetectedTime time.Time
|
|
|
|
currentRecording *gocv.VideoWriter
|
|
|
|
img, imgDelta, imgThresh gocv.Mat
|
|
|
|
mog2 gocv.BackgroundSubtractorMOG2
|
|
|
|
osdColor color.RGBA
|
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
if len(os.Args) < 2 {
|
|
|
|
fmt.Println("How to run:\n\tstorage-security [camera ID]")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
img = gocv.NewMat()
|
|
|
|
imgDelta = gocv.NewMat()
|
|
|
|
imgThresh = gocv.NewMat()
|
|
|
|
mog2 = gocv.NewBackgroundSubtractorMOG2()
|
|
|
|
osdColor = color.RGBA{0, 0, 255, 0}
|
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
defer img.Close()
|
|
|
|
defer imgDelta.Close()
|
|
|
|
defer imgThresh.Close()
|
|
|
|
defer mog2.Close()
|
|
|
|
|
|
|
|
// parse args
|
|
|
|
deviceID := os.Args[1]
|
|
|
|
|
|
|
|
webcam, err := gocv.OpenVideoCapture(deviceID)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Error opening video capture device: %v\n", deviceID)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer webcam.Close()
|
|
|
|
|
|
|
|
fmt.Printf("Start reading device: %v\n", deviceID)
|
|
|
|
|
2020-10-29 03:18:36 +00:00
|
|
|
// This is a warm up ladies and gentlemen.
|
2020-10-29 03:17:11 +00:00
|
|
|
for i := 0; i < 20; i++ {
|
|
|
|
if ok := webcam.Read(&img); !ok {
|
|
|
|
fmt.Printf("Device closed: %v\n", deviceID)
|
|
|
|
return
|
|
|
|
}
|
2020-10-29 03:18:36 +00:00
|
|
|
detectMotion(img)
|
2020-10-29 03:17:11 +00:00
|
|
|
}
|
|
|
|
|
2020-10-29 04:32:45 +00:00
|
|
|
frameCount := 0
|
|
|
|
|
2020-10-23 17:55:21 +00:00
|
|
|
// main loop
|
|
|
|
for {
|
|
|
|
if ok := webcam.Read(&img); !ok {
|
|
|
|
fmt.Printf("Device closed: %v\n", deviceID)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if img.Empty() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-10-29 04:32:45 +00:00
|
|
|
if frameCount >= motionDetectInterval {
|
|
|
|
if detectMotion(img) {
|
|
|
|
// Determine if a new recording needs to start
|
|
|
|
if time.Now().After(lastMotionDetectedTime.Add(time.Second * recordLengthAfterMotion)) {
|
|
|
|
fileName := fmt.Sprintf("storage-%s.avi", time.Now().Format(time.RFC3339))
|
|
|
|
log.Printf("motion detected, started recording to file named %s", fileName)
|
|
|
|
currentRecording, err = gocv.VideoWriterFile(fileName, "MJPG", 25, img.Cols(), img.Rows(), true)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("error opening video writer device: %v\n", err)
|
|
|
|
return
|
|
|
|
}
|
2020-10-23 17:55:21 +00:00
|
|
|
}
|
2020-10-29 04:32:45 +00:00
|
|
|
// And always update the timestamp
|
|
|
|
lastMotionDetectedTime = time.Now()
|
2020-10-23 17:55:21 +00:00
|
|
|
}
|
2020-10-29 04:32:45 +00:00
|
|
|
frameCount = 0
|
2020-10-23 17:55:21 +00:00
|
|
|
}
|
2020-10-29 04:34:44 +00:00
|
|
|
frameCount++
|
2020-10-23 17:55:21 +00:00
|
|
|
|
|
|
|
// Determine if we are currently recording and if so, then save the frame to the video
|
|
|
|
if currentRecording != nil {
|
|
|
|
// OSD / timestamp
|
|
|
|
gocv.PutText(&img, time.Now().Format(time.RFC3339), image.Pt(10, 20), gocv.FontHersheyPlain, 1.2, osdColor, 2)
|
|
|
|
|
|
|
|
currentRecording.Write(img)
|
|
|
|
// Determine if we should stop recording
|
2020-10-29 03:17:11 +00:00
|
|
|
if lastMotionDetectedTime.Add(time.Second * recordLengthAfterMotion).Before(time.Now()) {
|
2020-10-23 17:55:21 +00:00
|
|
|
log.Printf("motion has not been detected for the last %d seconds stopping recording to file", recordLengthAfterMotion)
|
|
|
|
err = currentRecording.Close()
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("failed to close openCV file recording handle: %v", err)
|
|
|
|
}
|
|
|
|
currentRecording = nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns true if motion detected in current frame
|
|
|
|
func detectMotion(frame gocv.Mat) bool {
|
|
|
|
// first phase of cleaning up image, obtain foreground only
|
|
|
|
mog2.Apply(frame, &imgDelta)
|
|
|
|
|
|
|
|
// remaining cleanup of the image to use for finding contours.
|
|
|
|
// first use threshold
|
|
|
|
gocv.Threshold(imgDelta, &imgThresh, 25, 255, gocv.ThresholdBinary)
|
|
|
|
|
|
|
|
// then dilate
|
|
|
|
kernel := gocv.GetStructuringElement(gocv.MorphRect, image.Pt(3, 3))
|
|
|
|
defer kernel.Close()
|
|
|
|
gocv.Dilate(imgThresh, &imgThresh, kernel)
|
|
|
|
|
|
|
|
// now find contours
|
|
|
|
contours := gocv.FindContours(imgThresh, gocv.RetrievalExternal, gocv.ChainApproxSimple)
|
|
|
|
for _, c := range contours {
|
|
|
|
area := gocv.ContourArea(c)
|
|
|
|
if area < minimumMotionArea {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|