Kivy strange behavior when it updates Image Texture - multithreading

I am trying to update the Kivy Image.texture with the OpenCV image, and I am using a new thread to do it. I found some discussion that "the graphics operation should be in the main thread". But still, I want to figure out why the code below works.
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import Image
from kivy.graphics.texture import Texture
import cv2
from threading import Thread
class MainApp(App):
def __init__(self):
super().__init__()
self.layout = FloatLayout()
self.image = Image()
self.layout.add_widget(self.image)
Thread(target=self.calculate).start()
def build(self):
return self.layout
def calculate(self):
img = cv2.imread("test.png")
w, h = img.shape[1], img.shape[0]
img = cv2.flip(img, flipCode=0)
buf = img.tostring()
texture = Texture(0, 0, 0).create(size=(w, h), colorfmt="bgr")
texture.blit_buffer(buf, colorfmt='bgr', bufferfmt='ubyte')
self.image.texture = texture
def main():
Image(source='test.png') # remove this line will froze the app
MainApp().run()
if __name__ == "__main__":
main()
If I remove this line:
Image(source='test.png')
the app is frozen. Can someone help me to understand why decalring an Image object outside the main loop will affect the MainApp running.
The "test.png" image can be any simple image, like below. You need to put the image in the same directory as this script.

Not sure exactly why that line affects your code, but the main problem is that not only is the GUI manipulation required to be done on the main thread, but also any blit_buffer() must also be done on the main thread. So, a working version of your code (with the Image() removed) looks like this:
from functools import partial
from kivy.app import App
from kivy.clock import Clock
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import Image
from kivy.graphics.texture import Texture
import cv2
from threading import Thread
class MainApp(App):
def __init__(self):
super().__init__()
self.layout = FloatLayout()
self.image = Image()
self.layout.add_widget(self.image)
Thread(target=self.calculate).start()
def build(self):
return self.layout
def calculate(self):
img = cv2.imread("test.png")
w, h = img.shape[1], img.shape[0]
img = cv2.flip(img, flipCode=0)
buf = img.tostring()
Clock.schedule_once(partial(self.do_blit, buf, w, h))
def do_blit(self, buf, w, h, dt):
texture = Texture(0, 0, 0).create(size=(w, h), colorfmt="bgr")
texture.blit_buffer(buf, colorfmt='bgr', bufferfmt='ubyte')
self.image.texture = texture
def main():
# Image(source='test.png') # remove this line will froze the app
MainApp().run()
if __name__ == "__main__":
main()

Related

How to record the video from a webcam in a pyqt5 gui using OpenCV and QThread?

I'm trying to make pyqt5 Gui that shows a webcam live feed, records the feed at the same time, and saves it locally when closed. I managed to acheieve this using Timer(QTimer) in pyqt gui but When I try to implement it using Qthread (Which I really require) only the live feed is working.
Whenever I add Code required for recording video and run the program it says Python has Stopped Working and closes. Here is my Code:
import cv2
import sys
from PyQt5.QtWidgets import QWidget, QLabel, QApplication, QVBoxLayout
from PyQt5.QtCore import QThread, Qt, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QImage, QPixmap
class Thread(QThread):
changePixmap = pyqtSignal(QImage)
def run(self):
self.cap = cv2.VideoCapture(0)
self.width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
self.height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
self.codec = cv2.VideoWriter_fourcc('X', 'V', 'I', 'D')
self.writer = cv2.VideoWriter('output.avi', self.codec, 30.0, (self.width, self.height))
while self.cap.isOpened():
ret, self.frame = self.cap.read()
if ret:
self.frame = cv2.flip(self.frame, 1)
rgbimage = cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB)
h, w, ch = rgbimage.shape
bytesPerLine = ch * w
convertToQtFormat = QImage(rgbimage.data, w, h, bytesPerLine, QImage.Format_RGB888)
p = convertToQtFormat.scaled(640, 480, Qt.KeepAspectRatio)
self.changePixmap.emit(p)
class MyApp(QWidget):
def __init__(self):
super(MyApp, self).__init__()
self.title = 'Camera'
self.initUI()
def initUI(self):
self.label = QLabel(self)
lay = QVBoxLayout()
lay.addWidget(self.label)
self.setLayout(lay)
self.th = Thread()
self.th.changePixmap.connect(self.setImage)
self.th.start()
self.show()
#pyqtSlot(QImage)
def setImage(self, image):
self.label.setPixmap(QPixmap.fromImage(image))
self.th.writer.write(image)
def main():
app = QApplication(sys.argv)
ex = MyApp()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
I tried placing the .write() inside the run() of Thread class as well which is showing the same error. Can you guys point out What I'm doing wrong and how to make it work. I'm new to python and pyqt.
Thanks in Advance.
You need to separate threads. First thread is for signal, second is for the record and the main thread is for GUI. Try the following code. There is a button to start/stop the record.
import sys
import cv2
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QLabel, QVBoxLayout
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtCore import QTimer, QThread, pyqtSignal, pyqtSlot
from PyQt5 import QtWidgets, QtCore, QtGui
#https://ru.stackoverflow.com/a/1150993/396441
class Thread1(QThread):
changePixmap = pyqtSignal(QImage)
def __init__(self, *args, **kwargs):
super().__init__()
def run(self):
self.cap1 = cv2.VideoCapture(0, cv2.CAP_DSHOW)
self.cap1.set(3,480)
self.cap1.set(4,640)
self.cap1.set(5,30)
while True:
ret1, image1 = self.cap1.read()
if ret1:
im1 = cv2.cvtColor(image1, cv2.COLOR_BGR2RGB)
height1, width1, channel1 = im1.shape
step1 = channel1 * width1
qImg1 = QImage(im1.data, width1, height1, step1, QImage.Format_RGB888)
self.changePixmap.emit(qImg1)
class Thread2(QThread):
def __init__(self, *args, **kwargs):
super().__init__()
self.active = True
def run(self):
if self.active:
self.fourcc = cv2.VideoWriter_fourcc(*'XVID')
self.out1 = cv2.VideoWriter('output.avi', self.fourcc, 30, (640,480))
self.cap1 = cv2.VideoCapture(0, cv2.CAP_DSHOW)
self.cap1.set(3, 480)
self.cap1.set(4, 640)
self.cap1.set(5, 30)
while self.active:
ret1, image1 = self.cap1.read()
if ret1:
self.out1.write(image1)
self.msleep(10)
def stop(self):
self.out1.release()
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.resize(660, 520)
self.control_bt = QPushButton('START')
self.control_bt.clicked.connect(self.controlTimer)
self.image_label = QLabel()
self.saveTimer = QTimer()
self.th1 = Thread1(self)
self.th1.changePixmap.connect(self.setImage)
self.th1.start()
vlayout = QVBoxLayout(self)
vlayout.addWidget(self.image_label)
vlayout.addWidget(self.control_bt)
#QtCore.pyqtSlot(QImage)
def setImage(self, qImg1):
self.image_label.setPixmap(QPixmap.fromImage(qImg1))
def controlTimer(self):
if not self.saveTimer.isActive():
# write video
self.saveTimer.start()
self.th2 = Thread2(self)
self.th2.active = True
self.th2.start()
# update control_bt text
self.control_bt.setText("STOP")
else:
# stop writing
self.saveTimer.stop()
self.th2.active = False
self.th2.stop()
self.th2.terminate()
# update control_bt text
self.control_bt.setText("START")
if __name__ == '__main__':
app = QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
sys.exit(app.exec_())
you placed the .write() inside the run() of Thread class is right way.
like:
...
while self.cap.isOpened():
ret, self.frame = self.cap.read()
if ret:
self.frame = cv2.flip(self.frame, 1)
rgbimage = cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB)
h, w, ch = rgbimage.shape
bytesPerLine = ch * w
convertToQtFormat = QImage(
rgbimage.data, w, h, bytesPerLine, QImage.Format_RGB888)
p = convertToQtFormat.scaled(640, 480, Qt.KeepAspectRatio)
# put your writer() here, make sure your param is frame
# not converted to QtImage format
self.writer.write(rgbimage)
self.changePixmap.emit(p)
...

PyQt5 VideoPlayer: Converting to Object Orientated Code Prevents Playback

I've been looking into how to incorporate a video or livestream into an app. I have found some functional code that works fine:
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import cv2 # OpenCV
import qimage2ndarray # for a memory leak,see gist
import sys # for exiting
# Minimal implementation...
def displayFrame():
ret, frame = cap.read()
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
image = qimage2ndarray.array2qimage(frame)
label.setPixmap(QPixmap.fromImage(image))
app = QApplication([])
window = QWidget()
# OPENCV
cap = cv2.VideoCapture("Vid.mp4")
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 320)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 240)
# timer for getting frames
timer = QTimer()
timer.timeout.connect(displayFrame)
timer.start(60)
label = QLabel('No Camera Feed')
button = QPushButton("Quiter")
button.clicked.connect(sys.exit) # quiter button
layout = QVBoxLayout()
layout.addWidget(button)
layout.addWidget(label)
window.setLayout(layout)
window.show()
app.exec_()
And I am trying to be able to use this in some object orientated code, with the aim of creating a video playback widget to incorporate into other apps:
import cv2
import sys
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import qimage2ndarray # for a memory leak,see gist
import sys # for exiting
# Minimal implementation...
class basicWindow(QMainWindow):
def __init__(self):
super(basicWindow, self).__init__()
# OPENCV
cap = cv2.VideoCapture("Vid.mp4")
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 320)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 240)
# timer for getting frames
timer = QTimer()
timer.timeout.connect(displayFrame)
timer.start(60)
label = QLabel('No Camera Feed')
button = QPushButton("Quiter")
button.clicked.connect(sys.exit) # quiter button
layout = QVBoxLayout()
layout.addWidget(button)
layout.addWidget(label)
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
def displayFrame():
ret, frame = cap.read()
if ret:
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
image = qimage2ndarray.array2qimage(frame)
try:
label.setPixmap(QPixmap.fromImage(image))
except Exception as e:
print(e)
if __name__ == '__main__':
app = QApplication(sys.argv)
windowExample = basicWindow()
windowExample.show()
sys.exit(app.exec_())
I'm new to both OO coding and PyQt5, so any advice on how I'm either misinterpreting how the code works or what Im missing would be great. I have tried already setting label to a global variable, as I wasnt sure the function displayFrame() was aware of what label to change with label.setPixmap, but otherwise Im a little lost.
In your first example it works because label is a global variable, so displayFrame can access it as such.
In the other case, label is only declared in the scope of the __init__ of the basicWindow instance, so displayFrame knows nothing about it.
Make label a member of the instance (self.label = QLabel()), displayFrame a method of the basicWindow class (def displayFrame(self):) and then access the label correctly; note that you also need to make both cap and timer member of the instance (self), otherwise their objects will be immediately "garbage collected" after __init__ returns.
class basicWindow(QMainWindow):
def __init__(self):
super(basicWindow, self).__init__()
# ...
self.cap = cv2.VideoCapture("Vid.mp4")
# ...
self.timer = QTimer()
self.timer.timeout.connect(self.displayFrame)
self.timer.start(60)
self.label = QLabel('No Camera Feed')
# ...
def displayFrame(self):
ret, frame = self.cap.read()
if ret:
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
image = qimage2ndarray.array2qimage(frame)
try:
self.label.setPixmap(QPixmap.fromImage(image))
except Exception as e:
print(e)
Since you're new to OO programming, I suggest you to start by studying how classes and instances and name resolution work in python.

How not to get a gtkdrawingarea cleared in the draw event?

My drawingarea is cleared everytime the draw event is called.
How to avoid a drawingarea to be cleared ?
Thanks
#!/usr/bin/env python3
import gi
gi.require_version('Gtk','3.0')
from gi.repository import Gtk, Gdk
import cairo
import math
class MouseButtons:
LEFT_BUTTON = 1
RIGHT_BUTTON = 3
class Example(Gtk.Window):
def __init__(self):
super(Example, self).__init__()
self.init_ui()
def init_ui(self):
self.darea = Gtk.DrawingArea()
self.darea.connect("draw", self.on_draw)
self.darea.set_events(Gdk.EventMask.BUTTON_PRESS_MASK)
self.add(self.darea)
self.set_title("Fill & stroke")
self.resize(230, 150)
self.set_position(Gtk.WindowPosition.CENTER)
self.connect("delete-event", Gtk.main_quit)
self.darea.connect("button-press-event", self.on_button_press)
self.coords = []
self.show_all()
def on_draw(self, wid, cr):
cr.set_source_rgb(0.6, 0.6, 0.6)
cr.arc(self.coords[0], self.coords[1], 40, 0, 2*math.pi)
cr.fill()
def on_button_press(self, w, e):
if e.type == Gdk.EventType.BUTTON_PRESS \
and e.button == MouseButtons.LEFT_BUTTON:
self.coords = [e.x, e.y]
self.darea.queue_draw()
def main():
app = Example()
Gtk.main()
if __name__ == "__main__":
main()
In this example, each time I click on the drawingarea, a circle is drawn. I would like to draw the new circle but without to redrawing the previous one.
Is it possible ?
I would recommend adjusting your mental model of the drawing area; don't think of it as being "cleared" every time the draw handler is called. Rather, think of it like this: the draw handler is called every time the drawing area needs to be redrawn from scratch (among other reasons: because some other window moved in front of it, or because your program asked for a draw update). The drawing area's contents, once drawn, are not persisted anywhere.
If you need persistent window contents, then you should use a backing store and draw that onto the screen in the draw handler, or you could use a canvas library if you want to treat existing drawn objects as independently existing.
I found he answer :
#!/usr/bin/env python3
import gi
gi.require_version('Gtk','3.0')
from gi.repository import Gtk, Gdk
import cairo
import math
class MouseButtons:
LEFT_BUTTON = 1
RIGHT_BUTTON = 3
class Example(Gtk.Window):
def __init__(self):
super(Example, self).__init__()
self.init_ui()
def init_ui(self):
self.darea = Gtk.DrawingArea()
self.darea.connect("draw", self.on_draw)
self.darea.set_events(Gdk.EventMask.BUTTON_PRESS_MASK)
self.add(self.darea)
self.set_title("Fill & stroke")
self.resize(230, 150)
self.set_position(Gtk.WindowPosition.CENTER)
self.connect("delete-event", Gtk.main_quit)
self.darea.connect("button-press-event", self.on_button_press)
self.show_all()
a = self.darea.get_allocation()
print (a.x, a.y, a.width, a.height)
self.img = cairo.ImageSurface(cairo.Format.RGB24, a.width, a.height)
def on_draw(self, wid, cr):
cr.set_source_surface(self.img, 0, 0)
cr.paint()
def on_button_press(self, w, e):
if e.type == Gdk.EventType.BUTTON_PRESS \
and e.button == MouseButtons.LEFT_BUTTON:
cr = cairo.Context(self.img)
cr.set_source_rgb(0.6, 0.6, 0.6)
cr.arc(e.x, e.y, 40, 0, 2*math.pi)
cr.fill()
self.darea.queue_draw()
def main():
app = Example()
Gtk.main()
if __name__ == "__main__":
main()

how to display a random point every second in kivy

I try to display a random point every second in kivy.
Here is my code. I know how to display the point, an ellipse in this case. But I don't know how to make its position to update every second.
from kivy.app import App
from kivy.clock import Clock
from kivy.uix.widget import Widget
from kivy.graphics import Ellipse
import time
import numpy as np
class RandomPoint(Widget):
def __init__(self,dimension):
super(RandomPoint,self).__init__()
self.d = dimension
self.point = Ellipse(pos=list(np.random.randint(0,1000,2)),size = (self.d, self.d))
def update(self, *args):
self.point = Ellipse(pos=list(np.random.randint(0,1000,2)),size = (self.d, self.d))
class TimeApp(App):
def build(self):
wid = Widget()
with wid.canvas:
p = RandomPoint(25)
Clock.schedule_interval(p.update, 1)
return wid
TimeApp().run()
How would you do that ?
Putting the Clock.schedule_interval call in the canvas block won't satisfy the requirement of these calls to happen in a canvas block. They are executed later, when the code exited the with block long ago. What you can do is use the same construction, but inside both __init__ and update, around your Ellipse instructions.
Also, at no point do you add your RandomPoint widget, to your root widget, so it won't be visible at all, whatever happens with its instructions.
class RandomPoint(Widget):
def __init__(self,dimension):
super(RandomPoint,self).__init__()
self.d = dimension
self.points = []
with self.canvas:
self.point.append(Ellipse(pos=list(np.random.randint(0,1000,2)),size = (self.d, self.d)))
def update(self, *args):
with self.canvas:
self.points.append(Ellipse(pos=list(np.random.randint(0,1000,2)),size = (self.d, self.d)))
class TimeApp(App):
def build(self):
wid = Widget()
p = RandomPoint(25)
wid.add_widget(p)
Clock.schedule_interval(p.update, 1)
return wid
TimeApp().run()

Reading frames from a folder and display in QGraphicsView in pyqt4

I am trying to read frame for a folder and display in QGraphics view.
import os
import PyQt4
from PyQt4 import QtCore, QtGui
dir = "frames"
for file in os.listdir(dir):
grview = QtGui.QGraphicsView()
scene = QtGui.QGraphicsScene()
#pixmap = QtGui.QPixmap(os.path.join(dir, file))
pixmap = QtGui.QPixmap(file)
item = QtGui.QGraphicsPixmapItem(pixmap)
self.scene.addItem(item)
grview.setScene()
grview.show()
#self.scene.update()
The folder named "frames" contains jpg files and is in the same folder as the code. But still I am not able to read the frames.
I have also tried general display without graphicsview to check if it works using the code below but it too doesn't work.
import cv2
import os
import PyQt4
from PyQt4 import QtGui,QtCore
import matplotlib.pyplot as plt
from PIL import Image
def load_images_from_folder(folder):
images = []
for filename in os.listdir(folder):
img = cv2.imread(os.path.join(folder,filename))
if img is not None:
images.append(img)
return images
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
img = load_images_from_folder('/frames')
image = Image.open(img)
image.show()
I am not able to understand what am I missing. I want to display the images sequentially in QGraphicsView resembling a video and in some particular frame I have to select the object of interest by draw a rectangle around it which has to hold in the subsequent frames as well. Is the task i am attempting possible? if yes any help is appreciated
Sorry, but I have PyQt5.
Try it:
import os
import sys
from PyQt5 import Qt
#from PyQt4 import QtCore, QtGui
class MainWindow(Qt.QMainWindow):
def __init__(self, parent=None):
Qt.QMainWindow.__init__(self, parent)
self.scene = Qt.QGraphicsScene()
self.grview = Qt.QGraphicsView(self.scene)
self.setCentralWidget(self.grview)
dir = "frames"
self.listFiles = os.listdir(dir)
self.timer = Qt.QTimer(self)
self.n = 0
self.timer.timeout.connect(self.on_timeout)
self.timer.start(1000)
self.setGeometry(700, 150, 300, 300)
self.show()
def on_timeout(self):
if self.n < len(self.listFiles):
self.scene.clear()
file = self.listFiles[self.n]
pixmap = Qt.QPixmap("frames\{}".format(file)) # !!! "frames\{}"
item = Qt.QGraphicsPixmapItem(pixmap)
self.scene.addItem(item)
self.grview.setScene(self.scene) # !!! (self.scene)
self.n += 1
else:
self.timer.stop()
app = Qt.QApplication(sys.argv)
GUI = MainWindow()
sys.exit(app.exec_())

Resources