Kivy Video Player delay/lag - python

I'm trying to display an rtsp stream via kivy video player, this runs fine but in my video I get a 2 or 3 second delay in the stream which I would ideally like to eliminate to 0.5 to 1 seconds.
Here's what I have:
from kivy.app import App
from kivy.uix.video import Video
class TestApp(App):
def build(self):
video = Video(source='rtsp://my-stream-address', state='play')
video.size = (720, 320)
video.opacity = 0
video.state = 'play'
video.bind(texture=self._play_started)
return video
def _play_started(self, instance, value):
instance.opacity = 1
if __name__ == '__main__':
TestApp().run()
EDIT
I have a working solution to the video streaming BUT I don't know how to get this into my kivy gui.
Here's my streaming solution:
from threading import Thread
import cv2, time
class ThreadedCamera(object):
def __init__(self, src=0):
self.capture = cv2.VideoCapture(src)
self.capture.set(cv2.CAP_PROP_BUFFERSIZE, 2)
self.FPS = 1/30
self.FPS_MS = int(self.FPS * 1000)
# Start frame retrieval thread
self.thread = Thread(target=self.update, args=())
self.thread.daemon = True
self.thread.start()
def update(self):
while True:
if self.capture.isOpened():
(self.status, self.frame) = self.capture.read()
time.sleep(self.FPS)
def show_frame(self):
cv2.imshow('frame', self.frame)
cv2.waitKey(self.FPS_MS)
if __name__ == '__main__':
src = 'rtsp://my-stream-address'
threaded_camera = ThreadedCamera(src)
while True:
try:
threaded_camera.show_frame()
except AttributeError:
pass
EDIT 2
I have also found this implementation of a kivy video widget not using the built in Video widget. I'm still unsure how to combine my working solution with a Kivy widget but perhaps this can help someone help me:
class KivyCamera(Image):
source = ObjectProperty()
fps = NumericProperty(30)
def __init__(self, **kwargs):
super(KivyCamera, self).__init__(**kwargs)
self._capture = None
if self.source is not None:
self._capture = cv2.VideoCapture(self.source)
Clock.schedule_interval(self.update, 1.0 / self.fps)
def on_source(self, *args):
if self._capture is not None:
self._capture.release()
self._capture = cv2.VideoCapture(self.source)
#property
def capture(self):
return self._capture
def update(self, dt):
ret, frame = self.capture.read()
if ret:
buf1 = cv2.flip(frame, 0)
buf = buf1.tostring()
image_texture = Texture.create(
size=(frame.shape[1], frame.shape[0]), colorfmt="bgr"
)
image_texture.blit_buffer(buf, colorfmt="bgr", bufferfmt="ubyte")
self.texture = image_texture
My initial question was for Kivy Video player widget. But now the solution I am finding is using threading with OpenCV, so I have changed the tags on this question and will accept any Kivy implementation of this solution.

Related

Changing QThread variable from a sub window

I am writing a WebCam Gui, which is supposed to take pictures and manipulate with WebCam parameters. After the video stream is activated on the main GUI window, an additional window can be opened to change the WebCamera parameters Screenshot 1, Screenshot 2.
I am using Qthread to stream on QLabel. Also, I was able to set the initial camera parameters on the camera properties' changing window. My problem is changing the Exposure parameter by using a slider on the sub-window and seeing results in real-time on the main window.
Please see the code.
import sys
from PyQt5.QtWidgets import *
from PyQt5 import uic
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import cv2
class MainFrame(QMainWindow):
def __init__(self):
super(MainFrame, self).__init__()
# Loading UI
uic.loadUi("MainFrame.ui", self)
# Remove maximize button to prevent crushing
self.setWindowFlags(Qt.WindowCloseButtonHint | Qt.WindowMinimizeButtonHint)
# Define Widgets
self.Video_Feed = self.findChild(QLabel, 'Video_Feed')
self.Case_Name = self.findChild(QLineEdit, "Case_Name")
self.Pictures_List = self.findChild(QListWidget, "Pictures_List")
self.Start_Video = self.findChild(QAction, 'actionStart_Video')
self.Start_Video.setShortcut('Shift+S')
self.Stop_Video = self.findChild(QAction, 'actionStop_Video')
self.Stop_Video.setShortcut('Shift+F')
self.Take_a_Picture = self.findChild(QAction, 'actionTake_a_picture')
self.Take_a_Timed_Picture = self.findChild(QAction, 'actionTake_a_timed_picture')
self.Camera_Properties = self.findChild(QAction, 'actionProperties')
# Initializing Video
self.Start_Video.triggered.connect(self.Start_Video_Clicked)
self.Stop_Video.triggered.connect(self.Stop_Video_Clicked)
self.Camera_Properties.triggered.connect(self.Camera_Properties_Clicked)
def Video_Feed_Update(self, Image):
self.Video_Feed.setPixmap(QPixmap.fromImage(Image))
def Start_Video_Clicked(self):
self.Video_Feed_is_Running = True
self.thread = QThread()
self.Video_Thread = Worker()
self.Video_Thread.moveToThread(self.thread)
self.Video_Thread.ImageUpdate.connect(self.Video_Feed_Update)
self.thread.started.connect(self.Video_Thread.run)
self.thread.start()
def Stop_Video_Clicked(self):
self.Video_Thread.stop_video()
self.Video_Feed.setText("Your video starts here")
def Camera_Properties_Clicked(self):
self.CP = CameraParameters()
Initial_Exposure = self.Video_Thread.Camera_Initial_Parameters()
self.CP.Setup_Exposure(int(Initial_Exposure))
self.CP.Exposure_Calibration.connect(self.Video_Thread.Exposure_update)
self.CP.show()
class Worker(QObject):
ImageUpdate = pyqtSignal(QImage)
def run(self):
self.ThreadActive = True
self.Capture = cv2.VideoCapture(1, cv2.CAP_DSHOW)
self.Capture.set(3, 1920)
self.Capture.set(4, 1080)
while self.ThreadActive:
ret, frame = self.Capture.read()
if ret:
image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# Converting Video into QT5 readable format
qt_video_format = QImage(image.data, image.shape[1], image.shape[0], QImage.Format_RGB888)
qt_picture = qt_video_format.scaled(1280, 720, Qt.KeepAspectRatio)
self.ImageUpdate.emit(qt_picture)
def stop_video(self):
self.ThreadActive = False
self.Capture.release()
def Camera_Initial_Parameters(self):
return self.Capture.get(cv2.CAP_PROP_EXPOSURE)
def Exposure_update(self, value):
self.Capture.set(cv2.CAP_PROP_EXPOSURE, value)
class CameraParameters(QDialog):
Exposure_Calibration = pyqtSignal(int)
def __init__(self):
super().__init__()
uic.loadUi('Cam_Parameters.ui', self)
# Sliders
self.Exposure_Slider = self.findChild(QSlider, 'ExposureSlider')
self.Exposure_Slider.setRange(-10, 10)
self.White_Balance_Slider = self.findChild(QSlider, 'WBSlider')
self.White_Balance_Slider.setMinimum(-10)
self.White_Balance_Slider.setMaximum(10)
self.Brightness_Slider = self.findChild(QSlider, 'BrightnessSlider')
self.Brightness_Slider.setMinimum(0)
self.Brightness_Slider.setMaximum(300)
self.Saturation_Slider = self.findChild(QSlider, 'SaturationSlider')
self.Saturation_Slider.setMinimum(0)
self.Saturation_Slider.setMaximum(300)
self.Contrast_Slider = self.findChild(QSlider, 'ContrastSlider')
self.Contrast_Slider.setMinimum(-10)
self.Contrast_Slider.setMaximum(10)
self.Gamma_Slider = self.findChild(QSlider, 'GammaSlider')
self.Gamma_Slider.setMinimum(-10)
self.Gamma_Slider.setMaximum(10)
self.Sharpness_Slider = self.findChild(QSlider, 'SharpnessSlider')
self.Sharpness_Slider.setMinimum(0)
self.Sharpness_Slider.setMaximum(100)
# Sliders values
self.Exposure_Value = self.findChild(QLabel, 'Exposure_Value')
self.White_Balance_Value = self.findChild(QLabel, 'WB_value')
self.Brightness_Value = self.findChild(QLabel, 'Brightness_value')
self.Saturation_Value = self.findChild(QLabel, 'Saturation_value')
self.Contrast_Value = self.findChild(QLabel, 'Contrast_value')
self.Gamma_Value = self.findChild(QLabel, 'Gamma_value')
self.Sharpness_Value = self.findChild(QLabel, 'Sharpness_value')
# Connections
self.Exposure_Slider.valueChanged.connect(self.Exposure_sliding)
def Setup_Exposure(self, value):
self.Exposure_Slider.setValue(value)
self.Exposure_Value.setText(str(value))
def Exposure_sliding(self, value):
self.Exposure_Value.setText(str(value))
self.Exposure_Calibration.emit(value)
if __name__ == "__main__":
App = QApplication(sys.argv)
Root = MainFrame()
Root.show()
sys.exit(App.exec())
Cam_Parameters.ui, MainFrame.ui for the GUI
I admit I don't know why this worked, but changing the connect to a lambda function did the trick.
self.CP.Exposure_Calibration.connect(lambda x: self.Video_Thread.Exposure_update(x))

Python 4-channel video streaming program QThread speedup problem

It is a 4 channel video streaming program.
When running with QThread, if you stream 4 at the same time, the speed of the video will slow down and CPU usage will be 100%.
Can I change this to multiprocessing?
I want to speed up a 4 channel cctv program.
threadMode.py
from PyQt5.QtCore import QThread, pyqtSignal, Qt
from PyQt5.QtGui import QImage
import cv2
class StreamingThread(QThread):
changePixmap = pyqtSignal(QImage)
def __init__(self):
super(StreamingThread, self).__init__()
self.running = True
self.camUrl = None
self.Qsize = None
self.cap = None
def setRtsp(self, camUrl):
self.camUrl = camUrl
def setSize(self, Qsize):
self.Qsize = Qsize
def run(self):
try:
self.cap = cv2.VideoCapture(self.camUrl)
if self.cap.isOpened():
while self.running:
success, frame = self.cap.read()
if success:
rgbImage = cv2.cvtColor(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(self.Qsize, Qt.KeepAspectRatio)
self.changePixmap.emit(p)
else:
print("RTSP(RTMP) Video Streaming Fail")
self.stop()
except Exception as e:
print(e)
self.stop()
def stop(self):
if self.running:
self.running = False
print("Streaming Stop")
self.quit()
Main.py
def setChannel1(self):
# Index 0: No Channel
if self.chComboBox1.currentIndex() == 0:
self.mStreamingThread1.terminate()
sched = BackgroundScheduler()
sched.add_job(self.clearChannel, 'date', run_date=datetime.datetime.now() + datetime.timedelta(seconds=1), args=[self.cctvStreaming1])
sched.start()
else:
ip, url, channel, boxId = self.findUrl(self.chComboBox1)
if url != '' and channel != '':
self.mStreamingThread1.terminate()
self.mStreamingThread1.wait(1)
self.mStreamingThread1.setRtsp(url)
self.mStreamingThread1.setSize(self.cctvStreaming1.size())
self.mStreamingThread1.changePixmap.connect(self.setImage1)
self.mStreamingThread1.start()
self.show()
logger.info("Channel1 Streaming Success")
#pyqtSlot(QImage)
def setImage1(self, image):
self.cctvStreaming1.setPixmap(QPixmap.fromImage(image))

PyQt widget keeps increasing in size and goes out the window

I have written an application in PyQt5. I am basically displaying a camera feed (in this case my web cam), but the problem is that the frame size keeps on increasing at run time and ultimately goes out of my laptop screen. I'm unable to figure out what the problem is.
Can anyone please explain what I'm doing wrong here?
Below is the code snippet.
from PyQt5 import QtCore, QtGui, QtWidgets
from threading import Thread
from collections import deque
from datetime import datetime
import time
import sys
import cv2
import imutils
class CameraWidget(QtWidgets.QWidget):
"""Independent camera feed
Uses threading to grab IP camera frames in the background
#param width - Width of the video frame
#param height - Height of the video frame
#param stream_link - IP/RTSP/Webcam link
#param aspect_ratio - Whether to maintain frame aspect ratio or force into fraame
"""
def __init__(self, width=0, height=0, aspect_ratio=False, parent=None, deque_size=1):
super(CameraWidget, self).__init__(parent)
# Initialize deque used to store frames read from the stream
self.deque = deque(maxlen=deque_size)
self.maintain_aspect_ratio = aspect_ratio
self.camera_stream_link = 0
# Flag to check if camera is valid/working
self.online = False
self.capture = None
self.video_frame = QtWidgets.QLabel()
self.load_network_stream()
# Start background frame grabbing
self.get_frame_thread = Thread(target=self.get_frame, args=())
self.get_frame_thread.daemon = True
self.get_frame_thread.start()
# Periodically set video frame to display
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.set_frame)
self.timer.start(.5)
print('Started camera: {}'.format(self.camera_stream_link))
def load_network_stream(self):
"""Verifies stream link and open new stream if valid"""
def load_network_stream_thread():
if self.verify_network_stream(self.camera_stream_link):
self.capture = cv2.VideoCapture(self.camera_stream_link)
self.online = True
self.load_stream_thread = Thread(target=load_network_stream_thread, args=())
self.load_stream_thread.daemon = True
self.load_stream_thread.start()
def verify_network_stream(self, link):
"""Attempts to receive a frame from given link"""
cap = cv2.VideoCapture(link)
if not cap.isOpened():
return False
cap.release()
return True
def get_frame(self):
# time.sleep(5)
"""Reads frame, resizes, and converts image to pixmap"""
while True:
try:
if self.capture.isOpened() and self.online:
# Read next frame from stream and insert into deque
status, frame = self.capture.read()
if status:
self.deque.append(frame)
else:
self.capture.release()
self.online = False
else:
# Attempt to reconnect
print('attempting to reconnect', self.camera_stream_link)
self.load_network_stream()
self.spin(2)
self.spin(.001)
except AttributeError:
pass
def spin(self, seconds):
"""Pause for set amount of seconds, replaces time.sleep so program doesnt stall"""
time_end = time.time() + seconds
while time.time() < time_end:
QtWidgets.QApplication.processEvents()
def set_frame(self):
"""Sets pixmap image to video frame"""
if not self.online:
self.spin(1)
return
if self.deque and self.online:
# Grab latest frame
frame = self.deque[-1]
# Keep frame aspect ratio
if self.maintain_aspect_ratio:
self.frame = imutils.resize(frame, width=self.screen_width)
# Force resize
else:
self.frame = cv2.resize(frame, (self.screen_width, self.screen_height))
self.frame = cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB)
h, w, ch = self.frame.shape
bytesPerLine = ch * w
# Convert to pixmap and set to video frame
self.img = QtGui.QImage(self.frame, w, h, bytesPerLine, QtGui.QImage.Format_RGB888)
self.pix = QtGui.QPixmap.fromImage(self.img)
self.video_frame.setPixmap(self.pix)
def set_frame_params(self, width, height):
self.screen_width = width
self.screen_height = height
def get_video_frame(self):
self.video_frame.setScaledContents(True)
return self.video_frame
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
# Middle frame
self.mid_frame = QtWidgets.QFrame()
self.mid_frame.setStyleSheet("background-color: rgb(153, 187, 255)")
self.camera = CameraWidget()
# Create camera widgets
print('Creating Camera Widgets...')
self.video_frame = self.camera.get_video_frame()
self.mid_layout = QtWidgets.QHBoxLayout()
self.mid_layout.addWidget(self.video_frame)
self.mid_frame.setLayout(self.mid_layout)
self.widget = QtWidgets.QWidget()
self.layout = QtWidgets.QVBoxLayout()
self.layout.addWidget(self.mid_frame)
self.layout.setContentsMargins(0,0,0,0)
self.layout.setSpacing(0)
self.widget.setLayout(self.layout)
self.setCentralWidget(self.widget)
def event(self, e):
if e.type() in (QtCore.QEvent.Show, QtCore.QEvent.Resize):
print("resize ", self.mid_frame.width(), self.mid_frame.height())
self.camera.set_frame_params(self.mid_frame.width()-10, self.mid_frame.height()-10)
return QtWidgets.QMainWindow.event(self, e)
if __name__ == '__main__':
# Create main application window
app = QtWidgets.QApplication([])
app.setStyle(QtWidgets.QStyleFactory.create("Cleanlooks"))
w = MainWindow()
w.showMaximized()
sys.exit(app.exec_())

Outputting a video feed (OpenCV + picamera) to GUI using Tkinter or guizero in Raspberry PI?

I'm following this tutorial
https://solarianprogrammer.com/2018/04/21/python-opencv-show-video-tkinter-window/
and found out that it is only for a usb camera.
I have this code for using picamera on my OpenCV scripts.
class VideoStream:
def __init__(self, resolution=(640,480),framerate=30):
from picamera.array import PiRGBArray
from picamera import PiCamera
self.camera = PiCamera()
self.camera.resolution = resolution
self.camera.framerate = framerate
self.rawCapture = PiRGBArray(self.camera,size=resolution)
self.stream = self.camera.capture_continuous(
self.rawCapture, format = "bgr", use_video_port = True)
self.frame = []
self.stopped = False
def start(self):
Thread(target=self.update,args=()).start()
return self
def update(self):
for f in self.stream:
self.frame = f.array
self.rawCapture.truncate(0)
if self.stopped:
self.stream.close()
self.rawCapture.close()
self.camera.close()
def read(self):
return self.frame
def stop(self):
self.stopped = True
This works if i'm using the GUI of OpenCV (cv2.imshow) but I want to have the output on a different GUI because I would like to add some texts and buttons to it.

OpenCV-Python VideoCapture.read() does not return False when rtsp connection is interrupted

I have the following class for streaming video through rtsp using OpenCV 3.1 with Python 3.4.3. Everything works fine, but if the camera is suddenly disconnected while running (ie. unplug camera), the program would hang at self.capture.read() and never returns the False value (or any value for that matter) to handle closing the connection. That's my understanding of handling sudden disconnections for VideoCapture in OpenCV. Is there a better way?
"""Classes for video processing"""
import cv2
from PyQt5 import QtGui, QtCore
from settings import CAMERA_IP_ADDRESS, CAMERA_PORT, FRAME_RATE
from structures import MessageLevel
class VideoStream(QtCore.QObject):
"""Class for displaying and recording video data."""
logEvent = QtCore.pyqtSignal(MessageLevel, str)
video_frame = QtCore.pyqtSignal(QtGui.QPixmap)
video_timer = QtCore.QTimer()
def __init__(self, parent=None):
super(VideoStream, self).__init__(parent)
self.connected = False
self.stream = False
self.video_timer.timeout.connect(self.stream_loop)
self.video_width = 1280
self.video_height = 720
self.frame_rate_milliseconds = int(round(1/FRAME_RATE * 1000))
self.save_path = None
self.address_ip = None
self.capture = None
self.video_file = None
self.url = 'rtsp://ip_path_to_camera'
def setup(self):
"""Function to set defautls and update GUI, should only be called once during GUI setup"""
# Initialize variables
self.set_address_ip(CAMERA_IP_ADDRESS)
def connect(self):
"""Connect to rtsp video stream"""
if not self.connected:
self.video_file = cv2.VideoWriter(self.save_path, cv2.VideoWriter_fourcc('X', 'V', 'I', 'D'),
FRAME_RATE, (self.video_width, self.video_height))
self.capture = cv2.VideoCapture(self.url)
if self.capture.isOpened():
self.connected = True
self.stream = True
self.video_timer.start(self.frame_rate_milliseconds)
else:
print('Device failed to connect')
def stop(self):
self.pause()
self.connected = False
self.capture.release()
self.video_file = None
def pause(self):
self.video_timer.stop()
self.stream = False
def stream_loop(self):
if self.stream:
ret, frame = self.capture.read()
if ret:
self.video_file.write(frame)
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
image = QtGui.QImage(frame, self.video_width, self.video_height, QtGui.QImage.Format_RGB888)
self.video_frame.emit(QtGui.QPixmap.fromImage(image))
else:
print('Device diconnected')
self.stop()
def set_address_ip(self, ip_address):
self.address_ip = ip_address
self.address_ip_signal.emit(self.address_ip)
def log_message(self, level, message):
self.logEvent.emit(level, message)

Categories

Resources