diff --git a/INSTALL.md b/INSTALL.md index 6df872c..4b600f2 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,2 +1,3 @@ pip install pyqt5 pip install opencv-python +pip install Pillow \ No newline at end of file diff --git a/photobooth/CameraOpenCV.py b/photobooth/CameraOpenCV.py index f097952..7706376 100644 --- a/photobooth/CameraOpenCV.py +++ b/photobooth/CameraOpenCV.py @@ -3,6 +3,7 @@ from Camera import Camera +from PIL import Image import cv2 class CameraOpenCV(Camera): @@ -14,9 +15,9 @@ class CameraOpenCV(Camera): self.hasPreview = True self.hasIdle = False - self._cap = cv2.VideoCapture(-1) - if not self._cap.isOpened(): - raise RuntimeError('Camera could not be opened') + # self._cap = cv2.VideoCapture(0) + # if not self._cap.isOpened(): + # raise RuntimeError('Camera could not be opened') def getPreview(self): @@ -25,9 +26,12 @@ class CameraOpenCV(Camera): def getPicture(self): + + status, frame = self._cap.read() + if not status: + raise RuntimeError('Failed to capture picture') - _, frame = self._cap.read() - # OpenCV yields frames in BGR format, - # see https://stackoverflow.com/a/32270308 - return cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + # OpenCV yields frames in BGR format, conversion to RGB necessary. + # (See https://stackoverflow.com/a/32270308) + return Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)) diff --git a/photobooth/Photobooth.py b/photobooth/Photobooth.py index bde307b..a5a0149 100644 --- a/photobooth/Photobooth.py +++ b/photobooth/Photobooth.py @@ -6,11 +6,18 @@ import Gui from PyQt5Gui import PyQt5Gui from CameraOpenCV import CameraOpenCV as Camera +from PIL import Image + from multiprocessing import Pipe, Process from time import time, sleep +output_size = (1920, 1080) +min_distance = (10, 10) +num_pictures = (2, 2) +pose_time = 2 + class Photobooth: def __init__(self): @@ -20,41 +27,87 @@ class Photobooth: def run(self, send, recv): + self._send = send + while True: try: event = recv.recv() except EOFError: return 1 else: - self.trigger(send) + self.trigger() return 0 - def trigger(self, send): - - send.send(Gui.PoseState()) - - sleep(2) + def showCounter(self): if self._cap.hasPreview: tic, toc = time(), 0 - while toc < 3: - send.send( Gui.PreviewState( - message = str(3 - int(toc)), + while toc < pose_time: + self._send.send( Gui.PreviewState( + message = str(pose_time - int(toc)), picture = self._cap.getPreview() ) ) toc = time() - tic else: - for i in range(3): - send.send( Gui.PreviewState(str(i)) ) + for i in range(pose_time): + self._send.send( Gui.PreviewState(str(i)) ) sleep(1) - send.send(Gui.PictureState(self._cap.getPicture())) + + def captureSinglePicture(self): + + self.showCounter() + + return self._cap.getPicture() + + + def assemblePictures(self, pictures): + + 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_picture_size = tuple( int(picture_size[0] * resize_factor) + for i in range(2) ) + output_picture_dist = tuple( int( ( output_size[i] - num_pictures[i] * + output_picture_size[i] ) / (num_pictures[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]) + 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) ) + output_image.paste(img, offset) + idx += 1 + + return output_image + + + def capturePictures(self): + + pictures = [self.captureSinglePicture() for i in range(2) for _ in range(num_pictures[i])] + return self.assemblePictures(pictures) + + + def trigger(self): + + self._send.send(Gui.PoseState()) sleep(2) - send.send(Gui.IdleState()) + self._send.send(Gui.PictureState(self.capturePictures())) + + sleep(5) + + self._send.send(Gui.IdleState()) def main_photobooth(send, recv): diff --git a/photobooth/PyQt5Gui.py b/photobooth/PyQt5Gui.py index 3a02125..506e39c 100644 --- a/photobooth/PyQt5Gui.py +++ b/photobooth/PyQt5Gui.py @@ -3,6 +3,8 @@ import Gui +from PIL import ImageQt + from PyQt5.QtCore import Qt, QObject, QThread, pyqtSignal from PyQt5.QtWidgets import (QApplication, QCheckBox, QComboBox, QFormLayout, QFrame, QGridLayout, QGroupBox, QHBoxLayout, QLabel, QLayout, QLineEdit, QMainWindow, QMessageBox, QPushButton, QVBoxLayout) from PyQt5.QtGui import QImage, QPainter, QPixmap @@ -56,13 +58,15 @@ class PyQt5Gui(Gui.Gui): if isinstance(state, Gui.IdleState): self.showIdle() elif isinstance(state, Gui.PoseState): - self._p.setCentralWidget(PyQt5PictureMessage('Pose!')) + self._p.setCentralWidget(PyQt5PictureMessage('Will capture 4 pictures!')) elif isinstance(state, Gui.PreviewState): - img = QImage(state.picture, state.picture.shape[1], state.picture.shape[0], QImage.Format_RGB888) + # img = QImage(state.picture, state.picture.shape[1], state.picture.shape[0], QImage.Format_RGB888) + img = ImageQt.ImageQt(state.picture) self._p.setCentralWidget(PyQt5PictureMessage(state.message, img)) elif isinstance(state, Gui.PictureState): - img = QImage(state.picture, state.picture.shape[1], state.picture.shape[0], QImage.Format_RGB888) - self._p.setCentralWidget(PyQt5PictureMessage('', QPixmap.fromImage(img))) + # img = QImage(state.picture, state.picture.shape[1], state.picture.shape[0], QImage.Format_RGB888) + img = ImageQt.ImageQt(state.picture) + self._p.setCentralWidget(PyQt5PictureMessage('', img)) else: raise ValueError('Unknown state') @@ -402,7 +406,11 @@ class PyQt5PictureMessage(QFrame): painter.begin(self) if self._picture != None: - pix = QPixmap(self._picture).scaled(self.rect().size(), Qt.KeepAspectRatio, Qt.SmoothTransformation) + if isinstance(self._picture, QImage): + pix = QPixmap.fromImage(self._picture) + else: + pix = QPixmap(self._picture) + pix = pix.scaled(self.rect().size(), Qt.KeepAspectRatio, Qt.SmoothTransformation) painter.drawPixmap(pix.rect(), pix, pix.rect()) painter.drawText(event.rect(), Qt.AlignCenter, self._message) diff --git a/photobooth/defaults.cfg b/photobooth/defaults.cfg index f720b88..48ccb2b 100644 --- a/photobooth/defaults.cfg +++ b/photobooth/defaults.cfg @@ -19,3 +19,11 @@ exit_channel = 24 trigger_channel = 23 # Pin 7 (Channel 4) switches the lamp on and off lamp_channel = 4 + +[Photobooth] +# Show preview while posing time (True/False) +preview = True +# Number of pictures in horizontal direction +num_pic_x = 2 +# Number of pictures in vertical direction +num_pic_y = 2 \ No newline at end of file