scottwalter
Posts: 3
Joined: Sun Aug 28, 2016 5:35 pm

Re: Lightweight python motion detection

Tue Aug 30, 2016 10:28 pm

Hello All,
This is a pretty old post but I found it extremely helpful in my efforts to build a basic motion capture process for a Raspberry Pi3b.

I did some work on the code and added the ability to build a timelapse video to the process along with some other changes to improve stability / operation. Here is my code, including a basic wrapper script for start / stop (which can be linked to /etc/init.d for automatic startup on reboot). I also tweaked the raspistill commands a bit to help with image captures.

Primary Python code (see wrapper script below for start / stop)

Code: Select all

#!/usr/bin/python
import StringIO
import subprocess
import os
import time
from datetime import datetime
from PIL import Image
import sys
import schedule
import threading

# Original code written by brainflakes and modified to exit
# image scanning for loop as soon as the sensitivity value is exceeded.
# this can speed taking of larger photo if motion detected early in scan
# Additional modifications by Scott Walter
# Added the ability to have a timelapse video created once an hour on a separate thread
# Added saving images / videos to a timestamped folder
# Changed image collection to be numeric, sequenced for easier processes to Timelapse video
# Added timestamp overlay on images
# Option to have imagemagick add an overlay as well (not active, might slow down image capture performance)
# Works on Raspberry Pi model 3 B
# Added signal handler for kill (from a wrapper script)
# Added exception handler to gracefully shutdown
# Added verbosity on startup to show parameters
# Minor modifications to use parameters where previously it was hard coded
# Modified replacements to use '{x}' instead of %s or %d substitutions - better Python code
# Requires https://github.com/dbader/schedule to function. Download it with pip
 
# Motion detection settings:
# need future changes to read values dynamically via command line parameter or xml file
# --------------------------
# 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)
# filepath       - location of folder to save photos
# filenamePrefix - string that prefixes the file name for easier identification of files.
threshold = 20
sensitivity = 1200
forceCapture = True
forceCaptureTime = 60 * 60 # Once an hour
filepath = "/apps/capture/images"
filepath_video = "/apps/capture/videos"

imageCounter = 1
# File photo size settings
saveWidth = 1296
saveHeight = 972
saveQuality = 75
#Scheduled time to create timelapse video from motion captures (Example 01:00 - 1 AM)
timelapse_compile=True
#Use compile_timelapse_video if you want to compile the timelapse once a day, then comment / uncomment the schedule block to match your needs
compile_timelapse_video="18:36"
#In case you want to compile yesterday's activity tomorrow.
timelapse_compile_yesterday=False

#Function to catch kill $PID signal
def signal_term_handler(signal, frame):
	sys.exit(0)

#Threading for timelapse so we can keep capturing while creating the video
def run_threaded(job_func):
    job_thread = threading.Thread(target=job_func)
    job_thread.start()

#Create timelapse video for images taken (previos day, if configured that way)
def createTimelapseVideo():
	if timelapse_compile:
		print "Starting video timelapse creation"
		sys.stdout.flush()
		#Get the folder name
		if timelapse_compile_yesterday:
		   fv = getFolderName(True)
		else:
		  fv = getFolderName(False)

		#Check video folder to make sure it is created
		check_folder(filepath_video+"/"+fv)
	
		image_path= filepath + "/" + fv
		video_path = filepath_video + "/" + fv+"/"+fv+".mp4"	
		deleteFile(video_path)

		#Compile the timelapse video
		subprocess.call("avconv -v quiet -framerate 1/.5 -i '{0}'/%d.jpg -c:v libx264 -r 2 -pix_fmt yuv420p '{1}'".format(image_path, video_path), shell=True) 
		
		print "Video created: "+video_path
		sys.stdout.flush()
	else:
		print "Video creation is disabled"
		sys.stdout.flush()
#Simple function to delete a file
def deleteFile(filename):
	if os.path.isfile(filename):	
		os.remove(filename)
		print "Removed: "+filename
		sys.stdout.flush()

#Simple function to manipulate folder-name based on time
def getFolderName(isYesterday):
	if isYesterday:
		t = datetime.now() - datetime.timedelta(days=1)
	else:
		t = datetime.now()
	return "%02d-%02d-%04d" % (t.month, t.day, t.year)

#Set the image counter to the hight file number plus one for a given day
#This will prevent overwriting files if a restart occurs
def setLastImageNumber():
	#build folder name structure
	ttemp = datetime.now()
	fn="%02d-%02d-%04d" % (ttemp.month, ttemp.day, ttemp.year)
	fp = filepath+"/"+fn

	for dirname,dirnames, filenames in os.walk(fp):
		for filename in filenames:
			f= filename.split('/',5)[0]
			n=f.split('.',1)[0]
			ni = int(n)
			if ni > imageCounter:
				setImageCounter(ni)

#Simple function to update the global imageCounter
def setImageCounter(ni):
	global imageCounter
	#bump it up one so we have a nice new number to use
	imageCounter=ni+1

#Simple function to increase the imageCounter by one
def increase_counter():
	global imageCounter
	imageCounter = imageCounter+1

# Capture a small test image (for motion detection)
def captureTestImage():
    command = "raspistill -t 1 -w %s -h %s -e bmp -o -" % (100, 75)
    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

#Folder checker
def check_folder(path):
	if not os.path.exists(path):
	    os.makedirs(path)
	    return True
	else:
	    return False

#Add timestamp to image
def add_timestamp_overlay(filename):
	subprocess.call("convert '{0}' -font /usr/share/fonts/truetype/gentium-basic/GenBkBasR.ttf -pointsize 72 -fill white -annotate +100+100 %[exif:DateTimeOriginal] '{1}'".format(
filename, filename), shell=True)

# Save a full size image to disk
def saveImage(width, height):
    time = datetime.now()
    folder_name = "%02d-%02d-%04d" % (time.month, time.day, time.year)
    #print "Folder_name = " + folder_name
    sys.stdout.flush()

    #See if this folder is present, might be a brand new day!
    if check_folder(filepath+"/"+folder_name):
	#Means we need to reset the imageCounter
	setImageCounter(0)
	print "Reset Image Counter to: "+str(imageCounter)

    filename = filepath + "/" + folder_name +"/"+ str(imageCounter) + ".jpg"
    subprocess.call("raspistill -t 1 -ex auto -ISO 800 -w '{0}' -h '{1}' -e jpg -a 12 -q '{2}' -o '{3}'".format(saveWidth,saveHeight, saveQuality, filename) , shell=True)
    #Add date timestamp to image capture so we can see when it happened! Replaced with -a 12 flag on raspistill
    #add_timestamp_overlay(filename)

    print "Captured %s" % filename
    increase_counter()
    sys.stdout.flush()

#Get last count number in case there was a restart
setLastImageNumber()

#Set up the the timelapse schedule
#schedule.every().day.at(compile_timelapse_video).do(createTimelapseVideo)
#schedule.every().hour.do(createTimelapseVideo)
#Run once an hour and 23:55 (catch end of day)
schedule.every().day.at("01:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("02:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("03:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("04:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("05:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("06:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("07:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("08:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("09:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("10:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("11:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("12:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("13:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("14:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("15:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("16:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("17:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("18:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("19:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("20:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("21:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("22:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("23:55").do(run_threaded,createTimelapseVideo)


# Get first image
image1, buffer1 = captureTestImage()

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

# added this to give visual feedback of camera motion capture activity.  Can be removed as required
#os.system('clear')
print "            Motion Detection Started"
print "            ------------------------"
print "Pixel Threshold (How much)   = " + str(threshold)
print "Sensitivity (changed Pixels) = " + str(sensitivity)
print "Image Width                  = " + str(saveWidth)
print "Image Height                 = " + str(saveHeight)
print "Image Quality                = " + str(saveQuality)
print "File Path for Image Save     = " + filepath
print "File Path for Video Save     =" + filepath_video
print "Last Image Number            = "+str(imageCounter)
print "---------- Motion Capture File Activity --------------"
sys.stdout.flush()

while (True):
	try:
		#Check schedule for timelapse video creation
		schedule.run_pending()
    		# Get comparison image
    		image2, buffer2 = captureTestImage()

    		# Count changed pixels
    		changedPixels = 0
    		for x in xrange(0, 100):
        		# Scan one line of image then check sensitivity for movement
        		for y in xrange(0, 75):
            		# 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

        		# Changed logic - If movement sensitivity exceeded then
        		# Save image and Exit before full image scan complete
        		if changedPixels > sensitivity:   
            			lastCapture = time.time()
            			saveImage(saveWidth, saveHeight)
            			break
        		continue

    		# Check force capture
    		if forceCapture:
        		if time.time() - lastCapture > forceCaptureTime:
            			changedPixels = sensitivity + 1

		#Check to see if we should compile a timelapse video 
  
    		# Swap comparison buffers
    		image1  = image2
    		buffer1 = buffer2
	except KeyboardInterrupt:
		print
		print "Finished"
		sys.exit()


Basic Start / Stop wrapper script

Code: Select all

#!/bin/sh
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/apps/capture
export pidfile=/apps/capture/log/app.pid
export datadir=/apps/capture
export logfile=$datadir/log/capture.log
export errfile=$datadir/log/error.log

do_start()
{
	  nohup python /apps/capture/c.py >$logfile 2>$errfile &
          echo $! >$pidfile
	  exit 0
}
do_stop()
{
	   #echo "pidfile: $pidfile"
	   #echo `cat $pidfile`
	   #Soft kill to clear the display, second hard kill in case we are midthread for web refresh
       	   /bin/kill `/bin/cat $pidfile`
       	   /bin/kill -9 `/bin/cat $pidfile`
	   rm $pidfile
	   exit 0
} 

case "$1" in
	start)
	 do_start  
	 ;;
	stop)
	 do_stop
	 ;;
	restart)
	  if [ -f "$pidfile" ]; then
     		 do_stop
		 sleep 5
	  	 do_start
	  else
	     echo "Service is not running, us start option"
             exit 1
          fi
	     ;;
	*)
	     echo "usage: cap.sh {start|stop|restart}" >&2
	     exit 3
	     ;;
esac

wirelessguyny
Posts: 15
Joined: Fri May 15, 2015 8:48 pm

Re: Lightweight python motion detection

Fri Sep 02, 2016 2:58 am

Hi,

Seeing that I still get notifications when items are posted to this thread I thought I'd add a quick comment here.

While I found the python scripts/app to work OK I eventually stopped using this because I came across a more full featured app out there.

should anyone else out there be interested in motion capture software with quite a few bells & whistles check out this thread:
viewtopic.php?t=115583

All the best.

Lasse
Posts: 3
Joined: Mon Sep 12, 2016 6:46 pm

Re: Lightweight python motion detection

Sat Nov 19, 2016 10:49 pm

Thank you for a very nice motion detection script. However, when the script has been running for approximately one week, "raspistil" stop and will not run again. This is a link yo my post.:

viewtopic.php?f=43&t=159657&p=1036680&h ... p#p1036680

I would appreciate if you have any suggestions that can help me to solve this.

BR
Lasse

Sword61
Posts: 2
Joined: Mon Jan 02, 2017 6:24 pm
Location: Germany

Re: Lightweight python motion detection

Sat Jan 07, 2017 7:16 pm

Hi,

i tried the file but in line 140 i have an error.....

imageData.write(subprocess.check_output(command, shell=TRUE))

Type Error: unicode argument expected, got 'str'

what can i do?

Eckhard

scottwalter
Posts: 3
Joined: Sun Aug 28, 2016 5:35 pm

Re: Lightweight python motion detection

Tue Jan 10, 2017 1:25 am

Hello, Can you post your complete code here so we can take a look? It appears that you may have a variable problem that is causing the "command" to not execute properly.

-Scott
Sword61 wrote:Hi,

i tried the file but in line 140 i have an error.....

imageData.write(subprocess.check_output(command, shell=TRUE))

Type Error: unicode argument expected, got 'str'

what can i do?

Eckhard

scottwalter
Posts: 3
Joined: Sun Aug 28, 2016 5:35 pm

Re: Lightweight python motion detection

Tue Jan 10, 2017 1:27 am

Raspistill is pretty particular, if an instance is already running it will fail to run again. Usually it is a hung raspistill process that causes this problem.

-Scott
Lasse wrote:Thank you for a very nice motion detection script. However, when the script has been running for approximately one week, "raspistil" stop and will not run again. This is a link yo my post.:

viewtopic.php?f=43&t=159657&p=1036680&h ... p#p1036680

I would appreciate if you have any suggestions that can help me to solve this.

BR
Lasse

Sword61
Posts: 2
Joined: Mon Jan 02, 2017 6:24 pm
Location: Germany

Re: Lightweight python motion detection

Thu Jan 12, 2017 7:33 pm

Code: Select all

#!/usr/bin/python
import StringIO
import subprocess
import os
import time
from datetime import datetime
from PIL import Image
import sys
import schedule
import threading

# Original code written by brainflakes and modified to exit
# image scanning for loop as soon as the sensitivity value is exceeded.
# this can speed taking of larger photo if motion detected early in scan
# Additional modifications by Scott Walter
# Added the ability to have a timelapse video created once an hour on a separate thread
# Added saving images / videos to a timestamped folder
# Changed image collection to be numeric, sequenced for easier processes to Timelapse video
# Added timestamp overlay on images
# Option to have imagemagick add an overlay as well (not active, might slow down image capture performance)
# Works on Raspberry Pi model 3 B
# Added signal handler for kill (from a wrapper script)
# Added exception handler to gracefully shutdown
# Added verbosity on startup to show parameters
# Minor modifications to use parameters where previously it was hard coded
# Modified replacements to use '{x}' instead of %s or %d substitutions - better Python code
# Requires https://github.com/dbader/schedule to function. Download it with pip
 
# Motion detection settings:
# need future changes to read values dynamically via command line parameter or xml file
# --------------------------
# 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)
# filepath       - location of folder to save photos
# filenamePrefix - string that prefixes the file name for easier identification of files.
threshold = 20
sensitivity = 1200
forceCapture = True
forceCaptureTime = 60 * 60 # Once an hour
filepath = "/apps/capture/images"
filepath_video = "/apps/capture/videos"

imageCounter = 1
# File photo size settings
saveWidth = 1296
saveHeight = 972
saveQuality = 75
#Scheduled time to create timelapse video from motion captures (Example 01:00 - 1 AM)
timelapse_compile=True
#Use compile_timelapse_video if you want to compile the timelapse once a day, then comment / uncomment the schedule block to match your needs
compile_timelapse_video="18:36"
#In case you want to compile yesterday's activity tomorrow.
timelapse_compile_yesterday=False

#Function to catch kill $PID signal
def signal_term_handler(signal, frame):
   sys.exit(0)

#Threading for timelapse so we can keep capturing while creating the video
def run_threaded(job_func):
    job_thread = threading.Thread(target=job_func)
    job_thread.start()

#Create timelapse video for images taken (previos day, if configured that way)
def createTimelapseVideo():
   if timelapse_compile:
      print "Starting video timelapse creation"
      sys.stdout.flush()
      #Get the folder name
      if timelapse_compile_yesterday:
         fv = getFolderName(True)
      else:
        fv = getFolderName(False)

      #Check video folder to make sure it is created
      check_folder(filepath_video+"/"+fv)
   
      image_path= filepath + "/" + fv
      video_path = filepath_video + "/" + fv+"/"+fv+".mp4"   
      deleteFile(video_path)

      #Compile the timelapse video
      subprocess.call("avconv -v quiet -framerate 1/.5 -i '{0}'/%d.jpg -c:v libx264 -r 2 -pix_fmt yuv420p '{1}'".format(image_path, video_path), shell=True) 
      
      print "Video created: "+video_path
      sys.stdout.flush()
   else:
      print "Video creation is disabled"
      sys.stdout.flush()
#Simple function to delete a file
def deleteFile(filename):
   if os.path.isfile(filename):   
      os.remove(filename)
      print "Removed: "+filename
      sys.stdout.flush()

#Simple function to manipulate folder-name based on time
def getFolderName(isYesterday):
   if isYesterday:
      t = datetime.now() - datetime.timedelta(days=1)
   else:
      t = datetime.now()
   return "%02d-%02d-%04d" % (t.month, t.day, t.year)

#Set the image counter to the hight file number plus one for a given day
#This will prevent overwriting files if a restart occurs
def setLastImageNumber():
   #build folder name structure
   ttemp = datetime.now()
   fn="%02d-%02d-%04d" % (ttemp.month, ttemp.day, ttemp.year)
   fp = filepath+"/"+fn

   for dirname,dirnames, filenames in os.walk(fp):
      for filename in filenames:
         f= filename.split('/',5)[0]
         n=f.split('.',1)[0]
         ni = int(n)
         if ni > imageCounter:
            setImageCounter(ni)

#Simple function to update the global imageCounter
def setImageCounter(ni):
   global imageCounter
   #bump it up one so we have a nice new number to use
   imageCounter=ni+1

#Simple function to increase the imageCounter by one
def increase_counter():
   global imageCounter
   imageCounter = imageCounter+1

# Capture a small test image (for motion detection)
def captureTestImage():
    command = "raspistill -t 1 -w %s -h %s -e bmp -o -" % (100, 75)
    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

#Folder checker
def check_folder(path):
   if not os.path.exists(path):
       os.makedirs(path)
       return True
   else:
       return False

#Add timestamp to image
def add_timestamp_overlay(filename):
   subprocess.call("convert '{0}' -font /usr/share/fonts/truetype/gentium-basic/GenBkBasR.ttf -pointsize 72 -fill white -annotate +100+100 %[exif:DateTimeOriginal] '{1}'".format(
filename, filename), shell=True)

# Save a full size image to disk
def saveImage(width, height):
    time = datetime.now()
    folder_name = "%02d-%02d-%04d" % (time.month, time.day, time.year)
    #print "Folder_name = " + folder_name
    sys.stdout.flush()

    #See if this folder is present, might be a brand new day!
    if check_folder(filepath+"/"+folder_name):
   #Means we need to reset the imageCounter
   setImageCounter(0)
   print "Reset Image Counter to: "+str(imageCounter)

    filename = filepath + "/" + folder_name +"/"+ str(imageCounter) + ".jpg"
    subprocess.call("raspistill -t 1 -ex auto -ISO 800 -w '{0}' -h '{1}' -e jpg -a 12 -q '{2}' -o '{3}'".format(saveWidth,saveHeight, saveQuality, filename) , shell=True)
    #Add date timestamp to image capture so we can see when it happened! Replaced with -a 12 flag on raspistill
    #add_timestamp_overlay(filename)

    print "Captured %s" % filename
    increase_counter()
    sys.stdout.flush()

#Get last count number in case there was a restart
setLastImageNumber()

#Set up the the timelapse schedule
#schedule.every().day.at(compile_timelapse_video).do(createTimelapseVideo)
#schedule.every().hour.do(createTimelapseVideo)
#Run once an hour and 23:55 (catch end of day)
schedule.every().day.at("01:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("02:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("03:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("04:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("05:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("06:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("07:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("08:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("09:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("10:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("11:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("12:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("13:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("14:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("15:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("16:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("17:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("18:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("19:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("20:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("21:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("22:00").do(run_threaded,createTimelapseVideo)
schedule.every().day.at("23:55").do(run_threaded,createTimelapseVideo)


# Get first image
image1, buffer1 = captureTestImage()

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

# added this to give visual feedback of camera motion capture activity.  Can be removed as required
#os.system('clear')
print "            Motion Detection Started"
print "            ------------------------"
print "Pixel Threshold (How much)   = " + str(threshold)
print "Sensitivity (changed Pixels) = " + str(sensitivity)
print "Image Width                  = " + str(saveWidth)
print "Image Height                 = " + str(saveHeight)
print "Image Quality                = " + str(saveQuality)
print "File Path for Image Save     = " + filepath
print "File Path for Video Save     =" + filepath_video
print "Last Image Number            = "+str(imageCounter)
print "---------- Motion Capture File Activity --------------"
sys.stdout.flush()

while (True):
   try:
      #Check schedule for timelapse video creation
      schedule.run_pending()
          # Get comparison image
          image2, buffer2 = captureTestImage()

          # Count changed pixels
          changedPixels = 0
          for x in xrange(0, 100):
              # Scan one line of image then check sensitivity for movement
              for y in xrange(0, 75):
                  # 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

              # Changed logic - If movement sensitivity exceeded then
              # Save image and Exit before full image scan complete
              if changedPixels > sensitivity:   
                     lastCapture = time.time()
                     saveImage(saveWidth, saveHeight)
                     break
              continue

          # Check force capture
          if forceCapture:
              if time.time() - lastCapture > forceCaptureTime:
                     changedPixels = sensitivity + 1

      #Check to see if we should compile a timelapse video 
  
          # Swap comparison buffers
          image1  = image2
          buffer1 = buffer2
   except KeyboardInterrupt:
      print
      print "Finished"
      sys.exit()
Hi scott, this is the code, that makes the error at line 140

Traceback (most recent call last):
File "motionpy.py", line 215, in <module>
image1, buffer1 = captureTestImage()
File "motionpy.py", line 140, in captureTestImage
imageData.write(subprocess.check_output(command, shell=True))
TypeError: unicode argument expected, got 'str'

hwl
Posts: 1
Joined: Thu Dec 07, 2017 8:50 am

Re: Lightweight python motion detection

Thu Dec 07, 2017 8:58 am

I upgraded the firmware and executed the code but getting the following error

python motionsensor.py
mmal: mmal_vc_component_enable: failed to enable component: ENOSPC
mmal: camera component couldn't be enabled
mmal: main: Failed to create camera component
mmal: Failed to run camera app. Please check for firmware updates

Traceback (most recent call last):
File "motionsensor.py", line 67, in <module>
image1, buffer1 = captureTestImage()
File "motionsensor.py", line 35, in captureTestImage
imageData.write(subprocess.check_output(command, shell=True))
File "/usr/lib/python2.7/subprocess.py", line 573, in check_output
raise CalledProcessError(retcode, cmd, output=output)
subprocess.CalledProcessError: Command 'raspistill -w 100 -h 75 -t 0 -e bmp -o -' returned non-zero exit status 70

Code is below is anything missing

Code: Select all

import StringIO
import subprocess
import os
import time
from datetime import datetime
from PIL import Image

# Original code written by brainflakes and modified to exit
# image scanning for loop as soon as the sensitivity value is exceeded.
# this can speed taking of larger photo if motion detected early in scan
 
# Motion detection settings:
# need future changes to read values dynamically via command line parameter or xml file
# --------------------------
# 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)
# filepath       - location of folder to save photos
# filenamePrefix - string that prefixes the file name for easier identification of files.
threshold = 10
sensitivity = 180
forceCapture = True
forceCaptureTime = 60 * 60 # Once an hour
filepath = "/home/pi/picam"
filenamePrefix = "capture"
# File photo size settings
saveWidth = 1280
saveHeight = 960
diskSpaceToReserve = 40 * 1024 * 1024 # Keep 40 mb free on disk

# Capture a small test image (for motion detection)
def captureTestImage():
    command = "raspistill -w %s -h %s -t 0 -e bmp -o -" % (100, 75)
    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(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 -hf -w 1296 -h 972 -t 0 -e jpg -q 15 -o %s" % filename, shell=True)
    print "Captured %s" % filename

# Keep free space above given level
def keepDiskSpaceFree(bytesToReserve):
    if (getFreeSpace() < bytesToReserve):
        for filename in sorted(os.listdir(".")):
            if filename.startswith(fileNamePrefix) and filename.endswith(".jpg"):
                os.remove(filename)
                print "Deleted %s to avoid filling disk" % filename
                if (getFreeSpace() > bytesToReserve):
                    return

# Get available disk space
def getFreeSpace():
    st = os.statvfs(".")
    du = st.f_bavail * st.f_frsize
    return du
        
# Get first image
image1, buffer1 = captureTestImage()

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

# added this to give visual feedback of camera motion capture activity.  Can be removed as required
os.system('clear')
print "            Motion Detection Started"
print "            ------------------------"
print "Pixel Threshold (How much)   = " + str(threshold)
print "Sensitivity (changed Pixels) = " + str(sensitivity)
print "File Path for Image Save     = " + filepath
print "---------- Motion Capture File Activity --------------"

while (True):

    # Get comparison image
    image2, buffer2 = captureTestImage()

    # Count changed pixels
    changedPixels = 0
    for x in xrange(0, 100):
        # Scan one line of image then check sensitivity for movement
        for y in xrange(0, 75):
            # 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

        # Changed logic - If movement sensitivity exceeded then
        # Save image and Exit before full image scan complete
        if changedPixels > sensitivity:   
            lastCapture = time.time()
            saveImage(saveWidth, saveHeight, diskSpaceToReserve)
            break
        continue

    # Check force capture
    if forceCapture:
        if time.time() - lastCapture > forceCaptureTime:
            changedPixels = sensitivity + 1
  
    # Swap comparison buffers
    image1  = image2
    buffer1 = buffer2


[code]

pws
Posts: 87
Joined: Mon Apr 11, 2016 4:16 pm

Re: Lightweight python motion detection

Thu Dec 07, 2017 1:16 pm

"Code is below is anything missing "

Yes - a CODE block

pageauc
Posts: 223
Joined: Fri Jan 04, 2013 10:52 pm

Re: Lightweight python motion detection

Thu Dec 07, 2017 2:09 pm

I was one of the original posters on this forum subject. This was the inspiration for developing things further that eventually evolved into the current version of pi-timolo and other motion tracking/detection projects. If you are interested in how far a project can go the pi-timolo project is now at Release 9.0 on GitHub . Below is the update message. Thanks for the inspiration Brainflakes.
Claude ...

viewtopic.php?f=38&t=199351
GitHub - https://github.com/pageauc
YouTube - https://www.youtube.com/user/pageaucp

Return to “Camera board”