From dc57fd26b58b0b3d9974237877830eb07cb3e0a9 Mon Sep 17 00:00:00 2001 From: Balthasar Reuter Date: Sat, 31 Mar 2018 21:25:18 +0200 Subject: [PATCH] Idle functionality for camera added. Some Photobooth config added to settings file and dialog. --- photobooth/Camera.py | 16 ++++++ photobooth/CameraOpenCV.py | 19 +++++-- photobooth/Photobooth.py | 103 ++++++++++++++++++++++++++++--------- photobooth/PyQt5Gui.py | 39 +++++++++++++- photobooth/defaults.cfg | 6 +-- 5 files changed, 152 insertions(+), 31 deletions(-) diff --git a/photobooth/Camera.py b/photobooth/Camera.py index 1331927..abe2029 100644 --- a/photobooth/Camera.py +++ b/photobooth/Camera.py @@ -37,6 +37,22 @@ class Camera: self._has_idle = value + def setActive(self): + + if not self.hasIdle: + pass + else: + raise NotImplementedError() + + + def setIdle(self): + + if not self.hasIdle: + raise RuntimeError('Camera does not have idle functionality') + + raise NotImplementedError() + + def getPreview(self): if not self.hasPreview: diff --git a/photobooth/CameraOpenCV.py b/photobooth/CameraOpenCV.py index 86d87ca..94f6816 100644 --- a/photobooth/CameraOpenCV.py +++ b/photobooth/CameraOpenCV.py @@ -13,11 +13,23 @@ class CameraOpenCV(Camera): super().__init__() self.hasPreview = True - self.hasIdle = False + self.hasIdle = True + + self._cap = cv2.VideoCapture() + + + def setActive(self): - self._cap = cv2.VideoCapture(0) if not self._cap.isOpened(): - raise RuntimeError('Camera could not be opened') + self._cap.open(0) + if not self._cap.isOpened(): + raise RuntimeError('Camera could not be opened') + + + def setIdle(self): + + if self._cap.isOpened(): + self._cap.release() def getPreview(self): @@ -27,6 +39,7 @@ class CameraOpenCV(Camera): def getPicture(self): + self.setActive() status, frame = self._cap.read() if not status: raise RuntimeError('Failed to capture picture') diff --git a/photobooth/Photobooth.py b/photobooth/Photobooth.py index 07e925f..d8f024d 100644 --- a/photobooth/Photobooth.py +++ b/photobooth/Photobooth.py @@ -19,22 +19,32 @@ from time import time, sleep, localtime, strftime output_size = (1920, 1080) min_distance = (10, 10) -num_pictures = (2, 2) +# num_pictures = (2, 2) pose_time = 2 picture_basename = strftime('%Y-%m-%d/photobooth', localtime()) class Photobooth: - def __init__(self): + def __init__(self, config): self._cap = Camera() + self._cfg = config + if ( self._cfg.getBool('Photobooth', 'show_preview') + and self._cap.hasPreview ): + self.showCounter = self.showCounterPreview + else: + self.showCounter = self.showCounterNoPreview + + self.numPictures = ( self._cfg.getInt('Photobooth', 'num_pictures_x') , + self._cfg.getInt('Photobooth', 'num_pictures_y') ) @property def getNextFilename(self): return self._get_next_filename + @getNextFilename.setter def getNextFilename(self, func): @@ -44,6 +54,36 @@ class Photobooth: self._get_next_filename = func + @property + def showCounter(self): + + return self._show_counter + + + @showCounter.setter + def showCounter(self, func): + + if not callable(func): + raise ValueError('showCounter must be callable') + + self._show_counter = func + + + @property + def numPictures(self): + + return self._num_pictures + + + @numPictures.setter + def numPictures(self, num_pictures): + + if len(num_pictures) != 2: + raise ValueError('num_pictures must have two entries') + + self._num_pictures = num_pictures + + def run(self, send, recv): self._send = send @@ -61,22 +101,35 @@ class Photobooth: self._send.send( Gui.ErrorState('Camera error', str(e)) ) return 0 + + + def setCameraActive(self): + + self._cap.setActive() + + + def setCameraIdle(self): + + if self._cap.hasIdle: + self._cap.setIdle() - def showCounter(self): + def showCounterPreview(self): - if self._cap.hasPreview: - tic, toc = time(), 0 + tic, toc = time(), 0 - while toc < pose_time: - self._send.send( Gui.PreviewState( - message = str(pose_time - int(toc)), - picture = ImageOps.mirror(self._cap.getPreview()) ) ) - toc = time() - tic - else: - for i in range(pose_time): - self._send.send( Gui.PreviewState(str(i)) ) - sleep(1) + while toc < pose_time: + self._send.send( Gui.PreviewState( + message = str(pose_time - int(toc)), + picture = ImageOps.mirror(self._cap.getPreview()) ) ) + toc = time() - tic + + + def showCounterNoPreview(self): + + for i in range(pose_time): + self._send.send( Gui.PreviewState(str(i)) ) + sleep(1) def captureSinglePicture(self): @@ -88,23 +141,24 @@ class Photobooth: def assemblePictures(self, pictures): + # TODO: determine sizes only once picture_size = pictures[0].size resize_factor = min( ( ( - ( output_size[i] - (num_pictures[i] + 1) * min_distance[i] ) / - ( num_pictures[i] * picture_size[i]) ) for i in range(2) ) ) + ( output_size[i] - (self.numPictures[i] + 1) * min_distance[i] ) / + ( self.numPictures[i] * picture_size[i]) ) for i in range(2) ) ) output_picture_size = tuple( int(picture_size[i] * resize_factor) for i in range(2) ) - output_picture_dist = tuple( ( output_size[i] - num_pictures[i] * - output_picture_size[i] ) // (num_pictures[i] + 1) + output_picture_dist = tuple( ( output_size[i] - self.numPictures[i] * + output_picture_size[i] ) // (self.numPictures[i] + 1) for i in range(2) ) output_image = Image.new('RGB', output_size, (255, 255, 255)) idx = 0 for img in pictures: - pos = (idx % num_pictures[0], idx // num_pictures[0]) + pos = (idx % self.numPictures[0], idx // self.numPictures[0]) img = img.resize(output_picture_size) offset = tuple( (pos[i] + 1) * output_picture_dist[i] + pos[i] * output_picture_size[i] for i in range(2) ) @@ -117,13 +171,14 @@ class Photobooth: def capturePictures(self): pictures = [self.captureSinglePicture() - for i in range(2) for _ in range(num_pictures[i])] + for i in range(2) for _ in range(self.numPictures[i])] return self.assemblePictures(pictures) def trigger(self): self._send.send(Gui.PoseState()) + self.setCameraActive() sleep(2) @@ -131,16 +186,18 @@ class Photobooth: img.save(self.getNextFilename(), 'JPEG') self._send.send(Gui.PictureState(img)) + self.setCameraIdle() + sleep(5) self._send.send(Gui.IdleState()) -def main_photobooth(send, recv): +def main_photobooth(config, send, recv): picture_list = PictureList(picture_basename) - photobooth = Photobooth() + photobooth = Photobooth(config) photobooth.getNextFilename = picture_list.getNext return photobooth.run(send, recv) @@ -153,7 +210,7 @@ def main(argv): event_recv, event_send = Pipe(duplex=False) gui_recv, gui_send = Pipe(duplex=False) - photobooth = Process(target=main_photobooth, args=(gui_send, event_recv), daemon=True) + photobooth = Process(target=main_photobooth, args=(config, gui_send, event_recv), daemon=True) photobooth.start() gui = PyQt5Gui(argv, config) diff --git a/photobooth/PyQt5Gui.py b/photobooth/PyQt5Gui.py index 1122cef..05a9a1e 100644 --- a/photobooth/PyQt5Gui.py +++ b/photobooth/PyQt5Gui.py @@ -58,7 +58,11 @@ class PyQt5Gui(Gui.Gui): if isinstance(state, Gui.IdleState): self.showIdle() elif isinstance(state, Gui.PoseState): - self._p.setCentralWidget(PyQt5PictureMessage('Will capture 4 pictures!')) + global cfg + num_pictures = ( cfg.getInt('Photobooth', 'num_pictures_x') * + cfg.getInt('Photobooth', 'num_pictures_y') ) + self._p.setCentralWidget( + PyQt5PictureMessage('Will capture {} pictures!'.format(num_pictures))) elif isinstance(state, Gui.PreviewState): img = ImageQt.ImageQt(state.picture) self._p.setCentralWidget(PyQt5PictureMessage(state.message, img)) @@ -86,7 +90,7 @@ class PyQt5Gui(Gui.Gui): def showIdle(self): self._p.handleKeypressEvent = self.handleKeypressEvent - self._p.setCentralWidget(PyQt5PictureMessage('Hit the button!'))#, 'homer.jpg')) + self._p.setCentralWidget(PyQt5PictureMessage('Hit the button!')) def showError(self, title, message): @@ -254,6 +258,7 @@ class PyQt5Settings(QFrame): grid.addWidget(self.createGuiSettings(), 0, 0) grid.addWidget(self.createGpioSettings(), 1, 0) grid.addWidget(self.createCameraSettings(), 0, 1) + grid.addWidget(self.createPhotoboothSettings(), 1, 1) layout = QVBoxLayout() layout.addLayout(grid) @@ -336,6 +341,32 @@ class PyQt5Settings(QFrame): return widget + def createPhotoboothSettings(self): + + global cfg + + self._value_widgets['Photobooth'] = {} + self._value_widgets['Photobooth']['show_preview'] = QCheckBox('Show preview while countdown (restart required)') + if cfg.getBool('Photobooth', 'show_preview'): + self._value_widgets['Photobooth']['show_preview'].toggle() + self._value_widgets['Photobooth']['num_pictures_x'] = QLineEdit(cfg.get('Photobooth', 'num_pictures_x')) + self._value_widgets['Photobooth']['num_pictures_y'] = QLineEdit(cfg.get('Photobooth', 'num_pictures_y')) + + layout = QFormLayout() + layout.addRow(self._value_widgets['Photobooth']['show_preview']) + + sublayout = QHBoxLayout() + sublayout.addWidget(QLabel('Number of pictures')) + sublayout.addWidget(self._value_widgets['Photobooth']['num_pictures_x']) + sublayout.addWidget(QLabel('x')) + sublayout.addWidget(self._value_widgets['Photobooth']['num_pictures_y']) + layout.addRow(sublayout) + + widget = QGroupBox('Photobooth settings') + widget.setLayout(layout) + return widget + + def createButtons(self): layout = QHBoxLayout() @@ -374,6 +405,10 @@ class PyQt5Settings(QFrame): cfg.set('Gpio', 'trigger_channel', self._value_widgets['Gpio']['trigger_channel'].text()) cfg.set('Gpio', 'lamp_channel', self._value_widgets['Gpio']['lamp_channel'].text()) + cfg.set('Photobooth', 'show_preview', str(self._value_widgets['Photobooth']['show_preview'].isChecked())) + cfg.set('Photobooth', 'num_pictures_x', self._value_widgets['Photobooth']['num_pictures_x'].text()) + cfg.set('Photobooth', 'num_pictures_y', self._value_widgets['Photobooth']['num_pictures_y'].text()) + wrapper_idx2val = [ 'commandline', 'piggyphoto', 'gphoto2-cffi' ] cfg.set('Camera', 'gphoto2_wrapper', wrapper_idx2val[self._value_widgets['Camera']['gphoto2_wrapper'].currentIndex()]) diff --git a/photobooth/defaults.cfg b/photobooth/defaults.cfg index 48ccb2b..b7b750b 100644 --- a/photobooth/defaults.cfg +++ b/photobooth/defaults.cfg @@ -22,8 +22,8 @@ lamp_channel = 4 [Photobooth] # Show preview while posing time (True/False) -preview = True +show_preview = True # Number of pictures in horizontal direction -num_pic_x = 2 +num_pictures_x = 2 # Number of pictures in vertical direction -num_pic_y = 2 \ No newline at end of file +num_pictures_y = 2