| | |
| |
|
| | import glob |
| | import math |
| | import os |
| | import time |
| | from dataclasses import dataclass |
| | from pathlib import Path |
| | from threading import Thread |
| | from urllib.parse import urlparse |
| |
|
| | import cv2 |
| | import numpy as np |
| | import requests |
| | import torch |
| | from PIL import Image |
| |
|
| | from ultralytics.yolo.data.utils import IMG_FORMATS, VID_FORMATS |
| | from ultralytics.yolo.utils import LOGGER, ROOT, is_colab, is_kaggle, ops |
| | from ultralytics.yolo.utils.checks import check_requirements |
| |
|
| |
|
| | @dataclass |
| | class SourceTypes: |
| | webcam: bool = False |
| | screenshot: bool = False |
| | from_img: bool = False |
| | tensor: bool = False |
| |
|
| |
|
| | class LoadStreams: |
| | |
| | def __init__(self, sources='file.streams', imgsz=640, vid_stride=1): |
| | """Initialize instance variables and check for consistent input stream shapes.""" |
| | torch.backends.cudnn.benchmark = True |
| | self.mode = 'stream' |
| | self.imgsz = imgsz |
| | self.vid_stride = vid_stride |
| | sources = Path(sources).read_text().rsplit() if os.path.isfile(sources) else [sources] |
| | n = len(sources) |
| | self.sources = [ops.clean_str(x) for x in sources] |
| | self.imgs, self.fps, self.frames, self.threads = [None] * n, [0] * n, [0] * n, [None] * n |
| | for i, s in enumerate(sources): |
| | |
| | st = f'{i + 1}/{n}: {s}... ' |
| | if urlparse(s).hostname in ('www.youtube.com', 'youtube.com', 'youtu.be'): |
| | |
| | s = get_best_youtube_url(s) |
| | s = eval(s) if s.isnumeric() else s |
| | if s == 0 and (is_colab() or is_kaggle()): |
| | raise NotImplementedError("'source=0' webcam not supported in Colab and Kaggle notebooks. " |
| | "Try running 'source=0' in a local environment.") |
| | cap = cv2.VideoCapture(s) |
| | if not cap.isOpened(): |
| | raise ConnectionError(f'{st}Failed to open {s}') |
| | w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) |
| | h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) |
| | fps = cap.get(cv2.CAP_PROP_FPS) |
| | self.frames[i] = max(int(cap.get(cv2.CAP_PROP_FRAME_COUNT)), 0) or float('inf') |
| | self.fps[i] = max((fps if math.isfinite(fps) else 0) % 100, 0) or 30 |
| |
|
| | success, self.imgs[i] = cap.read() |
| | if not success or self.imgs[i] is None: |
| | raise ConnectionError(f'{st}Failed to read images from {s}') |
| | 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)') |
| | self.threads[i].start() |
| | LOGGER.info('') |
| |
|
| | |
| | self.bs = self.__len__() |
| |
|
| | def update(self, i, cap, stream): |
| | """Read stream `i` frames in daemon thread.""" |
| | n, f = 0, self.frames[i] |
| | while cap.isOpened() and n < f: |
| | n += 1 |
| | cap.grab() |
| | if n % self.vid_stride == 0: |
| | success, im = cap.retrieve() |
| | if success: |
| | self.imgs[i] = im |
| | else: |
| | LOGGER.warning('WARNING ⚠️ Video stream unresponsive, please check your IP camera connection.') |
| | self.imgs[i] = np.zeros_like(self.imgs[i]) |
| | cap.open(stream) |
| | time.sleep(0.0) |
| |
|
| | def __iter__(self): |
| | """Iterates through YOLO image feed and re-opens unresponsive streams.""" |
| | self.count = -1 |
| | return self |
| |
|
| | def __next__(self): |
| | """Returns source paths, transformed and original images for processing YOLOv5.""" |
| | self.count += 1 |
| | if not all(x.is_alive() for x in self.threads) or cv2.waitKey(1) == ord('q'): |
| | cv2.destroyAllWindows() |
| | raise StopIteration |
| |
|
| | im0 = self.imgs.copy() |
| | return self.sources, im0, None, '' |
| |
|
| | def __len__(self): |
| | """Return the length of the sources object.""" |
| | return len(self.sources) |
| |
|
| |
|
| | class LoadScreenshots: |
| | |
| | def __init__(self, source, imgsz=640): |
| | """source = [screen_number left top width height] (pixels).""" |
| | check_requirements('mss') |
| | import mss |
| |
|
| | source, *params = source.split() |
| | self.screen, left, top, width, height = 0, None, None, None, None |
| | if len(params) == 1: |
| | self.screen = int(params[0]) |
| | elif len(params) == 4: |
| | left, top, width, height = (int(x) for x in params) |
| | elif len(params) == 5: |
| | self.screen, left, top, width, height = (int(x) for x in params) |
| | self.imgsz = imgsz |
| | self.mode = 'stream' |
| | self.frame = 0 |
| | self.sct = mss.mss() |
| | self.bs = 1 |
| |
|
| | |
| | monitor = self.sct.monitors[self.screen] |
| | self.top = monitor['top'] if top is None else (monitor['top'] + top) |
| | self.left = monitor['left'] if left is None else (monitor['left'] + left) |
| | self.width = width or monitor['width'] |
| | self.height = height or monitor['height'] |
| | self.monitor = {'left': self.left, 'top': self.top, 'width': self.width, 'height': self.height} |
| |
|
| | def __iter__(self): |
| | """Returns an iterator of the object.""" |
| | return self |
| |
|
| | def __next__(self): |
| | """mss screen capture: get raw pixels from the screen as np array.""" |
| | im0 = np.array(self.sct.grab(self.monitor))[:, :, :3] |
| | s = f'screen {self.screen} (LTWH): {self.left},{self.top},{self.width},{self.height}: ' |
| |
|
| | self.frame += 1 |
| | return str(self.screen), im0, None, s |
| |
|
| |
|
| | class LoadImages: |
| | |
| | def __init__(self, path, imgsz=640, vid_stride=1): |
| | """Initialize the Dataloader and raise FileNotFoundError if file not found.""" |
| | if isinstance(path, str) and Path(path).suffix == '.txt': |
| | path = Path(path).read_text().rsplit() |
| | files = [] |
| | for p in sorted(path) if isinstance(path, (list, tuple)) else [path]: |
| | p = str(Path(p).absolute()) |
| | if '*' in p: |
| | files.extend(sorted(glob.glob(p, recursive=True))) |
| | elif os.path.isdir(p): |
| | files.extend(sorted(glob.glob(os.path.join(p, '*.*')))) |
| | elif os.path.isfile(p): |
| | files.append(p) |
| | else: |
| | raise FileNotFoundError(f'{p} does not exist') |
| |
|
| | images = [x for x in files if x.split('.')[-1].lower() in IMG_FORMATS] |
| | videos = [x for x in files if x.split('.')[-1].lower() in VID_FORMATS] |
| | ni, nv = len(images), len(videos) |
| |
|
| | self.imgsz = imgsz |
| | self.files = images + videos |
| | self.nf = ni + nv |
| | self.video_flag = [False] * ni + [True] * nv |
| | self.mode = 'image' |
| | self.vid_stride = vid_stride |
| | self.bs = 1 |
| | if any(videos): |
| | self.orientation = None |
| | self._new_video(videos[0]) |
| | else: |
| | self.cap = None |
| | if self.nf == 0: |
| | raise FileNotFoundError(f'No images or videos found in {p}. ' |
| | f'Supported formats are:\nimages: {IMG_FORMATS}\nvideos: {VID_FORMATS}') |
| |
|
| | def __iter__(self): |
| | """Returns an iterator object for VideoStream or ImageFolder.""" |
| | self.count = 0 |
| | return self |
| |
|
| | def __next__(self): |
| | """Return next image, path and metadata from dataset.""" |
| | if self.count == self.nf: |
| | raise StopIteration |
| | path = self.files[self.count] |
| |
|
| | if self.video_flag[self.count]: |
| | |
| | self.mode = 'video' |
| | for _ in range(self.vid_stride): |
| | self.cap.grab() |
| | success, im0 = self.cap.retrieve() |
| | while not success: |
| | self.count += 1 |
| | self.cap.release() |
| | if self.count == self.nf: |
| | raise StopIteration |
| | path = self.files[self.count] |
| | self._new_video(path) |
| | success, im0 = self.cap.read() |
| |
|
| | self.frame += 1 |
| | |
| | s = f'video {self.count + 1}/{self.nf} ({self.frame}/{self.frames}) {path}: ' |
| |
|
| | else: |
| | |
| | self.count += 1 |
| | im0 = cv2.imread(path) |
| | if im0 is None: |
| | raise FileNotFoundError(f'Image Not Found {path}') |
| | s = f'image {self.count}/{self.nf} {path}: ' |
| |
|
| | return [path], [im0], self.cap, s |
| |
|
| | def _new_video(self, path): |
| | """Create a new video capture object.""" |
| | self.frame = 0 |
| | self.cap = cv2.VideoCapture(path) |
| | self.frames = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT) / self.vid_stride) |
| | if hasattr(cv2, 'CAP_PROP_ORIENTATION_META'): |
| | self.orientation = int(self.cap.get(cv2.CAP_PROP_ORIENTATION_META)) |
| | |
| | |
| |
|
| | def _cv2_rotate(self, im): |
| | """Rotate a cv2 video manually.""" |
| | if self.orientation == 0: |
| | return cv2.rotate(im, cv2.ROTATE_90_CLOCKWISE) |
| | elif self.orientation == 180: |
| | return cv2.rotate(im, cv2.ROTATE_90_COUNTERCLOCKWISE) |
| | elif self.orientation == 90: |
| | return cv2.rotate(im, cv2.ROTATE_180) |
| | return im |
| |
|
| | def __len__(self): |
| | """Returns the number of files in the object.""" |
| | return self.nf |
| |
|
| |
|
| | class LoadPilAndNumpy: |
| |
|
| | def __init__(self, im0, imgsz=640): |
| | """Initialize PIL and Numpy Dataloader.""" |
| | if not isinstance(im0, list): |
| | im0 = [im0] |
| | self.paths = [getattr(im, 'filename', f'image{i}.jpg') for i, im in enumerate(im0)] |
| | self.im0 = [self._single_check(im) for im in im0] |
| | self.imgsz = imgsz |
| | self.mode = 'image' |
| | |
| | self.bs = len(self.im0) |
| |
|
| | @staticmethod |
| | def _single_check(im): |
| | """Validate and format an image to numpy array.""" |
| | assert isinstance(im, (Image.Image, np.ndarray)), f'Expected PIL/np.ndarray image type, but got {type(im)}' |
| | if isinstance(im, Image.Image): |
| | if im.mode != 'RGB': |
| | im = im.convert('RGB') |
| | im = np.asarray(im)[:, :, ::-1] |
| | im = np.ascontiguousarray(im) |
| | return im |
| |
|
| | def __len__(self): |
| | """Returns the length of the 'im0' attribute.""" |
| | return len(self.im0) |
| |
|
| | def __next__(self): |
| | """Returns batch paths, images, processed images, None, ''.""" |
| | if self.count == 1: |
| | raise StopIteration |
| | self.count += 1 |
| | return self.paths, self.im0, None, '' |
| |
|
| | def __iter__(self): |
| | """Enables iteration for class LoadPilAndNumpy.""" |
| | self.count = 0 |
| | return self |
| |
|
| |
|
| | class LoadTensor: |
| |
|
| | def __init__(self, imgs) -> None: |
| | self.im0 = imgs |
| | self.bs = imgs.shape[0] |
| | self.mode = 'image' |
| |
|
| | def __iter__(self): |
| | """Returns an iterator object.""" |
| | self.count = 0 |
| | return self |
| |
|
| | def __next__(self): |
| | """Return next item in the iterator.""" |
| | if self.count == 1: |
| | raise StopIteration |
| | self.count += 1 |
| | return None, self.im0, None, '' |
| |
|
| | def __len__(self): |
| | """Returns the batch size.""" |
| | return self.bs |
| |
|
| |
|
| | def autocast_list(source): |
| | """ |
| | Merges a list of source of different types into a list of numpy arrays or PIL images |
| | """ |
| | files = [] |
| | for im in source: |
| | if isinstance(im, (str, Path)): |
| | files.append(Image.open(requests.get(im, stream=True).raw if str(im).startswith('http') else im)) |
| | elif isinstance(im, (Image.Image, np.ndarray)): |
| | files.append(im) |
| | else: |
| | raise TypeError(f'type {type(im).__name__} is not a supported Ultralytics prediction source type. \n' |
| | f'See https://docs.ultralytics.com/modes/predict for supported source types.') |
| |
|
| | return files |
| |
|
| |
|
| | LOADERS = [LoadStreams, LoadPilAndNumpy, LoadImages, LoadScreenshots] |
| |
|
| |
|
| | def get_best_youtube_url(url, use_pafy=True): |
| | """ |
| | Retrieves the URL of the best quality MP4 video stream from a given YouTube video. |
| | |
| | This function uses the pafy or yt_dlp library to extract the video info from YouTube. It then finds the highest |
| | quality MP4 format that has video codec but no audio codec, and returns the URL of this video stream. |
| | |
| | Args: |
| | url (str): The URL of the YouTube video. |
| | use_pafy (bool): Use the pafy package, default=True, otherwise use yt_dlp package. |
| | |
| | Returns: |
| | (str): The URL of the best quality MP4 video stream, or None if no suitable stream is found. |
| | """ |
| | if use_pafy: |
| | check_requirements(('pafy', 'youtube_dl==2020.12.2')) |
| | import pafy |
| | return pafy.new(url).getbest(preftype='mp4').url |
| | else: |
| | check_requirements('yt-dlp') |
| | import yt_dlp |
| | with yt_dlp.YoutubeDL({'quiet': True}) as ydl: |
| | info_dict = ydl.extract_info(url, download=False) |
| | for f in info_dict.get('formats', None): |
| | if f['vcodec'] != 'none' and f['acodec'] == 'none' and f['ext'] == 'mp4': |
| | return f.get('url', None) |
| |
|
| |
|
| | if __name__ == '__main__': |
| | img = cv2.imread(str(ROOT / 'assets/bus.jpg')) |
| | dataset = LoadPilAndNumpy(im0=img) |
| | for d in dataset: |
| | print(d[0]) |
| |
|