forked from jibbius/raspberry_pi_photo_booth
-
Notifications
You must be signed in to change notification settings - Fork 0
/
camera.py
311 lines (249 loc) · 9.22 KB
/
camera.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
#!/usr/bin/env python
"""
Raspberry Pi Photo Booth
This code is intended to be runs on a Raspberry Pi.
Currently both Python 2 and Python 3 are supported.
You can modify the config via [camera-config.yaml].
(The 1st time the code is run [camera-config.yaml] will be created based on [camera-config.example.yaml].
"""
__author__ = 'Jibbius (Jack Barker)'
__version__ = '2.1'
#Standard imports
from time import sleep
from shutil import copy2
from random import randint
import sys
import datetime
import os
#Need to do this early, in case import below fails:
REAL_PATH = os.path.dirname(os.path.realpath(__file__))
#Additional Imports
try:
from PIL import Image
from ruamel import yaml
import picamera
import RPi.GPIO as GPIO
except ImportError as missing_module:
print('--------------------------------------------')
print('ERROR:')
print(missing_module)
print('')
print(' - Please run the following command(s) to resolve:')
if sys.version_info < (3,0):
print(' pip install -r ' + REAL_PATH + '/requirements.txt')
else:
print(' python3 -m pip install -r ' + REAL_PATH + '/requirements.txt')
print('')
sys.exit()
#############################
### Load config from file ###
#############################
PATH_TO_CONFIG = 'camera-config.yaml'
PATH_TO_CONFIG_EXAMPLE = 'camera-config.example.yaml'
#Check if config file exists
if not os.path.exists(PATH_TO_CONFIG):
#Create a new config file, using the example file
print('Config file was not found. Creating:' + PATH_TO_CONFIG)
copy2(PATH_TO_CONFIG_EXAMPLE, PATH_TO_CONFIG)
#Read config file using YAML interpreter
with open(PATH_TO_CONFIG, 'r') as stream:
CONFIG = {}
try:
CONFIG = yaml.safe_load(stream)
except yaml.YAMLError as exc:
print(exc)
#Required config
try:
# Each of the following varibles, is now configured within [camera-config.yaml]:
CAMERA_BUTTON_PIN = CONFIG['CAMERA_BUTTON_PIN']
EXIT_BUTTON_PIN = CONFIG['EXIT_BUTTON_PIN']
PHOTO_W = CONFIG['PHOTO_W']
PHOTO_H = CONFIG['PHOTO_H']
SCREEN_W = CONFIG['SCREEN_W']
SCREEN_H = CONFIG['SCREEN_H']
CAMERA_ROTATION = CONFIG['CAMERA_ROTATION']
CAMERA_HFLIP = CONFIG['CAMERA_HFLIP']
DEBOUNCE_TIME = CONFIG['DEBOUNCE_TIME']
TESTMODE_AUTOPRESS_BUTTON = CONFIG['TESTMODE_AUTOPRESS_BUTTON']
SAVE_RAW_IMAGES_FOLDER = CONFIG['SAVE_RAW_IMAGES_FOLDER']
except KeyError as exc:
print('')
print('ERROR:')
print(' - Problems exist within configuration file: [' + PATH_TO_CONFIG + '].')
print(' - The expected configuration item ' + str(exc) + ' was not found.')
print(' - Please refer to the example file [' + PATH_TO_CONFIG_EXAMPLE + '], for reference.')
print('')
sys.exit()
#Optional config
except KeyError as exc:
pass
##############################
### Setup Objects and Pins ###
##############################
#Setup GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setup(CAMERA_BUTTON_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(EXIT_BUTTON_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
CAMERA = picamera.PiCamera()
CAMERA.rotation = CAMERA_ROTATION
CAMERA.annotate_text_size = 80
CAMERA.resolution = (PHOTO_W, PHOTO_H)
CAMERA.hflip = CAMERA_HFLIP
CAMERA.shutter_speed = 16667
########################
### Helper Functions ###
########################
def health_test_required_folders():
folders_list=[SAVE_RAW_IMAGES_FOLDER]
folders_checked=[]
for folder in folders_list:
if folder not in folders_checked:
folders_checked.append(folder)
else:
print('ERROR: Cannot use same folder path ('+folder+') twice. Refer config file.')
#Create folder if doesn't exist
if not os.path.exists(folder):
print('Creating folder: ' + folder)
os.makedirs(folder)
def get_base_filename_for_images():
"""
For each photo-capture cycle, a common base filename shall be used,
based on the current timestamp.
Example:
${ProjectRoot}/photos/2017-12-31_23-59-59
The example above, will later result in:
${ProjectRoot}/photos/2017-12-31_23-59-59_1of4.png, being used as a filename.
"""
base_filename = str(datetime.datetime.now()).split('.')[0]
base_filename = base_filename.replace(' ', '_')
base_filename = base_filename.replace(':', '-')
base_filepath = REAL_PATH + '/' + SAVE_RAW_IMAGES_FOLDER + '/' + base_filename
return base_filepath
# overlay one image on screen
def overlay_image(image_path, duration=0, layer=3, mode='RGB'):
"""
Add an overlay (and sleep for an optional duration).
If sleep duration is not supplied, then overlay will need to be removed later.
This function returns an overlay id, which can be used to remove_overlay(id).
"""
# Load the (arbitrarily sized) image
img = Image.open(image_path)
if( img.size[0] > SCREEN_W):
# To avoid memory issues associated with large images, we are going to resize image to match our screen's size:
basewidth = SCREEN_W
wpercent = (basewidth/float(img.size[0]))
hsize = int((float(img.size[1])*float(wpercent)))
img = img.resize((basewidth,hsize), Image.ANTIALIAS)
# "
# The camera`s block size is 32x16 so any image data
# provided to a renderer must have a width which is a
# multiple of 32, and a height which is a multiple of
# 16.
# "
# Refer:
# http://picamera.readthedocs.io/en/release-1.10/recipes1.html#overlaying-images-on-the-preview
# Create an image padded to the required size with mode 'RGB' / 'RGBA'
pad = Image.new(mode, (
((img.size[0] + 31) // 32) * 32,
((img.size[1] + 15) // 16) * 16,
))
# Paste the original image into the padded one
pad.paste(img, (0, 0))
#Get the padded image data
try:
padded_img_data = pad.tobytes()
except AttributeError:
padded_img_data = pad.tostring() # Note: tostring() is deprecated in PIL v3.x
# Add the overlay with the padded image as the source,
# but the original image's dimensions
o_id = CAMERA.add_overlay(padded_img_data, size=img.size)
o_id.layer = layer
if duration > 0:
sleep(duration)
CAMERA.remove_overlay(o_id)
o_id = -1 # '-1' indicates there is no overlay
return o_id # if we have an overlay (o_id > 0), we will need to remove it later
###############
### Screens ###
###############
def taking_photo(filename_prefix):
"""
This function captures the photo
"""
#get filename to use
filename = filename_prefix + '.jpg'
#Take still
CAMERA.annotate_text = ''
CAMERA.capture(filename, format="jpeg", quality=100)
print('Photo saved: ' + filename)
print('All done!')
image_number = randint(1, 5)
finished_image = REAL_PATH + '/assets/success_' + str(image_number) + '.png'
overlay_image(finished_image, 1.5)
def main():
"""
Main program loop
"""
#Start Program
print('Welcome to the photo booth!')
print('(version ' + __version__ + ')')
print('')
print('Press the \'Take photo\' button to take a photo')
print('Use [Ctrl] + [\\] to exit')
print('')
#Setup any required folders (if missing)
health_test_required_folders()
#Start camera preview
CAMERA.start_preview(resolution=(SCREEN_W, SCREEN_H))
#Display intro screen
intro_image_1 = REAL_PATH + '/assets/intro.png'
overlay_image(intro_image_1, 5)
#Wait for someone to push the button
#Use falling edge detection to see if button is being pushed in
GPIO.add_event_detect(CAMERA_BUTTON_PIN, GPIO.FALLING)
GPIO.add_event_detect(EXIT_BUTTON_PIN, GPIO.FALLING)
while True:
photo_button_is_pressed = None
exit_button_is_pressed = None
if GPIO.event_detected(CAMERA_BUTTON_PIN):
sleep(DEBOUNCE_TIME)
if GPIO.input(CAMERA_BUTTON_PIN) == 0:
photo_button_is_pressed = True
if GPIO.event_detected(EXIT_BUTTON_PIN):
sleep(DEBOUNCE_TIME)
if GPIO.input(EXIT_BUTTON_PIN) == 0:
exit_button_is_pressed = True
if exit_button_is_pressed is not None:
return #Exit the photo booth
if TESTMODE_AUTOPRESS_BUTTON:
photo_button_is_pressed = True
#Stay inside loop, until button is pressed
if photo_button_is_pressed is None:
#Regardless, restart loop
sleep(0.1)
continue
#Button has been pressed!
print('Button pressed! You folks are in for a treat.')
#Silence GPIO detection
GPIO.remove_event_detect(CAMERA_BUTTON_PIN)
GPIO.remove_event_detect(EXIT_BUTTON_PIN)
#Get filenames for images
filename_prefix = get_base_filename_for_images()
taking_photo(filename_prefix)
# If we were doing a test run, exit here.
if TESTMODE_AUTOPRESS_BUTTON:
break
# Otherwise, display intro screen again
GPIO.add_event_detect(CAMERA_BUTTON_PIN, GPIO.FALLING)
GPIO.add_event_detect(EXIT_BUTTON_PIN, GPIO.FALLING)
print('Press the button to take a photo')
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print('Goodbye')
finally:
CAMERA.stop_preview()
CAMERA.close()
GPIO.cleanup()
sys.exit()