猫咪走开!计算机视觉喷水驱猫装备


MAKER:CatawayCV/译:趣无尽 Cherry(转载请注明出处)
不得不承认,Maker 撩猫那可是一把好手。咱们的撩猫系列教程,前有《用 Arduino DIY 镭射激光逗猫神器》,后有《Petoi Nybble:树莓派猫奴玩家的福音》。
下面要介绍的这个用来为你看家护院,驱赶外来的随地大小便的入侵猫!

虽然本教程没有对计算机视觉的工作原理作特别详细的介绍,但会将项目构建的过程和代码分享给大家。
首先,这个喷水驱猫装备是一个低压的喷头,而且转动的速度比猫的反应要快。但请放心,它不会把猫咪喷成落汤猫,只是产生威慑效果,打消它们在后院大小便的念头。

来看一段简单视频:

材料清单


树莓派 Zero(Raspberry Pi Zero)×1
SD卡×1
树莓派摄像头×1
继电器×1
555定时器×1(可以用 Arduino 和一个继电器代替)
电磁阀×1
喷水头×1
电子产品的外壳×1
锤子×1
低分辨率的摄像头×1

系统介绍

1、树莓派摄像头检测到类似一只猫大小的物体的移动了数帧(下一步会说明)。
2、树莓派引爆喷水头。
3、猫落荒而逃。
4、视频自动上传到 Youtube 供大家伙儿围观。

编程


使用 openCV 的帧减法,你可以找到随时间变化的帧的区域,使用一些实用的函数,确定物体的大小以及其变化,最重要的是确实物体是否是猫。
你也可以自己在网上搜索有关帧减法的详细教程。

代码工作原理
1、摄像头要保持拍摄并将画面进行比较。检测到猫大小的物体将引起注意。
2、猫大小的物体变化持续超过 4 帧,树莓派将用 GPIO 为继电器供电并启动 Arduino。
3、Arduino 发送信号给第二个继电器供电五秒钟并激活电磁阀。电磁阀在通电时启动喷水头。
4、当喷水头启动时,摄像头会停止检测并记录视频。
5、将视频上传到你的 Youtube,同时上传到 Dropbox 进行微调系统。

使用两个继电器和一个 Arduino 来打开电磁阀的原因如下
1、录制视频时树莓派不能开关电磁阀,python 脚本直到视频结束后才能停止。因此当视频还在录制时,需要 Arduino 或555定时器作为独立的脚本来关闭电磁阀。
2、第一个继电器和 Arduino 可以用 555 定时器替换,它可以节省大量的时间和金钱。
3、树莓派不能直接触发电磁阀,因为树莓派的 GPIO 工作在 3.3v 和 51mA (最大值)上,而电磁阀需要 5V 并且大于 51mA 才触发。
4、可以裁剪每个画面的大小,除去不需要检测的区域,例如隔壁家的花园。
5、可能会错过一些画面,浪费时间来设置它。
代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
import cv2
import 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
 
 
 
#------------------------------------------------Upload to youtube---------------------------------------
def HDtoYoutube():
    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))
        #Upload to youtube
        print("Uploading to YouTube")
        os.system('sudo youtube-upload --title="Cat Got Wet {0}" --client-secrets=client_secret.json vid{0}.h264'.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():
    print("Uploading Still To Dropbox Function")
    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)
    print("Ready to detect")
 
 
    #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:
            HDtoYoutube()
            StillsToDropbox()
 
 
except KeyboardInterrupt:
    print("Keyboard Interupt")   
except:
    print("Other Error")   
finally:
    GPIO.cleanup()

组装





1、将所有电器安装防水外壳,并使用胶带和热熔胶固定到位。


2、开始投入运转。

误报的情况


设备也会存在误报的情况,也许它也会喷到你的家人或朋友。因此提供一个专业的意见——在门上安装一个关闭设备的开关。

教程中如有误的地方请多多包涵与指教。希望你喜欢这个项目。

https://make.quwj.com/project/124

via

这是一篇发布于 6年 前的文章,其中的信息可能已经有所发展或是发生改变,请了解。


2 评论

发表评论

你的邮件地址不会公开


*