# Cat-a-way - Computer Vision Cat Sprinkler

3,591

21

2

Problem - Cats using your garden as a toilet

Solution - Spend a too much time over engineering a cat sprinkler with auto youtube upload feature

This is not a step by step, but an overview of construction and some code

#BeforeYouCallPETA - The cats are fine, its a low pressure sprinkler just like rain, that they can outrun before it even pivots around to them. The project is not to soak a cat, but to shew them away before they litter my garden with the cat version of Barkers Eggs.

### Teacher Notes

Teachers! Did you use this instructable in your classroom?
Add a Teacher Note to share how you incorporated it into your lesson.

## Step 1: Main Items Required

Raspberry Pi zero & SD card

Raspberry Pi camera

Relay

555 timer.... (or an arduino and another relay if your 555 timers don't arrive)

Solenoid

Sprinkler

Some kind of housing for the electronics

A willingness to hit a metaphorical nail with a 6 tonne sledge hammer

A camera with so little resolution you can barely see the water, but can still see the cats running for cover

## Step 2: The System

1, Pi camera detects a cat sized object moving for a few camera frames (exaplined in next step)

2, Pi sets off sprinkler

3, Cat runs for cover

## Step 3: Coding Time

Using openCV using frame subtraction you can find areas of the frame that change over time, using some nifty functions you can figure out how big these changes are and if they persist over time, and most importantly find out if they are cat sized.

There are quite a few tutorials on frame subtraction that go into great detail if you do a quick google search.

Overview of how the code works

1, The camera keeps taking frames and comparing them to the last

2, If a cat sized shape is detected it is noted

3, If the cat sized change persists over around 4 frames the pi uses its GPIO to power relay to start arduino

4, The arduino sends a signal to power the second relay for 5 seconds which activates the solenoid

5, The solenoid when powered allows water to the sprinkler

6, While sprinkler is active camera stops detecting and records video

8, Stills uploaded to dropbox for fine tuning system

Note - Why i ended up using 2 relays and an arduino to turn on a solenoid for 5 seconds.....

1,The pi cannot start and stop the solenoid while recording the video as the python scrips pauses until the video is finished, hence the need for the arduino (or 555 timer) to allow the solenoid to open and close independant to the script while the video is still recording.

2,The first relay and arduino can be replaced with a 555 timer but that didn't come in the post in time for this project, 555 would save a lot of time money and steps.

3,The pi cannot trigger the solenoid directly as the Pi GPIO works on 3.3v and 51mA max, and the solenoid wants 5V and much more than 51mA to trigger.

4,Each frame can be cropped to remove motion detection in unwanted areas, such as a neigbors garden. Failure to do this will result in said neighbour giving confused looks into your garden because the sprinkler goes off every time he wants to go into his shed.

5,I probably missed something obvious and wasted my time setting it up like this.

Code below

```import cv2import numpy as np
import argparse #cat
import time
import RPi.GPIO as GPIO
import os
import dropbox
from picamera.array import PiRGBArray
from picamera import PiCamera

ctime = time.strftime("_%H-%M-%S")
cdate = time.strftime("_%d-%m-%Y")
vidname = ctime + cdate
#Trigger relay
GPIO.output(11,True)
time.sleep(.5)
GPIO.output(11,False)
print("Taking Video")
try:

#Take Video
os.system('raspivid -w 1640 -h 922 -o vid{0}.h264 -t 15000'.format(vidname))
#Remove video file when done
os.remove('vid{0}.h264'.format(vidname))
print("Video uploaded and removed from Pi")

except:
pass

#------------------------------------------------Stills to dropbox---------------------------------------
def StillsToDropbox():
access_token = 'Ah ah ah, you didn't say the magic word...Ah ah ah, you didn't say the magic word'
ctime = time.strftime("%H:%M:%S")
cdate = time.strftime("%d-%m-%Y")
try:
filename = "/Motion/{0}/DetectedAt_{1}.jpg".format(cdate, ctime)
print(filename)
client = dropbox.client.DropboxClient(access_token)
image = open("ToDropbox.jpg", 'rb')
client.put_file(filename, image)
image.close()
os.remove("ToDropbox.jpg")
except:
pass

#------------------------------------------------Detect motion-----------------------------------------
def DetectMotion():
#Define vars
min_area = 400
tolarance = 25 #change in pixel
bluramount = 21
timetoforget = 0.5
kernel = np.ones((5,5),np.uint8) #used for dialate
MotionCounter = 0
MinTargetArea = 600 #smallest size to detect
MaxTargetArea = 5000 #Largest size to detect
now = time.time()
then = time.time()

#initialise camera
camera = PiCamera()
camera.resolution = (640,480)
camera.framerate = 10
rawCapture = PiRGBArray(camera, size=(640,480))

#warmup camera
time.sleep(1)

#Grab first frame & prep it to go into cv2.acumulate weight
camera.capture(rawCapture, format="bgr")
avg = rawCapture.array

#Crop out unwanted region
PolyCrop = np.array( [[[362,480],[613,365],[628,161],[498,0],[640,0],[640,480]]], dtype=np.int32 )
cv2.fillPoly(avg, PolyCrop, 0,0,0)
#Process image
avg = cv2.cvtColor(avg, cv2.COLOR_BGR2GRAY)
avg = cv2.GaussianBlur(avg, (bluramount, bluramount), 0)
avg = avg.copy().astype("float")
rawCapture.truncate(0)

#capture frames
for frame in camera.capture_continuous(rawCapture, format="bgr", use_video_port=True):
#Pause Switch
loopgo = GPIO.input(PauseNow)
#print(loopgo)
while loopgo == 0:
#print(loopgo)
loopgo = GPIO.input(PauseNow)
time.sleep(1)

#grabs raw numpy array
currentframe = frame.array
key = cv2.waitKey(1) & 0xFF
#Crop out unwanted region
cv2.fillPoly(currentframe, PolyCrop, 0,0,0)
rawCapture.truncate(0) #Clear frame buffer for next loop
currentgray = cv2.cvtColor(currentframe, cv2.COLOR_BGR2GRAY)
currentgray = cv2.GaussianBlur(currentgray, (bluramount, bluramount), 0)
#make time average frame
cv2.accumulateWeighted(currentgray, avg, timetoforget)
#get difference in frame
frameDelta = cv2.absdiff(currentgray, cv2.convertScaleAbs(avg))
thresh = cv2.threshold(frameDelta, tolarance, 255, cv2.THRESH_BINARY)[1]
#Turn to blob
thresh = cv2.dilate(thresh, kernel, iterations = 10) #dilate
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel) #close holes
thresh = cv2.erode(thresh, kernel, iterations = 5) #erode

#contours
_, cnts, _= cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# loop over the contours
for c in cnts:
# if the contour is too small, ignore it
if cv2.contourArea(c) < min_area:
continue
# compute the bounding box for the contour, draw it on the frame,
# and update the textq
(x, y, w, h) = cv2.boundingRect(c)

#Too small : Red Box
if cv2.contourArea(c) < MinTargetArea:
cv2.rectangle(currentframe, (x, y), (x + w, y + h), (0, 0, 255), 2)
#MotionCounter = MotionCounter + 1 #Debug take all the pictures
print("MotionDetected")
#Just right : Green Box
if cv2.contourArea(c) >= MinTargetArea and cv2.contourArea(c) <= MaxTargetArea:
cv2.rectangle(currentframe, (x, y), (x + w, y + h), (0, 255, 0), 2)
MotionCounter = MotionCounter + 1 #Debug take all the pictures
print("MotionDetected")
#Too big : Blue Box
if cv2.contourArea(c) > MaxTargetArea:
cv2.rectangle(currentframe, (x, y), (x + w, y + h), (255, 0, 0), 2)
#MotionCounter = MotionCounter + 1 #Debug take all the pictures
print("MotionDetected")

#Keep now up to date
now = time.time()
#MotionCounterTimer
if (MotionCounter > 0):
if (now - then > 10):
MotionCounter = 0
then = time.time()
#Break loop on pressing Q
if key == ord("q"):
break

#If motion persists save current frame and activate countermeasures
if MotionCounter >= 4:
MotionCounter = 0
cv2.imwrite('ToDropbox.jpg', currentframe)
camera.close()
return True

#------------------------------------------------Main---------------------------------------
try:
#Set Pins
GPIO.setmode(GPIO.BOARD)
PauseNow=12
GPIO.setup(11,GPIO.OUT)
GPIO.setup(PauseNow,GPIO.IN,pull_up_down=GPIO.PUD_UP)

while True:
MotionDetected = False
MotionDetected = DetectMotion()
if MotionDetected == True:
StillsToDropbox()

except KeyboardInterrupt:
print("Keyboard Interupt")
except:
print("Other Error")
finally:
GPIO.cleanup()```

## Step 4: Putting It Together

Cram the electrics into a waterproof housing, screw things into walls and use lots of duct tape and hot glue

## Step 5: Results

When it works it works

## Step 6: False Positives

Pro-tip - Put a switch by the door that pauses the motion detection program..... then forget to use it and get soaked when putting the bins out.

Hope I made any experts in programming, electronics and DIY cringe with all the mistakes I probably made, and especially hope you enjoyed all my spelling mistakes.

## Recommendations

• ### Internet of Things Class

22,728 Enrolled

## 2 Discussions

Awesome! I need something like this to get rid of the squirrels in my garden.