Rudimentary trigger emulation using OpenCV

This commit is contained in:
Balthasar Reuter
2018-03-23 23:27:38 +01:00
parent 0688fa5fa6
commit 3e27a46618
6 changed files with 294 additions and 39 deletions

View File

@@ -1 +1,2 @@
pip install pyqt5
pip install opencv-python

58
photobooth/Camera.py Normal file
View File

@@ -0,0 +1,58 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
class Camera:
def __init__(self):
self.hasPreview = False
self.hasIdle = False
@property
def hasPreview(self):
return self._has_preview
@hasPreview.setter
def hasPreview(self, value):
if not isinstance(value, bool):
raise ValueError('Expected bool')
self._has_preview = value
@property
def hasIdle(self):
return self._has_idle
@hasIdle.setter
def hasIdle(self, value):
if not isinstance(value, bool):
raise ValueError('Expected bool')
self._has_idle = value
def getPreview(self):
if not self.hasPreview:
raise RuntimeError('Camera does not have preview functionality')
raise NotImplementedError()
def getPicture(self):
raise NotImplementedError()
def setIdle(self):
if not self.hasIdle:
raise RuntimeError('Camera does not support idle state')
raise NotImplementedError()

View File

@@ -0,0 +1,33 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from Camera import Camera
import cv2
class CameraOpenCV(Camera):
def __init__(self):
super().__init__()
self.hasPreview = True
self.hasIdle = False
self._cap = cv2.VideoCapture(-1)
if not self._cap.isOpened():
raise RuntimeError('Camera could not be opened')
def getPreview(self):
return self.getPicture()
def getPicture(self):
_, frame = self._cap.read()
# OpenCV yields frames in BGR format,
# see https://stackoverflow.com/a/32270308
return cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

89
photobooth/Gui.py Normal file
View File

@@ -0,0 +1,89 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
class Gui:
def __init__(self):
pass
def run(self, send, recv):
raise NotImplementedError()
class GuiState:
def __init__(self, **kwargs):
assert not kwargs
class IdleState(GuiState):
def __init__(self, **kwargs):
super().__init__(**kwargs)
class PictureState(GuiState):
def __init__(self, picture, **kwargs):
super().__init__(**kwargs)
self._pic = picture
@property
def picture(self):
return self._pic
@picture.setter
def picture(self, picture):
self._pic = picture
class MessageState(GuiState):
def __init__(self, message, **kwargs):
super().__init__(**kwargs)
self._msg = message
@property
def message(self):
return self._msg
@message.setter
def message(self, message):
if not isinstance(message, str):
raise ValueError('Message must be a string')
self._msg = message
class PoseState(GuiState):
def __init__(self, **kwargs):
super().__init__(**kwargs)
class PreviewState(MessageState, PictureState):
def __init__(self, **kwargs):
super().__init__(**kwargs)

View File

@@ -2,18 +2,20 @@
# -*- coding: utf-8 -*-
from Config import Config
import Gui
from PyQt5Gui import PyQt5Gui
from CameraOpenCV import CameraOpenCV as Camera
from multiprocessing import Pipe, Process
from time import sleep
from time import clock, sleep
class Photobooth:
def __init__(self):
pass
self._cap = Camera()
def run(self, send, recv):
@@ -24,7 +26,6 @@ class Photobooth:
except EOFError:
return 1
else:
print('Photobooth: ' + event)
self.trigger(send)
return 0
@@ -32,15 +33,29 @@ class Photobooth:
def trigger(self, send):
send.send('Pose')
sleep(3)
send.send('Picture')
send.send(Gui.PoseState())
sleep(2)
send.send('idle')
if self._cap.hasPreview:
tic = clock()
toc = clock() - tic
while toc < 3:
send.send( Gui.PreviewState(
message = str(3 - int(toc)),
picture = self._cap.getPreview() ) )
toc = clock() - tic
else:
for i in range(3):
send.send( Gui.PreviewState(str(i)) )
sleep(1)
send.send(Gui.PictureState(self._cap.getPicture()))
sleep(2)
send.send(Gui.IdleState())
def main_photobooth(send, recv):

View File

@@ -1,14 +1,18 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import Gui
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 QPainter, QPixmap
from PyQt5.QtGui import QImage, QPainter, QPixmap
class PyQt5Gui:
class PyQt5Gui(Gui.Gui):
def __init__(self, argv, config):
super().__init__()
global cfg
cfg = config
@@ -19,14 +23,56 @@ class PyQt5Gui:
def run(self, send, recv):
receiver = PyQt5Receiver(recv)
receiver.notify.connect(self._p.showMessage)
receiver.notify.connect(self.handleState)
receiver.start()
self._p.transport = send
self._p.handleEscape = self.showStart
self.showStart()
return self._app.exec_()
def close(self):
self._p.close()
def handleState(self, state):
if not isinstance(state, Gui.GuiState):
raise ValueError('Invalid data received')
if isinstance(state, Gui.IdleState):
self.showIdle()
elif isinstance(state, Gui.PoseState):
self._p.setCentralWidget(PyQt5PictureMessage('Pose!'))
elif isinstance(state, Gui.PreviewState):
img = QImage(state.picture, state.picture.shape[1], state.picture.shape[0], QImage.Format_RGB888)
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)))
else:
raise ValueError('Unknown state')
def showStart(self):
self._p.setCentralWidget(PyQt5Start(self))
def showSettings(self):
self._p.setCentralWidget(PyQt5Settings(self))
def showIdle(self):
self._p.setCentralWidget(PyQt5PictureMessage('Hit the button!', 'homer.jpg'))
class PyQt5Receiver(QThread):
notify = pyqtSignal(object)
@@ -38,21 +84,20 @@ class PyQt5Receiver(QThread):
self._transport = transport
def handle(self, event):
def handle(self, state):
self.notify.emit(event)
self.notify.emit(state)
def run(self):
while True:
try:
event = self._transport.recv()
state = self._transport.recv()
except EOFError:
break
else:
print('Connector: ' + event)
self.handle(event)
self.handle(state)
@@ -79,12 +124,25 @@ class PyQt5MainWindow(QMainWindow):
self._transport = new_transport
@property
def handleEscape(self):
return self._handle_escape
@handleEscape.setter
def handleEscape(self, func):
if not callable(func):
raise ValueError('Escape key handler must be callable')
self._handle_escape = func
def initUI(self):
global cfg
self.showStart()
# self.showStart()
self.setWindowTitle('Photobooth')
if cfg.getBool('Gui', 'fullscreen'):
@@ -95,21 +153,21 @@ class PyQt5MainWindow(QMainWindow):
self.show()
def showStart(self):
# def showStart(self):
content = PyQt5Start(self)
self.setCentralWidget(content)
# content = PyQt5Start(self)
# self.setCentralWidget(content)
def showSettings(self):
# def showSettings(self):
content = PyQt5Settings(self)
self.setCentralWidget(content)
# content = PyQt5Settings(self)
# self.setCentralWidget(content)
def showIdle(self):
# def showIdle(self):
self.showMessage('Hit the button!', 'homer.jpg')
# self.showMessage('Hit the button!', 'homer.jpg')
def showMessage(self, message, picture=None):
@@ -133,7 +191,7 @@ class PyQt5MainWindow(QMainWindow):
def keyPressEvent(self, event):
if event.key() == Qt.Key_Escape:
self.showStart()
self.handleEscape()
elif event.key() == Qt.Key_Space:
self.transport.send('triggered')
@@ -142,14 +200,14 @@ class PyQt5MainWindow(QMainWindow):
class PyQt5Start(QFrame):
def __init__(self, parent):
def __init__(self, gui):
super().__init__()
self.initFrame(parent)
self.initFrame(gui)
def initFrame(self, parent):
def initFrame(self, gui):
grid = QGridLayout()
grid.setSpacing(100)
@@ -157,28 +215,29 @@ class PyQt5Start(QFrame):
btnStart = QPushButton('Start Photobooth')
btnStart.resize(btnStart.sizeHint())
btnStart.clicked.connect(parent.showIdle)
btnStart.clicked.connect(gui.showIdle)
grid.addWidget(btnStart, 0, 0)
btnSettings = QPushButton('Settings')
btnSettings.resize(btnSettings.sizeHint())
btnSettings.clicked.connect(parent.showSettings)
btnSettings.clicked.connect(gui.showSettings)
grid.addWidget(btnSettings, 0, 1)
btnQuit = QPushButton('Quit')
btnQuit.resize(btnQuit.sizeHint())
btnQuit.clicked.connect(parent.close)
btnQuit.clicked.connect(gui.close)
grid.addWidget(btnQuit, 0, 2)
class PyQt5Settings(QFrame):
def __init__(self, parent):
def __init__(self, gui):
super().__init__()
self._parent = parent
self._gui = gui
self.initFrame()
@@ -284,7 +343,7 @@ class PyQt5Settings(QFrame):
btnCancel = QPushButton('Cancel')
btnCancel.resize(btnCancel.sizeHint())
btnCancel.clicked.connect(self._parent.showStart)
btnCancel.clicked.connect(self._gui.showStart)
layout.addWidget(btnCancel)
btnRestore = QPushButton('Restore defaults')
@@ -314,7 +373,7 @@ class PyQt5Settings(QFrame):
cfg.set('Camera', 'gphoto2_wrapper', wrapper_idx2val[self._value_widgets['Camera']['gphoto2_wrapper'].currentIndex()])
cfg.write()
self._parent.showStart()
self._gui.showStart()
def restoreDefaults(self):
@@ -322,7 +381,7 @@ class PyQt5Settings(QFrame):
global cfg
cfg.defaults()
self._parent.showSettings()
self._gui.showSettings()
@@ -340,7 +399,7 @@ class PyQt5PictureMessage(QFrame):
def initFrame(self):
self.setStyleSheet('background-color: black;')
self.setStyleSheet('background-color: white;')
def paintEvent(self, event):