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 motionDetectInterval = 30 //number of frames between motion detection attempts // TBD: Implement this, currently does nothing deviceID = 0 // raspberry pi camera index syncFolder = "/sync" ) var ( lastMotionDetectedTime time.Time currentRecording *gocv.VideoWriter img, imgDelta, imgThresh gocv.Mat mog2 gocv.BackgroundSubtractorMOG2 osdColor color.RGBA ) func init() { log.Print("storage-security starting") 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() f, err := os.OpenFile(fmt.Sprintf("%s/storage-security.log", syncFolder), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) if err != nil { log.Fatalf("error opening log file: %v", err) } defer f.Close() log.SetOutput(f) webcam, err := gocv.OpenVideoCapture(deviceID) if err != nil { log.Fatalf("error opening video capture device: %v\n", deviceID) return } defer webcam.Close() fmt.Printf("Start reading device: %v\n", deviceID) // This is a warm up ladies and gentlemen. for i := 0; i < 20; i++ { if ok := webcam.Read(&img); !ok { log.Fatalf("video capture device closed: %v\n", deviceID) return } detectMotion(img) } frameCount := 0 // main loop for { if ok := webcam.Read(&img); !ok { log.Fatalf("video capture device closed: %v\n", deviceID) return } if img.Empty() { continue } 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("%s/storage-security-%s.avi", syncFolder, 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 { log.Fatalf("error opening video writer device: %v\n", err) return } } // And always update the timestamp lastMotionDetectedTime = time.Now() } frameCount = 0 } frameCount++ // 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 if lastMotionDetectedTime.Add(time.Second * recordLengthAfterMotion).Before(time.Now()) { log.Printf("motion has not been detected for the last %d seconds stopping recording to file", recordLengthAfterMotion) err = currentRecording.Close() if err != nil { log.Fatalf("failed to close openCV file recording handle: %v", err) } currentRecording = nil } } } log.Printf("shutting down") } // 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 }