David Guidos
Created January 5, 2018

Bee Hive Health Monitor

Worldwide bee colonies are declining, and it's important for us to help save them.

45
Bee Hive Health Monitor

Things used in this project

Hardware components

Raspberry Pi Zero Wireless
Raspberry Pi Zero Wireless
×1
Hologram Nova
Hologram Nova
×1
Microphone
×1
Micro USB Hub
×1
Case, Raspberry Pi Zero W
×1
Power Adapter, 5V
×1

Story

Read more

Schematics

Photo of Circuit Prototype

View of how the hardware is connected. Will convert to Fritzing file when time permits.

Code

Bee Monitor Program

Python
The program runs on a Raspberry Pi using a USB microphone and a FLIR Lepton Thermal Image Camera to monitor the status of a bee hive and/or bee activity in a field.
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# beeMonitor.py
# Bee Monitor - Detects Bees Using a Microphone to Listen for a Characteristic Buzzing Frequency
#                Bees buzz at 190 Hz ... 250 Hz. Allowance made for doppler effects of moving bees.
# Designed by David Guidos, 2017

import sys  
import numpy
import time
import json
import pyaudio
import pygame
import argparse
import os
from subprocess import check_call
  
# get command line arguments
#parser = argparse.ArgumentParser(description='Arguments for BeeDetector.')
#parser.add_argument('usePyGame', metavar='N', type=bool, nargs=1,
#                    help='Display grahical data?')
#args = parser.parse_args()

# functional set up
# TODO: set up as command line parameters
usePyGame = True # TODO: allow disabling
useNova = False
samplesPerPublish = 20  # audio samples before publishing to Hologram Nova

# audio set up
mic = None
FS=44100       # sampling frequency; standard CD rate
SAMPLES=1024   # approx 40 sample packets / sec. if not overlapped using a callback, processing lowers to approx. 20 / sec
MIC_DEVICE = 1 # TODO: enumerate to find mic device?

# bee detection variables
beeMinFFTIndex = 30
beeMaxFFTIndex = 37

#   P Y G A M E
#
# set canvas parameters
size = width, height = 1100, 700
speed = [100, 100]

redColor = pygame.Color(255, 0, 0)
blueColor = pygame.Color(0, 0, 255)
greenColor = pygame.Color(0, 255, 0)
blackColor = pygame.Color(0, 0, 0)
whiteColor = pygame.Color(255, 255, 255)
grayColor = pygame.Color(150, 150, 150)



#   A U D I O
#
def get_audioSample():
    global mic
    if mic is None:
        pa = pyaudio.PyAudio()
        mic = pa.open(format=pyaudio.paInt16, channels=1, rate=FS,
                      input_device_index = MIC_DEVICE, input=True,
                      frames_per_buffer=SAMPLES)
    return numpy.fromstring(mic.read(SAMPLES), dtype=numpy.short)

def get_powerSpectrum(amplitudes):
    return abs(numpy.fft.fft(amplitudes / 32768.0))[:SAMPLES/2]

def plot_sound(amplitudes):
    # show title
    screen.blit(soundLabel, (10, 10))
    # determine canvas positions
    yUsable = (height / 2) - 60
    yRange = yUsable / 2
    yBase = 10
    x = -1
    maxAmplitude = max(max(amplitudes), -min(amplitudes))
    previousY = 0
    for amplitude in amplitudes:
        x += 1
        #y = (-float(amplitude) / float(maxAmplitude)) * yRange   # for automatic scaling
        y = (-float(amplitude) / 4096.0) * yRange   # fixed scaling
        # plot this amplitude
        #lineRect = pygame.draw.line(screen, blueColor, (x + 10, yRange + y + 10), (x + 10, yRange - y + 10), 1)
        lineRect = pygame.draw.line(screen, blueColor, (x + 10, yBase + yRange + previousY), (x + 10, yBase + yRange + y), 1)
        previousY = y
    #print max(amplitudes)

def plot_powerSpectrum(powerArray):  
    # show title
    screen.blit(spectrumLabel, (10, 330))
    screen.blit(spectrumFrequencies, (10, height - 25))
    # determine canvas positions
    yUsable = (height / 2) - 60
    yBase = height - 40
    x = -1
    freqIndex = powerArray.argmax(axis=0)   # primary frequency
    maximumPower = max(powerArray)
    print "Freq Index: " + str(freqIndex)
    for powerValue in powerArray:
        x += 1
        y = powerValue / maximumPower * yUsable
        if (x == freqIndex):
            # show power level for primary sample frequency in red
            lineColor = redColor
        else:
            # normal power levels in black
            lineColor = blackColor
        # plot the power level for this sample value
        lineRect = pygame.draw.line(screen, lineColor, (x + 10, yBase), (x + 10, yBase - y), 1)

def plot_detectionLevels(levels):
    yUsable = (height / 3) - 60
    yBase = height - 40
    xBase = width - 500
    x = -1
    maximumLevel = 50 # max(levels)
    lineColor = blueColor
    previousY = -1
    for level in levels:
        x += 1
        y = float(level) / float(maximumLevel) * float(yUsable)
        if previousY == -1:
            previousY = y
        # plot the detection level for this sample value 
        lineRect = pygame.draw.line(screen, lineColor, (xBase + (x - 1) * 2, yBase - previousY), (xBase + x * 2, yBase - y), 1)       
        previousY = y
    
def detectBees(frequencyIndex, previousFrequencyIndex):
    if (frequencyIndex >= beeMinFFTIndex) and (frequencyIndex <= beeMaxFFTIndex) and (previousFrequencyIndex >= beeMinFFTIndex) and (previousFrequencyIndex <= beeMaxFFTIndex):
        beesDetected = 1
    else:
        beesDetected = 0
    return beesDetected


#   F L I R   T H E R M A L   I M A G E   R O U T I N E S

currentThermalImageString = ""
previousThermalImageString = ""

def captureThermalImage():
    global currentThermalImageString
    global previousThermalImageString
    imageFileName = 'IMG_0000.pgm'
    check_call('./raspberrypi_capture')
    pgm_string = pgmString(imageFileName)
    if pgm_string != "":
        previousThermalImageString = currentThermalImageString
        currentThermalImageString = pgm_string
    os.remove(imageFileName)
    
def pgmString(pgmFileName):
    pgm_string = ""
    with open(pgmFileName, "r") as pgmFile:
        pgm_string = pgmFile.read()
    return pgm_string          

def drawPGM((x0, y0)):
    if currentThermalImageString != "":
        pgmList = currentThermalImageString.split()
        hdrP2 = pgmList[0]
        xSize =int(pgmList[1])
        ySize =int(pgmList[2])
        maxValue = int(pgmList[3])
        # double size in both x and y directions
        for n in range(4, len(pgmList)):
            x = (n - 4) % xSize
            y = int((n -4) / xSize)
            x = x * 2
            y = y * 2
            grayLevel = pgmList[n]
            if grayLevel > 255:
                grayLevel = 255
            c = (grayLevel, grayLevel, grayLevel)
            screen.set_at((x0 + x, y0 + y), c)
            screen.set_at((x0 + x + 1, y0 + y), c)
            screen.set_at((x0 + x, y0 + y + 1), c)
            screen.set_at((x0 + x + 1, y0 + y + 1), c)
        screen.blit(thermalLabel, (x0, y0 + ySize * 2 + 10))

def thermalImageAnomalyDetected():
    # check for significant thermal activity
    variationCount = 0
    if currentThermalImageString != "" and previousThermalImageString != "":
        currentList = currentThermalImageString.split()
        previousList = previousThermalImageString.split()
        for n in range(4, len(currentList)):
            if abs(int(currentList[n]) - int(previousList[n])) > 8:
                variationCount += 1
    return (variationCount > 128)


#   H O L O G R A M   N O V A

def sendAlert(message):
    alertCommand = 'sudo hologram send "' + message + '"'
    os.system(alertCommand)
    
def sendBeeActivity():
    alertCommand = 'sudo hologram send "Bee Activity Level:' + str(currentBees) + '"'
    os.system(alertCommand)
    
    
    
#   M A I N

# init the game engine
if usePyGame:
    pygame.init( )

    # display title on canvas and clear the display
    pygame.display.set_caption("Bee Detector")
    screen = pygame.display.set_mode(size)
    screen.fill(whiteColor)

    gameFont = pygame.font.SysFont("monospace", 15)
    gameFont2 = pygame.font.SysFont("monospace", 30)

    # create text
    soundLabel = gameFont.render("Sound data", 1, blackColor)
    spectrumLabel = gameFont.render("Power spectrum", 1, blackColor)
    spectrumFrequencies = gameFont.render("0Hz  5kHz 10kHz 15kHz 20kHz 25kHz 30kHz 35kHz 40kHz 45kHz", 1, blueColor)
    thermalLabel = gameFont.render("Thermal Image", 1, blackColor)

    # create bee image
    beeImage = pygame.image.load("bee.png")

    # render the surface
    pygame.display.flip()

  
#start = time.time()
abort = False
currentBees = 0
detectionLevels = []
previousFrequencyIndex = 0
sampleCount = 0
beesDetectedCount = 0
while not abort:
    try:
        amplitudes = get_audioSample()
        power = get_powerSpectrum(amplitudes)
        primaryFrequencyIndex = power.argmax(axis=0)
        # detect bees
        beesDetected = detectBees(primaryFrequencyIndex, previousFrequencyIndex)
        beesDetectedCount += beesDetected
        previousFrequencyIndex = primaryFrequencyIndex
        sampleCount += 1
        # update bee detection level
        if beesDetected == 1:
            currentBees += 2
        else:
            currentBees -= 1
            if currentBees < 0:
                currentBees = 0
        print ("Bee Detection Index: " + str(currentBees))
        
        # put current bee detection level into the detection levels array
        # limits the size to 200 elements
        detectionLevels.append(currentBees)
        if len(detectionLevels) > 200:
            detectionLevels.pop(0)
        
        # display bee level
        if usePyGame:
            beesDetectedLabel = gameFont.render("Bee Detection Index: ", 1, blackColor)
            beesDetectedValue = gameFont2.render(str(currentBees), 1, redColor)
    except (IOError):
        #print("IOError: " + str(IOError))
        continue

    if usePyGame:
        # clear the canvas
        screen.fill(whiteColor)
                    
        # show bees detected
        screen.blit(beesDetectedLabel, (600, 330))
        screen.blit(beesDetectedValue, (800, 320))

        # show bee image
        beeImageScaled = pygame.transform.scale(beeImage, (5 * currentBees + 25, 5 * currentBees + 25))
        screen.blit(beeImageScaled, (600, 360))
    
        # display the sound data
        plot_sound(amplitudes)
    
        # display the sound spectrum
        plot_powerSpectrum(power)

        # display the detection level history
        plot_detectionLevels(detectionLevels)
        
        # display the thermal image
        captureThermalImage()
        drawPGM((900, 550))
        if thermalImageAnomalyDetected():
            # send alert using Holgram Nova
            sendAlert("Thermal anomaly detected!")
            
        # update the display
        pygame.display.flip()

    # check whether to publish the sample data
    if sampleCount >= samplesPerPublish:
        # send bee activity level
        if currentBees > 50:
            sendBeeActivity()



    #abort = True

Credits

David Guidos

David Guidos

2 projects • 0 followers
Computing since 1969. US Patent 4,725,836. Software/firmware developer. Owner of Red Dog Circle Ranch and Victory Garden General Store.

Comments