Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e76e3db0f | ||
|
|
a0eb6c3237 | ||
|
|
fd35ad9653 | ||
|
|
14d23eb9df | ||
|
|
602d3af009 | ||
|
|
b8b08cacd3 | ||
|
|
c8868c3412 | ||
|
|
52a8193b4a | ||
|
|
51a1655bd4 | ||
|
|
38bf4b0f54 | ||
|
|
05705bd4f2 | ||
|
|
940bcb8bc5 | ||
|
|
a64a252ae3 | ||
|
|
4afc650eb2 | ||
|
|
8d091a7a00 | ||
|
|
ccafa3339d | ||
|
|
807785a820 | ||
|
|
66f81ceeb9 | ||
|
|
b7c1791236 | ||
|
|
0ac184b01d | ||
|
|
307ec8a042 | ||
|
|
d5d7187fe4 | ||
|
|
0e3851506c | ||
|
|
301f1df386 | ||
|
|
ceabb83ac6 | ||
|
|
dfb757b8cd | ||
|
|
ed0fd7b768 | ||
|
|
5b2f8e4dcc | ||
|
|
465830f3f6 | ||
|
|
ae84cdeb02 | ||
|
|
4fbec12b06 | ||
|
|
b5e33c5b30 | ||
|
|
5a3df85fc6 | ||
|
|
61a2f4e0e6 | ||
|
|
90a26747b6 | ||
|
|
507f08f27f | ||
|
|
fcf30b79d4 | ||
|
|
a7b26abe0e | ||
|
|
9590f3845e | ||
|
|
8d8bd3b997 | ||
|
|
6b11a1fdad | ||
|
|
cb485878cc | ||
|
|
8e70a3cd46 | ||
|
|
123dd77ba4 | ||
|
|
7687d4a873 | ||
|
|
d941fe256d | ||
|
|
d4a2adde9e | ||
|
|
8b229427e5 | ||
|
|
b4641245c1 | ||
|
|
bfcc3e40b2 | ||
|
|
1561bd0027 | ||
|
|
8d14a4d93b | ||
|
|
bd889ae3cc | ||
|
|
02c20e81a9 | ||
|
|
c236bb71d1 | ||
|
|
22fa6f0a3a | ||
|
|
c2ac59f992 |
@@ -0,0 +1,65 @@
|
||||
name: 🐞 Bug Report
|
||||
# title: " "
|
||||
description: Problems with Supervision
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for submitting a Supervision 🐞 Bug Report!
|
||||
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Search before asking
|
||||
description: >
|
||||
Please search the [issues](https://github.com/roboflow/supervision/issues) to see if a similar bug report already exists.
|
||||
options:
|
||||
- label: >
|
||||
I have searched the Supervision [issues](https://github.com/roboflow/supervision/issues) and found no similar bug report.
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Bug
|
||||
description: Provide console output with error messages and/or screenshots of the bug.
|
||||
placeholder: |
|
||||
💡 ProTip! Include as much information as possible (screenshots, logs, tracebacks etc.) to receive the most helpful response.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Environment
|
||||
description: Please specify the software and hardware you used to produce the bug.
|
||||
placeholder: |
|
||||
- Supervision: 0.1.0
|
||||
- OS: Ubuntu 20.04
|
||||
- Python: 3.8.10
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Minimal Reproducible Example
|
||||
description: >
|
||||
When asking a question, people will be better able to provide help if you provide code that they can easily understand and use to **reproduce** the problem.
|
||||
This is referred to by community members as creating a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example).
|
||||
placeholder: |
|
||||
```
|
||||
# Code to reproduce your issue here
|
||||
```
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional
|
||||
description: Anything else you would like to share?
|
||||
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Are you willing to submit a PR?
|
||||
description: >
|
||||
(Optional) We encourage you to submit a [Pull Request](https://github.com/roboflow/supervision/pulls) (PR) to help improve Supervision for everyone, especially if you have a good understanding of how to implement a fix or feature.
|
||||
options:
|
||||
- label: Yes I'd like to help by submitting a PR!
|
||||
@@ -0,0 +1,49 @@
|
||||
name: 🤩 Feature Request
|
||||
description: Suggest a Supervision idea
|
||||
# title: " "
|
||||
labels: [enhancement]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for submitting a Supervision 🤩 Feature Request!
|
||||
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Search before asking
|
||||
description: >
|
||||
Please search the [issues](https://github.com/roboflow/supervision/issues) to see if a similar feature request already exists.
|
||||
options:
|
||||
- label: >
|
||||
I have searched the Supervision [issues](https://github.com/roboflow/supervision/issues) and found no similar feature requests.
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
description: A short description of your feature.
|
||||
placeholder: |
|
||||
What new feature would you like to see in Supervision?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Use case
|
||||
description: |
|
||||
Describe the use case of your feature request. It will help us understand and prioritize the feature request.
|
||||
placeholder: |
|
||||
How would this feature be used, and who would use it?
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional
|
||||
description: Anything else you would like to share?
|
||||
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Are you willing to submit a PR?
|
||||
description: >
|
||||
(Optional) We encourage you to submit a [Pull Request](https://github.com/roboflow/supervision/pulls) (PR) to help improve Supervision for everyone, especially if you have a good understanding of how to implement a fix or feature.
|
||||
options:
|
||||
- label: Yes I'd like to help by submitting a PR!
|
||||
@@ -0,0 +1,33 @@
|
||||
name: ❓ Question
|
||||
description: Ask a Supervision question
|
||||
# title: " "
|
||||
labels: [question]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for asking a Supervision ❓ Question!
|
||||
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Search before asking
|
||||
description: >
|
||||
Please search the [issues](https://github.com/roboflow/supervision/issues) to see if a similar feature request already exists.
|
||||
options:
|
||||
- label: >
|
||||
I have searched the Supervision [issues](https://github.com/roboflow/supervision/issues) and found no similar feature requests.
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Question
|
||||
description: What is your question?
|
||||
placeholder: |
|
||||
💡 ProTip! Include as much information as possible (screenshots, logs, tracebacks etc.) to receive the most helpful response.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional
|
||||
description: Anything else you would like to share?
|
||||
@@ -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
|
||||
@@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.7, 3.8, 3.9]
|
||||
python-version: [3.7, 3.8, 3.9, '3.10']
|
||||
steps:
|
||||
- name: 🛎️ Checkout
|
||||
uses: actions/checkout@v3
|
||||
@@ -22,7 +22,11 @@ jobs:
|
||||
- name: 🦾 Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install ".[dev]"
|
||||
pip install -e .
|
||||
pip install pytest
|
||||
pip install isort
|
||||
pip install flake8
|
||||
pip install "black==22.3.0"
|
||||
- name: 🧹 Lint with flake8
|
||||
run: |
|
||||
make check_code_quality
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
name: Welcome WorkFlow
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
cff-version: 1.2.0
|
||||
author: Roboflow
|
||||
message: If you use this software, please cite it as below.
|
||||
title: supervision
|
||||
version: 0.1.0
|
||||
date-released: 2023-01-19
|
||||
license: MIT
|
||||
repository-code: https://github.com/roboflow/supervision
|
||||
keywords:
|
||||
- computer vision
|
||||
- image processing
|
||||
- video processing
|
||||
@@ -0,0 +1,38 @@
|
||||
# Contributing to Supervision 🛠️
|
||||
|
||||
Thank you for your interest in contributing to Supervision!
|
||||
|
||||
We are actively improving this library to reduce the amount of work you need to do to solve common computer vision problems.
|
||||
|
||||
## Contribution Guidelines
|
||||
|
||||
We welcome contributions to:
|
||||
|
||||
1. Add a new feature to the library (guidance below).
|
||||
2. Improve our documentation and add examples to make it clear how to leverage the supervision library.
|
||||
3. Report bugs and issues in the project.
|
||||
4. Submit a request for a new feature.
|
||||
5. Improve our test coverage.
|
||||
|
||||
### Contributing Features
|
||||
|
||||
Supervision is designed to provide generic utilities to solve problems. Thus, we focus on contributions that can have an impact on a wide range of projects.
|
||||
|
||||
For example, counting objects that cross a line anywhere on an image is a common problem in computer vision, but counting objects that cross a line 75% of the way through is less useful.
|
||||
|
||||
Before you contribute a new feature, consider submitting an Issue to discuss the feature so the community can weigh in and assist.
|
||||
|
||||
## How to Contribute Changes
|
||||
|
||||
First, fork this repository to your own GitHub account. Create a new branch that describes your changes (i.e. `line-counter-docs`). Push your changes to the branch on your fork and then submit a pull request to this repository.
|
||||
|
||||
When creating new functions, please ensure you have the following:
|
||||
|
||||
1. Docstrings for the function and all parameters.
|
||||
2. Unit tests for the function.
|
||||
3. Examples in the documentation for the function.
|
||||
4. Created an entry in our docs to autogenerate the documentation for the function.
|
||||
|
||||
All pull requests will be reviewed by the maintainers of the project. We will provide feedback and ask for changes if necessary.
|
||||
|
||||
PRs must pass all tests and linting requirements before they can be merged.
|
||||
@@ -16,34 +16,34 @@
|
||||
width="3%"
|
||||
/>
|
||||
</a>
|
||||
<img src="https://github.com/SkalskiP/SkalskiP/blob/master/icons/transparent.png" width="3%"/>
|
||||
<img src="https://raw.githubusercontent.com/ultralytics/assets/main/social/logo-transparent.png" width="3%"/>
|
||||
<a href="https://roboflow.com">
|
||||
<img
|
||||
src="https://media.roboflow.com/notebooks/template/icons/purple/roboflow-app.png?ik-sdk-version=javascript-1.4.3&updatedAt=1672949746649"
|
||||
width="3%"
|
||||
/>
|
||||
</a>
|
||||
<img src="https://github.com/SkalskiP/SkalskiP/blob/master/icons/transparent.png" width="3%"/>
|
||||
<img src="https://raw.githubusercontent.com/ultralytics/assets/main/social/logo-transparent.png" width="3%"/>
|
||||
<a href="https://www.linkedin.com/company/roboflow-ai/">
|
||||
<img
|
||||
src="https://media.roboflow.com/notebooks/template/icons/purple/linkedin.png?ik-sdk-version=javascript-1.4.3&updatedAt=1672949633691"
|
||||
width="3%"
|
||||
/>
|
||||
</a>
|
||||
<img src="https://github.com/SkalskiP/SkalskiP/blob/master/icons/transparent.png" width="3%"/>
|
||||
<img src="https://raw.githubusercontent.com/ultralytics/assets/main/social/logo-transparent.png" width="3%"/>
|
||||
<a href="https://docs.roboflow.com">
|
||||
<img
|
||||
src="https://media.roboflow.com/notebooks/template/icons/purple/knowledge.png?ik-sdk-version=javascript-1.4.3&updatedAt=1672949634511"
|
||||
width="3%"
|
||||
/>
|
||||
</a>
|
||||
<img src="https://github.com/SkalskiP/SkalskiP/blob/master/icons/transparent.png" width="3%"/>
|
||||
<img src="https://raw.githubusercontent.com/ultralytics/assets/main/social/logo-transparent.png" width="3%"/>
|
||||
<a href="https://disuss.roboflow.com">
|
||||
<img
|
||||
src="https://media.roboflow.com/notebooks/template/icons/purple/forum.png?ik-sdk-version=javascript-1.4.3&updatedAt=1672949633584"
|
||||
width="3%"
|
||||
/>
|
||||
<img src="https://github.com/SkalskiP/SkalskiP/blob/master/icons/transparent.png" width="3%"/>
|
||||
<img src="https://raw.githubusercontent.com/ultralytics/assets/main/social/logo-transparent.png" width="3%"/>
|
||||
<a href="https://blog.roboflow.com">
|
||||
<img
|
||||
src="https://media.roboflow.com/notebooks/template/icons/purple/blog.png?ik-sdk-version=javascript-1.4.3&updatedAt=1672949633605"
|
||||
@@ -63,9 +63,19 @@ and may change.
|
||||
|
||||
## 💻 install
|
||||
|
||||
```console
|
||||
Pip install the supervision package in a
|
||||
[**3.10>=Python>=3.7**](https://www.python.org/) environment.
|
||||
|
||||
```bash
|
||||
pip install supervision
|
||||
```
|
||||
|
||||
<details close>
|
||||
<summary>Install from source</summary>
|
||||
|
||||
```bash
|
||||
# clone repository and navigate to root directory
|
||||
git clone git@github.com:roboflow/supervision.git
|
||||
git clone https://github.com/roboflow/supervision.git
|
||||
cd supervision
|
||||
|
||||
# setup python environment and activate it
|
||||
@@ -76,7 +86,24 @@ source venv/bin/activate
|
||||
pip install -e ".[dev]"
|
||||
```
|
||||
|
||||
## 🧹 Code Quality
|
||||
</details>
|
||||
|
||||
## 📖 documentation
|
||||
|
||||
Curious how Supervision can help you solve problems on your project? Visit our [documentation](https://roboflow.github.io/supervision) page!
|
||||
|
||||
## 🎬 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>
|
||||
|
||||
## 🧹 code quality
|
||||
|
||||
We provide two handy commands inside the `Makefile`, namely:
|
||||
|
||||
@@ -85,6 +112,6 @@ 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.
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.3.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 600 600" style="enable-background:new 0 0 600 600;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#6706CE;}
|
||||
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;}
|
||||
</style>
|
||||
<g>
|
||||
<circle class="st0" cx="300" cy="300" r="300"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M279.34,267.67c27.7-21.58,15.55-51.11-30.24-45.4c-53.03,6.6-120.62,58.45-137.46,120.7
|
||||
c-1.38,5.09-2.45,10.27-3.19,15.5c-0.73,5.15-1.12,10.35-1.17,15.55c-0.05,5.15,0.25,10.3,0.89,15.4
|
||||
c0.64,5.11,1.64,10.17,2.98,15.14c1.36,5.03,3.06,9.97,5.09,14.76c2.09,4.92,4.51,9.68,7.24,14.26c2.83,4.76,5.98,9.31,9.4,13.64
|
||||
c11.67,14.78,26.65,27.16,42.63,37.02c3.46,2.14,7.18,4.21,11.25,4.41c5.94,0.28,10.44-4.44,10.68-10.23
|
||||
c0.09-2.18-0.46-4.24-1.46-6c-13.42-23.67-24.52-58.81-22.24-80.99C179.43,336.29,235.28,301.98,279.34,267.67z"/>
|
||||
<path class="st1" d="M500.28,363.84c-2.9-6.91-10.16-10.81-17.51-10.15c-5.02,0.45-9.17,3.13-12.55,6.68
|
||||
c-5.49,4.97-9.31,11.89-13.43,17.91c-4.86,7.08-9.94,14.02-15.41,20.63c-10.8,13.04-23.24,24.79-37.76,33.61
|
||||
c-24.63,14.95-47.17,22.12-74.27,8.31c-19.83-15.86-13.13-34.6,3.29-49.87c28.08-26.1,61.93-44.42,88.52-72.44
|
||||
c25-26.34,47.05-59.11,52.24-95.78c1.46-10.27,1.48-20.74-0.17-31c-7.8-48.31-51.48-78.97-95.59-92.01
|
||||
C321.47,83.13,259.6,88,206.48,112.56c-21.86,10.11-42.23,23.65-59.94,39.98c-7.85,7.24-15.24,15.01-22.1,23.2
|
||||
c-11.1,13.27-23.73,28.77-25.89,46.57c-1.33,10.99,8.58,18.59,18.99,17.18c6.98-0.94,13.16-5.15,18.3-9.74
|
||||
c5.97-5.33,11.45-11.25,17.21-16.82c17.38-16.82,35.94-32.47,58.01-42.79c28.11-13.15,59.61-19.55,90.6-16.32
|
||||
c31.43,3.27,69.67,17.44,81.15,49.98c11.97,33.97-13.73,67.36-37.18,89.06c-38.29,35.44-110.99,71.63-102.98,134.19
|
||||
c4.29,33.51,28.4,62.19,59.24,74.96c53.66,22.23,111.74-4.84,148.89-46.18c12.46-13.24,30.38-35.82,46.68-69.46
|
||||
C500.61,379.84,503.23,370.87,500.28,363.84z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.3.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 600 600" style="enable-background:new 0 0 600 600;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;}
|
||||
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#6706CE;}
|
||||
</style>
|
||||
<g>
|
||||
<circle class="st0" cx="300" cy="300" r="300"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M279.34,267.67c27.7-21.58,15.55-51.11-30.24-45.4c-53.03,6.6-120.62,58.45-137.46,120.7
|
||||
c-1.38,5.09-2.45,10.27-3.19,15.5c-0.73,5.15-1.12,10.35-1.17,15.55c-0.05,5.15,0.25,10.3,0.89,15.4
|
||||
c0.64,5.11,1.64,10.17,2.98,15.14c1.36,5.03,3.06,9.97,5.09,14.76c2.09,4.92,4.51,9.68,7.24,14.26c2.83,4.76,5.98,9.31,9.4,13.64
|
||||
c11.67,14.78,26.65,27.16,42.63,37.02c3.46,2.14,7.18,4.21,11.25,4.41c5.94,0.28,10.44-4.44,10.68-10.23
|
||||
c0.09-2.18-0.46-4.24-1.46-6c-13.42-23.67-24.52-58.81-22.24-80.99C179.43,336.29,235.28,301.98,279.34,267.67z"/>
|
||||
<path class="st1" d="M500.28,363.84c-2.9-6.91-10.16-10.81-17.51-10.15c-5.02,0.45-9.17,3.13-12.55,6.68
|
||||
c-5.49,4.97-9.31,11.89-13.43,17.91c-4.86,7.08-9.94,14.02-15.41,20.63c-10.8,13.04-23.24,24.79-37.76,33.61
|
||||
c-24.63,14.95-47.17,22.12-74.27,8.31c-19.83-15.86-13.13-34.6,3.29-49.87c28.08-26.1,61.93-44.42,88.52-72.44
|
||||
c25-26.34,47.05-59.11,52.24-95.78c1.46-10.27,1.48-20.74-0.17-31c-7.8-48.31-51.48-78.97-95.59-92.01
|
||||
C321.47,83.13,259.6,88,206.48,112.56c-21.86,10.11-42.23,23.65-59.94,39.98c-7.85,7.24-15.24,15.01-22.1,23.2
|
||||
c-11.1,13.27-23.73,28.77-25.89,46.57c-1.33,10.99,8.58,18.59,18.99,17.18c6.98-0.94,13.16-5.15,18.3-9.74
|
||||
c5.97-5.33,11.45-11.25,17.21-16.82c17.38-16.82,35.94-32.47,58.01-42.79c28.11-13.15,59.61-19.55,90.6-16.32
|
||||
c31.43,3.27,69.67,17.44,81.15,49.98c11.97,33.97-13.73,67.36-37.18,89.06c-38.29,35.44-110.99,71.63-102.98,134.19
|
||||
c4.29,33.51,28.4,62.19,59.24,74.96c53.66,22.23,111.74-4.84,148.89-46.18c12.46-13.24,30.38-35.82,46.68-69.46
|
||||
C500.61,379.84,503.23,370.87,500.28,363.84z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.3.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 600 600" style="enable-background:new 0 0 600 600;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;}
|
||||
</style>
|
||||
<path class="st0" d="M300,0C134.31,0,0,134.31,0,300s134.31,300,300,300s300-134.31,300-300S465.69,0,300,0z M195.98,472.42
|
||||
c1,1.76,1.55,3.82,1.46,6c-0.24,5.79-4.74,10.51-10.68,10.23c-4.07-0.19-7.78-2.27-11.25-4.41c-15.98-9.86-30.96-22.24-42.63-37.02
|
||||
c-3.42-4.33-6.57-8.89-9.4-13.64c-2.72-4.58-5.15-9.34-7.24-14.26c-2.03-4.79-3.74-9.72-5.09-14.76
|
||||
c-1.34-4.97-2.33-10.03-2.98-15.14c-0.64-5.1-0.94-10.25-0.89-15.4c0.05-5.2,0.45-10.4,1.17-15.55c0.74-5.23,1.81-10.41,3.19-15.5
|
||||
c16.84-62.24,84.43-114.09,137.46-120.7c45.79-5.71,57.94,23.81,30.24,45.4c-44.06,34.32-99.92,68.63-105.6,123.77
|
||||
C171.46,413.61,182.55,448.75,195.98,472.42z M497.44,386.39c-16.3,33.65-34.22,56.22-46.68,69.46
|
||||
c-37.14,41.34-95.23,68.4-148.89,46.18c-30.83-12.77-54.94-41.45-59.24-74.96c-8.02-62.55,64.69-98.74,102.98-134.19
|
||||
c23.45-21.71,49.16-55.1,37.18-89.06c-11.47-32.55-49.72-46.71-81.15-49.98c-30.99-3.23-62.49,3.17-90.6,16.32
|
||||
c-22.07,10.32-40.62,25.97-58.01,42.79c-5.75,5.57-11.24,11.49-17.21,16.82c-5.14,4.59-11.31,8.79-18.29,9.74
|
||||
c-10.41,1.41-20.32-6.18-18.99-17.18c2.16-17.8,14.79-33.29,25.89-46.57c6.86-8.19,14.25-15.96,22.1-23.2
|
||||
c17.71-16.34,38.08-29.87,59.94-39.98C259.6,88,321.47,83.13,377.64,99.73c44.11,13.04,87.79,43.7,95.59,92.01
|
||||
c1.65,10.25,1.63,20.72,0.17,31c-5.19,36.67-27.24,69.44-52.24,95.78c-26.59,28.02-60.44,46.35-88.52,72.44
|
||||
c-16.43,15.27-23.13,34.01-3.29,49.87c27.1,13.81,49.64,6.64,74.27-8.31c14.52-8.82,26.96-20.57,37.76-33.61
|
||||
c5.48-6.61,10.56-13.55,15.41-20.63c4.13-6.02,7.94-12.94,13.43-17.91c3.38-3.55,7.53-6.23,12.55-6.68
|
||||
c7.35-0.66,14.61,3.23,17.51,10.15C503.23,370.87,500.61,379.84,497.44,386.39z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,3 @@
|
||||
## Detections
|
||||
|
||||
:::supervision.detection.core.Detections
|
||||
@@ -0,0 +1,3 @@
|
||||
## generate_2d_mask
|
||||
|
||||
:::supervision.detection.utils.generate_2d_mask
|
||||
@@ -0,0 +1,7 @@
|
||||
## draw_line
|
||||
|
||||
:::supervision.draw.utils.draw_line
|
||||
|
||||
## draw_rectangle
|
||||
|
||||
:::supervision.draw.utils.draw_rectangle
|
||||
@@ -0,0 +1,37 @@
|
||||
<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>
|
||||
|
||||
## 👋 Welcome
|
||||
|
||||
Supervision is a set of easy-to-use utilities 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.
|
||||
|
||||
## 💻 How to Install
|
||||
|
||||
You can install `supervision` with pip in a
|
||||
[**3.10>=Python>=3.7**](https://www.python.org/) environment.
|
||||
|
||||
!!! example "Pip install method (recommended)"
|
||||
|
||||
```bash
|
||||
pip install supervision
|
||||
```
|
||||
|
||||
!!! 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,3 @@
|
||||
## show_frame_in_notebook
|
||||
|
||||
:::supervision.notebook.utils.show_frame_in_notebook
|
||||
@@ -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
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
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
|
||||
repo_name: roboflow/supervision
|
||||
repo_url: https://github.com/roboflow/supervision
|
||||
edit_uri: https://github.com/roboflow/supervision/tree/main/docs
|
||||
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
|
||||
analytics:
|
||||
provider: google
|
||||
property: G-P7ZG0Y19G5
|
||||
|
||||
nav:
|
||||
- Home: index.md
|
||||
- API reference:
|
||||
- Video: video.md
|
||||
- Detection:
|
||||
- Core: detection_core.md
|
||||
- Utils: detection_utils.md
|
||||
- Draw: draw.md
|
||||
- Geometry: geometry.md
|
||||
- Notebook: notebook.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
|
||||
@@ -0,0 +1,30 @@
|
||||
certifi==2022.12.7
|
||||
charset-normalizer==3.0.1
|
||||
click==8.1.3
|
||||
colorama==0.4.6
|
||||
ghp-import==2.1.0
|
||||
griffe==0.25.4
|
||||
idna==3.4
|
||||
importlib-metadata==6.0.0
|
||||
Jinja2==3.1.2
|
||||
Markdown==3.3.7
|
||||
MarkupSafe==2.1.2
|
||||
mergedeep==1.3.4
|
||||
mkdocs==1.4.2
|
||||
mkdocs-autorefs==0.4.1
|
||||
mkdocs-material==9.0.9
|
||||
mkdocs-material-extensions==1.1.1
|
||||
mkdocstrings==0.20.0
|
||||
mkdocstrings-python==0.8.3
|
||||
packaging==23.0
|
||||
Pygments==2.14.0
|
||||
pymdown-extensions==9.9.2
|
||||
python-dateutil==2.8.2
|
||||
PyYAML==6.0
|
||||
pyyaml_env_tag==0.1
|
||||
regex==2022.10.31
|
||||
requests==2.28.2
|
||||
six==1.16.0
|
||||
urllib3==1.26.14
|
||||
watchdog==2.2.1
|
||||
zipp==3.12.0
|
||||
@@ -24,8 +24,9 @@ setuptools.setup(
|
||||
long_description_content_type='text/markdown',
|
||||
url='https://github.com/roboflow/supervision',
|
||||
install_requires=[
|
||||
'numpy',
|
||||
'opencv-python'
|
||||
'numpy',
|
||||
'opencv-python',
|
||||
'matplotlib'
|
||||
],
|
||||
packages=find_packages(exclude=("tests",)),
|
||||
extras_require={
|
||||
@@ -36,7 +37,9 @@ setuptools.setup(
|
||||
'twine',
|
||||
'pytest',
|
||||
'wheel',
|
||||
'notebook'
|
||||
'notebook',
|
||||
'mkdocs-material',
|
||||
'mkdocstrings[python]'
|
||||
],
|
||||
},
|
||||
classifiers=[
|
||||
|
||||
+16
-1
@@ -1 +1,16 @@
|
||||
__version__ = "0.0.2"
|
||||
__version__ = "0.2.0"
|
||||
|
||||
from supervision.detection.core import BoxAnnotator, Detections
|
||||
from supervision.detection.polygon_zone import PolygonZone, PolygonZoneAnnotator
|
||||
from supervision.detection.utils import generate_2d_mask
|
||||
from supervision.draw.color import Color, ColorPalette
|
||||
from supervision.draw.utils import draw_filled_rectangle, draw_polygon, draw_text
|
||||
from supervision.geometry.core import Point, Position, Rect
|
||||
from supervision.geometry.utils import get_polygon_center
|
||||
from supervision.notebook.utils import show_frame_in_notebook
|
||||
from supervision.video import (
|
||||
VideoInfo,
|
||||
VideoSink,
|
||||
get_video_frames_generator,
|
||||
process_video,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Optional, Union
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
from supervision.draw.color import Color, ColorPalette
|
||||
from supervision.geometry.core import Position
|
||||
|
||||
|
||||
@dataclass
|
||||
class Detections:
|
||||
"""
|
||||
Data class containing information about the detections in a video frame.
|
||||
|
||||
Attributes:
|
||||
xyxy (np.ndarray): An array of shape `(n, 4)` containing the bounding boxes coordinates in format `[x1, y1, x2, y2]`
|
||||
confidence (np.ndarray): An array of shape `(n,)` containing the confidence scores of the detections.
|
||||
class_id (np.ndarray): An array of shape `(n,)` containing the class ids of the detections.
|
||||
tracker_id (Optional[np.ndarray]): An array of shape `(n,)` containing the tracker ids of the detections.
|
||||
"""
|
||||
|
||||
xyxy: np.ndarray
|
||||
confidence: np.ndarray
|
||||
class_id: np.ndarray
|
||||
tracker_id: Optional[np.ndarray] = None
|
||||
|
||||
def __post_init__(self):
|
||||
n = len(self.xyxy)
|
||||
validators = [
|
||||
(isinstance(self.xyxy, np.ndarray) and self.xyxy.shape == (n, 4)),
|
||||
(isinstance(self.confidence, np.ndarray) and self.confidence.shape == (n,)),
|
||||
(isinstance(self.class_id, np.ndarray) and self.class_id.shape == (n,)),
|
||||
self.tracker_id is None
|
||||
or (
|
||||
isinstance(self.tracker_id, np.ndarray)
|
||||
and self.tracker_id.shape == (n,)
|
||||
),
|
||||
]
|
||||
if not all(validators):
|
||||
raise ValueError(
|
||||
"xyxy must be 2d np.ndarray with (n, 4) shape, "
|
||||
"confidence must be 1d np.ndarray with (n,) shape, "
|
||||
"class_id must be 1d np.ndarray with (n,) shape, "
|
||||
"tracker_id must be None or 1d np.ndarray with (n,) shape"
|
||||
)
|
||||
|
||||
def __len__(self):
|
||||
"""
|
||||
Returns the number of detections in the Detections object.
|
||||
"""
|
||||
return len(self.xyxy)
|
||||
|
||||
def __iter__(self):
|
||||
"""
|
||||
Iterates over the Detections object and yield a tuple of `(xyxy, confidence, class_id, tracker_id)` for each detection.
|
||||
"""
|
||||
for i in range(len(self.xyxy)):
|
||||
yield (
|
||||
self.xyxy[i],
|
||||
self.confidence[i],
|
||||
self.class_id[i],
|
||||
self.tracker_id[i] if self.tracker_id is not None else None,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_yolov5(cls, yolov5_detections):
|
||||
"""
|
||||
Creates a Detections instance from a YOLOv5 output Detections
|
||||
|
||||
Attributes:
|
||||
yolov5_detections (yolov5.models.common.Detections): The output Detections instance from YOLOv5
|
||||
|
||||
Returns:
|
||||
|
||||
Example:
|
||||
```python
|
||||
>>> import torch
|
||||
>>> from supervision import Detections
|
||||
|
||||
>>> model = torch.hub.load('ultralytics/yolov5', 'yolov5s')
|
||||
>>> results = model(frame)
|
||||
>>> detections = Detections.from_yolov5(results)
|
||||
```
|
||||
"""
|
||||
yolov5_detections_predictions = yolov5_detections.pred[0].cpu().cpu().numpy()
|
||||
return cls(
|
||||
xyxy=yolov5_detections_predictions[:, :4],
|
||||
confidence=yolov5_detections_predictions[:, 4],
|
||||
class_id=yolov5_detections_predictions[:, 5].astype(int),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_yolov8(cls, yolov8_results):
|
||||
"""
|
||||
Creates a Detections instance from a YOLOv8 output Results
|
||||
|
||||
Attributes:
|
||||
yolov8_results (ultralytics.yolo.engine.results.Results): The output Results instance from YOLOv8
|
||||
|
||||
Returns:
|
||||
|
||||
Example:
|
||||
```python
|
||||
>>> from ultralytics import YOLO
|
||||
>>> from supervision import Detections
|
||||
|
||||
>>> model = YOLO('yolov8s.pt')
|
||||
>>> results = model(frame)
|
||||
>>> detections = Detections.from_yolov8(results)
|
||||
```
|
||||
"""
|
||||
return cls(
|
||||
xyxy=yolov8_results.boxes.xyxy.cpu().numpy(),
|
||||
confidence=yolov8_results.boxes.conf.cpu().numpy(),
|
||||
class_id=yolov8_results.boxes.cls.cpu().numpy().astype(int),
|
||||
)
|
||||
|
||||
def filter(self, mask: np.ndarray, inplace: bool = False) -> Optional[Detections]:
|
||||
"""
|
||||
Filter the detections by applying a mask.
|
||||
|
||||
Attributes:
|
||||
mask (np.ndarray): A mask of shape `(n,)` containing a boolean value for each detection indicating if it should be included in the filtered detections
|
||||
inplace (bool): If True, the original data will be modified and self will be returned.
|
||||
|
||||
Returns:
|
||||
Optional[np.ndarray]: A new instance of Detections with the filtered detections, if inplace is set to `False`. `None` otherwise.
|
||||
"""
|
||||
if inplace:
|
||||
self.xyxy = self.xyxy[mask]
|
||||
self.confidence = self.confidence[mask]
|
||||
self.class_id = self.class_id[mask]
|
||||
self.tracker_id = (
|
||||
self.tracker_id[mask] if self.tracker_id is not None else None
|
||||
)
|
||||
return self
|
||||
else:
|
||||
return Detections(
|
||||
xyxy=self.xyxy[mask],
|
||||
confidence=self.confidence[mask],
|
||||
class_id=self.class_id[mask],
|
||||
tracker_id=self.tracker_id[mask]
|
||||
if self.tracker_id is not None
|
||||
else None,
|
||||
)
|
||||
|
||||
def get_anchor_coordinates(self, anchor: Position) -> np.ndarray:
|
||||
"""
|
||||
Returns the bounding box coordinates for a specific anchor.
|
||||
|
||||
Properties:
|
||||
anchor (Position): Position of bounding box anchor for which to return the coordinates.
|
||||
|
||||
Returns:
|
||||
np.ndarray: An array of shape `(n, 2)` containing the bounding box anchor coordinates in format `[x, y]`.
|
||||
"""
|
||||
if anchor == Position.CENTER:
|
||||
return np.array(
|
||||
[
|
||||
(self.xyxy[:, 0] + self.xyxy[:, 2]) / 2,
|
||||
(self.xyxy[:, 1] + self.xyxy[:, 3]) / 2,
|
||||
]
|
||||
).transpose()
|
||||
elif anchor == Position.BOTTOM_CENTER:
|
||||
return np.array(
|
||||
[(self.xyxy[:, 0] + self.xyxy[:, 2]) / 2, self.xyxy[:, 3]]
|
||||
).transpose()
|
||||
|
||||
raise ValueError(f"{anchor} is not supported.")
|
||||
|
||||
def __getitem__(self, index: np.ndarray) -> Detections:
|
||||
if isinstance(index, np.ndarray) and index.dtype == np.bool:
|
||||
return Detections(
|
||||
xyxy=self.xyxy[index],
|
||||
confidence=self.confidence[index],
|
||||
class_id=self.class_id[index],
|
||||
tracker_id=self.tracker_id[index]
|
||||
if self.tracker_id is not None
|
||||
else None,
|
||||
)
|
||||
raise TypeError(
|
||||
f"Detections.__getitem__ not supported for index of type {type(index)}."
|
||||
)
|
||||
|
||||
|
||||
class BoxAnnotator:
|
||||
def __init__(
|
||||
self,
|
||||
color: Union[Color, ColorPalette] = ColorPalette.default(),
|
||||
thickness: int = 2,
|
||||
text_color: Color = Color.black(),
|
||||
text_scale: float = 0.5,
|
||||
text_thickness: int = 1,
|
||||
text_padding: int = 10,
|
||||
):
|
||||
"""
|
||||
A class for drawing bounding boxes on an image using detections provided.
|
||||
|
||||
Attributes:
|
||||
color (Union[Color, ColorPalette]): The color to draw the bounding box, can be a single color or a color palette
|
||||
thickness (int): The thickness of the bounding box lines, default is 2
|
||||
text_color (Color): The color of the text on the bounding box, default is white
|
||||
text_scale (float): The scale of the text on the bounding box, default is 0.5
|
||||
text_thickness (int): The thickness of the text on the bounding box, default is 1
|
||||
text_padding (int): The padding around the text on the bounding box, default is 5
|
||||
|
||||
"""
|
||||
self.color: Union[Color, ColorPalette] = color
|
||||
self.thickness: int = thickness
|
||||
self.text_color: Color = text_color
|
||||
self.text_scale: float = text_scale
|
||||
self.text_thickness: int = text_thickness
|
||||
self.text_padding: int = text_padding
|
||||
|
||||
def annotate(
|
||||
self,
|
||||
scene: np.ndarray,
|
||||
detections: Detections,
|
||||
labels: Optional[List[str]] = None,
|
||||
skip_label: bool = False,
|
||||
) -> np.ndarray:
|
||||
"""
|
||||
Draws bounding boxes on the frame using the detections provided.
|
||||
|
||||
Parameters:
|
||||
scene (np.ndarray): The image on which the bounding boxes will be drawn
|
||||
detections (Detections): The detections for which the bounding boxes will be drawn
|
||||
labels (Optional[List[str]]): An optional list of labels corresponding to each detection. If labels is provided, the confidence score of the detection will be replaced with the label.
|
||||
skip_label (bool): Is set to True, skips bounding box label annotation.
|
||||
Returns:
|
||||
np.ndarray: The image with the bounding boxes drawn on it
|
||||
"""
|
||||
font = cv2.FONT_HERSHEY_SIMPLEX
|
||||
for i, (xyxy, confidence, class_id, tracker_id) in enumerate(detections):
|
||||
x1, y1, x2, y2 = xyxy.astype(int)
|
||||
color = (
|
||||
self.color.by_idx(class_id)
|
||||
if isinstance(self.color, ColorPalette)
|
||||
else self.color
|
||||
)
|
||||
cv2.rectangle(
|
||||
img=scene,
|
||||
pt1=(x1, y1),
|
||||
pt2=(x2, y2),
|
||||
color=color.as_bgr(),
|
||||
thickness=self.thickness,
|
||||
)
|
||||
if skip_label:
|
||||
continue
|
||||
|
||||
text = (
|
||||
f"{confidence:0.2f}"
|
||||
if (labels is None or len(detections) != len(labels))
|
||||
else labels[i]
|
||||
)
|
||||
|
||||
text_width, text_height = cv2.getTextSize(
|
||||
text=text,
|
||||
fontFace=font,
|
||||
fontScale=self.text_scale,
|
||||
thickness=self.text_thickness,
|
||||
)[0]
|
||||
|
||||
text_x = x1 + self.text_padding
|
||||
text_y = y1 - self.text_padding
|
||||
|
||||
text_background_x1 = x1
|
||||
text_background_y1 = y1 - 2 * self.text_padding - text_height
|
||||
|
||||
text_background_x2 = x1 + 2 * self.text_padding + text_width
|
||||
text_background_y2 = y1
|
||||
|
||||
cv2.rectangle(
|
||||
img=scene,
|
||||
pt1=(text_background_x1, text_background_y1),
|
||||
pt2=(text_background_x2, text_background_y2),
|
||||
color=color.as_bgr(),
|
||||
thickness=cv2.FILLED,
|
||||
)
|
||||
cv2.putText(
|
||||
img=scene,
|
||||
text=text,
|
||||
org=(text_x, text_y),
|
||||
fontFace=font,
|
||||
fontScale=self.text_scale,
|
||||
color=self.text_color.as_rgb(),
|
||||
thickness=self.text_thickness,
|
||||
lineType=cv2.LINE_AA,
|
||||
)
|
||||
return scene
|
||||
@@ -3,29 +3,37 @@ from typing import Dict
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
from supervision.detection.core import Detections
|
||||
from supervision.draw.color import Color
|
||||
from supervision.geometry.dataclasses import Point, Rect, Vector
|
||||
from supervision.tools.detections import Detections
|
||||
from supervision.geometry.core import Point, Rect, Vector
|
||||
|
||||
|
||||
class LineCounter:
|
||||
class LineZone:
|
||||
"""
|
||||
Count the number of objects that cross a line.
|
||||
"""
|
||||
|
||||
def __init__(self, start: Point, end: Point):
|
||||
"""
|
||||
Initialize a LineCounter object.
|
||||
|
||||
:param start: Point : The starting point of the line.
|
||||
:param end: Point : The ending point of the line.
|
||||
Attributes:
|
||||
start (Point): The starting point of the line.
|
||||
end (Point): The ending point of the line.
|
||||
|
||||
"""
|
||||
self.vector = Vector(start=start, end=end)
|
||||
self.tracker_state: Dict[str, bool] = {}
|
||||
self.in_count: int = 0
|
||||
self.out_count: int = 0
|
||||
|
||||
def update(self, detections: Detections):
|
||||
def trigger(self, detections: Detections):
|
||||
"""
|
||||
Update the in_count and out_count for the detections that cross the line.
|
||||
|
||||
:param detections: Detections : The detections for which to update the counts.
|
||||
Attributes:
|
||||
detections (Detections): The detections for which to update the counts.
|
||||
|
||||
"""
|
||||
for xyxy, confidence, class_id, tracker_id in detections:
|
||||
# handle detections with no tracker_id
|
||||
@@ -63,7 +71,7 @@ class LineCounter:
|
||||
self.out_count += 1
|
||||
|
||||
|
||||
class LineCounterAnnotator:
|
||||
class LineZoneAnnotator:
|
||||
def __init__(
|
||||
self,
|
||||
thickness: float = 2,
|
||||
@@ -77,13 +85,15 @@ class LineCounterAnnotator:
|
||||
"""
|
||||
Initialize the LineCounterAnnotator object with default values.
|
||||
|
||||
:param thickness: float : The thickness of the line that will be drawn.
|
||||
:param color: Color : The color of the line that will be drawn.
|
||||
:param text_thickness: float : The thickness of the text that will be drawn.
|
||||
:param text_color: Color : The color of the text that will be drawn.
|
||||
:param text_scale: float : The scale of the text that will be drawn.
|
||||
:param text_offset: float : The offset of the text that will be drawn.
|
||||
:param text_padding: int : The padding of the text that will be drawn.
|
||||
Attributes:
|
||||
thickness (float): The thickness of the line that will be drawn.
|
||||
color (Color): The color of the line that will be drawn.
|
||||
text_thickness (float): The thickness of the text that will be drawn.
|
||||
text_color (Color): The color of the text that will be drawn.
|
||||
text_scale (float): The scale of the text that will be drawn.
|
||||
text_offset (float): The offset of the text that will be drawn.
|
||||
text_padding (int): The padding of the text that will be drawn.
|
||||
|
||||
"""
|
||||
self.thickness: float = thickness
|
||||
self.color: Color = color
|
||||
@@ -93,13 +103,17 @@ class LineCounterAnnotator:
|
||||
self.text_offset: float = text_offset
|
||||
self.text_padding: int = text_padding
|
||||
|
||||
def annotate(self, frame: np.ndarray, line_counter: LineCounter) -> np.ndarray:
|
||||
def annotate(self, frame: np.ndarray, line_counter: LineZone) -> np.ndarray:
|
||||
"""
|
||||
Draws the line on the frame using the line_counter provided.
|
||||
|
||||
:param frame: np.ndarray : The image on which the line will be drawn
|
||||
:param line_counter: LineCounter : The line counter that will be used to draw the line
|
||||
:return: np.ndarray : The image with the line drawn on it
|
||||
Attributes:
|
||||
frame (np.ndarray): The image on which the line will be drawn.
|
||||
line_counter (LineCounter): The line counter that will be used to draw the line.
|
||||
|
||||
Returns:
|
||||
np.ndarray: The image with the line drawn on it.
|
||||
|
||||
"""
|
||||
cv2.line(
|
||||
frame,
|
||||
@@ -0,0 +1,80 @@
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
from supervision import Detections
|
||||
from supervision.detection.utils import generate_2d_mask
|
||||
from supervision.draw.color import Color
|
||||
from supervision.draw.utils import draw_polygon, draw_text
|
||||
from supervision.geometry.core import Position
|
||||
from supervision.geometry.utils import get_polygon_center
|
||||
|
||||
|
||||
class PolygonZone:
|
||||
def __init__(
|
||||
self,
|
||||
polygon: np.ndarray,
|
||||
frame_resolution_wh: Tuple[int, int],
|
||||
triggering_position: Position = Position.BOTTOM_CENTER,
|
||||
):
|
||||
self.polygon = polygon
|
||||
self.frame_resolution_wh = frame_resolution_wh
|
||||
self.triggering_position = triggering_position
|
||||
self.mask = generate_2d_mask(polygon=polygon, resolution_wh=frame_resolution_wh)
|
||||
self.current_count = 0
|
||||
|
||||
def trigger(self, detections: Detections) -> np.ndarray:
|
||||
anchors = (
|
||||
np.ceil(
|
||||
detections.get_anchor_coordinates(anchor=self.triggering_position)
|
||||
).astype(int)
|
||||
- 1
|
||||
)
|
||||
is_in_zone = self.mask[anchors[:, 1], anchors[:, 0]]
|
||||
self.current_count = np.sum(is_in_zone)
|
||||
return is_in_zone.astype(bool)
|
||||
|
||||
|
||||
class PolygonZoneAnnotator:
|
||||
def __init__(
|
||||
self,
|
||||
zone: PolygonZone,
|
||||
color: Color,
|
||||
thickness: int = 2,
|
||||
text_color: Color = Color.black(),
|
||||
text_scale: float = 0.5,
|
||||
text_thickness: int = 1,
|
||||
text_padding: int = 10,
|
||||
):
|
||||
self.zone = zone
|
||||
self.color = color
|
||||
self.thickness = thickness
|
||||
self.text_color = text_color
|
||||
self.text_scale = text_scale
|
||||
self.text_thickness = text_thickness
|
||||
self.text_padding = text_padding
|
||||
self.font = cv2.FONT_HERSHEY_SIMPLEX
|
||||
self.center = get_polygon_center(polygon=zone.polygon)
|
||||
|
||||
def annotate(self, scene: np.ndarray, label: Optional[str] = None) -> np.ndarray:
|
||||
annotated_frame = draw_polygon(
|
||||
scene=scene,
|
||||
polygon=self.zone.polygon,
|
||||
color=self.color,
|
||||
thickness=self.thickness,
|
||||
)
|
||||
|
||||
annotated_frame = draw_text(
|
||||
scene=annotated_frame,
|
||||
text=str(self.zone.current_count) if label is None else label,
|
||||
text_anchor=self.center,
|
||||
background_color=self.color,
|
||||
text_color=self.text_color,
|
||||
text_scale=self.text_scale,
|
||||
text_thickness=self.text_thickness,
|
||||
text_padding=self.text_padding,
|
||||
text_font=self.font,
|
||||
)
|
||||
|
||||
return annotated_frame
|
||||
@@ -0,0 +1,20 @@
|
||||
from typing import Tuple
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
|
||||
def generate_2d_mask(polygon: np.ndarray, resolution_wh: Tuple[int, int]) -> np.ndarray:
|
||||
"""Generate a 2D mask from a polygon.
|
||||
|
||||
Properties:
|
||||
polygon (np.ndarray): The polygon for which the mask should be generated, given as a list of vertices.
|
||||
resolution_wh (Tuple[int, int]): The width and height of the desired resolution.
|
||||
|
||||
Returns:
|
||||
np.ndarray: The generated 2D mask, where the polygon is marked with `1`'s and the rest is filled with `0`'s.
|
||||
"""
|
||||
width, height = resolution_wh
|
||||
mask = np.zeros((height, width), dtype=np.uint8)
|
||||
cv2.fillPoly(mask, [polygon], color=1)
|
||||
return mask
|
||||
@@ -1,6 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Tuple
|
||||
|
||||
DEFAULT_COLOR_PALETTE = [
|
||||
@@ -94,11 +94,11 @@ class Color:
|
||||
|
||||
@dataclass
|
||||
class ColorPalette:
|
||||
colors: List[Color] = field(
|
||||
default_factory=lambda: [
|
||||
Color.from_hex(color_hex) for color_hex in DEFAULT_COLOR_PALETTE
|
||||
]
|
||||
)
|
||||
colors: List[Color]
|
||||
|
||||
@classmethod
|
||||
def default(cls) -> ColorPalette:
|
||||
return ColorPalette.from_hex(color_hex_list=DEFAULT_COLOR_PALETTE)
|
||||
|
||||
@classmethod
|
||||
def from_hex(cls, color_hex_list: List[str]):
|
||||
|
||||
+116
-12
@@ -1,8 +1,10 @@
|
||||
from typing import Optional
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
from supervision.commons.dataclasses import Point, Rect
|
||||
from supervision.draw.color import Color
|
||||
from supervision.geometry.core import Point, Rect
|
||||
|
||||
|
||||
def draw_line(
|
||||
@@ -11,12 +13,15 @@ def draw_line(
|
||||
"""
|
||||
Draws a line on a given scene.
|
||||
|
||||
:param scene: np.ndarray : The scene on which the line will be drawn
|
||||
:param start: Point : The starting point of the line
|
||||
:param end: Point : The end point of the line
|
||||
:param color: Color : The color of the line
|
||||
:param thickness: int : The thickness of the line
|
||||
:return: np.ndarray : The scene with the line drawn on it
|
||||
Parameters:
|
||||
scene (np.ndarray): The scene on which the line will be drawn
|
||||
start (Point): The starting point of the line
|
||||
end (Point): The end point of the line
|
||||
color (Color): The color of the line
|
||||
thickness (int): The thickness of the line
|
||||
|
||||
Returns:
|
||||
np.ndarray: The scene with the line drawn on it
|
||||
"""
|
||||
cv2.line(
|
||||
scene,
|
||||
@@ -34,11 +39,14 @@ def draw_rectangle(
|
||||
"""
|
||||
Draws a rectangle on an image.
|
||||
|
||||
:param scene: np.ndarray : The image on which to draw the rectangle.
|
||||
:param rect: Rect : The rectangle to draw.
|
||||
:param color: Color : The color of the rectangle.
|
||||
:param thickness: int : The thickness of the rectangle border.
|
||||
:return: np.ndarray : The image with the rectangle drawn on it.
|
||||
Attributes:
|
||||
scene (np.ndarray): The scene on which the rectangle will be drawn
|
||||
rect (Rect): The rectangle to be drawn
|
||||
color (Color): The color of the rectangle
|
||||
thickness (int): The thickness of the rectangle border
|
||||
|
||||
Returns:
|
||||
np.ndarray: The scene with the rectangle drawn on it
|
||||
"""
|
||||
cv2.rectangle(
|
||||
scene,
|
||||
@@ -58,6 +66,14 @@ def draw_filled_rectangle(scene: np.ndarray, rect: Rect, color: Color) -> np.nda
|
||||
:param rect: Rect : The rectangle to be drawn.
|
||||
:param color: Color : The color of the rectangle.
|
||||
:return: np.ndarray : The updated scene with the filled rectangle drawn on it.
|
||||
|
||||
Attributes:
|
||||
scene (np.ndarray): The scene on which the rectangle will be drawn
|
||||
rect (Rect): The rectangle to be drawn
|
||||
color (Color): The color of the rectangle
|
||||
|
||||
Returns:
|
||||
np.ndarray: The scene with the rectangle drawn on it
|
||||
"""
|
||||
cv2.rectangle(
|
||||
scene,
|
||||
@@ -67,3 +83,91 @@ def draw_filled_rectangle(scene: np.ndarray, rect: Rect, color: Color) -> np.nda
|
||||
-1,
|
||||
)
|
||||
return scene
|
||||
|
||||
|
||||
def draw_polygon(
|
||||
scene: np.ndarray, polygon: np.ndarray, color: Color, thickness: int = 2
|
||||
) -> np.ndarray:
|
||||
"""Draw a polygon on a scene.
|
||||
|
||||
Attributes:
|
||||
scene (np.ndarray): The scene to draw the polygon on.
|
||||
polygon (np.ndarray): The polygon to be drawn, given as a list of vertices.
|
||||
color (Color): The color of the polygon.
|
||||
thickness (int, optional): The thickness of the polygon lines, by default 2.
|
||||
|
||||
Returns:
|
||||
np.ndarray: The scene with the polygon drawn on it.
|
||||
"""
|
||||
cv2.polylines(
|
||||
scene, [polygon], isClosed=True, color=color.as_bgr(), thickness=thickness
|
||||
)
|
||||
return scene
|
||||
|
||||
|
||||
def draw_text(
|
||||
scene: np.ndarray,
|
||||
text: str,
|
||||
text_anchor: Point,
|
||||
text_color: Color = Color.black(),
|
||||
text_scale: float = 0.5,
|
||||
text_thickness: int = 1,
|
||||
text_padding: int = 10,
|
||||
text_font: int = cv2.FONT_HERSHEY_SIMPLEX,
|
||||
background_color: Optional[Color] = None,
|
||||
) -> np.ndarray:
|
||||
"""
|
||||
Draw text on a scene.
|
||||
|
||||
This function takes in a 2-dimensional numpy ndarray representing an image or scene, and draws text on it using OpenCV's putText function. The text is anchored at a specified Point, and its appearance can be customized using arguments such as color, scale, and font. An optional background color and padding can be specified to draw a rectangle behind the text.
|
||||
|
||||
Parameters:
|
||||
scene (np.ndarray): A 2-dimensional numpy ndarray representing an image or scene.
|
||||
text (str): The text to be drawn.
|
||||
text_anchor (Point): The anchor point for the text, represented as a Point object with x and y attributes.
|
||||
text_color (Color, optional): The color of the text. Defaults to black.
|
||||
text_scale (float, optional): The scale of the text. Defaults to 0.5.
|
||||
text_thickness (int, optional): The thickness of the text. Defaults to 1.
|
||||
text_padding (int, optional): The amount of padding to add around the text when drawing a rectangle in the background. Defaults to 10.
|
||||
text_font (int, optional): The font to use for the text. Defaults to cv2.FONT_HERSHEY_SIMPLEX.
|
||||
background_color (Color, optional): The color of the background rectangle, if one is to be drawn. Defaults to None.
|
||||
|
||||
Returns:
|
||||
np.ndarray: The input scene with the text drawn on it.
|
||||
|
||||
Examples:
|
||||
```python
|
||||
>>> scene = np.zeros((100, 100, 3), dtype=np.uint8)
|
||||
>>> text_anchor = Point(x=50, y=50)
|
||||
>>> scene = draw_text(scene=scene, text="Hello, world!", text_anchor=text_anchor)
|
||||
```
|
||||
"""
|
||||
text_width, text_height = cv2.getTextSize(
|
||||
text=text,
|
||||
fontFace=text_font,
|
||||
fontScale=text_scale,
|
||||
thickness=text_thickness,
|
||||
)[0]
|
||||
text_rect = Rect(
|
||||
x=text_anchor.x - text_width // 2,
|
||||
y=text_anchor.y - text_height // 2,
|
||||
width=text_width,
|
||||
height=text_height,
|
||||
).pad(text_padding)
|
||||
|
||||
if background_color is not None:
|
||||
scene = draw_filled_rectangle(
|
||||
scene=scene, rect=text_rect, color=background_color
|
||||
)
|
||||
|
||||
cv2.putText(
|
||||
img=scene,
|
||||
text=text,
|
||||
org=(text_anchor.x - text_width // 2, text_anchor.y + text_height // 2),
|
||||
fontFace=text_font,
|
||||
fontScale=text_scale,
|
||||
color=text_color.as_bgr(),
|
||||
thickness=text_thickness,
|
||||
lineType=cv2.LINE_AA,
|
||||
)
|
||||
return scene
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from typing import Tuple
|
||||
|
||||
|
||||
class Position(Enum):
|
||||
CENTER = "CENTER"
|
||||
BOTTOM_CENTER = "BOTTOM_CENTER"
|
||||
|
||||
@classmethod
|
||||
def list(cls):
|
||||
return list(map(lambda c: c.value, cls))
|
||||
|
||||
|
||||
@dataclass
|
||||
class Point:
|
||||
x: float
|
||||
@@ -0,0 +1,28 @@
|
||||
import numpy as np
|
||||
|
||||
from supervision.geometry.core import Point
|
||||
|
||||
|
||||
def get_polygon_center(polygon: np.ndarray) -> Point:
|
||||
"""
|
||||
Calculate the center of a polygon.
|
||||
|
||||
This function takes in a polygon as a 2-dimensional numpy ndarray and returns the center of the polygon as a Point object. The center is calculated as the mean of the polygon's vertices along each axis, and is rounded down to the nearest integer.
|
||||
|
||||
Parameters:
|
||||
polygon (np.ndarray): A 2-dimensional numpy ndarray representing the vertices of the polygon.
|
||||
|
||||
Returns:
|
||||
Point: The center of the polygon, represented as a Point object with x and y attributes.
|
||||
|
||||
Examples:
|
||||
```python
|
||||
>>> from supervision.geometry.utils import get_polygon_center
|
||||
|
||||
>>> vertices = np.array([[0, 0], [0, 1], [1, 1], [1, 0]])
|
||||
>>> get_center(vertices)
|
||||
Point(x=0.5, y=0.5)
|
||||
```
|
||||
"""
|
||||
center = np.mean(polygon, axis=0).astype(int)
|
||||
return Point(x=center[0], y=center[1])
|
||||
@@ -11,9 +11,18 @@ def show_frame_in_notebook(
|
||||
"""
|
||||
Display a frame in Jupyter Notebook using Matplotlib
|
||||
|
||||
:param frame: np.ndarray : The frame to be displayed.
|
||||
:param size: Tuple[int, int] : The size of the plot. default:(10,10)
|
||||
:param cmap: str : the colormap to use for single channel images. default:gray
|
||||
Attributes:
|
||||
frame (np.ndarray): The frame to be displayed.
|
||||
size (Tuple[int, int]): The size of the plot. default:(10,10)
|
||||
cmap (str): the colormap to use for single channel images. default:gray
|
||||
|
||||
Examples:
|
||||
```python
|
||||
>>> from supervision.notebook.utils import show_frame_in_notebook
|
||||
|
||||
%matplotlib inline
|
||||
show_frame_in_notebook(frame, (16, 16))
|
||||
```
|
||||
"""
|
||||
if frame.ndim == 2:
|
||||
plt.figure(figsize=size)
|
||||
|
||||
@@ -1,205 +0,0 @@
|
||||
from typing import List, Optional, Union
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
from supervision.draw.color import Color, ColorPalette
|
||||
|
||||
|
||||
class Detections:
|
||||
def __init__(
|
||||
self,
|
||||
xyxy: np.ndarray,
|
||||
confidence: np.ndarray,
|
||||
class_id: np.ndarray,
|
||||
tracker_id: Optional[np.ndarray] = None,
|
||||
):
|
||||
"""
|
||||
Data class containing information about the detections in a video frame.
|
||||
|
||||
:param xyxy: np.ndarray : An array of shape (n, 4) containing the bounding boxes coordinates in format [x1, y1, x2, y2]
|
||||
:param confidence: np.ndarray : An array of shape (n,) containing the confidence scores of the detections.
|
||||
:param class_id: np.ndarray : An array of shape (n,) containing the class ids of the detections.
|
||||
:param tracker_id: Optional[np.ndarray] : An array of shape (n,) containing the tracker ids of the detections.
|
||||
"""
|
||||
self.xyxy: np.ndarray = xyxy
|
||||
self.confidence: np.ndarray = confidence
|
||||
self.class_id: np.ndarray = class_id
|
||||
self.tracker_id: Optional[np.ndarray] = tracker_id
|
||||
|
||||
n = len(self.xyxy)
|
||||
validators = [
|
||||
(isinstance(self.xyxy, np.ndarray) and self.xyxy.shape == (n, 4)),
|
||||
(isinstance(self.confidence, np.ndarray) and self.confidence.shape == (n,)),
|
||||
(isinstance(self.class_id, np.ndarray) and self.class_id.shape == (n,)),
|
||||
self.tracker_id is None
|
||||
or (
|
||||
isinstance(self.tracker_id, np.ndarray)
|
||||
and self.tracker_id.shape == (n,)
|
||||
),
|
||||
]
|
||||
if not all(validators):
|
||||
raise ValueError(
|
||||
"xyxy must be 2d np.ndarray with (n, 4) shape, "
|
||||
"confidence must be 1d np.ndarray with (n,) shape, "
|
||||
"class_id must be 1d np.ndarray with (n,) shape, "
|
||||
"tracker_id must be None or 1d np.ndarray with (n,) shape"
|
||||
)
|
||||
|
||||
def __len__(self):
|
||||
"""
|
||||
Returns the number of detections in the Detections object.
|
||||
"""
|
||||
return len(self.xyxy)
|
||||
|
||||
def __iter__(self):
|
||||
"""
|
||||
Iterates over the Detections object and yield a tuple of (xyxy, confidence, class_id, tracker_id) for each detection.
|
||||
"""
|
||||
for i in range(len(self.xyxy)):
|
||||
yield (
|
||||
self.xyxy[i],
|
||||
self.confidence[i],
|
||||
self.class_id[i],
|
||||
self.tracker_id[i] if self.tracker_id is not None else None,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_yolov5(cls, yolov5_output: np.ndarray):
|
||||
"""
|
||||
Creates a Detections instance from a YOLOv5 output tensor
|
||||
|
||||
:param yolov5_output: np.ndarray : The output tensor from YOLOv5
|
||||
:return: Detections : A Detections instance representing the detections in the frame
|
||||
|
||||
Example:
|
||||
detections = Detections.from_yolov5(yolov5_output)
|
||||
"""
|
||||
xyxy = yolov5_output[:, :4]
|
||||
confidence = yolov5_output[:, 4]
|
||||
class_id = yolov5_output[:, 5].astype(int)
|
||||
return cls(xyxy, confidence, class_id)
|
||||
|
||||
def filter(self, mask: np.ndarray, inplace: bool = False) -> Optional[np.ndarray]:
|
||||
"""
|
||||
Filter the detections by applying a mask
|
||||
|
||||
:param mask: np.ndarray : A mask of shape (n,) containing a boolean value for each detection indicating if it should be included in the filtered detections
|
||||
:param inplace: bool : If True, the original data will be modified and self will be returned.
|
||||
:return: Optional[np.ndarray] : A new instance of Detections with the filtered detections, if inplace is set to False. None otherwise.
|
||||
"""
|
||||
if inplace:
|
||||
self.xyxy = self.xyxy[mask]
|
||||
self.confidence = self.confidence[mask]
|
||||
self.class_id = self.class_id[mask]
|
||||
self.tracker_id = (
|
||||
self.tracker_id[mask] if self.tracker_id is not None else None
|
||||
)
|
||||
return self
|
||||
else:
|
||||
return Detections(
|
||||
xyxy=self.xyxy[mask],
|
||||
confidence=self.confidence[mask],
|
||||
class_id=self.class_id[mask],
|
||||
tracker_id=self.tracker_id[mask]
|
||||
if self.tracker_id is not None
|
||||
else None,
|
||||
)
|
||||
|
||||
|
||||
class BoxAnnotator:
|
||||
def __init__(
|
||||
self,
|
||||
color: Union[Color, ColorPalette],
|
||||
thickness: int = 2,
|
||||
text_color: Color = Color.black(),
|
||||
text_scale: float = 0.5,
|
||||
text_thickness: int = 1,
|
||||
text_padding: int = 10,
|
||||
):
|
||||
"""
|
||||
A class for drawing bounding boxes on an image using detections provided.
|
||||
|
||||
:param color: Union[Color, ColorPalette] : The color to draw the bounding box, can be a single color or a color palette
|
||||
:param thickness: int : The thickness of the bounding box lines, default is 2
|
||||
:param text_color: Color : The color of the text on the bounding box, default is white
|
||||
:param text_scale: float : The scale of the text on the bounding box, default is 0.5
|
||||
:param text_thickness: int : The thickness of the text on the bounding box, default is 1
|
||||
:param text_padding: int : The padding around the text on the bounding box, default is 5
|
||||
"""
|
||||
self.color: Union[Color, ColorPalette] = color
|
||||
self.thickness: int = thickness
|
||||
self.text_color: Color = text_color
|
||||
self.text_scale: float = text_scale
|
||||
self.text_thickness: int = text_thickness
|
||||
self.text_padding: int = text_padding
|
||||
|
||||
def annotate(
|
||||
self,
|
||||
frame: np.ndarray,
|
||||
detections: Detections,
|
||||
labels: Optional[List[str]] = None,
|
||||
) -> np.ndarray:
|
||||
"""
|
||||
Draws bounding boxes on the frame using the detections provided.
|
||||
|
||||
:param frame: np.ndarray : The image on which the bounding boxes will be drawn
|
||||
:param detections: Detections : The detections for which the bounding boxes will be drawn
|
||||
:param labels: Optional[List[str]] : An optional list of labels corresponding to each detection. If labels is provided, the confidence score of the detection will be replaced with the label.
|
||||
:return: np.ndarray : The image with the bounding boxes drawn on it
|
||||
"""
|
||||
font = cv2.FONT_HERSHEY_SIMPLEX
|
||||
for i, (xyxy, confidence, class_id, tracker_id) in enumerate(detections):
|
||||
color = (
|
||||
self.color.by_idx(class_id)
|
||||
if isinstance(self.color, ColorPalette)
|
||||
else self.color
|
||||
)
|
||||
text = (
|
||||
f"{confidence:0.2f}"
|
||||
if (labels is None or len(detections) != len(labels))
|
||||
else labels[i]
|
||||
)
|
||||
|
||||
x1, y1, x2, y2 = xyxy.astype(int)
|
||||
text_width, text_height = cv2.getTextSize(
|
||||
text=text,
|
||||
fontFace=font,
|
||||
fontScale=self.text_scale,
|
||||
thickness=self.text_thickness,
|
||||
)[0]
|
||||
|
||||
text_x = x1 + self.text_padding
|
||||
text_y = y1 - self.text_padding
|
||||
|
||||
text_background_x1 = x1
|
||||
text_background_y1 = y1 - 2 * self.text_padding - text_height
|
||||
|
||||
text_background_x2 = x1 + 2 * self.text_padding + text_width
|
||||
text_background_y2 = y1
|
||||
|
||||
cv2.rectangle(
|
||||
img=frame,
|
||||
pt1=(x1, y1),
|
||||
pt2=(x2, y2),
|
||||
color=color.as_bgr(),
|
||||
thickness=self.thickness,
|
||||
)
|
||||
cv2.rectangle(
|
||||
img=frame,
|
||||
pt1=(text_background_x1, text_background_y1),
|
||||
pt2=(text_background_x2, text_background_y2),
|
||||
color=color.as_bgr(),
|
||||
thickness=cv2.FILLED,
|
||||
)
|
||||
cv2.putText(
|
||||
img=frame,
|
||||
text=text,
|
||||
org=(text_x, text_y),
|
||||
fontFace=font,
|
||||
fontScale=self.text_scale,
|
||||
color=self.text_color.as_rgb(),
|
||||
thickness=self.text_thickness,
|
||||
lineType=cv2.LINE_AA,
|
||||
)
|
||||
return frame
|
||||
@@ -0,0 +1,161 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Callable, Generator, Optional, Tuple
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
|
||||
@dataclass
|
||||
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 import VideoInfo
|
||||
|
||||
>>> video_info = VideoInfo.from_video_path(video_path='video.mp4')
|
||||
|
||||
>>> video_info
|
||||
VideoInfo(width=3840, height=2160, fps=25, total_frames=538)
|
||||
|
||||
>>> video_info.resolution_wh
|
||||
(3840, 2160)
|
||||
```
|
||||
"""
|
||||
|
||||
width: int
|
||||
height: int
|
||||
fps: int
|
||||
total_frames: Optional[int] = None
|
||||
|
||||
@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_wh(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 import VideoInfo, 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_wh,
|
||||
)
|
||||
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 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 import process_video
|
||||
|
||||
>>> def process_frame(scene: 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()
|
||||
+16
-18
@@ -7,27 +7,25 @@ from supervision.draw.color import Color
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'color_hex, expected_result, exception',
|
||||
"color_hex, expected_result, exception",
|
||||
[
|
||||
('fff', Color.white(), DoesNotRaise()),
|
||||
('#fff', Color.white(), DoesNotRaise()),
|
||||
('ffffff', Color.white(), DoesNotRaise()),
|
||||
('#ffffff', Color.white(), DoesNotRaise()),
|
||||
('f00', Color.red(), DoesNotRaise()),
|
||||
('0f0', Color.green(), DoesNotRaise()),
|
||||
('00f', Color.blue(), DoesNotRaise()),
|
||||
('#808000', Color(r=128, g=128, b=0), DoesNotRaise()),
|
||||
('', None, pytest.raises(ValueError)),
|
||||
('00', None, pytest.raises(ValueError)),
|
||||
('0000', None, pytest.raises(ValueError)),
|
||||
('0000000', None, pytest.raises(ValueError)),
|
||||
('ffg', None, pytest.raises(ValueError)),
|
||||
]
|
||||
("fff", Color.white(), DoesNotRaise()),
|
||||
("#fff", Color.white(), DoesNotRaise()),
|
||||
("ffffff", Color.white(), DoesNotRaise()),
|
||||
("#ffffff", Color.white(), DoesNotRaise()),
|
||||
("f00", Color.red(), DoesNotRaise()),
|
||||
("0f0", Color.green(), DoesNotRaise()),
|
||||
("00f", Color.blue(), DoesNotRaise()),
|
||||
("#808000", Color(r=128, g=128, b=0), DoesNotRaise()),
|
||||
("", None, pytest.raises(ValueError)),
|
||||
("00", None, pytest.raises(ValueError)),
|
||||
("0000", None, pytest.raises(ValueError)),
|
||||
("0000000", None, pytest.raises(ValueError)),
|
||||
("ffg", None, pytest.raises(ValueError)),
|
||||
],
|
||||
)
|
||||
def test_color_from_hex(
|
||||
color_hex,
|
||||
expected_result: Optional[Color],
|
||||
exception: Exception
|
||||
color_hex, expected_result: Optional[Color], exception: Exception
|
||||
) -> None:
|
||||
with exception:
|
||||
result = Color.from_hex(color_hex=color_hex)
|
||||
|
||||
@@ -1,40 +1,31 @@
|
||||
import pytest
|
||||
|
||||
from supervision.geometry.dataclasses import Vector, Point
|
||||
from supervision.geometry.core import Vector, Point
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'vector, point, expected_result',
|
||||
"vector, point, expected_result",
|
||||
[
|
||||
(Vector(start=Point(x=0, y=0), end=Point(x=5, y=5)), Point(x=-1, y=1), False),
|
||||
(Vector(start=Point(x=0, y=0), end=Point(x=5, y=5)), Point(x=6, y=6), False),
|
||||
(Vector(start=Point(x=0, y=0), end=Point(x=5, y=5)), Point(x=3, y=6), False),
|
||||
|
||||
(Vector(start=Point(x=5, y=5), end=Point(x=0, y=0)), Point(x=-1, y=1), True),
|
||||
(Vector(start=Point(x=5, y=5), end=Point(x=0, y=0)), Point(x=6, y=6), False),
|
||||
(Vector(start=Point(x=5, y=5), end=Point(x=0, y=0)), Point(x=3, y=6), True),
|
||||
|
||||
(Vector(start=Point(x=0, y=0), end=Point(x=1, y=0)), Point(x=0, y=0), False),
|
||||
(Vector(start=Point(x=0, y=0), end=Point(x=1, y=0)), Point(x=0, y=-1), True),
|
||||
(Vector(start=Point(x=0, y=0), end=Point(x=1, y=0)), Point(x=0, y=1), False),
|
||||
|
||||
(Vector(start=Point(x=1, y=0), end=Point(x=0, y=0)), Point(x=0, y=0), False),
|
||||
(Vector(start=Point(x=1, y=0), end=Point(x=0, y=0)), Point(x=0, y=-1), False),
|
||||
(Vector(start=Point(x=1, y=0), end=Point(x=0, y=0)), Point(x=0, y=1), True),
|
||||
|
||||
(Vector(start=Point(x=1, y=1), end=Point(x=1, y=3)), Point(x=0, y=0), False),
|
||||
(Vector(start=Point(x=1, y=1), end=Point(x=1, y=3)), Point(x=1, y=4), False),
|
||||
(Vector(start=Point(x=1, y=1), end=Point(x=1, y=3)), Point(x=2, y=4), True),
|
||||
|
||||
(Vector(start=Point(x=1, y=3), end=Point(x=1, y=1)), Point(x=0, y=0), True),
|
||||
(Vector(start=Point(x=1, y=3), end=Point(x=1, y=1)), Point(x=1, y=4), False),
|
||||
(Vector(start=Point(x=1, y=3), end=Point(x=1, y=1)), Point(x=2, y=4), False),
|
||||
]
|
||||
],
|
||||
)
|
||||
def test_vector_is_in(
|
||||
vector: Vector,
|
||||
point: Point,
|
||||
expected_result: bool
|
||||
) -> None:
|
||||
def test_vector_is_in(vector: Vector, point: Point, expected_result: bool) -> None:
|
||||
result = vector.is_in(point=point)
|
||||
assert result == expected_result
|
||||
|
||||
Reference in New Issue
Block a user