initial commit - untested (hardware pending)

This commit is contained in:
Steven Polley 2020-10-23 17:55:21 +00:00
parent 991f6c3920
commit f977240dfe
2 changed files with 176 additions and 1 deletions

View File

@ -1,3 +1,51 @@
# storage-security # storage-security
Security solution for my storage locker. Deployed to a raspberry pi with an attached camera Security solution for my storage locker. Deployed to a raspberry pi with an attached camera.
### Technology Stack
* Raspberry Pi 4 w/ camera
* GoCV
* Syncthing
The raspberry pi is configured as a WLAN AP which my phone will connect to. My phone will also be running syncthing and have the RPI configured as a sync device. The phone will pull logs and videos taken from the RPI which have been saved to the sync folder each time my phone connects.
The same folder on my phone is also a syncthing destination with spud, so when I come back upstairs, it uploads it to my server.
This isn't a foolproof method in case the intruder locates the RPI / camera and disables / destroys it / removes it. The data is still stored on the RPI until the next time I'm within proximity. This is an acceptable risk given the constraints, however if a better method is discovered to immediately store the data outside of the storage unit that would be preferred (something low powered sitting in my vehicle? )
### Raspberry Pi Setup
Full steps to re-build this system are below.
##### Prerequisites
1. Connect the camera
2. Image the SDcard with Raspberry Pi OS Lite (minimal image based on debian) - make sure to pick lite - do not use the desktop version.
##### Boot optimizations
Edit /boot/config.txt
```conf
# Disable the rainbow splash screen
disable_splash=1
# Disable bluetooth
dtoverlay=pi3-disable-bt
# Set the bootloader delay to 0 seconds. The default is 1s if not specified.
boot_delay=0
```
Edit /boot/cmdline.txt to make kernel quiet. The following is an example, the key part is the quiet flag
```conf
dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=PARTUUID=32e07f87-02 rootfstype=ext4 elevator=deadline fsck.repair=yes quiet rootwait
```
Disable dhcpcd - useless service in this case
```bash
sudo systemctl disable dhcpcd.service
```

127
main.go Normal file
View File

@ -0,0 +1,127 @@
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)
// 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).After(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
}