Fa poc vaig rebre la Raspberry Pi Camera i he aprofitat per fer quatre proves i enxampar un llangardaix ocel·lat que volta prop de casa però que s’amaga de seguida al apropar-nos. Sabent on té el seu cau, i que acostuma a sortir quan hi toca el sol, vaig deixar la càmera apuntant-hi i executant un script python per anar fent fotos cada cert temps o quan detectés moviment.
Si la raspberry es col·loca en algun indret proper al router i s’agafa el wifi, facilita molt les coses ja que es pot accedir via ssh i control·lar la càmera des de l’ordinador de sobretaula. En el cas de no tenir wifi, una solució per control·lar la raspberry és accedir-hi des del telèfon mòbil on es disposa de pantalla i teclat.
Accés a la RPi des d’un mòbil android:
Per connectar el mòbil android a la raspberry només cal configurar un fitxer de xarxa a la raspberry i després endollar el cable usb des del mòbil a la raspberry i utilitzar alguna app per connectar-s’hi. A tenir en compte que el mòbil ha de tenir l’opció de “USB Tethering” per poder-ho fer.
sudo nano /etc/network/interfaces
I afegir:
allow-hotplug usb0
auto usb0
iface usb0 inet static
address 192.168.42.42
netmask 255.255.255.0
network 192.168.42.0
broadcast 192.168.42.255
I reiniciar la RPi.
A partir d’ara ja es pot manipular la raspberry des del mòbil. Connectar el cable usb des del mòbil a la raspberry. Al mòbil, activar l’opció “USB tethering” (amb el meu Motorola Razr m’obliga a desconnectar el wifi per poder-ho activar). Des de l’app ConnectBot o similars connectar-se a pi@192.168.42.42 per control·lar la raspberry. Ídem amb l’app ES File explorer per accedir als fitxers de la raspberry i per exemple veure o baixar les fotos fetes.
Scripts en Python per automatizar les fotos a fer:
Per tal de fer les fotos vaig partir d’un script de www.raspberrypi.org/forums/viewtopic.php?f=43&t=45235 que bàsicament vaig retocar per entrar els paràmetres al executar-lo i no haver d’editar el codi pyhton des del mòbil.
La part de detecció de moviment cal ajustar-la segons les condicions de llum, vent, etc, i requereix fer alguna prova amb diferents paràmetres per què pugui ser més o menys fiable.
Un dels paràmetres de l’aplicació de la raspberry per fer les fotos, raspistill, és el timeout, quan temps triga a fer la foto, en microsegons. Si es posa un valor molt baix com 200 i no hi ha gaire llum les fotos es veuen negres, i aleshores és preferible augmentar el valor a 1000 o més, suposo que per donar temps a auto-enfocar millor. Amb un petit script faig un bucle amb diferents opcions i trio la més adient. Una altra opció podria ser no utilitzar els ajustaments automàtics i definir-los explícitament segons les condicions.
I per crear un vídeo timelapse amb totes les fotos fetes només cal executar un script que s’encarrega de codificar-ho en un fitxer avi.
Descarregar scripts pipcapture, pipmovie i piptest en python
python piptest.py
#!/usr/bin/python # -*- coding: latin-1 -*- import os import time import subprocess filepath = "/home/pi/picam" filenamePrefix = "test" fixedSettings = "-w 2592 -h 1944 -q 100 -n" variableSettings = [ '-t 200', '-t 500', '-t 1000', '-t 2000', '-t 5000' ] #variableSettings = [ # '-t 500 -ISO 100', # '-t 500 -ISO 400', # '-t 500 -ISO 800' #] total_photos = len(variableSettings) print "Starting photo sequence (%i images)" % (total_photos) photo_counter = 0 for varSetting in variableSettings: photo_counter = photo_counter + 1 filename = filepath + "/" + filenamePrefix + "_%02d%s.jpg" % (photo_counter, varSetting.replace(' ', '')) subprocess.call("raspistill %s %s -o %s" % (fixedSettings, varSetting, filename), shell=True) print ' [' + str(photo_counter) + ' of ' + str(total_photos) + '] ' + filename time.sleep(0.25) # Interval between photos (seconds) print "Finished photo sequence"
python pipcapture.py
#!/usr/bin/python # -*- coding: latin-1 -*- # original script by brainflakes, improved by pageauc, peewee2 and Kesthal # www.raspberrypi.org/phpBB3/viewtopic.php?f=43&t=45235 # Retocs per activar/desactivar motion, capturar imatge inicial, beep, ... # You need to install PIL to run this script # type "sudo apt-get install python-imaging-tk" in an terminal window to do this import StringIO import subprocess import os import time from datetime import datetime from PIL import Image def default_input( message, defaultVal ): if defaultVal: return raw_input( "%s [%s]:" % (message,defaultVal) ) or defaultVal else: return raw_input( "%s " % (message) ) print "----------------------------- Settings ---------------------------------------" # INPUT-SETTINGS: # forceCapture - whether to force an image to be captured every forceCaptureTime seconds, values True or False # forceCaptureTime - seconds between captures (1 hour => 60 * 60) (5 minutes => 60 * 5) # forceCaptureStart - wheter to capture an image at start optTimer = default_input("Capture by Time? (y/n)", "y") if optTimer in ('y', 'Y', 'yes', 'YES', 's', 'S', 'si', 'SI'): forceCapture = True forceCaptureTime = int(default_input("Seconds between every capture", 60 * 5)) forceCaptureStart = True else: forceCapture = False forceCaptureTime = 0 forceCaptureStart = False # forceMotion - whether to force motion detection, values True or False # Threshold - how much a pixel has to change by to be marked as "changed" # Sensitivity - how many changed pixels before capturing an image, needs to be higher if noisy view # testDelay - microseconds value for -t parameter at raspistill optMotion = default_input("Capture by Motion? (y/n)", "y") if optMotion in ('y', 'Y', 'yes', 'YES', 's', 'S', 'si', 'SI'): forceMotion = True threshold = int(default_input("Threshold", 50)) sensitivity = int(default_input("Sensitivity", 50)) testDelay = int(default_input("Delay for motion detection shooting", 1000)) else: forceMotion = False if not(forceCapture) and not(forceMotion): print "Nothing to capture ;-(" sys.exit() # filepath - location of folder to save photos # filenamePrefix - string that prefixes the file name for easier identification of files. # saveWidth, saveHeight - image width & height to save # saveDelay - microseconds value for -t parameter at raspistill filepath = default_input("Path", "/home/pi/picam") filenamePrefix = default_input("Prefix", "img") saveWidth = int(default_input("Image Width", 2592)) saveHeight = int(default_input("Image Height", 1944)) saveDelay = int(default_input("Delay for camera shooting", 5000)) extraSettings = default_input("Extra settings for raspistill", "-q 100") # -ISO 200 # debugLevel - 0 none. 1 capture & motion dectection msgs. 2 all. # in debugLevel 2, a file debug.bmp is written to disk with marked changed pixel an with marked border of scan-area (should only be turned on while testing the parameters) debugLevel = int(default_input("Debug level (0 none. 1 capture msgs. 2 all.", 1)) # END INPUT-SETTINGS: # Delay per Raspistill # -t 5000 si condicions de llum dolentes per donar temps a enfocar. -t 1000 pq no trigui tant a fer-se la foto quan hi ha motion. # -t 1000 si no hi ha massa llum. -t 200 per detecció més ràpida (si es pilla bé la foto. amb no gaire llum quedaria negra) # settings of the photos to save cameraSettings = "-t %i %s" % (saveDelay, extraSettings) # settings of the photos to detect motion if (forceMotion): testCameraSettings = "-t %i" % (testDelay) # FIXED-SETTINGS: # diskSpaceToReserve - Delete oldest images to avoid filling disk. How much byte to keep free on disk. diskSpaceToReserve = 200 * 1024 * 1024 # Keep 200 mb free on disk # Test-Image settings testWidth = 100 testHeight = 75 # this is the default setting, if the whole image should be scanned for changed pixel testAreaCount = 1 testBorders = [ [[1,testWidth],[1,testHeight]] ] # [ [[start pixel on left side,end pixel on right side],[start pixel on top side,stop pixel on bottom side]] ] # testBorders are NOT zero-based, the first pixel is 1 and the last pixel is testWith or testHeight # with "testBorders", you can define areas, where the script should scan for changed pixel # for example, if your picture looks like this: # # ....XXXX # ........ # ........ # # "." is a street or a house, "X" are trees which move arround like crazy when the wind is blowing # because of the wind in the trees, there will be taken photos all the time. to prevent this, your setting might look like this: # testAreaCount = 2 # testBorders = [ [[1,50],[1,75]], [[51,100],[26,75]] ] # area y=1 to 25 not scanned in x=51 to 100 # even more complex example # testAreaCount = 4 # testBorders = [ [[1,39],[1,75]], [[40,67],[43,75]], [[68,85],[48,75]], [[86,100],[41,75]] ] # END FIXED-SETTINGS print "------------------------------------------------------------------------------" # Capture a small test image (for motion detection) def captureTestImage(settings, width, height): command = "raspistill %s -w %s -h %s -e bmp -n -o -" % (settings, width, height) imageData = StringIO.StringIO() imageData.write(subprocess.check_output(command, shell=True)) imageData.seek(0) im = Image.open(imageData) buffer = im.load() imageData.close() return im, buffer # Save a full size image to disk def saveImage(settings, width, height, diskSpaceToReserve): keepDiskSpaceFree(diskSpaceToReserve) time = datetime.now() filename = filepath + "/" + filenamePrefix + "-%04d%02d%02d-%02d%02d%02d.jpg" % (time.year, time.month, time.day, time.hour, time.minute, time.second) subprocess.call("raspistill %s -w %s -h %s -e jpg -n -o %s" % (settings, width, height, filename), shell=True) if debugLevel > 0: print "Captured %s" % filename # Keep free space above given level def keepDiskSpaceFree(bytesToReserve): if (getFreeSpace() < bytesToReserve): for filename in sorted(os.listdir(filepath + "/")): if filename.startswith(filenamePrefix) and filename.endswith(".jpg"): os.remove(filepath + "/" + filename) if debugLevel > 0: print "Deleted %s/%s to avoid filling disk" % (filepath,filename) if (getFreeSpace() > bytesToReserve): return # Get available disk space def getFreeSpace(): st = os.statvfs(filepath + "/") du = st.f_bavail * st.f_frsize return du # Get first image if (forceCaptureStart): saveImage(cameraSettings, saveWidth, saveHeight, diskSpaceToReserve) # Get first comparison image if (forceMotion): image1, buffer1 = captureTestImage(testCameraSettings, testWidth, testHeight) # Reset last capture time lastCapture = time.time() while (True): # Count changed pixels changedPixels = 0 takePicture = False # Check motion capture if (forceMotion): # Get comparison image image2, buffer2 = captureTestImage(testCameraSettings, testWidth, testHeight) if (debugLevel > 1): # in debug mode, save a bitmap-file with marked changed pixels and with visible testarea-borders debugimage = Image.new("RGB",(testWidth, testHeight)) debugim = debugimage.load() for z in xrange(0, testAreaCount): # = xrange(0,1) with default-values = z will only have the value of 0 = only one scan-area = whole picture for x in xrange(testBorders[z][0][0]-1, testBorders[z][0][1]): # = xrange(0,100) with default-values for y in xrange(testBorders[z][1][0]-1, testBorders[z][1][1]): # = xrange(0,75) with default-values; testBorders are NOT zero-based, buffer1[x,y] are zero-based (0,0 is top left of image, testWidth-1,testHeight-1 is botton right) if (debugLevel > 1): debugim[x,y] = buffer2[x,y] if ((x == testBorders[z][0][0]-1) or (x == testBorders[z][0][1]-1) or (y == testBorders[z][1][0]-1) or (y == testBorders[z][1][1]-1)): # print "Border %s %s" % (x,y) debugim[x,y] = (0, 0, 255) # in debug mode, mark all border pixel to blue # Just check green channel as it's the highest quality channel pixdiff = abs(buffer1[x,y][1] - buffer2[x,y][1]) if pixdiff > threshold: changedPixels += 1 if (debugLevel > 1): debugim[x,y] = (0, 255, 0) # in debug mode, mark all changed pixel to green # Save an image if pixels changed if (changedPixels > sensitivity): takePicture = True # will shoot the photo later if ((debugLevel < 2) and (changedPixels > sensitivity)): break # break the y loop if ((debugLevel < 2) and (changedPixels > sensitivity)): break # break the x loop if ((debugLevel < 2) and (changedPixels > sensitivity)): break # break the z loop if (debugLevel > 1): debugimage.save(filepath + "/debug.bmp") # save debug image as bmp print "debug.bmp saved, %s changed pixel" % changedPixels # else: # print "%s changed pixel" % changedPixels if (debugLevel > 0) and (changedPixels > sensitivity): print "\aMotion detected" # \a to beep at every motion detection (remove if not desired) # Swap comparison buffers image1 = image2 buffer1 = buffer2 # Check force capture if forceCapture: if time.time() - lastCapture > forceCaptureTime: takePicture = True if takePicture: lastCapture = time.time() saveImage(cameraSettings, saveWidth, saveHeight, diskSpaceToReserve)
python pipmovie.py
#!/usr/bin/python # -*- coding: latin-1 -*- # You need to install MENCODER to run this script # type "sudo apt-get install mencoder" in a terminal window import StringIO import subprocess import os def default_input( message, defaultVal ): if defaultVal: return raw_input( "%s [%s]:" % (message,defaultVal) ) or defaultVal else: return raw_input( "%s " % (message) ) # filepath : On hi ha els fitxers jpg a tractar i on es desarà el vídeo # filenamePrefix : Prefixe que tenen els jpg a tractar i que tindrà el vídeo # widthHeight : Mida del vídeo. Ex: 640:480 800:600 1024:768 1296:972 1920:1080 # fps : Frames per Segon. Nombre de JPGs en un segon. Ex: 24 12 6 4 3 print "--------- Movie encoder from JPGs ---------" filepath = default_input("Path", "/home/pi/picam") filenamePrefix = default_input("Prefix", "img") widthHeight = default_input("Width:Height", "1024:768") fps = default_input("FPS", "12") print "-------------------------------------------" # calcular aspect radio segons mides enlloc de demanar-lo ww, hh = widthHeight.split(":") a0 = float(ww) / float(hh) a1 = 4.0 / 3.0 a2 = 16.0 / 9.0 aspect = "16/9" if abs(a0 - a1) > abs(a0 - a2) else "4/3" ordre = "ls " + filepath + "/" + filenamePrefix + "*.jpg > pipmovie.tmp" exit_status = subprocess.call(ordre, shell=True) print "Creating video timelapse. This will take a while ..." ordre = "mencoder -nosound -ovc lavc -lavcopts vcodec=mpeg4:aspect=" + aspect + ":vbitrate=8000000 -vf scale=" + widthHeight + " -o " + filepath + "/" + filenamePrefix + "_timelapse.avi -mf type=jpeg:fps=" + fps + " mf://@pipmovie.tmp" exit_status = subprocess.call(ordre, shell=True) print "Created " + filepath + "/" + filenamePrefix + "_timelapse.avi" exit_status = subprocess.call("rm pipmovie.tmp", shell=True)