zirkulärer Fotobuffer / Raspberry Kamera

Code-Stücke können hier veröffentlicht werden.
Antworten
andi.manuel
User
Beiträge: 3
Registriert: Samstag 24. Januar 2015, 12:26

Samstag 24. Januar 2015, 12:37

Hi, Python-Noob hier.

Ich möchte als erstes Skript eine Raspberry Camera programmieren.
Die soll alle x Sekunden ein Bild in einen Pufferspeicher schreiben, der bis zu y Bilder groß ist, und nur bei Bewegung alle Bilder im Puffer auf die HDD speichern.

Da die Hardware noch bei der Post liegt hab ich als Quelle ein paar vorhandene Fotos eingelesen, und statt Bewegung die Tastatureingabe.

Code: Alles auswählen

from io import BytesIO
from collections import deque
import shutil
import time

image_buffer = deque(maxlen=3)

while True:

    for i in range(1010494,1010502):

        filename = 'P%d.jpg' % i
        with open(filename, 'rb') as image_file:
            image_buffer.append([BytesIO(image_file.read()), time.strftime("%Y-%m-%d um %H-%M-%S",time.localtime())])

        if raw_input() == "x":
            for _ in range(0, len(image_buffer)):
                pufferfoto = image_buffer.popleft()
                new_filename = '%s.jpg' % (pufferfoto[1])
                with open(new_filename, 'wb') as new_img:
                    new_img.write(pufferfoto[0].getvalue())

        print image_buffer

Das funktioniert testweise, aber gibt es elegantere Möglichkeiten?
Wird der Speicher der BytesIO-Objekte freigegeben, wenn die aus dem deque fallen, oder müll ich mir damit den Arbeitsspeicher zu?
BlackJack

Samstag 24. Januar 2015, 14:39

@andi.manuel: Die Speicherverwaltung kümmert sich da schon drum. Wobei mir das `BytesIO`-Objekt hier unnötig erscheint. Du benutzt davon eigentlich gar nichts, und man braucht davon hier auch gar nichts. Man könnte genau so gut einfach die eingelesenen Daten selbst direkt in die Queue stecken.

Die innere ``for``-Schleife könnte man auch über `image_buffer` machen und am Ende dann die Schlange auf einen Schlag mit der `clear()`-Methode leeren.
andi.manuel
User
Beiträge: 3
Registriert: Samstag 24. Januar 2015, 12:26

Donnerstag 29. Januar 2015, 23:24

Vielen Dank auch noch!
Auf BytesIO bin ich durch die Dokumentation des Camera-Moduls gekommen. Ich glaub das braucht ein file-like-Objekt, wenn ich es dann einbinde.

Inzwischen hab ich (immer noch ohne Hardware) u.a. einen Picasa-Upload reinkopiert:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys, os, cPickle as pickle
import gdata.service
import atom

from io import BytesIO
from collections import deque
import shutil
import time
from PIL import Image
import random

WAIT_FOR_LIGHT = 10	# Zeitabstand zwischen Checks ob es schon hell genug ist
CHECK_FOR_NIGHT = 50	# Zeitabstand zwischen Checks ob es noch hell genug ist
SNAPSHOT_INTERVAL = 3   # Zeitabstand zwischen Fotos in Sek.
UPLOAD_INTERVAL = 30    # minimaler Zeitabstand zwischen Uploads in Sek.
BUFFER_SIZE = 3         # maximale Anzahl der Bilder im Pufferspeicher
FEED_URL = 'http://picasaweb.google.com/data/feed/api/user/****/albumid/****'


def main():
    last_upload_time = 0
    image_buffer = deque(maxlen=BUFFER_SIZE)
    username, password = auth(False)
    gds = login(username, password)
    
    while True:
	
        while dark():
	    print "zu dunkel..."
	    time.sleep(WAIT_FOR_LIGHT)
	    
	print "hell genug!"
    	now_in_s = last_light = time.time()

	while (now_in_s - last_light) < CHECK_FOR_NIGHT:
	    now = time.localtime()
	    now_in_s = time.mktime(now)

	    with open("P1010494.jpg", 'rb') as image_file:
		dateiname = time.strftime("%Y-%m-%d um %H-%M-%S", now)+".jpg"
		image_buffer.append([BytesIO(image_file.read()), dateiname])
		print "Foto '%s' aufgenommen." % dateiname
		
	    if movement():
		print 'Bewegung erkannt!'
		for pufferfoto in image_buffer:
		    with open(pufferfoto[1], 'wb') as new_img:
			new_img.write(pufferfoto[0].getvalue())
		    print "Foto '%s' abgespeichert." % pufferfoto[1]    
		if (now_in_s - last_upload_time) > UPLOAD_INTERVAL:
		    last_upload_time = now_in_s
		    uploadfoto = image_buffer.pop()
		    postfiles(shrink(uploadfoto[0]), uploadfoto[1] , gds)		    
		image_buffer.clear()
		   
	    sleepytime = round((SNAPSHOT_INTERVAL + now_in_s - time.time()), 2)
	    if sleepytime >0:
		time.sleep(sleepytime)
		
    sys.exit()


def dark():
    if random.randint(1, 2) == 1:
        return True
    return False
    
def movement():
    if random.randint(1, 5) == 1:
        return True
    return False
    
def shrink(foto):
    """ Gibt übergebenes Foto verkleinert zurück
    """
    bild=Image.open(foto)
    bild=bild.resize((400,300))
    bild2=BytesIO()
    bild.save(bild2, format='jpeg', quality=50)
    return bild2
   

def auth(isinitrun=False):
    """Read authentication data from user input and store it to file.

    Read authentication data from user and store it to file;
    if file exists and user auth is not forced, read data from
    file and return them.
    """
    authfile = '~/.google_auth'
    def getsaved():
        try:
            return open (os.path.expanduser(authfile), 'r')
        except:
            raise # pass exception to the top call
            return None

    def askandwrite():
        username = str(raw_input('Login (without suffix @gmail.com): '))
        password = getpass('Password (would not be displayed): ')
        try:
            authdata = open (os.path.expanduser(authfile), 'w')
            pickle.dump((username, password), authdata)
            authdata.close()
        except IOError, (errno, strerror):
            print "I/O error(%s): %s" % (errno, strerror)
        return (username, password)

    if isinitrun == True:
        username, password = askandwrite()
    else:
        try:
            authdata = getsaved()
            username, password = pickle.load(authdata)
            authdata.close()
        except IOError, (errno, strerror):
            print "I/O error(%s): %s" % (errno, strerror)
            username, password = askandwrite()
        except:
            print "Unexpected error:", sys.exc_info()[0]
            username, password = askandwrite()
    return (username, password)

def login(username, password):
    """Perform login procedure to picasaweb.google.com.
    """
    gds = gdata.service.GDataService()
    gds.email = username
    gds.password = password
    gds.service = 'lh2' # where to get the list of all possible values?
    try:
        gds.ProgrammaticLogin()
    except gdata.service.CaptchaRequired:
        sys.exit('Required Captcha')
    except gdata.service.BadAuthentication:
        sys.exit('Bad Authentication')
    except gdata_client.Error:
        sys.exit('Login Error')
    return gds


def postfiles(uploadfoto, filename, gds):
    """Post files to album via specified feed url.
    """
    uploadfoto.seek(0, 2)
    fil_len = uploadfoto.tell()
    uploadfoto.seek(0)

    media = gdata.MediaSource(file_handle=uploadfoto, content_type='image/jpeg', content_length=fil_len, file_name=filename) # object for storing file's binary data

    try:
	posted_photo = gds.Post(
		None, # used for metadata ?
		FEED_URL, # album feed url
		media_source = media, # object with file's data
    		)
    except gdata.service.RequestError:
	print 'Error sending file'
    except:
	print 'Unexpected error:', sys.exc_info()
    else:
	print "Foto '%s' hochgeladen!" % filename


if __name__ == "__main__":
    main()
Da mein Wollen noch einiges größer als mein Können ist, ist das wahrscheinlich ziemlich hemdsärmelig. Tipps werden gerne angenommen! :mrgreen:
andi.manuel
User
Beiträge: 3
Registriert: Samstag 24. Januar 2015, 12:26

Donnerstag 5. Februar 2015, 21:53

Hi, ich mal wieder.

Ich möchte bei meinem Script per Pillow ein Bild verkleinern, bevor ich es hochlade, und bekomme folgende Fehlermeldung:

Code: Alles auswählen

Traceback (most recent call last):
  File "kestrelcam2.py", line 287, in <module>
    main()
  File "kestrelcam2.py", line 73, in main
    foto = image_buffer.pop().verkleinern()
  File "kestrelcam2.py", line 175, in verkleinern
    im=Image.open(self.inhalt)
  File "/usr/local/lib/python2.7/dist-packages/PIL/Image.py", line 2274, in open
    % (filename if filename else fp))
IOError: cannot identify image file <_io.BytesIO object at 0xd43720>
Weiß allerdings nicht was ich falsch mache. :oops:
Unten der aktuelle Code dazu...

edit:
Nachdem ich probehalber einen seek(0) eingebaut hatte, bekam ich die Fehlermeldung: "IOError: decoder jpeg not available"
jpeglib-dev nachinstalliert, und es läuft :D

Trotzdem nehme ich gern Anregungen entgegen, wie ich das ganze betriebssicher bekomm.
Ich denke ich muss mich mal in Sachen "exceptions abfangen" einlesen

Außerdem bin ich für TIpps dankbar, wie ich wvdial (für die Surfstick-Verbindung) am besten aufrufen sollte.
Im Moment mach ich die Verbindung nach Bedarf über Popen auf, und beende per terminate. Bsp. siehe Zeile 183

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys, os, cPickle as pickle
import gdata.service
import atom

from io import BytesIO
from collections import deque
import shutil
import time
from PIL import Image
import picamera
import RPi.GPIO as GPIO
from getpass import getpass
import threading
import logging
from subprocess import Popen, PIPE


CHECK_LIGHT = 60	        # Zeitabstand zwischen Helligkeitschecks
DARK_THRESHOLD = 200
SNAPSHOT_INTERVAL = 5       # Zeitabstand zwischen Fotos in Sek.
UPLOAD_INTERVAL = 60       # minimaler Zeitabstand zwischen Uploads in Sek.
VIDEO_INTERVAL = 1200       # minimaler Zeitabstand zwischen Videos in Sek.
BUFFER_SIZE = 3             # maximale Anzahl der Bilder im Pufferspeicher
FOTO_PATH ='/media/PICAM/FOTOS/'  
VIDEO_PATH ='/media/PICAM/VIDEO/'
VIDEO_LENGTH = 20           # Videolänge in Sek.
FEED_URL = 'http://picasaweb.google.com/data/feed/api/user/xxx/albumid/xxx'
PIR_PIN = 14
STOP_PIN = 19
PR_A_PIN = 20
PR_B_PIN = 21


def main():
    logging.basicConfig(filename="/media/PICAM/kestrelcam.log", level=logging.DEBUG, format='%(asctime)s - %(message)s', datefmt='%d.%m.%Y %H:%M:%S')
    logging.info("Programm gestartet")
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(PIR_PIN, GPIO.IN)
    GPIO.setup(STOP_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
    GPIO.add_event_detect(PIR_PIN, GPIO.RISING)
    GPIO.add_event_detect(STOP_PIN, GPIO.FALLING)
    last_upload_time = 0
    last_video_time = 0
    image_buffer = deque(maxlen=BUFFER_SIZE)
    username, password = auth()
    gds = login(username, password)
    red = Led(6)
    yellow = Led(12)
    green = Led(13)
    green.led_on()

    darkcheck=Dark()
    darkcheck.start()

    while not GPIO.event_detected(STOP_PIN):
        now_in_s = time.time()   
        now = time.strftime("%Y-%m-%d um %H-%M-%S", time.localtime(now_in_s))         
        foto = Foto(now)
        foto.aufnehmen(yellow)
        image_buffer.append(foto) 

        if GPIO.event_detected(PIR_PIN):   
            for foto in image_buffer:
                foto.speichern(red)   
            if (now_in_s - last_video_time) > VIDEO_INTERVAL:
                last_video_time = now_in_s
                #video(now, red)
            if (now_in_s - last_upload_time) > UPLOAD_INTERVAL:
                last_upload_time = now_in_s
                foto = image_buffer.pop().verkleinern()
#                foto.hochladen(gds)		    
            image_buffer.clear()
        
        while not darkcheck.light:
            time.sleep(1)
     
        sleepytime = round((SNAPSHOT_INTERVAL + now_in_s - time.time()), 1)                    
        if sleepytime>0.1:            
            yellow.led_blink(0.1, sleepytime*10)

    darkcheck.stop()
    GPIO.cleanup()
    sys.exit()


class Dark(threading.Thread):
    """
    Helligkeitscheck im Hintergrund
    """
    def __init__(self):

        self._stopevent = threading.Event()
        threading.Thread.__init__(self, name="dark")
        self.light = True
        
    def run(self):
        """
        Kondensator an Fotowiderstand laden / entladen
        """
        while not self._stopevent.isSet():
            # entladen
            GPIO.setup(PR_A_PIN, GPIO.IN)
            GPIO.setup(PR_B_PIN, GPIO.OUT)
            GPIO.output(PR_B_PIN, False)
            time.sleep(CHECK_LIGHT)
            # laden
            GPIO.setup(PR_B_PIN, GPIO.IN)
            GPIO.setup(PR_A_PIN, GPIO.OUT)
            count = 0
            GPIO.output(PR_A_PIN, True)
            while not GPIO.input(PR_B_PIN):
                count = count + 1
            logging.info("Helligkeit: %s", count)
            if count > DARK_THRESHOLD:
                self.light=False
            else:
                self.light=True    
    
    def stop(self):
        self._stopevent.set()
        self.join()


class Led:
    """
    LEDs steuern
    """
    def __init__(self, pin):
        self.pin = pin
        GPIO.setup(pin, GPIO.OUT)

    def led_on(self):
        GPIO.output(self.pin,GPIO.HIGH)

    def led_off(self):
        GPIO.output(self.pin,GPIO.LOW)

    def led_blink(self, dauer, repeat):
        for _ in range(0, int(repeat)):
            self.led_on()
            time.sleep(dauer/2)
            self.led_off()
            time.sleep(dauer/2)

    
class Foto:
    def __init__(self, datum):
        self.inhalt = BytesIO()
        self.datum = datum
        self.name = datum+".jpg"

    def aufnehmen(self, led):
        led.led_on()
        with picamera.PiCamera(resolution=(2592, 1944)) as camera:
            camera.meter_mode = 'matrix'
            camera.exposure_mode = 'antishake'
            camera.hflip = True
            camera.vflip = True
            time.sleep(2)
            camera.capture(self.inhalt, 'jpeg', quality=20, thumbnail=(64, 48, 10))
        led.led_off()
        logging.info("Foto aufgenommen")

    def speichern(self, led):
        with open(FOTO_PATH+self.name, 'wb') as new_img:
            new_img.write(self.inhalt.getvalue())
            led.led_blink(0.1, 1)
        logging.info("Foto '%s' abgespeichert", self.name)

    def verkleinern(self):
        print self.inhalt
        im=Image.open(self.inhalt)
        im.resize((640,480))
        klein = Foto(self.datum)
        klein.name = "k"+klein.name
        im.save(klein.inhalt, 'jpeg')
        return klein

    def hochladen(self, gds):
        wvdial = Popen(["wvdial", "umts"])
        time.sleep(4) 
        self.inhalt.seek(0, 2)
        fil_len = self.inhalt.tell()
        self.inhalt.seek(0)

        media = gdata.MediaSource(file_handle=self.inhalt, content_type='image/jpeg', content_length=fil_len, file_name=self.name) # object for storing file's binary data

        try:
	        posted_photo = gds.Post(
	        	None, # used for metadata ?
	        	FEED_URL, # album feed url
	        	media_source = media, # object with file's data
    		)
        except gdata.service.RequestError:
	        logging.debug('Error sending file')
        except:
	        logging.debug('Unexpected error: %s', sys.exc_info())
        else:
	        logging.info("Foto '%s' hochgeladen!", self.name)
        finally:
            wvdial.terminate()        

def video(datum, led):
    led.led_on()
    dateiname = VIDEO_PATH+datum+'.h264'
    with picamera.PiCamera() as camera:
        camera.resolution = (640, 480)
        camera.hflip = True
        camera.vflip = True
        camera.start_recording(dateiname)
        camera.wait_recording(VIDEO_LENGTH)
        camera.stop_recording()
    led.led_off()
    logging.info("Video '%s' aufgenommen", dateiname) 


def auth():
    """Read authentication data from user input and store it to file.

    Read authentication data from user and store it to file;
    if file exists and user auth is not forced, read data from
    file and return them.
    """
    authfile = '~/.google_auth'
    def getsaved():
        try:
            return open (os.path.expanduser(authfile), 'r')
        except:
            raise # pass exception to the top call
            return None

    def askandwrite():
        username = str(raw_input('Login (without suffix @gmail.com): '))
        password = getpass('Password (would not be displayed): ')
        try:
            authdata = open (os.path.expanduser(authfile), 'w')
            pickle.dump((username, password), authdata)
            authdata.close()
        except IOError, (errno, strerror):
            print "I/O error(%s): %s" % (errno, strerror)
        return (username, password)

    try:
        authdata = getsaved()
        username, password = pickle.load(authdata)
        authdata.close()
    except IOError, (errno, strerror):
        print "I/O error(%s): %s" % (errno, strerror)
        username, password = askandwrite()
    except:
        print "Unexpected error:", sys.exc_info()[0]
        username, password = askandwrite()
    return (username, password)


def login(username, password):
    """Perform login procedure to picasaweb.google.com.
    """
    wvdial = Popen(["wvdial", "umts"])
    time.sleep(5)    
    gds = gdata.service.GDataService()
    gds.email = username
    gds.password = password
    gds.service = 'lh2' # where to get the list of all possible values?
    try:
        gds.ProgrammaticLogin()
    except gdata.service.CaptchaRequired:
        sys.exit('Required Captcha')
    except gdata.service.BadAuthentication:
        sys.exit('Bad Authentication')
    except gdata_client.Error:
        sys.exit('Login Error')
    else:
        logging.info("bei Google eingeloggt")
    finally:
        wvdial.terminate()    
    return gds


if __name__ == "__main__":
    main()
Antworten