|
|
@ -39,7 +39,7 @@ class LoadStreams:
|
|
|
|
sources = Path(sources).read_text().rsplit() if os.path.isfile(sources) else [sources]
|
|
|
|
sources = Path(sources).read_text().rsplit() if os.path.isfile(sources) else [sources]
|
|
|
|
n = len(sources)
|
|
|
|
n = len(sources)
|
|
|
|
self.sources = [ops.clean_str(x) for x in sources] # clean source names for later
|
|
|
|
self.sources = [ops.clean_str(x) for x in sources] # clean source names for later
|
|
|
|
self.imgs, self.fps, self.frames, self.threads = [None] * n, [0] * n, [0] * n, [None] * n
|
|
|
|
self.imgs, self.fps, self.frames, self.threads, self.shape = [[]] * n, [0] * n, [0] * n, [None] * n, [None] * n
|
|
|
|
for i, s in enumerate(sources): # index, source
|
|
|
|
for i, s in enumerate(sources): # index, source
|
|
|
|
# Start thread to read frames from video stream
|
|
|
|
# Start thread to read frames from video stream
|
|
|
|
st = f'{i + 1}/{n}: {s}... '
|
|
|
|
st = f'{i + 1}/{n}: {s}... '
|
|
|
@ -59,9 +59,11 @@ class LoadStreams:
|
|
|
|
self.frames[i] = max(int(cap.get(cv2.CAP_PROP_FRAME_COUNT)), 0) or float('inf') # infinite stream fallback
|
|
|
|
self.frames[i] = max(int(cap.get(cv2.CAP_PROP_FRAME_COUNT)), 0) or float('inf') # infinite stream fallback
|
|
|
|
self.fps[i] = max((fps if math.isfinite(fps) else 0) % 100, 0) or 30 # 30 FPS fallback
|
|
|
|
self.fps[i] = max((fps if math.isfinite(fps) else 0) % 100, 0) or 30 # 30 FPS fallback
|
|
|
|
|
|
|
|
|
|
|
|
success, self.imgs[i] = cap.read() # guarantee first frame
|
|
|
|
success, im = cap.read() # guarantee first frame
|
|
|
|
if not success or self.imgs[i] is None:
|
|
|
|
if not success or im is None:
|
|
|
|
raise ConnectionError(f'{st}Failed to read images from {s}')
|
|
|
|
raise ConnectionError(f'{st}Failed to read images from {s}')
|
|
|
|
|
|
|
|
self.imgs[i].append(im)
|
|
|
|
|
|
|
|
self.shape[i] = im.shape
|
|
|
|
self.threads[i] = Thread(target=self.update, args=([i, cap, s]), daemon=True)
|
|
|
|
self.threads[i] = Thread(target=self.update, args=([i, cap, s]), daemon=True)
|
|
|
|
LOGGER.info(f'{st}Success ✅ ({self.frames[i]} frames of shape {w}x{h} at {self.fps[i]:.2f} FPS)')
|
|
|
|
LOGGER.info(f'{st}Success ✅ ({self.frames[i]} frames of shape {w}x{h} at {self.fps[i]:.2f} FPS)')
|
|
|
|
self.threads[i].start()
|
|
|
|
self.threads[i].start()
|
|
|
@ -74,17 +76,20 @@ class LoadStreams:
|
|
|
|
"""Read stream `i` frames in daemon thread."""
|
|
|
|
"""Read stream `i` frames in daemon thread."""
|
|
|
|
n, f = 0, self.frames[i] # frame number, frame array
|
|
|
|
n, f = 0, self.frames[i] # frame number, frame array
|
|
|
|
while cap.isOpened() and n < f:
|
|
|
|
while cap.isOpened() and n < f:
|
|
|
|
n += 1
|
|
|
|
# Only read a new frame if the buffer is empty
|
|
|
|
cap.grab() # .read() = .grab() followed by .retrieve()
|
|
|
|
if not self.imgs[i]:
|
|
|
|
if n % self.vid_stride == 0:
|
|
|
|
n += 1
|
|
|
|
success, im = cap.retrieve()
|
|
|
|
cap.grab() # .read() = .grab() followed by .retrieve()
|
|
|
|
if success:
|
|
|
|
if n % self.vid_stride == 0:
|
|
|
|
self.imgs[i] = im
|
|
|
|
success, im = cap.retrieve()
|
|
|
|
else:
|
|
|
|
if success:
|
|
|
|
LOGGER.warning('WARNING ⚠️ Video stream unresponsive, please check your IP camera connection.')
|
|
|
|
self.imgs[i].append(im) # add image to buffer
|
|
|
|
self.imgs[i] = np.zeros_like(self.imgs[i])
|
|
|
|
else:
|
|
|
|
cap.open(stream) # re-open stream if signal was lost
|
|
|
|
LOGGER.warning('WARNING ⚠️ Video stream unresponsive, please check your IP camera connection.')
|
|
|
|
time.sleep(0.0) # wait time
|
|
|
|
self.imgs[i].append(np.zeros(self.shape[i]))
|
|
|
|
|
|
|
|
cap.open(stream) # re-open stream if signal was lost
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
time.sleep(0.01) # wait until the buffer is empty
|
|
|
|
|
|
|
|
|
|
|
|
def __iter__(self):
|
|
|
|
def __iter__(self):
|
|
|
|
"""Iterates through YOLO image feed and re-opens unresponsive streams."""
|
|
|
|
"""Iterates through YOLO image feed and re-opens unresponsive streams."""
|
|
|
@ -92,14 +97,18 @@ class LoadStreams:
|
|
|
|
return self
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
|
|
def __next__(self):
|
|
|
|
def __next__(self):
|
|
|
|
"""Returns source paths, transformed and original images for processing YOLOv5."""
|
|
|
|
"""Returns source paths, transformed and original images for processing."""
|
|
|
|
self.count += 1
|
|
|
|
self.count += 1
|
|
|
|
if not all(x.is_alive() for x in self.threads) or cv2.waitKey(1) == ord('q'): # q to quit
|
|
|
|
|
|
|
|
cv2.destroyAllWindows()
|
|
|
|
|
|
|
|
raise StopIteration
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
im0 = self.imgs.copy()
|
|
|
|
# Wait until a frame is available in each buffer
|
|
|
|
return self.sources, im0, None, ''
|
|
|
|
while not all(self.imgs):
|
|
|
|
|
|
|
|
if not all(x.is_alive() for x in self.threads) or cv2.waitKey(1) == ord('q'): # q to quit
|
|
|
|
|
|
|
|
cv2.destroyAllWindows()
|
|
|
|
|
|
|
|
raise StopIteration
|
|
|
|
|
|
|
|
time.sleep(1 / min(self.fps))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Get and remove the next frame from imgs buffer
|
|
|
|
|
|
|
|
return self.sources, [x.pop(0) for x in self.imgs], None, ''
|
|
|
|
|
|
|
|
|
|
|
|
def __len__(self):
|
|
|
|
def __len__(self):
|
|
|
|
"""Return the length of the sources object."""
|
|
|
|
"""Return the length of the sources object."""
|
|
|
|