7.2 KiB
Bots Readme — Motion Detection (mofin) Pipeline Addition
Overview
Added a motion detection stage (mofin) between runCam and terp in the video surveillance pipeline.
Old Pipeline
runCam ──TCP──▶ terp ──TCP──▶ coordinator
New Pipeline
runCam ──TCP──▶ mofin ──TCP──▶ terp ──TCP──▶ coordinator
└──TCP──▶ coordinator (no-motion clips)
mofin runs pixel-level motion detection on each clip. Clips with motion are forwarded to terp for classification (Haar cascade object detection). Clips without motion are sent directly to coordinator for storage, bypassing classification.
Files Created
frame/motion.go
Contains motion detection types and methods in the frame package.
Comparison struct:
type Comparison struct {
Guid1, Guid2 []byte
PixelsChanged int
Threshold uint8
}
Stores the result of comparing two adjacent frames in a clip — their GUIDs, how many pixels changed, and the pixel-value threshold used.
(*Frame).compareTo(next *Frame, threshold uint8) (int, error):
- Converts both frames to
gocv.MatviaToMat() - Computes
AbsDiffbetween the two Mats - Converts to grayscale if multi-channel (
CvtColor) - Applies
Threshold(pixel values abovethresholdcount as changed) - Returns
CountNonZero(number of changed pixels) or(0, error)on failure
(*Clip).CountChangedPixels(threshold uint8):
- Iterates over adjacent frame pairs in
c.PixelMats - For each pair, creates lightweight
Frameobjects from the clip's metadata and callscompareTo - Logs any error returned by
compareTo(avoiding writing to ephemeral Frame copies) - Appends a
Comparisonentry toc.Comparisons
mofin/main.go
A new pipeline binary that:
| Flag | Default | Purpose |
|---|---|---|
-listen |
:8083 |
TCP address to receive clips from runCam |
-terp |
localhost:8081 |
Forward address for clips with motion |
-coordinator |
localhost:8082 |
Forward address for clips without motion |
-threshold |
5000 |
Minimum PixelsChanged to consider motion present |
-passphrase |
"" |
Decryption/re-encryption passphrase (mirrors terp pattern) |
Concurrency: Clips are processed in parallel — each incoming clip gets its own goroutine (tracked by procWg). On shutdown, the listener is closed, in-flight decode goroutines drain via acceptWg, clipChan is closed, and procWg.Wait() blocks until all processing finishes before exiting.
processClip flow:
- If
passphraseis set, decrypt each frame'sPixelBytesin-place viaSublimate+Decrypt - Call
clip.CountChangedPixels(30)— pixel-value sensitivity of 30 - Check if any
Comparison.PixelsChanged >= thresholdflag - Re-encrypt if passphrase was set
- Forward to
terpaddress (motion) orcoordinatoraddress (no motion)
mofin/go.mod
Standard module setup with replace frame => ../frame (same pattern as runCam, terp, coordinator).
Files Modified
frame/frame.go
- Added
Errors []stringfield toFrame— stores descriptive pipeline errors for backend review - Added
Comparisons []Comparisonfield toClip— stores pairwise motion comparison results - Removed a stale duplicate
Detectmethod declaration at end of file
runCam/main.go
- Renamed
-terpflag to-mofin(defaultlocalhost:8083) - Updated all internal references from
terpAddr→mofinAddrinmain(),rebalance(), andrunCamera()
config.yaml
Added mofin1 node between cam1 and terp1:
- name: "mofin1"
binary: "mofin"
flags:
listen: ":8083"
terp: "localhost:8081"
coordinator: "localhost:8082"
threshold: 5000
Also updated cam1 to point mofin: "localhost:8083" instead of terp: "localhost:8081".
Tests
frame/frame_test.go — 19 tests
| Test | What it covers |
|---|---|
TestToMat_Roundtrip |
ToMat() returns an identical Mat from Frame data |
TestToMat_NonNilResult |
ToMat() succeeds with valid 2×2 grayscale data |
TestToMat_EmptyBytes |
ToMat() errors on zero-size buffer |
TestEncryptDecrypt_Roundtrip |
Encrypt then decrypt with same passphrase restores original bytes |
TestEncryptDecrypt_WrongPassphrase |
Decrypt with wrong passphrase fails |
TestEncryptDecrypt_EmptyBytes |
Encrypt/Decrypt on nil PixelBytes is a no-op |
TestDecrypt_TooShort |
Decrypt on truncated data fails |
TestEncrypt_UniqueSaltPerCall |
Two encryptions of same data produce different ciphertexts |
TestSublimate_Basic |
Clip.Sublimate() produces correct Frame count, metadata, GUIDs, timestamps |
TestSublimate_EmptyClip |
Empty clip produces zero frames |
TestSublimate_DetectionsCarryOver |
Frame detections are correctly mapped from Clip |
TestCheckLenCorrelations_Match |
Equal-length slices return true |
TestCheckLenCorrelations_PixelMatsGuidsMismatch |
Mismatched PixelMats/Guids returns false |
TestCheckLenCorrelations_TimestampsMismatch |
Mismatched Timestamps returns false |
TestCheckLenCorrelations_Empty |
Empty clip returns true |
TestClipSend |
Send/Receive roundtrip over TCP preserves Clip data |
TestClipSend_InvalidAddress |
Send to unreachable address returns error |
TestFrameFields_ZeroValues |
Zero-value Frame has nil Errors, nil Detections |
TestClipFields_ZeroValues |
Zero-value Clip has nil Comparisons |
frame/motion_test.go — 12 tests
| Test | What it covers |
|---|---|
TestCompareTo_IdenticalFrames |
Two identical frames → 0 changed pixels |
TestCompareTo_AllPixelsChanged |
0→255 → all 16 pixels of 4×4 detected as changed |
TestCompareTo_PartialChange |
3 of 16 pixels differing → exactly 3 counted |
TestCompareTo_ThresholdFiltersSmallDiffs |
Pixels with diff > threshold counted; diff ≤ threshold ignored |
TestCompareTo_MultiChannel |
Multi-channel (BGR) → grayscale conversion works, changed pixels detected |
TestCompareTo_DimensionMismatch |
Different sizes return error |
TestCompareTo_ChannelMismatch |
Different channel counts return error |
TestCompareTo_IdenticalPixelData |
Same pixel bytes → 0 changed (no crash with equal data) |
TestCountChangedPixels_Basic |
3 frames → 2 comparisons, correct pixel counts |
TestCountChangedPixels_SingleFrame |
1 frame → 0 comparisons |
TestCountChangedPixels_EmptyClip |
0 frames → 0 comparisons |
TestCountChangedPixels_ResetsComparisons |
Pre-existing Comparisons are cleared before run |
TestCountChangedPixels_ThresholdParameter |
Pixel-value threshold correctly filters small diffs |
TestComparison_Fields |
Comparison struct fields store/retrieve correctly |
frame/utils_test.go — 16 tests
Camera-dependent tests skip with -short flag. Pure-logic tests (empty range, negative index, struct fields, Sscanf) always run.
Running
go test -short ./... # fast (skips camera hardware scans)
go test ./... # full suite (attempts V4L device probes)
All 42+ tests pass. Pre-existing ScanCams tests preserved and corrected.
Build Verification
All five modules pass go build ./... and go vet ./...:
frame/runCam/mofin/terp/coordinator/