Pi NoIR Photos: Feeding Birds 29/04/2020

Spread the love

Today we step into a lighter topic. I have adjusted the Pi NoIR camera for myself a touch. After the photos I will share what I made, and did, and talk about the slight trouble I have at the moment.

The Photos

Please note: feel free to share these photos, they’re no-copyright, but it would be awesome to open up to more people.

These are just a few at random I chose, the entire collection of 308 photos can be downloaded here, 802mb. It will stay available for a few weeks.

Thoughts and Code

As you will note, I have messed up the blue filter, a Roscolux #2007 Storaro Blue, and currently cant use it. I noticed I could get it for around R500 for 20×24 inch. Not quite what I’m willing to spend money on.

So it would be nice to get ideas for how to get a new copy of that filter to use, that is why the hues are like this today. I feel bad about it, but it isn’t a problem that would mean I can never use the camera again.

So, before sharing my gimpy mount, here is the code. I adjusted it from open source code I found, no memory of where, but similar to this.

#!/usr/bin/python
import os
import os.path
from os import path
import io
import subprocess
import os
import time
from datetime import datetime
from PIL import Image
import shutil
import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BCM)
GPIO.setup(22, GPIO.OUT) # red
GPIO.setup(23, GPIO.OUT) # green

# this is needed to reset awb every boot
os.system('sudo vcdbg set awb_mode 0')
time.sleep(5)

# usb move files code
def usb_callback():
    print('usb_callback')
    path = '/home/pi/Desktop/_photos/'
    dest = '/cake'
    GPIO.output(22, True)
    while True:
        print('    - restart: sleep 5')
        try:
            time.sleep(5)
            num_files = 0
            if os.path.isdir(path):
                num_files = len([f for f in os.listdir(path)if os.path.isfile(os.path.join(path, f))])
            print('    - num files:')
            print(num_files)
            while num_files > 0:
                for r, d, f in os.walk('/media/pi/'):
                    for folder in d:
                        if os.path.isdir(os.path.join('/media/pi/', folder)):
                            dest = os.path.join('/media/pi/', folder)
                            print(dest)
                print(num_files)
                morefile = True
                if os.path.isdir(dest):
                    print('    - dest is here'    )
                    while num_files > 0:
                        files = os.listdir(path)
                        for f in files:
                            shutil.move(path+f, dest)
                        
                        num_files = len([f for f in os.listdir(path)if os.path.isfile(os.path.join(path, f))])   
                num_files = len([f for f in os.listdir(path)if os.path.isfile(os.path.join(path, f))])
                print('    - sleep 5')
                time.sleep(5)
            if os.path.isdir('/home/pi/Desktop/_photos'):
                print('    - rm dir')
                os.rmdir('/home/pi/Desktop/_photos')
            print('try makke complete file')
            GPIO.output(22, False)
            shutil.copy('/home/pi/Desktop/done', dest)
            time.sleep(5)
            print('bye bye')
        except Exception as e:
            print(e)
            time.sleep(60)

# Motion detection settings:
# 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
# ForceCapture       - whether to force an image to be captured every forceCaptureTime seconds, values True or False
# filepath           - location of folder to save photos
# filenamePrefix     - string that prefixes the file name for easier identification of files.
# diskSpaceToReserve - Delete oldest images to avoid filling disk. How much byte to keep free on disk.
# cameraSettings     - "" = no extra settings; "-hf" = Set horizontal flip of image; "-vf" = Set vertical flip; "-hf -vf" = both horizontal and vertical flip
threshold = 50
sensitivity = 100
forceCapture = True
forceCaptureTime = 60 * 60 # Once an hour
filepath = "/home/pi/Desktop/_photos"
filenamePrefix = "capture"
diskSpaceToReserve = 40 * 1024 * 1024 # Keep 40 mb free on disk
cameraSettings = ""

# settings of the photos to save
saveWidth   = 3280
saveHeight  = 2464
saveQuality = 100 # Set jpeg quality (0 to 100)

# 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

print('Preparing')

# in debug mode, a file debug.bmp is written to disk with marked changed pixel an with marked border of scan-area
# debug mode should only be turned on while testing the parameters above
debugMode = False # False or True

# Capture a small test image (for motion detection)
def captureTestImage(settings, width, height):
    command = "raspistill %s -w %s -h %s -t 200 -e bmp -n -o -" % (settings, width, height)
    #imageData = StringIO.StringIO()
    imageData = io.BytesIO()
    imageData.write(subprocess.check_output(command, shell=True))
    imageData.seek(0)
    im = Image.open(imageData)
    buffer = im.load()
    imageData.close()
    return im, buffer

# Capture a small test image (for motion detection)
def captureTestImage(settings, width, height):
    command = "raspistill %s -w %s -h %s -t 200 -e bmp -n -o -" % (settings, width, height)
    #imageData = StringIO.StringIO()
    imageData = io.BytesIO()
    imageData.write(subprocess.check_output(command, shell=True))
    imageData.seek(0)
    im = Image.open(imageData)
    buffer = im.load()
    imageData.close()
    return im, buffer

# Keep free space above given level
def keepDiskSpaceFree(bytesToReserve):
    if (getFreeSpace() < bytesToReserve):
        sys.exit()

# Get available disk space
def getFreeSpace():
    st = os.statvfs(filepath + "/")
    du = st.f_bavail * st.f_frsize
    return du

# Save a full size image to disk
def saveImage(settings, width, height, quality, diskSpaceToReserve, count):
    if (getFreeSpace() > diskSpaceToReserve):
        time = datetime.now()
        filename = filepath + "/" + filenamePrefix + "-%06d.a.jpg" % count
        subprocess.call("raspistill %s -w %s -h %s -t 200 -e jpg -q %s -n -o %s" % (settings, width, height, quality, filename), shell=True)
        print("Captured %s" % filename)

def motion():
    path = '/home/pi/Desktop/_photos/'
    count = len([f for f in os.listdir(path)if os.path.isfile(os.path.join(path, f))])
    count = count + 1

    # Get first image
    image1, buffer1 = captureTestImage(cameraSettings, testWidth, testHeight)

    # Reset last capture time
    lastCapture = time.time()

    while (True):
        # Get comparison image
        image2, buffer2 = captureTestImage(cameraSettings, testWidth, testHeight)

        # Count changed pixels
        changedPixels = 0
        takePicture = False

        if (debugMode): # 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 range(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 range(testBorders[z][0][0]-1, testBorders[z][0][1]): # = xrange(0,100) with default-values
                for y in range(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 (debugMode):
                        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 (debugMode):
                            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 ((debugMode == False) and (changedPixels > sensitivity)):
                        break  # break the y loop
                if ((debugMode == False) and (changedPixels > sensitivity)):
                    break  # break the x loop
            if ((debugMode == False) and (changedPixels > sensitivity)):
                break  # break the z loop
        print(takePicture)
        if (debugMode):
            debugimage.save(filepath + "/debug.bmp") # save debug image as bmp
            print("debug.bmp saved, %s changed pixel" % changedPixels)
        # else:
        #     print "%s changed pixel" % changedPixels

        # Check force capture
        if forceCapture:
            if time.time() - lastCapture > forceCaptureTime:
                takePicture = True

        if takePicture:
            lastCapture = time.time()
            saveImage(cameraSettings, saveWidth, saveHeight, saveQuality, diskSpaceToReserve, count)
            count = count + 1

        # Swap comparison buffers
        image1 = image2
        buffer1 = buffer2

if not path.exists('/home/pi/Desktop/_photos'):
    os.mkdir('/home/pi/Desktop/_photos')
    GPIO.output(23, True)
    motion()
else:
    usb_callback()

So, my SD card has 6Gb free, so it makes a folder _photos on the desktop, then starts the camera for motion detection. When it boots, if it has the folder _photos it automatically starts to try moving them to an inserted media.

You will also notice I added two LEDs to indicate if it is working. A green flashing one for when it is taking photos, a red one for if it needs to copy and remove the folder. The red LED then turns off after the photos were all copied, the folder removed, and then it tries to make a file called ‘done’ on the flash drive.

I know the code needs several changes, such as slightly easier motion detection, I believe the scale is not quite adjusted to my liking. Also, the original code would just delete older photos when the SD card got too full, I didn’t like that. I don’t mind if I miss because photos stop coming through on something that happens later when we run out of dish space.

I also know certain imports are lazily added, though, so apologies for the duplicate ideas added. I will eventually clean the code a little, I just felt lazy today. I need to start bringing more content again, if at all possible.

The second thing to share is how it all looks for me now. The use, and so on. I essentially put effort into making it easier for my to safely mount it somewhere to use for the photos. The photos below are before the above.

Get the usable resources
Bend it to use as a cover
Lastly open the view port for the camera
First attempt to feed the birds, forgot to copy run.py in – no photos that day
Day 2’s photos are the ones shared, this was fun

So, I will make a cover for the camera mount I have. I built it with the idea that when I go to camping site in a game reserve with the animals able to come through the camp I can set it up in tons of places to get motion detection photographs of the wildlife.

Finally, since I mentioned wanting to sort the filters out again is what I need for the future, I figure I can point out my other plans.

I also want to make it a lid, so to speak. I want to cover it at the top so that if birds land on it they cant let their bowel make the system dirty. I also believe it could be harmful to the Pi itself.

I also figure I will make lens cover for where the camera sees out the mount. You will not in half the photos I shared it was too bright, I’m going to test with temporary solutions till I find the perfect fit.

I have the need for IR LEDs, which I will be picking up at some point, so that I can take a look at my night’s sleep cycle. I just felt like doing that.

I also have several small solar panels that I can get to around 6 to 9 volts, I will be making a solar charger for my rechargeable battery packs. Similarly, I want to make a meter for myself to read the voltage they’re on.

Any further thoughts are always welcome. I’m having tons of fun with the Pi NoIR camera already, the reason I love to take steps further in what I try out and make.