initial commit - untested (hardware pending)
This commit is contained in:
parent
991f6c3920
commit
f977240dfe
50
README.md
50
README.md
@ -1,3 +1,51 @@
|
||||
# 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
127
main.go
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user