py + opencv 打造樹莓派監控,場景有變化自動拍照上傳到百度雲
阿新 • • 發佈:2019-02-12
大家用樹莓派來做監控,文章裡面一般都是使用 fswebcam 或 motion。motion 非常強大,可以監測畫面變化後儲存成 mpeg 或 jpeg,還可以執行成 http 伺服器模式。但是樹莓派放在家裡,從外面訪問有時也會訪問不了(比如 IP 變了等原因)。其實使用 Python + OpenCV 打造一個對運動畫面能夠進行簡單判斷的程式並不困難。下面的程式每個 0.5 秒做一下檢測,如果畫面有變化就儲存下來,並且將其上傳到百度的雲端儲存中。為了保證隱私,上傳之前還可以給照片做 AES 加密,只有知道密碼才能檢視照片的內容。不過,OpenCV 在樹莓派上跑還是挺吃力的,CPU 基本保持在 6-70% 左右。
註冊為百度開發者(http://developer.baidu.com/)就可以建立自己的百度雲端儲存空間了。然後在雲端儲存中新建一個 bucket,把程式碼中所有的 'homepics' 替換成你的 bucket 名稱。
要執行這個程式,在樹莓派上需要安裝有 python2.7 和 PyCrypto、OpenCV、numpy、requests 模組。
在命令列執行:cv.py -d 0 -i /home/img -p qwerty123456qwerty123456 -u bcs:[app_key]:[sk]
如果沒有 -p 引數照片就不加密,沒有 -u 引數就不會存到百度雲上。
另外,還做了一個 web 站點,把 bucket 中圖片都列出來,用瀏覽器就可以直接檢視: http://eaho.sinaapp.com/
#coding: cp936 import os, time, datetime, multiprocessing, urllib, base64, hashlib, hmac, argparse, tempfile import cv2, numpy, requests from Crypto.Cipher import AES from Crypto.Util import Counter class Bcs: def __init__(self, ak, sk): self.base_url = 'http://bcs.duapp.com' self.ak = ak self.sk = sk self.MAX_FILE_SIZE = 250000 def getContent(self, method = 'GET', bucket = '', obj = '', time = None, ip = None, size = None): content = 'Method=%s\n' % method content += 'Bucket=%s\n' % bucket content += 'Object=/%s\n' % obj flag = 'MBO' if time: flag += 'T' content += 'Time=%d\n' % time if ip: flag += 'I' content += 'Ip=%s\n' % ip if size: flag += 'S' content += 'Size=%d\n' % size return flag, flag + '\n' + content def getSignature(self, content): return urllib.quote_plus(base64.encodestring(hmac.new(self.sk, content, hashlib.sha1).digest())[:-1]) def getUrl(self, flag, signature, bucket = '', obj = '', time = None, size = None, oparam = ''): if obj: bucket += '/%s' % obj url = [] param = '' if time: url.append('time=%d' % time) if size: url.append('size=%d' % size) if url: param = '&' + '&'.join(url) if oparam: param += '&' + oparam return '%s/%s?sign=%s:%s:%s%s' % (self.base_url, bucket, flag, self.ak, signature, param) def upload(self, path, bucket): name = os.path.split(path) filename = name[1] file_size = self.MAX_FILE_SIZE timestamp = int(time.time() + 60) flag, content = self.getContent(method = 'POST', bucket = bucket, obj = filename, time = timestamp, size = file_size) signature = self.getSignature(content) url = self.getUrl(flag, signature, bucket, filename, timestamp, file_size) print self.postFile(url, path) def upload2(self, filename, bucket, text): file_size = self.MAX_FILE_SIZE timestamp = int(time.time() + 60) flag, content = self.getContent(method = 'POST', bucket = bucket, obj = filename, time = timestamp, size = file_size) signature = self.getSignature(content) url = self.getUrl(flag, signature, bucket, filename, timestamp, file_size) print self.postFileContent(url, text) def postFile(self, url, path): f = open(path, 'rb') files = {'file': f} result = requests.post(url, files=files) f.close() return result.text def postFileContent(self, url, content): files = {'file': content} result = requests.post(url, files=files) return result.text def listBucket(self): flag, content = self.getContent(method = 'GET') signature = self.getSignature(content) url = self.getUrl(flag, signature) result = requests.get(url).json() return result def listObject(self, bucket, start = 0, limit = 20): flag, content = self.getContent(method = 'GET', bucket = bucket) signature = self.getSignature(content) url = self.getUrl(flag, signature, bucket = bucket, oparam = 'start=%d&limit=%d' % (start, limit)) result = requests.get(url).json() return result def getImg(self, bucket, obj): flag, content = self.getContent(method = 'GET', bucket = bucket, obj = obj) signature = self.getSignature(content) url = self.getUrl(flag, signature, bucket, obj) result = requests.get(url) return result.content class MotionDetect: def __init__(self, device = 0, base_path = '', skey = '', upload_mode = ''): self.mhi = None self.lastImg = None self.diff_threshold = 30 self.MHI_DURATION = 0.5 self.MAX_TIME_DELTA = 0.25 self.MIN_TIME_DELTA = 0.05 self.device = device self.skey = skey self.upload_mode = upload_mode if base_path == '': self.base_path = tempfile.gettempdir() else: self.base_path = base_path self.pipe = multiprocessing.Pipe() self.worker = None def update(self, img): h, w = img.shape[:2] if self.mhi == None: self.mhi = numpy.zeros((h, w), numpy.float32) if self.lastImg == None: self.lastImg = img frame_diff = cv2.absdiff(img, self.lastImg) gray_diff = cv2.cvtColor(frame_diff, cv2.COLOR_BGR2GRAY) ret, silh = cv2.threshold(gray_diff, self.diff_threshold, 1, cv2.THRESH_BINARY) timestamp = cv2.getTickCount() / cv2.getTickFrequency() cv2.updateMotionHistory(silh, self.mhi, timestamp, self.MHI_DURATION) mask, orient = cv2.calcMotionGradient(self.mhi, self.MAX_TIME_DELTA, self.MIN_TIME_DELTA, apertureSize=5) segmask, seg_bounds = cv2.segmentMotion(self.mhi, timestamp, self.MAX_TIME_DELTA) self.lastImg = img count = 0 for i, rect in enumerate([(0, 0, w, h)] + list(seg_bounds)): x, y, rw, rh = rect area = rw*rh if area < 64**2: continue count += 1 return count def encrypt(self, img, timestamp): ctr = Counter.new(128) aes = AES.new(self.skey, AES.MODE_CTR, counter = ctr) ret, text = cv2.imencode('.jpg', img, [int(cv2.IMWRITE_JPEG_QUALITY), 80]) cbin = aes.encrypt(text) filename = os.path.join(self.base_path, timestamp + '.jpg') outfile = open(filename, 'wb') outfile.write(cbin) outfile.close() return filename def encrypt2(self, img): ret, text = cv2.imencode('.jpg', img, [int(cv2.IMWRITE_JPEG_QUALITY), 80]) if self.skey != '': ctr = Counter.new(128) aes = AES.new(self.skey, AES.MODE_CTR, counter = ctr) text = aes.encrypt(text) return text def decrypt(self, filename): cbin = open(filename, 'rb').read() ctr = Counter.new(128) aes = AES.new(self.skey, AES.MODE_CTR, counter = ctr) text = aes.decrypt(cbin) open(filename + '.jpg', 'wb').write(text) return filename + '.jpg' def detect(self): cap = cv2.VideoCapture(self.device) time.sleep(2) while True: s = time.clock() timestamp = datetime.datetime.now().strftime('%Y%m%d%H%M%S%f')[:-3] ret, img = cap.read() count = self.update(img) if count > 3: cbin = self.encrypt2(img) filename = os.path.join(self.base_path, timestamp + '.jpg') print filename outfile = open(filename, 'wb') outfile.write(cbin) outfile.close() self.processImage(timestamp) print time.clock() - s time.sleep(0.2) if self.worker.is_alive(): self.worker.join() def processImage(self, timestamp): if self.worker == None or self.worker.is_alive() == False: print 'start new process' self.worker = multiprocessing.Process(target = self.processWorker, args = (self.pipe[0],)) self.worker.start() print 'send...', timestamp self.pipe[1].send(timestamp) def processWorker(self, pipe): while pipe.poll(10): timestamp = pipe.recv() print 'processing... %s' % timestamp if self.upload_mode != '': if self.upload_mode.startswith('bcs:'): _, ak, sk = self.upload_mode.split(':') bcs = Bcs(ak, sk) try: filename = os.path.join(self.base_path, timestamp + '.jpg') bcs.upload(, 'homepics') os.remove(filename) print 'uploaded ' + timestamp except: print 'upload failed: ' + timestamp if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('-d', action = 'store', dest = 'device_index', default = 0, type = int, help = 'video device index, default is 0.') parser.add_argument('-D', action = 'store', dest = 'decrypt', default = '', help = 'decrypt file.') parser.add_argument('-i', action = 'store', dest = 'image_path', default = '', help = 'image storage directory.') parser.add_argument('-p', action = 'store', dest = 'pwd', default = '', help = 'the password for encrypt the image file. the file will not be encrypted if pwd is null.') parser.add_argument('-u', action = 'store', dest = 'upload_mode', default = '', help = '''store the image to Internet storage service. Baidu Cloud Storage. parame --> bcs:app_key(ak):screct_key(sk) Baidu PCS. [unsupported] Sina vdisk. [unsupported] Huawei dbank. [unsupported] ''') args = parser.parse_args() motion = MotionDetect(args.device_index, args.image_path, args.pwd, args.upload_mode) if args.decrypt != '': if args.pwd != '': motion.decrypt(args.decrypt) else: print 'decrypt need a password(-p pwd)' else: motion.detect()