🎬 poc of video utils documentation
This commit is contained in:
parent
8d14a4d93b
commit
1561bd0027
|
|
@ -0,0 +1,18 @@
|
|||
name: Docs WorkFlow
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.x
|
||||
- run: pip install mkdocs-material
|
||||
- run: pip install "mkdocstrings[python]"
|
||||
- run: mkdocs gh-deploy --force
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
name: Welcome WorkFlow
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
|
|
|||
29
README.md
29
README.md
|
|
@ -61,18 +61,6 @@ A set of easy-to-use utils that will come in handy in any Computer Vision projec
|
|||
pre-release stage. 🚧 Keep your eyes open for potential bugs and be aware that at this stage our API is still fluid
|
||||
and may change.
|
||||
|
||||
## 🎬 videos
|
||||
|
||||
Learn how to use YOLOv8, ByteTrack and **Supervision** to detect, track and count objects. 🔥
|
||||
[Subscribe](https://www.youtube.com/@Roboflow), and stay up to date with our latest YouTube videos!
|
||||
|
||||
<p align="center">
|
||||
<a href="https://youtu.be/OS5qI9YBkfk">
|
||||
<img src="https://user-images.githubusercontent.com/26109316/213702005-ddd568f0-b902-46c2-9af9-e6ff33db23bf.jpg" alt="latest-roboflow-tutorial">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
|
||||
## 💻 install
|
||||
|
||||
Pip install the supervision package in a
|
||||
|
|
@ -100,9 +88,18 @@ pip install -e ".[dev]"
|
|||
|
||||
</details>
|
||||
|
||||
## 🎬 videos
|
||||
|
||||
Learn how to use YOLOv8, ByteTrack and **Supervision** to detect, track and count objects. 🔥
|
||||
[Subscribe](https://www.youtube.com/@Roboflow), and stay up to date with our latest YouTube videos!
|
||||
|
||||
## 🧹 Code Quality
|
||||
<p align="center">
|
||||
<a href="https://youtu.be/OS5qI9YBkfk">
|
||||
<img src="https://user-images.githubusercontent.com/26109316/213702005-ddd568f0-b902-46c2-9af9-e6ff33db23bf.jpg" alt="latest-roboflow-tutorial">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## 🧹 code quality
|
||||
|
||||
We provide two handy commands inside the `Makefile`, namely:
|
||||
|
||||
|
|
@ -111,6 +108,10 @@ We provide two handy commands inside the `Makefile`, namely:
|
|||
|
||||
So far, **there is no types checking with mypy**. See [issue](https://github.com/roboflow-ai/template-python/issues/4).
|
||||
|
||||
## 🧪 Tests
|
||||
## 🧪 tests
|
||||
|
||||
[`pytests`](https://docs.pytest.org/en/7.1.x/) is used to run our tests.
|
||||
|
||||
## 🪪
|
||||
|
||||
Supervision is available under the MIT license - see the [LICENSE](https://github.com/roboflow/supervision/blob/main/LICENSE.md) file for details.
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<div align="center">
|
||||
<p>
|
||||
<a align="center" href="" target="_blank">
|
||||
<img
|
||||
width="850"
|
||||
src="https://media.roboflow.com/open-source/supervision/roboflow-supervision-banner.png?ik-sdk-version=javascript-1.4.3&updatedAt=1674062891088"
|
||||
>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
## 👋 hello
|
||||
|
||||
A set of easy-to-use utils that will come in handy in any Computer Vision project. **Supervision** is still in
|
||||
pre-release stage. 🚧 Keep your eyes open for potential bugs and be aware that at this stage our API is still fluid
|
||||
and may change.
|
||||
|
||||
## 💻 install
|
||||
|
||||
Pip install the supervision package in a
|
||||
[**3.10>=Python>=3.7**](https://www.python.org/) environment.
|
||||
|
||||
!!! example "Pip install method (recommended)"
|
||||
|
||||
```bash
|
||||
pip install subervision
|
||||
```
|
||||
|
||||
!!! example "Git clone method (for development)"
|
||||
|
||||
```bash
|
||||
git https://github.com/roboflow/supervision.git
|
||||
cd supervision
|
||||
pip install -e '.[dev]'
|
||||
```
|
||||
See contributing section to know more about contributing to the project
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
## VideoInfo
|
||||
|
||||
:::supervision.video.VideoInfo
|
||||
|
||||
## VideoSink
|
||||
|
||||
:::supervision.video.VideoSink
|
||||
|
||||
## get_video_frames_generator
|
||||
|
||||
:::supervision.video.get_video_frames_generator
|
||||
|
||||
## process_video
|
||||
|
||||
:::supervision.video.process_video
|
||||
20
mkdocs.yml
20
mkdocs.yml
|
|
@ -1,4 +1,4 @@
|
|||
site_name: supervision
|
||||
site_name: Supervision
|
||||
site_url: https://roboflow.github.io/supervision
|
||||
site_author: Roboflow
|
||||
site_description: A set of easy-to-use utils that will come in handy in any Computer Vision project
|
||||
|
|
@ -9,17 +9,35 @@ copyright: Roboflow 2023. All rights reserved.
|
|||
|
||||
extra:
|
||||
social:
|
||||
- icon: fontawesome/brands/github
|
||||
link: https://github.com/roboflow
|
||||
- icon: fontawesome/brands/youtube
|
||||
link: https://www.youtube.com/roboflow
|
||||
- icon: fontawesome/brands/linkedin
|
||||
link: https://www.linkedin.com/company/roboflow-ai/mycompany/
|
||||
- icon: fontawesome/brands/twitter
|
||||
link: https://twitter.com/roboflow
|
||||
|
||||
nav:
|
||||
- Home: index.md
|
||||
- Video: video.md
|
||||
|
||||
theme:
|
||||
name: 'material'
|
||||
logo: https://raw.githubusercontent.com/roboflow/supervision/main/docs/assets/roboflow_logomark_white.svg
|
||||
favicon: https://raw.githubusercontent.com/roboflow/supervision/main/docs/assets/roboflow_logomark_color.svg
|
||||
palette:
|
||||
primary: 'deep purple'
|
||||
accent: 'teal'
|
||||
font:
|
||||
text: Roboto
|
||||
code: Roboto Mono
|
||||
|
||||
plugins:
|
||||
- mkdocstrings
|
||||
- search
|
||||
|
||||
markdown_extensions:
|
||||
- admonition
|
||||
- pymdownx.details
|
||||
- pymdownx.superfences
|
||||
|
|
|
|||
3
setup.py
3
setup.py
|
|
@ -37,7 +37,8 @@ setuptools.setup(
|
|||
'pytest',
|
||||
'wheel',
|
||||
'notebook',
|
||||
'mkdocs-material'
|
||||
'mkdocs-material',
|
||||
'mkdocstrings[python]'
|
||||
],
|
||||
},
|
||||
classifiers=[
|
||||
|
|
|
|||
|
|
@ -0,0 +1,151 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Callable, Generator, Optional, Tuple
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
|
||||
class VideoInfo:
|
||||
"""
|
||||
A class to store video information, including width, height, fps and total number of frames.
|
||||
|
||||
Attributes:
|
||||
width (int): width of the video in pixels
|
||||
height (int): height of the video in pixels
|
||||
fps (int): frames per second of the video
|
||||
total_frames (int, optional): total number of frames in the video, default is None
|
||||
|
||||
Examples:
|
||||
```python
|
||||
>>> from supervision.video import VideoInfo
|
||||
|
||||
>>> video_info = VideoInfo.from_video_path(video_path='video.mp4')
|
||||
|
||||
>>> video_info
|
||||
VideoInfo(width=3840, height=2160, fps=25, total_frames=538)
|
||||
```
|
||||
"""
|
||||
def __init__(self, width: int, height: int, fps: int, total_frames: Optional[int] = None):
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.fps = fps
|
||||
self.total_frames = total_frames
|
||||
|
||||
@classmethod
|
||||
def from_video_path(cls, video_path: str) -> VideoInfo:
|
||||
video = cv2.VideoCapture(video_path)
|
||||
if not video.isOpened():
|
||||
raise Exception(f"Could not open video at {video_path}")
|
||||
|
||||
width = int(video.get(cv2.CAP_PROP_FRAME_WIDTH))
|
||||
height = int(video.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
||||
fps = int(video.get(cv2.CAP_PROP_FPS))
|
||||
total_frames = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
|
||||
video.release()
|
||||
return VideoInfo(width, height, fps, total_frames)
|
||||
|
||||
@property
|
||||
def resolution(self) -> Tuple[int, int]:
|
||||
return self.width, self.height
|
||||
|
||||
|
||||
class VideoSink:
|
||||
"""
|
||||
Context manager that saves video frames to a file using OpenCV.
|
||||
|
||||
Attributes:
|
||||
target_path (str): The path to the output file where the video will be saved.
|
||||
video_info (VideoInfo): Information about the video resolution, fps, and total frame count.
|
||||
|
||||
Examples:
|
||||
```python
|
||||
>>> from supervision.video import VideoInfo
|
||||
>>> from supervision.video import VideoSink
|
||||
|
||||
>>> video_info = VideoInfo.from_video_path(video_path='source_video.mp4')
|
||||
|
||||
>>> with VideoSink(target_path='target_video.mp4', video_info=video_info) as s:
|
||||
... frame = ...
|
||||
... s.write_frame(frame=frame)
|
||||
```
|
||||
"""
|
||||
|
||||
def __init__(self, target_path: str, video_info: VideoInfo):
|
||||
self.target_path = target_path
|
||||
self.video_info = video_info
|
||||
self.__fourcc = cv2.VideoWriter_fourcc(*"mp4v")
|
||||
self.__writer = None
|
||||
|
||||
def __enter__(self):
|
||||
self.__writer = cv2.VideoWriter(
|
||||
self.target_path,
|
||||
self.__fourcc,
|
||||
self.video_info.fps,
|
||||
self.video_info.resolution,
|
||||
)
|
||||
return self
|
||||
|
||||
def write_frame(self, frame: np.ndarray):
|
||||
self.__writer.write(frame)
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.__writer.release()
|
||||
|
||||
|
||||
def get_video_frames_generator(source_path: str) -> Generator[np.ndarray, None, None]:
|
||||
"""
|
||||
Get a generator that yields the frames of the video.
|
||||
|
||||
Args:
|
||||
source_path (str): The path of the video file.
|
||||
|
||||
Returns:
|
||||
(Generator[np.ndarray, None, None]): A generator that yields the frames of the video.
|
||||
|
||||
Examples:
|
||||
```python
|
||||
>>> from supervision.video import get_video_frames_generator
|
||||
|
||||
>>> for frame in get_video_frames_generator(source_path='source_video.mp4'):
|
||||
... ...
|
||||
```
|
||||
"""
|
||||
video = cv2.VideoCapture(source_path)
|
||||
if not video.isOpened():
|
||||
raise Exception(f"Could not open video at {source_path}")
|
||||
success, frame = video.read()
|
||||
while success:
|
||||
yield frame
|
||||
success, frame = video.read()
|
||||
video.release()
|
||||
|
||||
|
||||
def process_video(source_path: str, target_path: str, callback: Callable[[np.ndarray, int], np.ndarray]) -> None:
|
||||
"""
|
||||
Process a video file by applying a callback function on each frame and saving the result to a target video file.
|
||||
|
||||
Args:
|
||||
source_path (str): The path to the source video file.
|
||||
target_path (str): The path to the target video file.
|
||||
callback (Callable[[np.ndarray, int], np.ndarray]): A function that takes in a numpy ndarray representation of a video frame and an int index of the frame and returns a processed numpy ndarray representation of the frame.
|
||||
|
||||
Examples:
|
||||
```python
|
||||
>>> from supervision.video import process_video
|
||||
|
||||
>>> def process_frame(frame: np.ndarray) -> np.ndarray:
|
||||
... ...
|
||||
|
||||
>>> process_video(
|
||||
... source_path='source_video.mp4',
|
||||
... target_path='target_video.mp4',
|
||||
... callback=process_frame
|
||||
... )
|
||||
```
|
||||
"""
|
||||
source_video_info = VideoInfo.from_video_path(video_path=source_path)
|
||||
with VideoSink(target_path=target_path, video_info=source_video_info) as sink:
|
||||
for index, frame in enumerate(get_video_frames_generator(source_path=source_path)):
|
||||
result_frame = callback(frame, index)
|
||||
sink.write_frame(frame=result_frame)
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import cv2
|
||||
|
||||
|
||||
@dataclass
|
||||
class VideoInfo:
|
||||
"""
|
||||
Data class containing information about the video resolution, fps, and total frame count.
|
||||
|
||||
:param width: int : The width of the video frames in pixels.
|
||||
:param height: int : The height of the video frames in pixels.
|
||||
:param fps: int : The frames per second of the video.
|
||||
:param total_frames: int : The total number of frames in the video.
|
||||
"""
|
||||
|
||||
width: int
|
||||
height: int
|
||||
fps: int
|
||||
total_frames: Optional[int] = None
|
||||
|
||||
@classmethod
|
||||
def from_video_path(cls, video_path: str) -> VideoInfo:
|
||||
"""
|
||||
Returns a VideoInfo data class containing information about the video resolution, fps, and total frame count.
|
||||
|
||||
:param video_path: str : The path of the video file.
|
||||
:return: VideoInfo : A data class containing information about the video resolution, fps, and total frame count.
|
||||
"""
|
||||
video = cv2.VideoCapture(video_path)
|
||||
if not video.isOpened():
|
||||
raise Exception(f"Could not open video at {video_path}")
|
||||
|
||||
width = int(video.get(cv2.CAP_PROP_FRAME_WIDTH))
|
||||
height = int(video.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
||||
fps = int(video.get(cv2.CAP_PROP_FPS))
|
||||
total_frames = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
|
||||
video.release()
|
||||
return VideoInfo(width, height, fps, total_frames)
|
||||
|
||||
@property
|
||||
def resolution(self) -> Tuple[int, int]:
|
||||
return self.width, self.height
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
import cv2
|
||||
import numpy as np
|
||||
|
||||
from supervision.video.dataclasses import VideoInfo
|
||||
|
||||
|
||||
class VideoSink:
|
||||
"""
|
||||
A context manager that uses OpenCV to save video frames to a file.
|
||||
|
||||
:param output_path: str : The path to the output file where the video will be saved.
|
||||
:param video_info: VideoInfo : An instance of VideoInfo containing information about the video resolution, fps, and total frame count.
|
||||
"""
|
||||
|
||||
def __init__(self, output_path: str, video_info: VideoInfo):
|
||||
"""
|
||||
Initializes the VideoSink with the specified output path and video information.
|
||||
"""
|
||||
self.output_path = output_path
|
||||
self.video_info = video_info
|
||||
self.fourcc = cv2.VideoWriter_fourcc(*"mp4v")
|
||||
self.writer = None
|
||||
|
||||
def __enter__(self):
|
||||
"""
|
||||
Opens the output file and returns the VideoSink instance.
|
||||
"""
|
||||
self.writer = cv2.VideoWriter(
|
||||
self.output_path,
|
||||
self.fourcc,
|
||||
self.video_info.fps,
|
||||
self.video_info.resolution,
|
||||
)
|
||||
return self
|
||||
|
||||
def write_frame(self, frame: np.ndarray):
|
||||
"""
|
||||
Writes a frame to the output video file.
|
||||
|
||||
:param frame: np.ndarray : The frame to be written.
|
||||
"""
|
||||
self.writer.write(frame)
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
"""
|
||||
Closes the output file.
|
||||
"""
|
||||
self.writer.release()
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
from typing import Generator
|
||||
|
||||
import cv2
|
||||
|
||||
|
||||
def get_video_frames_generator(video_path: str) -> Generator[int, None, None]:
|
||||
"""
|
||||
Returns a generator that yields the frames of the video.
|
||||
|
||||
:param video_path: str : The path of the video file.
|
||||
:return: Generator[int, None, None] : Generator that yields the frames of the video.
|
||||
"""
|
||||
video = cv2.VideoCapture(video_path)
|
||||
if not video.isOpened():
|
||||
raise Exception(f"Could not open video at {video_path}")
|
||||
success, frame = video.read()
|
||||
while success:
|
||||
yield frame
|
||||
success, frame = video.read()
|
||||
video.release()
|
||||
Loading…
Reference in New Issue