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、开始投入运转。
误报的情况
设备也会存在误报的情况,也许它也会喷到你的家人或朋友。因此提供一个专业的意见——在门上安装一个关闭设备的开关。
教程中如有误的地方请多多包涵与指教。希望你喜欢这个项目。
首先你要有个房子,带后院那种
加上一个热成像仪。