大家用树莓派来做监控,文章里面一般都是使用 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()