Showing posts with label TKinter. Show all posts
Showing posts with label TKinter. Show all posts

Tuesday, 26 November 2013

Creating a Time-Lapse Camera with the Raspberry Pi and Python

After the success using openCV with the Raspberry Pi camera to determine the positions of circles in an image, I thought it would be nice to explore more uses of the camera. My inspiration came from a chat with a consultancy I visited. They were in the middle of a large expansion of their site, and were having a lot of building work carried out. To document the building work they had set up a Raspberry Pi with a camera to capture the work and to create a time-lapse video of it.

How much fun does that sound?

My mind was made up - I decided to write a time-lapse video program using Python.

I knew taking the images would be quite simple, but the conversion into video would be more tricky.

My first thought was to make use of the openCV libraries again to turn the images into video. However while I believe this is possible, I really struggled to find the solution. Documentation for openCV is very much geared to C++ and not Python.

My second attempt was to use ffmpeg. Some googling had shown this was a nice solution for the conversion. However after installing and running it I got a message saying


More googling told me I should install ffmpeg from binaries, which I am more than comfortable to do, but it does add complexity into my blog post...

Wait a minute! What was that last sentence on the error message? "Please use avconv instead"

More googling required!

It turns out that avconv does exactly what I need it to do. It converts a pile of images into a video. There are a lot of examples on the web explaining what settings you should use with avconv. However while the avconv website has a lot of information on there I found the best explanation came from the excellent Raspberry Pi Spy website, whose post was also explaining how to create a time-lapse video. Its worth having a look at his page, as he explains how to take images and create video using only the command line.

http://www.raspberrypi-spy.co.uk/2013/05/creating-timelapse-videos-with-the-raspberry-pi-camera/

Right so how do we write a Python program to create a time-lapse video?

The first thing you need to do is to install libav-tools.

Type the following into a command line.

sudo apt-get install -y libav-tools

Here is the full program, have a read through it and see if you can figure out what each line is doing. I will then explain each line in more detail.
import os
import time

FRAMES = 1000
FPS_IN = 10
FPS_OUT = 24
TIMEBETWEEN = 6
FILMLENGTH = float(FRAMES / FPS_IN)


frameCount = 0
while frameCount < FRAMES:
    imageNumber = str(frameCount).zfill(7)
    os.system("raspistill -o image%s.jpg"%(imageNumber))
    frameCount += 1
    time.sleep(TIMEBETWEEN - 6) #Takes roughly 6 seconds to take a picture

os.system("avconv -r %s -i image%s.jpg -r %s -vcodec libx264 -crf 20 -g 15 -vf crop=2592:1458,scale=1280:720 timelapse.mp4"%(FPS_IN,'%7d',FPS_OUT))

To begin with we need to import two libraries, os which will allow us to interact with the command line, and time to enable us to set the time between frames.

import os
import time

Now we will set some global variables. The nice thing about global variables is that you can change a variable that appears all through your program by just changing it in one location. Global variables also make your code more readable, as words explain what the variable is better than a number appearing thorughout your code. It also makes for code which is easier to modify as you know that all your global variables are specified at the top of your code.

We will set 5 global variables.

FRAMES = 1000
FPS_IN = 10
FPS_OUT = 24
TIMEBETWEEN = 6
FILMLENGTH = float(FRAMES / FPS_IN)

FRAMES sets the number of frames or images you will take and add to your video.
FPS_IN sets the number of the Frames Per Second (FPS) that go into the video. So if you want 10 of your frames to be used per second put the value to 10.
FPS_OUT sets the Frames Per Second of the video created. i.e. creates a video running at 24 Frames Per Second. If FPS_IN is less that FPS_OUT some FPS_IN frames will be used several times to bring the number up to FPS_OUT. Setting this value to 24 is a good value for digital video.
TIMEBETWEEN states the time (in seconds) between the frames that you are shooting with your camera. The Raspberry Pi camera takes roughly 6 seconds to take an image, so 6 seconds is the shortest time between shots.
FILMLENGTH works out how long your film will be in seconds. If you want to know this value, then you get your program to print it using the following line.

print FILMLENGTH

I am not going to print this out, but I do use it as a reminder of how long the film I am making will be.

Now we have set up all our variables we can get down to business. We want to take images every so often and save them as files. We will want to keep track of how many images we have taken so we know when to stop. So lets create a variable to do that.

frameCount = 0

The next thing we will do is enter a WHILE loop. A while loop keeps going WHILE something is true

while frameCount < FRAMES:

So while our number in frameCount is less than ( < ) the number we have stored in FRAMES we will run through the next 4 lines of code.

We will create a name for the pictures we want to save.

    imageNumber = str(frameCount).zfill(7)
The reason we want to do this is we want the files to be stored with incremental numbers. This line is quite clever (I think!)

It says we will create a variable called imageNumber. In that variable we will store a string of the value in frameCount. Remember a string is classed as text and not a number. Then using the .zfill(7) command we will ensure that the string has 7 digits by filling all preceding digits with a zero if there are not enough numbers. Some examples are:

'1' becomes '0000001'
'123456' becomes '0123456'

It's not a tool you use very often, but if you want something to be a certain amount of characters it's very useful!

Now we have the name of the image file we are going to create, lets take the image! We are going to use the line you can type into the shell command to take the image.

    os.system("raspistill -o image%s.jpg"%(imageNumber))

What does this line mean? Well the line for taking pictures with the Raspberry Pi camera and storing it as image.jpg is

raspistill -o image.jpg

but this needs to be typed into the command line. Well os.system allows you to access the command line.

You will notice there is a %s in there with %(imageNumber) after the text.

This says, take whatever is in the value imageNumber and put it in place of the %s. So if imageNumber was 0000001 our file would be called image0000001.jpg.

This is a great technique of easily modifying what is in a string.

As we want our number to increase each time we go through the while loop let us now increase frameCount.

    frameCount += 1

With imageNumber being made up of frameCount and some leading zeros, each time we run through the while loop we will get a different number as frameCount is increased each time.

Finally we want to be able to vary the time between taking each frame. This allows us to take our images a set distance apart. It takes roughly 6 seconds to take a picture with the Raspberry Pi camera.

    time.sleep(TIMEBETWEEN - 6) #Takes roughly 6 seconds to take a picture

Therefore the minimum time between each frame is 6 seconds. If we want the camera to wait 10 seconds per image, as it takes 6 seconds to take a picture we only want to sleep between frames for 4 seconds. Therefore we tell the program to sleep for a period of TIMEBETWEEN - 6.

This brings us to the last line of the code. As I mentioned earlier I found the Raspberry Pi Spy website to have the best details on how to use avconv. They suggest typing the following line into the command line to create video from the images. They also explain why.

avconv -r 10 -i image%4d.jpg -r 10 -vcodec libx264 -crf 20 -g 15 -vf crop=2592:1458,scale=1280:720 timelapse.mp4

I have modified this line slightly to make it a little more suitable for our program. I want to be able to set some of the variables using our global variables. Therefore I have changed the line slightly to this.
os.system("avconv -r %s -i image%s.jpg -r %s -vcodec libx264 -crf 20 -g 15 -vf crop=2592:1458,scale=1280:720 timelapse.mp4"%(FPS_IN,'%7d',FPS_OUT))
We already know why we use the os.system command, as this allows us access to the command line. I have also added in a few %s commands to switch in our global variables. This is using the same technique we used on the line where we took the picture. The difference is there are three variables we are switching in.

Let us look at he code inside the os.system brackets.

Most of the code is the same as from the Raspberry Pi Spy webpage, but there are a few differences.


  • -r %s this sets the frame rate for the number of our frames we want to use in the video per second. The %s calls the first item in the list %(FPS_IN,'%7d',FPS_OUT), which is FPS_IN, one of our global variables. 
  • -i image%s.jpg determines the name of the images we want to load into our video. Again we call something within our list  %(FPS_IN,'%7d',FPS_OUT), this time the second item. What does %7d do? Remember this is being run in the command line, so it's not a python command. The %7d iterates through 7 digit numbers. This is neat as we created our image files with 7 digit numbers. So it is iterating through the images we created. 
  • -r %s as for the first -r %s in this line this sets the number of FPS. However we call the FPS_OUT variable and insert this and not the FPS_IN variable. This will create a video of so many Frames Per Second depending on the value of FPS_OUT. If you are unsure 24 is a good default number to use.


All that is left now is to run your program. One word of warning is that avconv is not quick on the Raspberry Pi. 1000 Frames took 3.5 hours to turn into a video. However it's not a huge problem, you just leave it running over night!

I hope you found this tutorial interesting and I look forward to seeing some of your time-lapse videos!

Thursday, 16 May 2013

Python - Rock Paper Scissors inside a GUI

In a previous blog post I explained how to write a program to play Rock Paper and Scissors against a computer. This worked very well, and probably provided hours of entertainment ;-), but it did not look very pretty. Everything was displayed by text scrolling in a shell window.

It would be much nicer if we could put this game into a GUI (Graphical User Interface)

Well it turns out this is easier than you think! There are a few tools available in Python to allow you to program a GUI. In this blog I will show you how to use one particular tool called Tkinter to do this.

We can re-use a lot of code from our RockPaperScissors.py program, so lets start with that as our base code. If you need a copy of the old program you can download it from the link below.

Download RockPaperScissors.py

So how does Tkinter work?

Well in our previous RockPaperScissors.py program we interacted with the program from a shell window. We typed 'R' to select Rock, 'P' for Paper, and 'S' for Scissors. Pressing return set off a chain of events which caused the computer to randomly select one of Rock, Paper or Scissors, which then went on to compare the two and finally declare a winner.

Instead of typing into a text window, by using Tkinter we can assign widgets such as buttons or radio buttons to input our options, and to set the program running.

The program is doing the same thing, but we are communicating with it in a slightly different way.

At first sight, it looks complicated, but it's not.

The first thing we need to do is to import the tkinter libraries into our program. So at the very top of the program, above import random and import sys add the following lines.

from Tkinter import *
from ttk import *



The next thing we need to do is to define our GUI window. Right at the bottom of the RockPaperScissors code type the following.

root = Tk()
root.title ('Rock Paper Scissors')



The first line defines the root window, and the second line adds a title into the window. Rock Paper Scissors seems like a good enough title to me.

Now within our window we need to have a frame. A frame can split the window into different areas. We only need one frame. These next few lines defines how this frame fits within our window. Type the following below the lines you have just added.

mainframe = Frame(root, padding = '3 3 12 12')
mainframe.grid(column=0, row = 0, sticky=(N,W,E,S))
mainframe.columnconfigure(0, weight=1)
mainframe.rowconfigure(0,weight=1)




The first row defines the padding, or space between our frame and window. We may modify this later to improve the look of our game. The next three lines help determine the layout style of the window.

In TKinter there are several methods you can use for layout. We are going to use the grid option in this game. The grid system gives more control than some of the others.

So what is the grid system? Well imagine splitting your window up into columns and rows. The top row would be row 0 and the left column would be column 0. If I wanted to put something such as a button in the top left I would place it in column = 0 and row = 0. If I wanted to put something to the right of this it would be column = 1 and row = 0. Below the original item would be row 1 and column 0. Very similar to a graph axis or map co-ordinates, with top left being row 0 and column 0.

If for example you didn't have anything in row 0, or column 0, then the program would ignore these, until something was put in there. So if I put my first item in row 1 and column 1 that would be in the top left.

Lets do just that now.

I am going to put a label in the top left of the window. A label is a Tkinter widget, that allows you to display text. Probably the simplest of widgets available.

Label(mainframe, text='Player').grid(column=1, row = 1, sticky = W)



Looks complicated right? Well no... remember with programming to break things down into little bits, and that's the same with looking at code.

Label - tells the program you want to add a label widget.
mainframe - you want the widget in the mainframe. We only have one frame in this program, but its feasible you could have others.
text - 'Player', This allows us to input what our label should say. Put your name in here if you want to.
grid - remember we are using the grid layout system. grid and then the items in the bracket allow us to define where we want the label in our grid.
column = 1 - We want to place our widget in column 1
row = 1 - We want to place our widget in row 1
sticky = W Says we want to align our text with the left side of the box. Tkinter uses points of a compass (N,S,E,W) to describe where we want to align our items.

There we go, see I told you it wasn't complicated!

Should we test this now? Well before we do lets comment out everything in our code from While True: to print " "

To do this highlight everything you want to comment out by dragging the mouse over it and then click on Format, and then Comment Out Region. You could highlight the area and press Alt and 3 to do the same thing. The lines commented out will be shown in red.



Why do we do this? Well as I said previously Tkinter calls things in a different way, by using buttons and other widgets. The GUI also has its own eternal loop running, so the program doesn't quit. Therefore we do not need to have a while loop to keep our program running.

lets add root.mainloop() right at the bottom of the program.

root.mainloop()

This is responsible for telling the program to run the GUI.

Press F5 and save the file as RockPaperScissorsGUI.py when asked.

Does your window look like this?



How cool is that? Ok its not massively inspiring, but it is the building blocks for greater things!!

So how should we structure the rest of the GUI?

My plan is underneath the player label we will have the option to chose either Rock, Paper or Scissors. There are a few ways to do this, but I like the option of Radiobuttons. A Radiobutton allows you to select one, and only one, of a few different options. As we only want the Player to select one of Rock, Paper or Scissors sounds like a good option.

So lets sort out the three Radiobuttons.

The radiobuttons use mainly the same options as we saw in the label with three exceptions.

Radiobutton(mainframe, text ='Rock', variable = player_choice, value = 'Rock').grid(column=1, row=2, sticky=W)
Radiobutton(mainframe, text ='Paper', variable = player_choice, value = 'Paper').grid(column=1, row=3, sticky=W)
Radiobutton(mainframe, text ='Scissors', variable = player_choice, value = 'Scissors').grid(column=1, row=4, sticky=W)



Lets look at the Radiobutton widget and see how this varies when compared to the Label widget.

Radiobutton - This has changed from Label to Radiobutton, which is obvious, as we are calling a different type of widget.
text - defines the text next to the Radiobutton so you know what each one refers to.
variable - This is new to Radiobutton. You use the variable to store some information to indicate which Radiobutton you have selected.
value - defines what you store in that variable. We are using the strings Rock, Paper and Scissors.
grid onwards remains the same as for Label and defines where the radiobuttons are positioned. In this case they are all in column 1, but below each other in Row 2, 3 and 4.

Before we run the program to have a look at it we need to create the variable we refer to which is called player_choice. This is the variable which will hold either Rock, Paper or Scissors, depending on the Radiobutton selected.

I try to keep all my variables together, and put them before the start of the layout code. Therefore above the line of code specifying the first label you created put the following line.

player_choice = StringVar()



This defines the variable player_choice, as a String Variable. Tkinter is able to refer back to this variable at any time.

Again press F5 to save and run your program. Does it look like this?



Try clicking on the Radio Buttons. Notice how they light up as you pick each one. The program is really coming together now.

We want to do the same thing for the computer. So try and create the code to show a label named as Computer and to reference a variable called computer_choice for the Radiobuttons. Ensure these labels and buttons are in column 3.

Did your code look like this?

Label(mainframe, text='Computer').grid(column=3, row = 1, sticky = W)
Radiobutton(mainframe, text ='Rock', variable = computer_choice, value = 'Rock').grid(column=3, row=2, sticky=W)
Radiobutton(mainframe, text ='Paper', variable = computer_choice, value = 'Paper').grid(column=3, row=3, sticky=W)
Radiobutton(mainframe, text ='Scissors', variable = computer_choice, value = 'Scissors').grid(column=3, row=4, sticky=W)

Did you also remember to create a new String Variable called computer_choice?

computer_choice = StringVar()


OK we have Radiobuttons and labels for both the computer and for the player. We now need a button to tell the program we have made our choice, and we want to play against the computer.

Lets put the button between the existing columns i.e. into column 2.

Button(mainframe, text="Play", command = play).grid(column = 2, row = 2, sticky = W)



The button widget is again like the other widgets. It is in the mainframe, it has text to label the button and it has some information in the grid section to describe the layout of where we want the button. Additionally it has command = play.

When we press the button we obviously want it to do something, and it is this command statement which allows us to tell the program what we want it to do. In this case we are saying command = play, which tells our program that when the button is pressed we want to run the function called play.

Now in our code, we do not have a play function. Before we started to work on the GUI our program used a while loop to continuously run the program. The play function we need to write is carrying out a lot of the work which was done in our while loop. It requires us to get the human choice, the computer choice and compare them, then declare a winner. Therefore we can modify the while loop to become our play function.

First uncomment out the While loop. To do this highlight the text you commented out earlier, and select Format and then Uncomment Region. Alt and 4 is a the shortcut keys to do this.

Now change
While True:

to

def play():



This creates a function called play using the code from the while loop, as we want this to be the basis of the play function which the button calls when pressed.

Now the first line in our new play function asks you to call the function makeYourChoice, and stores the result of this as a variable called human_choice. However the makeYourChoice function explained to the user what key they should press to make their choice of Rock, Paper or Scissors. We don't need any of that with our program, as we are using the Radio Buttons to do the same task. So we can delete the makeYourChoice function and all its contents.

Instead the humanChoice variable needs to be the choice of the RadioButtons we created earlier. Remember we stored the selected choice in a variable called player_choice? Therefore we should be able to just call this variable and get the information from inside it.

To get the info from inside player_choice we use the get command.

humanChoice = player_choice.get()




This line gets the information stored in player_choice, and stores it in a variable called humanChoice.

The next line calls computerRandom.

We we still want to the computer to pick a random choice, but we also want to store that random choice in the StringVar called computer_choice. This will change the computer Radiobuttons to match the choice the computer has made.

Lets make that change inside of the computerRandom function.

Inside the computerRandom function, after the line randomchoice = random.randomint(0,2) add the following line

computer_choice.set(options[randomChoice])



This line will set the value stored in computer_choice as either Rock, Paper or Scissors depending on the random choice created by the previous line.

That is the only modification we need to make to the computerRandom function so we can go back into the play function.

We see the next two lines are printing the choice of the human and the computer. We don't need these any more, as our choices are displayed graphically in the GUI, so these lines can be deleted.

The result line is the same. We still want to compare the humanChoice against the computerChoice.

Now we get into reporting the result. We don't just want to print to the shell window as we did previously, it would be much nicer if we could report back the result into the GUI.

Well we can by using a Label widget again.

Under where you created the Button widget add a new Label widget

Label(mainframe, textvariable = result_set).grid(column = 1, row = 5, sticky =W, columnspan = 2)



This is the same as the previous Label widgets you have created with two exceptions.

The first is instead of text you have textvariable = result_set. This is because you want the text to change depending on who won. So the label will be populated with whatever text is stored in the result_set variable.

The second change is we have added columnspan = 2. What this is saying is although we have defined our text to be in column = 1, if it doesn't fit, allow it to span into the next column.

OK before we can start to populate the variable result_set we have to create it first.

Head back to the area where all the variables are defined and add

result_set = StringVar() underneath the two already defined.



We can then finish off the play function by populating the results_set variable with the result.

This is done by the command result_set.set("text goes here")

We need to report back a Rock, Paper or Scissors.

By modifying the text which previously printed the result into the Python shell window we can update the label we have just created to report back the result.

To do that instead of printing we need to set the data in the result_set variable.

if result == "Draw":
        result_set.set("Its a draw")
    elif result == "Computer Wins":
        result_set.set("Unlucky you lost!")
    else:  result_set.set("Well done you won!")



Delete the print " " line.

Lets save and test your program by pressing F5.

Does this work as you expected?

One other thing I notice is that the Player doesn't have a choice to begin with. Which is fine, but what happens if the player doesn't choose anything? The program states that the Player is the winner. Hardly seems fair that the player can win without making a choice, so lets think how we can fix that.

There are two ways I can think of immediately.
  • We could ensure the player has a default option picked.
  • We could check to ensure there is an option picked and report back if there isn't.
To set the default option for the player we can set the player_choice variable to be Rock by default.

Under where you set your variables, we will assign Rock to player_choice.

player_choice.set("Rock")




Running the program shows that Rock is chosen by default.

Or if you would prefer modify the play() function to check that a player_choice has been set.

if player_choice.get() != '':

Then indent the remainder of the function adding the following at the end.

    else:
result_set.set("Please choose an option")



I will let you decide which one of those you would like to include in your program, if any!

Hope you enjoyed your first venture into TKinter. I would suggest playing around with it a little so you can see what changes.

For those of you who would like the source code you can down load it from the link below. I have left the code, which I have said delete, commented out so you can see the changes. The code has both options for overcoming the fact the player could win without selecting a choice, but they are both commented out. You can choose which one you want to implement.

RockPaperScissors-GUI.py