Skip to content

Commit a2e0380

Browse files
committed
add new files
1 parent 7e441e3 commit a2e0380

File tree

2 files changed

+160
-0
lines changed

2 files changed

+160
-0
lines changed

base_thread.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
from queue import Queue
2+
import cv2
3+
from PyQt5.QtCore import (QThread, QTime, QMutex, pyqtSignal, QMutexLocker)
4+
5+
from structures import ThreadStatisticsData
6+
7+
8+
class BaseThread(QThread):
9+
10+
"""
11+
Base class for all types of threads (capture, processing, stitching, ...,
12+
etc). Mainly for collecting statistics of this thread.
13+
"""
14+
15+
FPS_STAT_QUEUE_LENGTH = 32
16+
17+
update_statistics_gui = pyqtSignal(ThreadStatisticsData)
18+
19+
def __init__(self, parent=None):
20+
super(BaseThread, self).__init__(parent)
21+
self.init_commons()
22+
23+
def init_commons(self):
24+
self.stopped = False
25+
self.stop_mutex = QMutex()
26+
self.clock = QTime()
27+
self.fps = Queue()
28+
self.processing_time = 0
29+
self.processing_mutex = QMutex()
30+
self.fps_sum = 0
31+
self.stat_data = ThreadStatisticsData()
32+
33+
def stop(self):
34+
with QMutexLocker(self.stop_mutex):
35+
self.stopped = True
36+
37+
def update_fps(self, dt):
38+
# add instantaneous fps value to queue
39+
if dt > 0:
40+
self.fps.put(1000 / dt)
41+
42+
# discard redundant items in the fps queue
43+
if self.fps.qsize() > self.FPS_STAT_QUEUE_LENGTH:
44+
self.fps.get()
45+
46+
# update statistics
47+
if self.fps.qsize() == self.FPS_STAT_QUEUE_LENGTH:
48+
while not self.fps.empty():
49+
self.fps_sum += self.fps.get()
50+
51+
self.stat_data.average_fps = round(self.fps_sum / self.FPS_STAT_QUEUE_LENGTH, 2)
52+
self.fps_sum = 0

capture_thread.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import cv2
2+
from PyQt5.QtCore import qDebug
3+
4+
from base_thread import BaseThread
5+
from structures import ImageFrame
6+
from utils import gstreamer_pipeline
7+
8+
9+
class CaptureThread(BaseThread):
10+
11+
def __init__(self,
12+
device_id,
13+
flip_method=2,
14+
drop_if_full=True,
15+
api_preference=cv2.CAP_GSTREAMER,
16+
resolution=None,
17+
parent=None):
18+
"""
19+
device_id: device number of the camera.
20+
flip_method: 0 for identity, 2 for 180 degree rotation (if the camera is installed
21+
up-side-down).
22+
drop_if_full: drop the frame if buffer is full.
23+
api_preference: cv2.CAP_GSTREAMER for csi cameras, usually cv2.CAP_ANY would suffice.
24+
resolution: camera resolution (width, height).
25+
"""
26+
super(CaptureThread, self).__init__(parent)
27+
self.device_id = device_id
28+
self.flip_method = flip_method
29+
self.drop_if_full = drop_if_full
30+
self.api_preference = api_preference
31+
self.resolution = resolution
32+
self.cap = cv2.VideoCapture()
33+
# an instance of the MultiBufferManager object,
34+
# for synchronizing this thread with other cameras.
35+
self.buffer_manager = None
36+
37+
def run(self):
38+
if self.buffer_manager is None:
39+
raise ValueError("This thread has not been binded to any buffer manager yet")
40+
41+
while True:
42+
self.stop_mutex.lock()
43+
if self.stopped:
44+
self.stopped = False
45+
self.stop_mutex.unlock()
46+
break
47+
self.stop_mutex.unlock()
48+
49+
# save capture time
50+
self.processing_time = self.clock.elapsed()
51+
# start timer (used to calculate capture rate)
52+
self.clock.start()
53+
54+
# synchronize with other streams (if enabled for this stream)
55+
self.buffer_manager.sync(self.device_id)
56+
57+
if not self.cap.grab():
58+
continue
59+
60+
# retrieve frame and add it to buffer
61+
_, frame = self.cap.retrieve()
62+
img_frame = ImageFrame(self.clock.msecsSinceStartOfDay(), frame)
63+
self.buffer_manager.get_device(self.device_id).add(img_frame, self.drop_if_full)
64+
65+
# update statistics
66+
self.update_fps(self.processing_time)
67+
self.stat_data.frames_processed_count += 1
68+
# inform GUI of updated statistics
69+
self.update_statistics_gui.emit(self.stat_data)
70+
71+
qDebug("Stopping capture thread...")
72+
73+
def connect_camera(self):
74+
options = gstreamer_pipeline(cam_id=self.device_id, flip_method=self.flip_method)
75+
self.cap.open(options, self.api_preference)
76+
# return false if failed to open camera
77+
if not self.cap.isOpened():
78+
qDebug("Cannot open camera {}".format(self.device_id))
79+
return False
80+
else:
81+
# try to set camera resolution
82+
if self.resolution is not None:
83+
width, height = self.resolution
84+
self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)
85+
self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)
86+
# some camera may become closed if the resolution is not supported
87+
if not self.cap.isOpened():
88+
qDebug("Resolution not supported by camera device: {}".format(self.resolution))
89+
return False
90+
# use the default resolution
91+
else:
92+
width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
93+
height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
94+
self.resolution = (width, height)
95+
96+
return True
97+
98+
def disconnect_camera(self):
99+
# disconnect camera if it's already openned.
100+
if self.cap.isOpened():
101+
self.cap.release()
102+
return True
103+
# else do nothing and return
104+
else:
105+
return False
106+
107+
def is_camera_connected(self):
108+
return self.cap.isOpened()

0 commit comments

Comments
 (0)