1. 程式人生 > >py + opencv 打造樹莓派監控,場景有變化自動拍照上傳到百度雲

py + opencv 打造樹莓派監控,場景有變化自動拍照上傳到百度雲

大家用樹莓派來做監控,文章裡面一般都是使用 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()