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 = 1 //number of seconds between motion detection attempts // TBD: Implement this, currently does nothing ) 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) // This is a warm up ladies and gentlemen. for i := 0; i < 20; i++ { if ok := webcam.Read(&img); !ok { fmt.Printf("Device closed: %v\n", deviceID) return } } // main loop for { if ok := webcam.Read(&img); !ok { fmt.Printf("Device closed: %v\n", deviceID) return } if img.Empty() { continue } 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 } } // And always update the timestamp lastMotionDetectedTime = time.Now() } // 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.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 }