Hackster is hosting Hackster Holidays, Ep. 7: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Friday!Stream Hackster Holidays, Ep. 7 on Friday!

Go There

Urban 5x5x5x5x15 Project Site selection for Provocation 3: International House corner

Full instructions provided683
Go There

Story

Read more

Code

tracker.py

Python
tracker.py
#!/usr/bin/env python

import os
import cv
import time
import sys
import math
import re
import serial
import pygame
import time
import numpy
from datetime import datetime, timedelta
from ConfigParser import *

CAMERA = 1

class Source:
    def __init__(self, id, flip=True):
        self.capture = cv.CaptureFromCAM(CAMERA)

    def grab_frame(self):
        self.frame = cv.QueryFrame(self.capture)
        if not self.frame:
            print "can't grab frame"
            sys.exit(2)
        cv.Flip(self.frame, None, 1)
        return self.frame

class Music:
    def __init__(self):
        self.songs = []
        self.staticWidth = .2
        self.staticRange = self.staticWidth * 2;

        pygame.init()
        pygame.mixer.init()

        self.staticSoundFile = pygame.mixer.Sound("static.wav")
        self.staticSoundFile.set_volume(0) 
        self.staticSoundFile.play(loops=-1)

        self.addSongObjects("finalChinese.wav")
        self.addSongObjects("finalBrazil.wav")
        self.addSongObjects("finalIndian.wav")
        self.addSongObjects("finalFrance.wav")

        for singleSong in self.songs:
            singleSong.play(loops=-1)

        self.muteAll()

    def setVolumesForPosition(self, position):
        div = 1.0 / (2*len(self.songs))
        #set song volume
        for x in numpy.arange(div, 1, 2*div):
            diff = abs(x - position)
            song = int(x * len(self.songs))
            
            if diff <= div:
                audioLevel = 1 - diff / div
                self.songs[song].set_volume(audioLevel)
            else:
                #audioLevel = 0.5 * self.songs[i].get_volume()
                self.songs[song].set_volume(0)

        #set static volume
        for x in numpy.arange(2*div, 1 - div, 2*div):
            diff = abs(x - position)
            if (diff <= div/2):
                audioLevel = 1 - diff/div
                self.staticSoundFile.set_volume(audioLevel)
                break
            else:
                self.staticSoundFile.set_volume(0)



        # mappedPosition = position * (len(self.songs) - 1)
        # for i in range(len(self.songs)):
        #     difference = abs(i - mappedPosition)
        #     if difference < .5:
        #         audioLevel = 1 - (2 * difference)
        #         self.songs[i].set_volume(audioLevel)
        #     else:
        #         self.songs[i].set_volume(0)
        # # Static controled by global var static width
        # justDecimal = mappedPosition - int(mappedPosition)
        # if justDecimal > (.5 - self.staticWidth) and (justDecimal < .5 +  self.staticWidth):
        #     shiftedDown = justDecimal - ((.5 - self.staticWidth))
        #     staticLevel = 0
        #     if shiftedDown < self.staticWidth: ##going up
        #         staticLevel = shiftedDown/ self.staticWidth      
        #     else: #going down
        #         staticLevel = self.staticRange - shiftedDown
        #         staticLevel = staticLevel/ self.staticWidth      
        #         self.staticSoundFile.set_volume(staticLevel) 
        # else:  
        #     self.staticSoundFile.set_volume(0) 


    def addSongObjects(self, myString):
        soundFile = pygame.mixer.Sound(myString)
        soundFile.set_volume(0)
        self.songs.append(soundFile)

    def muteAll(self):
        for singleSong in self.songs:
            singleSong.set_volume(0)

class Setup:
    def __init__(self):
        self.mixer = None
        self.LoadIniData()
        self.source = Source(self.cameraid)
        self.storage = cv.CreateMemStorage(0)
        self.orig = self.source.grab_frame()

        self.lastDetected = datetime.now()
        self.last = 0
        self.old_center = (0,0)
        self.width = self.orig.width
        self.height = self.orig.height
        self.size = (self.width, self.height)
        self.smallheight = self.working_height
        self.smallwidth = int(self.width * self.smallheight/self.height * 1.0)
        self.smallsize = (self.smallwidth, self.smallheight)

        # alloc mem for images used at various stages
        self.small = cv.CreateImage(self.smallsize, cv.IPL_DEPTH_8U, 3)
        self.motion = cv.CreateImage(self.smallsize, cv.IPL_DEPTH_8U, 3)
        self.eye = cv.CreateImage(self.smallsize, cv.IPL_DEPTH_8U, 3)
        self.mhi = cv.CreateImage(self.smallsize, cv.IPL_DEPTH_32F, 1)
        self.orient = cv.CreateImage(self.smallsize,cv.IPL_DEPTH_32F, 1)
        self.segmask = cv.CreateImage(self.smallsize,cv.IPL_DEPTH_32F, 1)
        self.mask = cv.CreateImage(self.smallsize,cv.IPL_DEPTH_8U, 1)
        self.temp = cv.CreateImage(self.smallsize, cv.IPL_DEPTH_8U, 3)
        self.buf = range(10) 
        for i in range(self.n_frames):
            self.buf[i] = cv.CreateImage(self.smallsize, cv.IPL_DEPTH_8U, 1)
            cv.SetZero(self.buf[i])

        self.combined = cv.CreateImage((self.smallwidth * self.xwindows, self.smallheight), cv.IPL_DEPTH_8U, 3)

        if self.store:
            self.writer = cv.CreateVideoWriter(self.output, 0, 15, cv.GetSize(self.combined), 1)

        # make window
        #cv.NamedWindow("Motion Detection") 

        #cv.CreateTrackbar('Height', 'Motion Detection', self.height_value, 100, self.change_height)

        #cv.CreateTrackbar('Jitter', 'Motion Detection', self.jitter_value, 100, self.change_jitter)

    def setMixer(self, mixer):
        self.mixer = mixer

    def LoadIniData(self):
        FileName = re.sub(r'\.py$', "", os.path.abspath( __file__ )) + '.ini'
        self.cp=ConfigParser()
        try:
            self.cp.readfp(open(FileName,'r'))
        # f.close()
        except IOError:
            raise Exception,'NoFileError'

        self.store = self.cp.getboolean('Variables', 'store')
        self.cameraid = self.cp.getint('Variables', 'cameraid')
        self.output = self.cp.get('Variables', 'output')
        self.FPS = self.cp.getint('Variables', 'fps')
        self.xwindows = self.cp.getint('Variables', 'xwindows')
        self.working_height = self.cp.getint('Variables', 'working_height')
        self.clocks_per_sec = self.cp.getfloat('Variables', 'clocks_per_sec')
        self.mhi_duration = self.cp.getfloat('Variables', 'mhi_duration')
        self.max_time_delta = self.cp.getfloat('Variables', 'max_time_delta')
        self.min_time_delta = self.cp.getfloat('Variables', 'min_time_delta')
        self.n_frames = self.cp.getint('Variables', 'n_frames')
        self.height_value = self.cp.getint('Variables', 'height_value')
        self.jitter_value = self.cp.getint('Variables', 'jitter_value')

        return

    def process_motion(self,img):
        center = (-1, -1)
        # a lot of stuff from this section was taken from the code motempl.py, 
        #  openCV's python sample code
        timestamp = time.clock() / self.clocks_per_sec # get current time in seconds
        idx1 = self.last
        cv.CvtColor(img, self.buf[self.last], cv.CV_BGR2GRAY) # convert frame to grayscale
        idx2 = (self.last + 1) % self.n_frames 
        self.last = idx2
        silh = self.buf[idx2]
        cv.AbsDiff(self.buf[idx1], self.buf[idx2], silh) # get difference between frames
        cv.Threshold(silh, silh, 30, 1, cv.CV_THRESH_BINARY) # and threshold it
        cv.UpdateMotionHistory(silh, self.mhi, timestamp, self.mhi_duration) # update MHI
        cv.ConvertScale(self.mhi, self.mask, 255./self.mhi_duration, 
                        (self.mhi_duration - timestamp)*255./self.mhi_duration)
        cv.SetZero(img)
        cv.Merge(self.mask, None, None, None, img)
        cv.CalcMotionGradient(self.mhi, self.mask, self.orient, self.max_time_delta, self.min_time_delta, 3)
        seq = cv.SegmentMotion(self.mhi, self.segmask, self.storage, timestamp, self.max_time_delta)
        inc = 0
        a_max = 1000
        max_rect = -1
    
        # there are lots of things moving around
        #  in this case just find find the biggest change on the image
        for (area, value, comp_rect) in seq:
            if comp_rect[2] + comp_rect[3] > 10: # reject small changes
                if area > a_max: 
                    a_max = area
                    max_rect = inc
            inc += 1

        # found it, now just do some processing on the area.
        if max_rect != -1:
            (area, value, comp_rect) = seq[max_rect]
            color = cv.CV_RGB(255, 0,0)
            silh_roi = cv.GetSubRect(silh, comp_rect)
            # calculate number of points within silhouette ROI
            count = cv.Norm(silh_roi, None, cv.CV_L1, None)

            # this rectangle contains the overall motion ROI
            cv.Rectangle(self.motion, (comp_rect[0], comp_rect[1]), 
                         (comp_rect[0] + comp_rect[2], 
                          comp_rect[1] + comp_rect[3]), (0,0,255), 1)

            # the goal is to report back a center of movement contained in a rectangle
            # adjust the height based on the number generated by the slider bar
            h = int(comp_rect[1] + (comp_rect[3] * (float(self.height_value) / 100)))
            # then calculate the center
            center = ((comp_rect[0] + comp_rect[2] / 2), h)

        return center


    def dejitter(self, center):
        # if the center was not found, just draw the center on the old location. 
        if center == (-1,-1):
            color  = cv.CV_RGB(0,225,0)
            r = 15 * .7
            self.old_center = self.old_center
        else: 
            # anti-jittering section
            x1 = self.old_center[0]
            y1 = self.old_center[1]
            x2 = center[0] 
            y2 = center[1]
            dx = x2 - x1
            dy = y2 - y1
            d = math.sqrt( (dx)**2 + (dy)**2 ) # calculate a distance from old location to new.
            angle = math.atan2(dy, dx)  # get an angle while we're doing the math

            if d > self.jitter_value: # if the distance is really far....
                self.old_center = (x2, y2)
                # calculate a near point that exists on the line between the old and new point
                self.old_center = (cv.Round(x1 + self.jitter_value * math.cos(angle)),
                                   cv.Round(y1 + self.jitter_value * math.sin(angle)))
                # and draw it
                r = 15
                color  = cv.CV_RGB(225,0,0)
            else: # the distance is not far so just draw it
                self.old_center = (x2, y2)
                r = 15
                color  = cv.CV_RGB(0,225,0)

        return(r, color)


    def pipeline(self):
        presentation = []
        self.orig = self.source.grab_frame()
        cv.Resize(self.orig, self.small)

        size = self.smallsize

        # store the raw image
        presentation.append((self.small, 'raw'))

        self.motion = cv.CloneImage(self.small)

        # location of the thingy moving on the screen is defined by center
        center = self.process_motion(self.motion)
        
        
        # if an object is found draw the circle on the same location.
        if center != (-1,-1):
            cv.Circle(self.motion, center, cv.Round(15), cv.CV_RGB(0,225,0), 3, cv.CV_AA, 0)
            self.mixer.setVolumesForPosition(center[0] / float(self.smallwidth))
            self.lastDetected = datetime.now()
        else:
        
            if datetime.now() - self.lastDetected > timedelta(seconds=7) and (abs(self.old_center[0] - self.smallwidth/2) > self.smallwidth/4):
                self.mixer.muteAll()
            elif datetime.now() - self.lastDetected > timedelta(minutes=15):
                self.mixer.muteAll()
            else:
                self.mixer.setVolumesForPosition(self.old_center[0] / float(self.smallwidth))

        # store the picture
        presentation.append((self.motion, 'motion')) 

        ####
        # processing of final image
        self.eye = cv.CloneImage(self.small) # get a copy

        (r, color) = self.dejitter(center)
        cv.Circle(self.eye, self.old_center, cv.Round(r), color, 3, cv.CV_AA, 0)

        presentation.append((self.eye, 'final'))

         # combine and show the results
        combined = self.combine_images(presentation)
        cv.ShowImage('Motion detection', combined )

        if self.store:
            cv.WriteFrame(self.writer, self.combined)

    def change_jitter(self, position):
        self.jitter_value = position

    def change_height(self, position):
        self.height_value = position

    def combine_images(self, images):

        font = cv.InitFont(cv.CV_FONT_HERSHEY_SIMPLEX, 1.4, 1.4, 0, 2, 8)

        for i,(image, name) in enumerate(images):
            if image.nChannels == 1:
                cv.Merge(image, image, image, None, self.temp)
            else:
                cv.Copy(image, self.temp)
            xoffset = (i % self.xwindows) * self.smallsize[0]
            yoffset = (i / self.xwindows) * self.smallsize[1]
            cv.SetImageROI(self.combined, (xoffset, yoffset, self.smallsize[0],
                self.smallsize[1]))
            cv.Copy(self.temp, self.combined)
            cv.PutText(self.combined, name, (5, 40), font, (30, 200, 200))
            cv.ResetImageROI(self.combined)
        return self.combined

    def run(self):
        while True:
            t = time.time()
            self.pipeline()
            wait = max(2, (1000/self.FPS)-int((time.time()-t)*1000))
            c = cv.WaitKey(wait) % 0x100
            if c == 27 or c == 10:
                break

if __name__ == '__main__':
    s = Setup()
    mixer = Music()
    s.setMixer(mixer)
    s.run()

Credits

Sahana Rajasekar
5 projects • 5 followers
Kevin Simons
3 projects • 0 followers
Senior studying Electrical Engineering and Computer Science at UC: Berkeley
Clare Lin
5 projects • 1 follower
I enjoy reading fiction, playing music and games, and consuming dark chocolate and jasmine green milk tea, though generally not all at the same time.
Amir Mohtashami
4 projects • 4 followers

Comments