Commit e63cf68a authored by chenzk's avatar chenzk
Browse files

v1.0

parents
Pipeline #2842 canceled with stages
# Ultralytics YOLO 🚀, AGPL-3.0 license
# Builds ultralytics/ultralytics:latest-cpu image on DockerHub https://hub.docker.com/r/ultralytics/ultralytics
# Image is CPU-optimized for ONNX, OpenVINO and PyTorch YOLO11 deployments
# Use official Python base image for reproducibility (3.11.10 for export and 3.12.6 for inference)
FROM python:3.11.10-slim-bookworm
# Set environment variables
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PIP_NO_CACHE_DIR=1 \
PIP_BREAK_SYSTEM_PACKAGES=1
# Downloads to user config dir
ADD https://github.com/ultralytics/assets/releases/download/v0.0.0/Arial.ttf \
https://github.com/ultralytics/assets/releases/download/v0.0.0/Arial.Unicode.ttf \
/root/.config/Ultralytics/
# Install linux packages
# g++ required to build 'tflite_support' and 'lap' packages, libusb-1.0-0 required for 'tflite_support' package
RUN apt-get update && \
apt-get install -y --no-install-recommends \
python3-pip git zip unzip wget curl htop libgl1 libglib2.0-0 libpython3-dev gnupg g++ libusb-1.0-0 \
&& rm -rf /var/lib/apt/lists/*
# Create working directory
WORKDIR /ultralytics
# Copy contents and configure git
COPY . .
RUN sed -i '/^\[http "https:\/\/github\.com\/"\]/,+1d' .git/config
ADD https://github.com/ultralytics/assets/releases/download/v8.3.0/yolo11n.pt .
# Install pip packages
RUN pip install uv
RUN uv pip install --system -e ".[export]" --extra-index-url https://download.pytorch.org/whl/cpu --index-strategy unsafe-first-match
# Run exports to AutoInstall packages
RUN yolo export model=tmp/yolo11n.pt format=edgetpu imgsz=32
RUN yolo export model=tmp/yolo11n.pt format=ncnn imgsz=32
# Requires Python<=3.10, bug with paddlepaddle==2.5.0 https://github.com/PaddlePaddle/X2Paddle/issues/991
RUN uv pip install --system "paddlepaddle>=2.6.0" x2paddle
# Remove extra build files
RUN rm -rf tmp /root/.config/Ultralytics/persistent_cache.json
# Usage Examples -------------------------------------------------------------------------------------------------------
# Build and Push
# t=ultralytics/ultralytics:latest-python && sudo docker build -f docker/Dockerfile-python -t $t . && sudo docker push $t
# Run
# t=ultralytics/ultralytics:latest-python && sudo docker run -it --ipc=host $t
# Pull and Run
# t=ultralytics/ultralytics:latest-python && sudo docker pull $t && sudo docker run -it --ipc=host $t
# Pull and Run with local volume mounted
# t=ultralytics/ultralytics:latest-python && sudo docker pull $t && sudo docker run -it --ipc=host -v "$(pwd)"/shared/datasets:/datasets $t
# Ultralytics YOLO 🚀, AGPL-3.0 license
# Builds GitHub actions CI runner image for deployment to DockerHub https://hub.docker.com/r/ultralytics/ultralytics
# Image is CUDA-optimized for YOLO11 single/multi-GPU training and inference tests
# Start FROM Ultralytics GPU image
FROM ultralytics/ultralytics:latest
# Set environment variables
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PIP_NO_CACHE_DIR=1 \
PIP_BREAK_SYSTEM_PACKAGES=1 \
RUNNER_ALLOW_RUNASROOT=1 \
DEBIAN_FRONTEND=noninteractive
# Set the working directory
WORKDIR /actions-runner
# Download and unpack the latest runner from https://github.com/actions/runner
RUN FILENAME=actions-runner-linux-x64-2.320.0.tar.gz && \
curl -o $FILENAME -L https://github.com/actions/runner/releases/download/v2.320.0/$FILENAME && \
tar xzf $FILENAME && \
rm $FILENAME
# Install runner dependencies
RUN uv pip install --system pytest-cov
RUN ./bin/installdependencies.sh && \
apt-get -y install libicu-dev
# Inline ENTRYPOINT command to configure and start runner with default TOKEN and NAME
ENTRYPOINT sh -c './config.sh --url https://github.com/ultralytics/ultralytics \
--token ${GITHUB_RUNNER_TOKEN:-TOKEN} \
--name ${GITHUB_RUNNER_NAME:-NAME} \
--labels gpu-latest \
--replace && \
./run.sh'
# Usage Examples -------------------------------------------------------------------------------------------------------
# Build and Push
# t=ultralytics/ultralytics:latest-runner && sudo docker build -f docker/Dockerfile-runner -t $t . && sudo docker push $t
# Pull and Run in detached mode with access to GPUs 0 and 1
# t=ultralytics/ultralytics:latest-runner && sudo docker run -d -e GITHUB_RUNNER_TOKEN=TOKEN -e GITHUB_RUNNER_NAME=NAME --ipc=host --gpus '"device=0,1"' $t
## Ultralytics Examples
This directory features a collection of real-world applications and walkthroughs, provided as either Python files or notebooks. Explore the examples below to see how YOLO can be integrated into various applications.
### Ultralytics YOLO Example Applications
| Title | Format | Contributor |
| ----------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ----------------------------------------------------------------------------------------- |
| [YOLO ONNX Detection Inference with C++](./YOLOv8-CPP-Inference) | C++/ONNX | [Justas Bartnykas](https://github.com/JustasBart) |
| [YOLO OpenCV ONNX Detection Python](./YOLOv8-OpenCV-ONNX-Python) | OpenCV/Python/ONNX | [Farid Inawan](https://github.com/frdteknikelektro) |
| [YOLO C# ONNX-Runtime](https://github.com/dme-compunet/YoloSharp) | .NET/ONNX-Runtime | [Compunet](https://github.com/dme-compunet) |
| [YOLO .Net ONNX Detection C#](https://www.nuget.org/packages/Yolov8.Net) | C# .Net | [Samuel Stainback](https://github.com/sstainba) |
| [YOLOv8 on NVIDIA Jetson(TensorRT and DeepStream)](https://wiki.seeedstudio.com/YOLOv8-DeepStream-TRT-Jetson/) | Python | [Lakshantha](https://github.com/lakshanthad) |
| [YOLOv8 ONNXRuntime Python](./YOLOv8-ONNXRuntime) | Python/ONNXRuntime | [Semih Demirel](https://github.com/semihhdemirel) |
| [RTDETR ONNXRuntime Python](./RTDETR-ONNXRuntime-Python) | Python/ONNXRuntime | [Semih Demirel](https://github.com/semihhdemirel) |
| [YOLOv8 ONNXRuntime CPP](./YOLOv8-ONNXRuntime-CPP) | C++/ONNXRuntime | [DennisJcy](https://github.com/DennisJcy), [Onuralp Sezer](https://github.com/onuralpszr) |
| [RTDETR ONNXRuntime C#](https://github.com/Kayzwer/yolo-cs/blob/master/RTDETR.cs) | C#/ONNX | [Kayzwer](https://github.com/Kayzwer) |
| [YOLOv8 SAHI Video Inference](https://github.com/RizwanMunawar/ultralytics/blob/main/examples/YOLOv8-SAHI-Inference-Video/yolov8_sahi.py) | Python | [Muhammad Rizwan Munawar](https://github.com/RizwanMunawar) |
| [YOLOv8 Region Counter](https://github.com/RizwanMunawar/ultralytics/blob/main/examples/YOLOv8-Region-Counter/yolov8_region_counter.py) | Python | [Muhammad Rizwan Munawar](https://github.com/RizwanMunawar) |
| [YOLOv8 Segmentation ONNXRuntime Python](./YOLOv8-Segmentation-ONNXRuntime-Python) | Python/ONNXRuntime | [jamjamjon](https://github.com/jamjamjon) |
| [YOLOv8 LibTorch CPP](./YOLOv8-LibTorch-CPP-Inference) | C++/LibTorch | [Myyura](https://github.com/Myyura) |
| [YOLOv8 OpenCV INT8 TFLite Python](./YOLOv8-TFLite-Python) | Python | [Wamiq Raza](https://github.com/wamiqraza) |
| [YOLOv8 All Tasks ONNXRuntime Rust](./YOLOv8-ONNXRuntime-Rust) | Rust/ONNXRuntime | [jamjamjon](https://github.com/jamjamjon) |
| [YOLOv8 OpenVINO CPP](./YOLOv8-OpenVINO-CPP-Inference) | C++/OpenVINO | [Erlangga Yudi Pradana](https://github.com/rlggyp) |
| [YOLOv5-YOLO11 ONNXRuntime Rust](./YOLO-Series-ONNXRuntime-Rust) | Rust/ONNXRuntime | [jamjamjon](https://github.com/jamjamjon) |
### How to Contribute
We greatly appreciate contributions from the community, including examples, applications, and guides. If you'd like to contribute, please follow these guidelines:
1. **Create a pull request (PR)** with the title prefix `[Example]`, adding your new example folder to the `examples/` directory within the repository.
2. **Ensure your project adheres to the following standards:**
- Makes use of the `ultralytics` package.
- Includes a `README.md` with clear instructions for setting up and running the example.
- Avoids adding large files or dependencies unless they are absolutely necessary for the example.
- Contributors should be willing to provide support for their examples and address related issues.
For more detailed information and guidance on contributing, please visit our [contribution documentation](https://docs.ultralytics.com/help/contributing/).
If you encounter any questions or concerns regarding these guidelines, feel free to open a PR or an issue in the repository, and we will assist you in the contribution process.
# RTDETR - ONNX Runtime
This project implements RTDETR using ONNX Runtime.
## Installation
To run this project, you need to install the required dependencies. The following instructions will guide you through the installation process.
### Installing Required Dependencies
You can install the required dependencies by running the following command:
```bash
pip install -r requirements.txt
```
### Installing `onnxruntime-gpu`
If you have an NVIDIA GPU and want to leverage GPU acceleration, you can install the onnxruntime-gpu package using the following command:
```bash
pip install onnxruntime-gpu
```
Note: Make sure you have the appropriate GPU drivers installed on your system.
### Installing `onnxruntime` (CPU version)
If you don't have an NVIDIA GPU or prefer to use the CPU version of onnxruntime, you can install the onnxruntime package using the following command:
```bash
pip install onnxruntime
```
### Usage
After successfully installing the required packages, you can run the RTDETR implementation using the following command:
```bash
python main.py --model rtdetr-l.onnx --img image.jpg --conf-thres 0.5 --iou-thres 0.5
```
Make sure to replace rtdetr-l.onnx with the path to your RTDETR ONNX model file, image.jpg with the path to your input image, and adjust the confidence threshold (conf-thres) and IoU threshold (iou-thres) values as needed.
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
import argparse
import cv2
import numpy as np
import onnxruntime as ort
import torch
from ultralytics.utils import ASSETS, yaml_load
from ultralytics.utils.checks import check_requirements, check_yaml
class RTDETR:
"""RTDETR object detection model class for handling inference and visualization."""
def __init__(self, model_path, img_path, conf_thres=0.5, iou_thres=0.5):
"""
Initializes the RTDETR object with the specified parameters.
Args:
model_path: Path to the ONNX model file.
img_path: Path to the input image.
conf_thres: Confidence threshold for object detection.
iou_thres: IoU threshold for non-maximum suppression
"""
self.model_path = model_path
self.img_path = img_path
self.conf_thres = conf_thres
self.iou_thres = iou_thres
# Set up the ONNX runtime session with CUDA and CPU execution providers
self.session = ort.InferenceSession(model_path, providers=["CUDAExecutionProvider", "CPUExecutionProvider"])
self.model_input = self.session.get_inputs()
self.input_width = self.model_input[0].shape[2]
self.input_height = self.model_input[0].shape[3]
# Load class names from the COCO dataset YAML file
self.classes = yaml_load(check_yaml("coco8.yaml"))["names"]
# Generate a color palette for drawing bounding boxes
self.color_palette = np.random.uniform(0, 255, size=(len(self.classes), 3))
def draw_detections(self, box, score, class_id):
"""
Draws bounding boxes and labels on the input image based on the detected objects.
Args:
box: Detected bounding box.
score: Corresponding detection score.
class_id: Class ID for the detected object.
Returns:
None
"""
# Extract the coordinates of the bounding box
x1, y1, x2, y2 = box
# Retrieve the color for the class ID
color = self.color_palette[class_id]
# Draw the bounding box on the image
cv2.rectangle(self.img, (int(x1), int(y1)), (int(x2), int(y2)), color, 2)
# Create the label text with class name and score
label = f"{self.classes[class_id]}: {score:.2f}"
# Calculate the dimensions of the label text
(label_width, label_height), _ = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)
# Calculate the position of the label text
label_x = x1
label_y = y1 - 10 if y1 - 10 > label_height else y1 + 10
# Draw a filled rectangle as the background for the label text
cv2.rectangle(
self.img,
(int(label_x), int(label_y - label_height)),
(int(label_x + label_width), int(label_y + label_height)),
color,
cv2.FILLED,
)
# Draw the label text on the image
cv2.putText(
self.img, label, (int(label_x), int(label_y)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 1, cv2.LINE_AA
)
def preprocess(self):
"""
Preprocesses the input image before performing inference.
Returns:
image_data: Preprocessed image data ready for inference.
"""
# Read the input image using OpenCV
self.img = cv2.imread(self.img_path)
# Get the height and width of the input image
self.img_height, self.img_width = self.img.shape[:2]
# Convert the image color space from BGR to RGB
img = cv2.cvtColor(self.img, cv2.COLOR_BGR2RGB)
# Resize the image to match the input shape
img = cv2.resize(img, (self.input_width, self.input_height))
# Normalize the image data by dividing it by 255.0
image_data = np.array(img) / 255.0
# Transpose the image to have the channel dimension as the first dimension
image_data = np.transpose(image_data, (2, 0, 1)) # Channel first
# Expand the dimensions of the image data to match the expected input shape
image_data = np.expand_dims(image_data, axis=0).astype(np.float32)
# Return the preprocessed image data
return image_data
def bbox_cxcywh_to_xyxy(self, boxes):
"""
Converts bounding boxes from (center x, center y, width, height) format to (x_min, y_min, x_max, y_max) format.
Args:
boxes (numpy.ndarray): An array of shape (N, 4) where each row represents
a bounding box in (cx, cy, w, h) format.
Returns:
numpy.ndarray: An array of shape (N, 4) where each row represents
a bounding box in (x_min, y_min, x_max, y_max) format.
"""
# Calculate half width and half height of the bounding boxes
half_width = boxes[:, 2] / 2
half_height = boxes[:, 3] / 2
# Calculate the coordinates of the bounding boxes
x_min = boxes[:, 0] - half_width
y_min = boxes[:, 1] - half_height
x_max = boxes[:, 0] + half_width
y_max = boxes[:, 1] + half_height
# Return the bounding boxes in (x_min, y_min, x_max, y_max) format
return np.column_stack((x_min, y_min, x_max, y_max))
def postprocess(self, model_output):
"""
Postprocesses the model output to extract detections and draw them on the input image.
Args:
model_output: Output of the model inference.
Returns:
np.array: Annotated image with detections.
"""
# Squeeze the model output to remove unnecessary dimensions
outputs = np.squeeze(model_output[0])
# Extract bounding boxes and scores from the model output
boxes = outputs[:, :4]
scores = outputs[:, 4:]
# Get the class labels and scores for each detection
labels = np.argmax(scores, axis=1)
scores = np.max(scores, axis=1)
# Apply confidence threshold to filter out low-confidence detections
mask = scores > self.conf_thres
boxes, scores, labels = boxes[mask], scores[mask], labels[mask]
# Convert bounding boxes to (x_min, y_min, x_max, y_max) format
boxes = self.bbox_cxcywh_to_xyxy(boxes)
# Scale bounding boxes to match the original image dimensions
boxes[:, 0::2] *= self.img_width
boxes[:, 1::2] *= self.img_height
# Draw detections on the image
for box, score, label in zip(boxes, scores, labels):
self.draw_detections(box, score, label)
# Return the annotated image
return self.img
def main(self):
"""
Executes the detection on the input image using the ONNX model.
Returns:
np.array: Output image with annotations.
"""
# Preprocess the image for model input
image_data = self.preprocess()
# Run the model inference
model_output = self.session.run(None, {self.model_input[0].name: image_data})
# Process and return the model output
return self.postprocess(model_output)
if __name__ == "__main__":
# Set up argument parser for command-line arguments
parser = argparse.ArgumentParser()
parser.add_argument("--model", type=str, default="rtdetr-l.onnx", help="Path to the ONNX model file.")
parser.add_argument("--img", type=str, default=str(ASSETS / "bus.jpg"), help="Path to the input image.")
parser.add_argument("--conf-thres", type=float, default=0.5, help="Confidence threshold for object detection.")
parser.add_argument("--iou-thres", type=float, default=0.5, help="IoU threshold for non-maximum suppression.")
args = parser.parse_args()
# Check for dependencies and set up ONNX runtime
check_requirements("onnxruntime-gpu" if torch.cuda.is_available() else "onnxruntime")
# Create the detector instance with specified parameters
detection = RTDETR(args.model, args.img, args.conf_thres, args.iou_thres)
# Perform detection and get the output image
output_image = detection.main()
# Display the annotated output image
cv2.namedWindow("Output", cv2.WINDOW_NORMAL)
cv2.imshow("Output", output_image)
cv2.waitKey(0)
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
[package]
name = "YOLO-ONNXRuntime-Rust"
version = "0.1.0"
edition = "2021"
authors = ["Jamjamjon <xxyydzml@outlook.com>"]
[dependencies]
anyhow = "1.0.92"
clap = "4.5.20"
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
usls = { version = "0.0.19", features = ["auto"] }
# YOLO-Series ONNXRuntime Rust Demo for Core YOLO Tasks
This repository provides a Rust demo for key YOLO-Series tasks such as `Classification`, `Segmentation`, `Detection`, `Pose Detection`, and `OBB` using ONNXRuntime. It supports various YOLO models (v5 - 11) across multiple vision tasks.
## Introduction
- This example leverages the latest versions of both ONNXRuntime and YOLO models.
- We utilize the [usls](https://github.com/jamjamjon/usls/tree/main) crate to streamline YOLO model inference, providing efficient data loading, visualization, and optimized inference performance.
## Features
- **Extensive Model Compatibility**: Supports `YOLOv5`, `YOLOv6`, `YOLOv7`, `YOLOv8`, `YOLOv9`, `YOLOv10`, `YOLO11`, `YOLO-world`, `RTDETR`, and others, covering a wide range of YOLO versions.
- **Versatile Task Coverage**: Includes `Classification`, `Segmentation`, `Detection`, `Pose`, and `OBB`.
- **Precision Flexibility**: Works with `FP16` and `FP32` ONNX models.
- **Execution Providers**: Accelerated support for `CPU`, `CUDA`, `CoreML`, and `TensorRT`.
- **Dynamic Input Shapes**: Dynamically adjusts to variable `batch`, `width`, and `height` dimensions for flexible model input.
- **Flexible Data Loading**: The `DataLoader` handles images, folders, videos, and video streams.
- **Real-Time Display and Video Export**: `Viewer` provides real-time frame visualization and video export functions, similar to OpenCV’s `imshow()` and `imwrite()`.
- **Enhanced Annotation and Visualization**: The `Annotator` facilitates comprehensive result rendering, with support for bounding boxes (HBB), oriented bounding boxes (OBB), polygons, masks, keypoints, and text labels.
## Setup Instructions
### 1. ONNXRuntime Linking
<details>
<summary>You have two options to link the ONNXRuntime library:</summary>
- **Option 1: Manual Linking**
- For detailed setup, consult the [ONNX Runtime linking documentation](https://ort.pyke.io/setup/linking).
- **Linux or macOS**:
1. Download the ONNX Runtime package from the [Releases page](https://github.com/microsoft/onnxruntime/releases).
2. Set up the library path by exporting the `ORT_DYLIB_PATH` environment variable:
```shell
export ORT_DYLIB_PATH=/path/to/onnxruntime/lib/libonnxruntime.so.1.19.0
```
- **Option 2: Automatic Download**
- Use the `--features auto` flag to handle downloading automatically:
```shell
cargo run -r --example yolo --features auto
```
</details>
### 2. \[Optional\] Install CUDA, CuDNN, and TensorRT
- The CUDA execution provider requires CUDA version `12.x`.
- The TensorRT execution provider requires both CUDA `12.x` and TensorRT `10.x`.
### 3. \[Optional\] Install ffmpeg
To view video frames and save video inferences, install `rust-ffmpeg`. For instructions, see:
[https://github.com/zmwangx/rust-ffmpeg/wiki/Notes-on-building#dependencies](https://github.com/zmwangx/rust-ffmpeg/wiki/Notes-on-building#dependencies)
## Get Started
```Shell
# customized
cargo run -r -- --task detect --ver v8 --nc 6 --model xxx.onnx # YOLOv8
# Classify
cargo run -r -- --task classify --ver v5 --scale s --width 224 --height 224 --nc 1000 # YOLOv5
cargo run -r -- --task classify --ver v8 --scale n --width 224 --height 224 --nc 1000 # YOLOv8
cargo run -r -- --task classify --ver v11 --scale n --width 224 --height 224 --nc 1000 # YOLO11
# Detect
cargo run -r -- --task detect --ver v5 --scale n # YOLOv5
cargo run -r -- --task detect --ver v6 --scale n # YOLOv6
cargo run -r -- --task detect --ver v7 --scale t # YOLOv7
cargo run -r -- --task detect --ver v8 --scale n # YOLOv8
cargo run -r -- --task detect --ver v9 --scale t # YOLOv9
cargo run -r -- --task detect --ver v10 --scale n # YOLOv10
cargo run -r -- --task detect --ver v11 --scale n # YOLO11
cargo run -r -- --task detect --ver rtdetr --scale l # RTDETR
# Pose
cargo run -r -- --task pose --ver v8 --scale n # YOLOv8-Pose
cargo run -r -- --task pose --ver v11 --scale n # YOLO11-Pose
# Segment
cargo run -r -- --task segment --ver v5 --scale n # YOLOv5-Segment
cargo run -r -- --task segment --ver v8 --scale n # YOLOv8-Segment
cargo run -r -- --task segment --ver v11 --scale n # YOLOv8-Segment
cargo run -r -- --task segment --ver v8 --model yolo/FastSAM-s-dyn-f16.onnx # FastSAM
# OBB
cargo run -r -- --ver v8 --task obb --scale n --width 1024 --height 1024 --source images/dota.png # YOLOv8-Obb
cargo run -r -- --ver v11 --task obb --scale n --width 1024 --height 1024 --source images/dota.png # YOLO11-Obb
```
**`cargo run -- --help` for more options**
For more details, please refer to [usls-yolo](https://github.com/jamjamjon/usls/tree/main/examples/yolo).
use anyhow::Result;
use clap::Parser;
use usls::{
models::YOLO, Annotator, DataLoader, Device, Options, Viewer, Vision, YOLOScale, YOLOTask,
YOLOVersion, COCO_SKELETONS_16,
};
#[derive(Parser, Clone)]
#[command(author, version, about, long_about = None)]
pub struct Args {
/// Path to the ONNX model
#[arg(long)]
pub model: Option<String>,
/// Input source path
#[arg(long, default_value_t = String::from("../../ultralytics/assets/bus.jpg"))]
pub source: String,
/// YOLO Task
#[arg(long, value_enum, default_value_t = YOLOTask::Detect)]
pub task: YOLOTask,
/// YOLO Version
#[arg(long, value_enum, default_value_t = YOLOVersion::V8)]
pub ver: YOLOVersion,
/// YOLO Scale
#[arg(long, value_enum, default_value_t = YOLOScale::N)]
pub scale: YOLOScale,
/// Batch size
#[arg(long, default_value_t = 1)]
pub batch_size: usize,
/// Minimum input width
#[arg(long, default_value_t = 224)]
pub width_min: isize,
/// Input width
#[arg(long, default_value_t = 640)]
pub width: isize,
/// Maximum input width
#[arg(long, default_value_t = 1024)]
pub width_max: isize,
/// Minimum input height
#[arg(long, default_value_t = 224)]
pub height_min: isize,
/// Input height
#[arg(long, default_value_t = 640)]
pub height: isize,
/// Maximum input height
#[arg(long, default_value_t = 1024)]
pub height_max: isize,
/// Number of classes
#[arg(long, default_value_t = 80)]
pub nc: usize,
/// Class confidence
#[arg(long)]
pub confs: Vec<f32>,
/// Enable TensorRT support
#[arg(long)]
pub trt: bool,
/// Enable CUDA support
#[arg(long)]
pub cuda: bool,
/// Enable CoreML support
#[arg(long)]
pub coreml: bool,
/// Use TensorRT half precision
#[arg(long)]
pub half: bool,
/// Device ID to use
#[arg(long, default_value_t = 0)]
pub device_id: usize,
/// Enable performance profiling
#[arg(long)]
pub profile: bool,
/// Disable contour drawing, for saving time
#[arg(long)]
pub no_contours: bool,
/// Show result
#[arg(long)]
pub view: bool,
/// Do not save output
#[arg(long)]
pub nosave: bool,
}
fn main() -> Result<()> {
let args = Args::parse();
// logger
if args.profile {
tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.init();
}
// model path
let path = match &args.model {
None => format!(
"yolo/{}-{}-{}.onnx",
args.ver.name(),
args.scale.name(),
args.task.name()
),
Some(x) => x.to_string(),
};
// saveout
let saveout = match &args.model {
None => format!(
"{}-{}-{}",
args.ver.name(),
args.scale.name(),
args.task.name()
),
Some(x) => {
let p = std::path::PathBuf::from(&x);
p.file_stem().unwrap().to_str().unwrap().to_string()
}
};
// device
let device = if args.cuda {
Device::Cuda(args.device_id)
} else if args.trt {
Device::Trt(args.device_id)
} else if args.coreml {
Device::CoreML(args.device_id)
} else {
Device::Cpu(args.device_id)
};
// build options
let options = Options::new()
.with_model(&path)?
.with_yolo_version(args.ver)
.with_yolo_task(args.task)
.with_device(device)
.with_trt_fp16(args.half)
.with_ixx(0, 0, (1, args.batch_size as _, 4).into())
.with_ixx(0, 2, (args.height_min, args.height, args.height_max).into())
.with_ixx(0, 3, (args.width_min, args.width, args.width_max).into())
.with_confs(if args.confs.is_empty() {
&[0.2, 0.15]
} else {
&args.confs
})
.with_nc(args.nc)
.with_find_contours(!args.no_contours) // find contours or not
// .with_names(&COCO_CLASS_NAMES_80) // detection class names
// .with_names2(&COCO_KEYPOINTS_17) // keypoints class names
// .exclude_classes(&[0])
// .retain_classes(&[0, 5])
.with_profile(args.profile);
// build model
let mut model = YOLO::new(options)?;
// build dataloader
let dl = DataLoader::new(&args.source)?
.with_batch(model.batch() as _)
.build()?;
// build annotator
let annotator = Annotator::default()
.with_skeletons(&COCO_SKELETONS_16)
.without_masks(true) // no masks plotting when doing segment task
.with_bboxes_thickness(3)
.with_keypoints_name(false) // enable keypoints names
.with_saveout_subs(&["YOLO"])
.with_saveout(&saveout);
// build viewer
let mut viewer = if args.view {
Some(Viewer::new().with_delay(5).with_scale(1.).resizable(true))
} else {
None
};
// run & annotate
for (xs, _paths) in dl {
let ys = model.forward(&xs, args.profile)?;
let images_plotted = annotator.plot(&xs, &ys, !args.nosave)?;
// show image
match &mut viewer {
Some(viewer) => viewer.imshow(&images_plotted)?,
None => continue,
}
// check out window and key event
match &mut viewer {
Some(viewer) => {
if !viewer.is_open() || viewer.is_key_pressed(usls::Key::Escape) {
break;
}
}
None => continue,
}
// write video
if !args.nosave {
match &mut viewer {
Some(viewer) => viewer.write_batch(&images_plotted)?,
None => continue,
}
}
}
// finish video write
if !args.nosave {
if let Some(viewer) = &mut viewer {
viewer.finish_write()?;
}
}
Ok(())
}
# YOLOv13 FastAPI REST API
**What is this?**
A REST API server that detects objects in images using YOLOv13 AI models. Upload an image, get back detection results with bounding boxes and confidence scores.
**Key Benefits:**
- Real-time detection (~6.9 FPS with YOLOv13n)
- Multiple YOLO model support (YOLOv13, YOLOv8)
- Simple REST API interface
- Production-ready with error handling
## Quick Start
Before starting the server, make sure you have installed this extra requirement:
```bash
pip install huggingface-hub
```
Then, start the server:
```bash
# Install dependencies
pip install -r requirements.txt
# Start the server
python yolov13_fastapi_api.py
```
Server runs at: http://localhost:8000
API docs: http://localhost:8000/docs
## Usage
### Basic Detection
```bash
curl -X POST "http://localhost:8000/detect" \
-F "image=@your_image.jpg" \
-F "model=yolov13n"
```
### With Custom Settings
```bash
curl -X POST "http://localhost:8000/detect" \
-F "image=@your_image.jpg" \
-F "model=yolov13n" \
-F "conf=0.25" \
-F "iou=0.45"
```
### Get Available Models
```bash
curl http://localhost:8000/models
```
## Available Models
- **YOLOv13**: yolov13n, yolov13s, yolov13m, yolov13l, yolov13x
- **YOLOv8**: yolov8n, yolov8s, yolov8m, yolov8l, yolov8x
**Recommended for real-time**: yolov13n (fastest)
## Response Format
```json
{
"success": true,
"model_used": "yolov13n",
"inference_time": 0.146,
"detections": [
{
"bbox": [x1, y1, x2, y2],
"confidence": 0.85,
"class_id": 0,
"class_name": "person"
}
],
"num_detections": 1,
"image_info": {
"width": 640,
"height": 480,
"channels": 3
}
}
```
## Deployment
### Docker Deployment
```bash
# Build image
docker build -t yolov13-api .
# Run container
docker run -p 8000:8000 yolov13-api
```
### Docker Compose
```yaml
version: '3.8'
services:
yolov13-api:
build: .
ports:
- "8000:8000"
volumes:
- ./models:/app/models # Optional: for custom models
```
### Production Deployment
```bash
# Install production server
pip install gunicorn
# Run with gunicorn
gunicorn -w 4 -k uvicorn.workers.UvicornWorker main:app --bind 0.0.0.0:8000
```
### Environment Variables
```bash
export MODEL_PATH=/path/to/custom/model.pt # Optional
export API_HOST=0.0.0.0
export API_PORT=8000
```
## Performance
- **YOLOv13n**: ~0.146s inference (~6.9 FPS)
- **YOLOv8n**: ~0.169s inference (~5.9 FPS)
YOLOv13n is **13.5% faster** than YOLOv8n with identical accuracy.
fastapi>=0.104.1
uvicorn[standard]>=0.24.0
ultralytics>=8.0.0
opencv-python>=4.8.0
numpy>=1.24.0
python-multipart>=0.0.6
pydantic>=2.0.0
Pillow>=10.0.0
\ No newline at end of file
#!/usr/bin/env python3
"""
YOLOv13 FastAPI REST API Example
A scalable FastAPI server demonstrating real-time object detection capabilities
using YOLOv13 and other YOLO models. This implementation can be easily extended
to support any YOLO model variant for production deployment.
Key Features:
- Real-time object detection via REST API
- Multi-model support (YOLOv13, YOLOv8, and other variants)
- Configurable inference parameters (confidence, IoU thresholds)
- Production-ready error handling and validation
- Performance monitoring and benchmarking
Performance Highlights:
- YOLOv13n: ~0.146s inference time (6.9 FPS theoretical)
- Scalable to any YOLO model family
- Optimized for real-time applications
For a complete production implementation with advanced features, see:
https://github.com/MohibShaikh/yolov13-fastapi-complete
Usage:
pip install fastapi uvicorn ultralytics python-multipart
python yolov13_fastapi_api.py
# Test real-time detection:
curl -X POST "http://localhost:8000/detect" \
-F "image=@path/to/image.jpg" \
-F "model=yolov13n"
Author: MohibShaikh
"""
import time
import logging
from typing import List, Dict, Any, Optional
from pathlib import Path
import cv2
import numpy as np
from fastapi import FastAPI, File, UploadFile, Form, HTTPException
from fastapi.responses import JSONResponse
from pydantic import BaseModel
import uvicorn
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Initialize FastAPI app
app = FastAPI(
title="YOLOv13 Real-Time Detection API",
description="Scalable real-time object detection supporting multiple YOLO models",
version="1.0.0"
)
# Global model cache
models = {}
class DetectionResult(BaseModel):
"""Detection result model"""
success: bool
model_used: str
inference_time: float
detections: List[Dict[str, Any]]
num_detections: int
image_info: Dict[str, int]
def load_model(model_name: str):
"""Load and cache YOLO model"""
if model_name not in models:
try:
from ultralytics import YOLO
logger.info(f"Loading {model_name} model...")
models[model_name] = YOLO(f"{model_name}.pt")
logger.info(f"Model {model_name} loaded successfully")
except Exception as e:
logger.error(f"Failed to load {model_name}: {e}")
raise HTTPException(status_code=500, detail=f"Model loading failed: {e}")
return models[model_name]
def process_image(image_data: bytes) -> np.ndarray:
"""Convert uploaded image to OpenCV format"""
try:
# Convert bytes to numpy array
nparr = np.frombuffer(image_data, np.uint8)
# Decode image
image = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
if image is None:
raise ValueError("Invalid image format")
return image
except Exception as e:
raise HTTPException(status_code=400, detail=f"Image processing failed: {e}")
@app.get("/")
async def root():
"""Root endpoint with API information"""
return {
"message": "YOLOv13 Real-Time Object Detection API",
"description": "Scalable multi-model detection server",
"capabilities": {
"real_time_detection": "Sub-second inference times",
"multi_model_support": "YOLOv13, YOLOv8, and other variants",
"configurable_parameters": "Confidence and IoU thresholds",
"production_ready": "Error handling and validation"
},
"performance": {
"yolov13n_fps": "~6.9 FPS theoretical",
"inference_time": "~0.146s average"
},
"endpoints": {
"/detect": "POST - Real-time object detection",
"/models": "GET - Available models",
"/performance": "GET - Performance metrics",
"/docs": "GET - API documentation"
}
}
@app.get("/models")
async def get_models():
"""Get available YOLO models for real-time detection"""
available_models = ["yolov13n", "yolov13s", "yolov13m", "yolov13l", "yolov13x",
"yolov8n", "yolov8s", "yolov8m", "yolov8l", "yolov8x"]
return {
"available_models": available_models,
"loaded_models": list(models.keys()),
"recommended_for_realtime": "yolov13n",
"model_info": {
"nano_models": ["yolov13n", "yolov8n"],
"small_models": ["yolov13s", "yolov8s"],
"medium_models": ["yolov13m", "yolov8m"],
"large_models": ["yolov13l", "yolov8l"],
"extra_large": ["yolov13x", "yolov8x"]
},
"scaling_note": "All models supported - choose based on speed/accuracy requirements"
}
@app.post("/detect", response_model=DetectionResult)
async def detect_objects(
image: UploadFile = File(..., description="Image file for real-time object detection"),
model: str = Form("yolov13n", description="YOLO model to use (any variant supported)"),
conf: float = Form(0.25, ge=0.0, le=1.0, description="Confidence threshold"),
iou: float = Form(0.45, ge=0.0, le=1.0, description="IoU threshold")
):
"""
Real-time object detection using configurable YOLO models
This endpoint demonstrates scalable real-time detection capabilities.
Supports all YOLO model variants - choose based on your speed/accuracy requirements.
Returns detection results with bounding boxes, confidence scores, and performance metrics.
"""
# Validate model name
valid_models = ["yolov13n", "yolov13s", "yolov13m", "yolov13l", "yolov13x",
"yolov8n", "yolov8s", "yolov8m", "yolov8l", "yolov8x"]
if model not in valid_models:
raise HTTPException(
status_code=400,
detail=f"Invalid model. Choose from: {', '.join(valid_models)}"
)
# Validate image
if not image.content_type or not image.content_type.startswith('image/'):
raise HTTPException(status_code=400, detail="File must be an image")
try:
# Read and process image
image_data = await image.read()
img = process_image(image_data)
# Load model
yolo_model = load_model(model)
# Run inference with timing
start_time = time.time()
results = yolo_model(img, conf=conf, iou=iou, verbose=False)
inference_time = time.time() - start_time
# Process results
detections = []
if len(results) > 0 and results[0].boxes is not None:
boxes = results[0].boxes
for i in range(len(boxes)):
box = boxes[i]
detection = {
"bbox": box.xyxy[0].cpu().numpy().tolist(), # [x1, y1, x2, y2]
"confidence": float(box.conf[0]),
"class_id": int(box.cls[0]),
"class_name": yolo_model.names[int(box.cls[0])]
}
detections.append(detection)
# Return results
return DetectionResult(
success=True,
model_used=model,
inference_time=round(inference_time, 3),
detections=detections,
num_detections=len(detections),
image_info={
"width": img.shape[1],
"height": img.shape[0],
"channels": img.shape[2]
}
)
except HTTPException:
raise
except Exception as e:
logger.error(f"Detection failed: {e}")
raise HTTPException(status_code=500, detail=f"Detection failed: {str(e)}")
@app.get("/performance")
async def get_performance_metrics():
"""Get real-time performance metrics and scaling information"""
return {
"real_time_capabilities": {
"yolov13n": {
"inference_time": "~0.146s",
"fps_theoretical": 6.9,
"use_case": "Real-time applications",
"model_tier": "Nano (fastest)"
},
"performance_scaling": {
"nano_models": "Best for real-time (6-7 FPS)",
"small_models": "Balanced speed/accuracy",
"medium_models": "Higher accuracy, ~3-4 FPS",
"large_models": "Maximum accuracy, ~1-2 FPS"
}
},
"deployment_guidelines": {
"real_time_streaming": "Use nano models (yolov13n, yolov8n)",
"batch_processing": "Use larger models for better accuracy",
"edge_devices": "Nano models recommended",
"server_deployment": "Any model size supported"
},
"scalability": {
"supported_models": "All YOLO variants",
"model_switching": "Runtime model selection",
"configuration": "Adjustable confidence and IoU thresholds",
"extensibility": "Easy to add new YOLO models"
}
}
if __name__ == "__main__":
print("Starting YOLOv13 Real-Time Detection Server...")
print("Multi-model support: YOLOv13, YOLOv8, and other variants")
print("Real-time capability: ~6.9 FPS with YOLOv13n")
print("API Docs: http://localhost:8000/docs")
uvicorn.run(
app,
host="0.0.0.0",
port=8000,
log_level="info"
)
\ No newline at end of file
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
import argparse
import time
from collections import defaultdict
from typing import List, Optional, Tuple
from urllib.parse import urlparse
import cv2
import numpy as np
import torch
from transformers import AutoModel, AutoProcessor
from ultralytics import YOLO
from ultralytics.data.loaders import get_best_youtube_url
from ultralytics.utils.plotting import Annotator
from ultralytics.utils.torch_utils import select_device
class TorchVisionVideoClassifier:
"""Classifies videos using pretrained TorchVision models; see https://pytorch.org/vision/stable/."""
from torchvision.models.video import (
MViT_V1_B_Weights,
MViT_V2_S_Weights,
R3D_18_Weights,
S3D_Weights,
Swin3D_B_Weights,
Swin3D_T_Weights,
mvit_v1_b,
mvit_v2_s,
r3d_18,
s3d,
swin3d_b,
swin3d_t,
)
model_name_to_model_and_weights = {
"s3d": (s3d, S3D_Weights.DEFAULT),
"r3d_18": (r3d_18, R3D_18_Weights.DEFAULT),
"swin3d_t": (swin3d_t, Swin3D_T_Weights.DEFAULT),
"swin3d_b": (swin3d_b, Swin3D_B_Weights.DEFAULT),
"mvit_v1_b": (mvit_v1_b, MViT_V1_B_Weights.DEFAULT),
"mvit_v2_s": (mvit_v2_s, MViT_V2_S_Weights.DEFAULT),
}
def __init__(self, model_name: str, device: str or torch.device = ""):
"""
Initialize the VideoClassifier with the specified model name and device.
Args:
model_name (str): The name of the model to use.
device (str or torch.device, optional): The device to run the model on. Defaults to "".
Raises:
ValueError: If an invalid model name is provided.
"""
if model_name not in self.model_name_to_model_and_weights:
raise ValueError(f"Invalid model name '{model_name}'. Available models: {self.available_model_names()}")
model, self.weights = self.model_name_to_model_and_weights[model_name]
self.device = select_device(device)
self.model = model(weights=self.weights).to(self.device).eval()
@staticmethod
def available_model_names() -> List[str]:
"""
Get the list of available model names.
Returns:
list: List of available model names.
"""
return list(TorchVisionVideoClassifier.model_name_to_model_and_weights.keys())
def preprocess_crops_for_video_cls(self, crops: List[np.ndarray], input_size: list = None) -> torch.Tensor:
"""
Preprocess a list of crops for video classification.
Args:
crops (List[np.ndarray]): List of crops to preprocess. Each crop should have dimensions (H, W, C)
input_size (tuple, optional): The target input size for the model. Defaults to (224, 224).
Returns:
torch.Tensor: Preprocessed crops as a tensor with dimensions (1, T, C, H, W).
"""
if input_size is None:
input_size = [224, 224]
from torchvision.transforms import v2
transform = v2.Compose(
[
v2.ToDtype(torch.float32, scale=True),
v2.Resize(input_size, antialias=True),
v2.Normalize(mean=self.weights.transforms().mean, std=self.weights.transforms().std),
]
)
processed_crops = [transform(torch.from_numpy(crop).permute(2, 0, 1)) for crop in crops]
return torch.stack(processed_crops).unsqueeze(0).permute(0, 2, 1, 3, 4).to(self.device)
def __call__(self, sequences: torch.Tensor):
"""
Perform inference on the given sequences.
Args:
sequences (torch.Tensor): The input sequences for the model. The expected input dimensions are
(B, T, C, H, W) for batched video frames or (T, C, H, W) for single video frames.
Returns:
torch.Tensor: The model's output.
"""
with torch.inference_mode():
return self.model(sequences)
def postprocess(self, outputs: torch.Tensor) -> Tuple[List[str], List[float]]:
"""
Postprocess the model's batch output.
Args:
outputs (torch.Tensor): The model's output.
Returns:
List[str]: The predicted labels.
List[float]: The predicted confidences.
"""
pred_labels = []
pred_confs = []
for output in outputs:
pred_class = output.argmax(0).item()
pred_label = self.weights.meta["categories"][pred_class]
pred_labels.append(pred_label)
pred_conf = output.softmax(0)[pred_class].item()
pred_confs.append(pred_conf)
return pred_labels, pred_confs
class HuggingFaceVideoClassifier:
"""Zero-shot video classifier using Hugging Face models for various devices."""
def __init__(
self,
labels: List[str],
model_name: str = "microsoft/xclip-base-patch16-zero-shot",
device: str or torch.device = "",
fp16: bool = False,
):
"""
Initialize the HuggingFaceVideoClassifier with the specified model name.
Args:
labels (List[str]): List of labels for zero-shot classification.
model_name (str): The name of the model to use. Defaults to "microsoft/xclip-base-patch16-zero-shot".
device (str or torch.device, optional): The device to run the model on. Defaults to "".
fp16 (bool, optional): Whether to use FP16 for inference. Defaults to False.
"""
self.fp16 = fp16
self.labels = labels
self.device = select_device(device)
self.processor = AutoProcessor.from_pretrained(model_name)
model = AutoModel.from_pretrained(model_name).to(self.device)
if fp16:
model = model.half()
self.model = model.eval()
def preprocess_crops_for_video_cls(self, crops: List[np.ndarray], input_size: list = None) -> torch.Tensor:
"""
Preprocess a list of crops for video classification.
Args:
crops (List[np.ndarray]): List of crops to preprocess. Each crop should have dimensions (H, W, C)
input_size (tuple, optional): The target input size for the model. Defaults to (224, 224).
Returns:
torch.Tensor: Preprocessed crops as a tensor (1, T, C, H, W).
"""
if input_size is None:
input_size = [224, 224]
from torchvision import transforms
transform = transforms.Compose(
[
transforms.Lambda(lambda x: x.float() / 255.0),
transforms.Resize(input_size),
transforms.Normalize(
mean=self.processor.image_processor.image_mean, std=self.processor.image_processor.image_std
),
]
)
processed_crops = [transform(torch.from_numpy(crop).permute(2, 0, 1)) for crop in crops] # (T, C, H, W)
output = torch.stack(processed_crops).unsqueeze(0).to(self.device) # (1, T, C, H, W)
if self.fp16:
output = output.half()
return output
def __call__(self, sequences: torch.Tensor) -> torch.Tensor:
"""
Perform inference on the given sequences.
Args:
sequences (torch.Tensor): The input sequences for the model. Batched video frames with shape (B, T, H, W, C).
Returns:
torch.Tensor: The model's output.
"""
input_ids = self.processor(text=self.labels, return_tensors="pt", padding=True)["input_ids"].to(self.device)
inputs = {"pixel_values": sequences, "input_ids": input_ids}
with torch.inference_mode():
outputs = self.model(**inputs)
return outputs.logits_per_video
def postprocess(self, outputs: torch.Tensor) -> Tuple[List[List[str]], List[List[float]]]:
"""
Postprocess the model's batch output.
Args:
outputs (torch.Tensor): The model's output.
Returns:
List[List[str]]: The predicted top3 labels.
List[List[float]]: The predicted top3 confidences.
"""
pred_labels = []
pred_confs = []
with torch.no_grad():
logits_per_video = outputs # Assuming outputs is already the logits tensor
probs = logits_per_video.softmax(dim=-1) # Use softmax to convert logits to probabilities
for prob in probs:
top2_indices = prob.topk(2).indices.tolist()
top2_labels = [self.labels[idx] for idx in top2_indices]
top2_confs = prob[top2_indices].tolist()
pred_labels.append(top2_labels)
pred_confs.append(top2_confs)
return pred_labels, pred_confs
def crop_and_pad(frame, box, margin_percent):
"""Crop box with margin and take square crop from frame."""
x1, y1, x2, y2 = map(int, box)
w, h = x2 - x1, y2 - y1
# Add margin
margin_x, margin_y = int(w * margin_percent / 100), int(h * margin_percent / 100)
x1, y1 = max(0, x1 - margin_x), max(0, y1 - margin_y)
x2, y2 = min(frame.shape[1], x2 + margin_x), min(frame.shape[0], y2 + margin_y)
# Take square crop from frame
size = max(y2 - y1, x2 - x1)
center_y, center_x = (y1 + y2) // 2, (x1 + x2) // 2
half_size = size // 2
square_crop = frame[
max(0, center_y - half_size) : min(frame.shape[0], center_y + half_size),
max(0, center_x - half_size) : min(frame.shape[1], center_x + half_size),
]
return cv2.resize(square_crop, (224, 224), interpolation=cv2.INTER_LINEAR)
def run(
weights: str = "yolo11n.pt",
device: str = "",
source: str = "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
output_path: Optional[str] = None,
crop_margin_percentage: int = 10,
num_video_sequence_samples: int = 8,
skip_frame: int = 2,
video_cls_overlap_ratio: float = 0.25,
fp16: bool = False,
video_classifier_model: str = "microsoft/xclip-base-patch32",
labels: List[str] = None,
) -> None:
"""
Run action recognition on a video source using YOLO for object detection and a video classifier.
Args:
weights (str): Path to the YOLO model weights. Defaults to "yolo11n.pt".
device (str): Device to run the model on. Use 'cuda' for NVIDIA GPU, 'mps' for Apple Silicon, or 'cpu'. Defaults to auto-detection.
source (str): Path to mp4 video file or YouTube URL. Defaults to a sample YouTube video.
output_path (Optional[str], optional): Path to save the output video. Defaults to None.
crop_margin_percentage (int, optional): Percentage of margin to add around detected objects. Defaults to 10.
num_video_sequence_samples (int, optional): Number of video frames to use for classification. Defaults to 8.
skip_frame (int, optional): Number of frames to skip between detections. Defaults to 4.
video_cls_overlap_ratio (float, optional): Overlap ratio between video sequences. Defaults to 0.25.
fp16 (bool, optional): Whether to use half-precision floating point. Defaults to False.
video_classifier_model (str, optional): Name or path of the video classifier model. Defaults to "microsoft/xclip-base-patch32".
labels (List[str], optional): List of labels for zero-shot classification. Defaults to predefined list.
Returns:
None</edit>
"""
if labels is None:
labels = [
"walking",
"running",
"brushing teeth",
"looking into phone",
"weight lifting",
"cooking",
"sitting",
]
# Initialize models and device
device = select_device(device)
yolo_model = YOLO(weights).to(device)
if video_classifier_model in TorchVisionVideoClassifier.available_model_names():
print("'fp16' is not supported for TorchVisionVideoClassifier. Setting fp16 to False.")
print(
"'labels' is not used for TorchVisionVideoClassifier. Ignoring the provided labels and using Kinetics-400 labels."
)
video_classifier = TorchVisionVideoClassifier(video_classifier_model, device=device)
else:
video_classifier = HuggingFaceVideoClassifier(
labels, model_name=video_classifier_model, device=device, fp16=fp16
)
# Initialize video capture
if source.startswith("http") and urlparse(source).hostname in {"www.youtube.com", "youtube.com", "youtu.be"}:
source = get_best_youtube_url(source)
elif not source.endswith(".mp4"):
raise ValueError("Invalid source. Supported sources are YouTube URLs and MP4 files.")
cap = cv2.VideoCapture(source)
# Get video properties
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = cap.get(cv2.CAP_PROP_FPS)
# Initialize VideoWriter
if output_path is not None:
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
out = cv2.VideoWriter(output_path, fourcc, fps, (frame_width, frame_height))
# Initialize track history
track_history = defaultdict(list)
frame_counter = 0
track_ids_to_infer = []
crops_to_infer = []
pred_labels = []
pred_confs = []
while cap.isOpened():
success, frame = cap.read()
if not success:
break
frame_counter += 1
# Run YOLO tracking
results = yolo_model.track(frame, persist=True, classes=[0]) # Track only person class
if results[0].boxes.id is not None:
boxes = results[0].boxes.xyxy.cpu().numpy()
track_ids = results[0].boxes.id.cpu().numpy()
# Visualize prediction
annotator = Annotator(frame, line_width=3, font_size=10, pil=False)
if frame_counter % skip_frame == 0:
crops_to_infer = []
track_ids_to_infer = []
for box, track_id in zip(boxes, track_ids):
if frame_counter % skip_frame == 0:
crop = crop_and_pad(frame, box, crop_margin_percentage)
track_history[track_id].append(crop)
if len(track_history[track_id]) > num_video_sequence_samples:
track_history[track_id].pop(0)
if len(track_history[track_id]) == num_video_sequence_samples and frame_counter % skip_frame == 0:
start_time = time.time()
crops = video_classifier.preprocess_crops_for_video_cls(track_history[track_id])
end_time = time.time()
preprocess_time = end_time - start_time
print(f"video cls preprocess time: {preprocess_time:.4f} seconds")
crops_to_infer.append(crops)
track_ids_to_infer.append(track_id)
if crops_to_infer and (
not pred_labels
or frame_counter % int(num_video_sequence_samples * skip_frame * (1 - video_cls_overlap_ratio)) == 0
):
crops_batch = torch.cat(crops_to_infer, dim=0)
start_inference_time = time.time()
output_batch = video_classifier(crops_batch)
end_inference_time = time.time()
inference_time = end_inference_time - start_inference_time
print(f"video cls inference time: {inference_time:.4f} seconds")
pred_labels, pred_confs = video_classifier.postprocess(output_batch)
if track_ids_to_infer and crops_to_infer:
for box, track_id, pred_label, pred_conf in zip(boxes, track_ids_to_infer, pred_labels, pred_confs):
top2_preds = sorted(zip(pred_label, pred_conf), key=lambda x: x[1], reverse=True)
label_text = " | ".join([f"{label} ({conf:.2f})" for label, conf in top2_preds])
annotator.box_label(box, label_text, color=(0, 0, 255))
# Write the annotated frame to the output video
if output_path is not None:
out.write(frame)
# Display the annotated frame
cv2.imshow("YOLOv8 Tracking with S3D Classification", frame)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cap.release()
if output_path is not None:
out.release()
cv2.destroyAllWindows()
def parse_opt():
"""Parse command line arguments."""
parser = argparse.ArgumentParser()
parser.add_argument("--weights", type=str, default="yolo11n.pt", help="ultralytics detector model path")
parser.add_argument("--device", default="", help='cuda device, i.e. 0 or 0,1,2,3 or cpu/mps, "" for auto-detection')
parser.add_argument(
"--source",
type=str,
default="https://www.youtube.com/watch?v=dQw4w9WgXcQ",
help="video file path or youtube URL",
)
parser.add_argument("--output-path", type=str, default="output_video.mp4", help="output video file path")
parser.add_argument(
"--crop-margin-percentage", type=int, default=10, help="percentage of margin to add around detected objects"
)
parser.add_argument(
"--num-video-sequence-samples", type=int, default=8, help="number of video frames to use for classification"
)
parser.add_argument("--skip-frame", type=int, default=2, help="number of frames to skip between detections")
parser.add_argument(
"--video-cls-overlap-ratio", type=float, default=0.25, help="overlap ratio between video sequences"
)
parser.add_argument("--fp16", action="store_true", help="use FP16 for inference")
parser.add_argument(
"--video-classifier-model", type=str, default="microsoft/xclip-base-patch32", help="video classifier model name"
)
parser.add_argument(
"--labels",
nargs="+",
type=str,
default=["dancing", "singing a song"],
help="labels for zero-shot video classification",
)
return parser.parse_args()
def main(opt):
"""Main function."""
run(**vars(opt))
if __name__ == "__main__":
opt = parse_opt()
main(opt)
# Zero-shot Action Recognition with YOLOv8 (Inference on Video)
- Action recognition is a technique used to identify and classify actions performed by individuals in a video. This process enables more advanced analyses when multiple actions are considered. The actions can be detected and classified in real time.
- The system can be customized to recognize specific actions based on the user's preferences and requirements.
## Table of Contents
- [Step 1: Install the Required Libraries](#step-1-install-the-required-libraries)
- [Step 2: Run the Action Recognition Using Ultralytics YOLOv8](#step-2-run-the-action-recognition-using-ultralytics-yolov8)
- [Usage Options](#usage-options)
- [FAQ](#faq)
## Step 1: Install the Required Libraries
Clone the repository, install dependencies and `cd` to this local directory for commands in Step 2.
```bash
# Clone ultralytics repo
git clone https://github.com/ultralytics/ultralytics
# cd to local directory
cd examples/YOLOv8-Action-Recognition
# Install dependencies
pip install -U -r requirements.txt
```
## Step 2: Run the Action Recognition Using Ultralytics YOLOv8
Here are the basic commands for running the inference:
### Note
The action recognition model will automatically detect and track people in the video, and classify their actions based on the specified labels. The results will be displayed in real-time on the video output. You can customize the action labels by modifying the `--labels` argument when running the script.
```bash
# Quick start
python action_recognition.py
# Basic usage
python action_recognition.py --source "https://www.youtube.com/watch?v=dQw4w9WgXcQ" --labels "dancing" "singing a song"
# Use local video file
python action_recognition.py --source path/to/video.mp4
# Better detector performance
python action_recognition.py --weights yolov8m.pt
# Run on CPU
python action_recognition.py --device cpu
# Use a different video classifier model
python action_recognition.py --video-classifier-model "s3d"
# Use FP16 for inference (only for HuggingFace models)
python action_recognition.py --fp16
# Export output as mp4
python action_recognition.py --output-path output.mp4
# Combine multiple options
python action_recognition.py --source "https://www.youtube.com/watch?v=dQw4w9WgXcQ" --device 0 --video-classifier-model "microsoft/xclip-base-patch32" --labels "dancing" "singing a song" --fp16
```
## Usage Options
- `--weights`: Path to the YOLO model weights (default: "yolov8n.pt")
- `--device`: Cuda device, i.e. 0 or 0,1,2,3 or cpu (default: auto-detect)
- `--source`: Video file path or YouTube URL (default: "[rickroll](https://www.youtube.com/watch?v=dQw4w9WgXcQ)")
- `--output-path`: Output video file path
- `--crop-margin-percentage`: Percentage of margin to add around detected objects (default: 10)
- `--num-video-sequence-samples`: Number of video frames to use for classification (default: 8)
- `--skip-frame`: Number of frames to skip between detections (default: 1)
- `--video-cls-overlap-ratio`: Overlap ratio between video sequences (default: 0.25)
- `--fp16`: Use FP16 for inference (only for HuggingFace models)
- `--video-classifier-model`: Video classifier model name or path (default: "microsoft/xclip-base-patch32")
- `--labels`: Labels for zero-shot video classification (default: \["dancing" "singing a song"\])
## FAQ
**1. What Does Action Recognition Involve?**
Action recognition is a computational method used to identify and classify actions or activities performed by individuals in recorded video or real-time streams. This technique is widely used in video analysis, surveillance, and human-computer interaction, enabling the detection and understanding of human behaviors based on their motion patterns and context.
**2. Is Custom Action Labels Supported by the Action Recognition?**
Yes, custom action labels are supported by the action recognition system. The `action_recognition.py` script allows users to specify their own custom labels for zero-shot video classification. This can be done using the `--labels` argument when running the script. For example:
```bash
python action_recognition.py --source https://www.youtube.com/watch?v=dQw4w9WgXcQ --labels "dancing" "singing" "jumping"
```
You can adjust these labels to match the specific actions you want to recognize in your video. The system will then attempt to classify the detected actions based on these custom labels.
Additionally, you can choose between different video classification models:
1. For Hugging Face models, you can use any compatible video classification model. The default is set to:
- "microsoft/xclip-base-patch32"
2. For TorchVision models (no support for zero-shot labels), you can select from the following options:
- "s3d"
- "r3d_18"
- "swin3d_t"
- "swin3d_b"
- "mvit_v1_b"
- "mvit_v2_s"
**3. Why Combine Action Recognition with YOLOv8?**
YOLOv8 specializes in the detection and tracking of objects in video streams. Action recognition complements this by enabling the identification and classification of actions performed by individuals, making it a valuable application of YOLOv8.
**4. Can I Employ Other YOLO Versions?**
Certainly, you have the flexibility to specify different YOLO model weights using the `--weights` option.
# Ultralytics YOLO 🚀, AGPL-3.0 license
ultralytics
transformers
cmake_minimum_required(VERSION 3.5)
project(Yolov8CPPInference VERSION 0.1)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
# CUDA
set(CUDA_TOOLKIT_ROOT_DIR "/usr/local/cuda")
find_package(CUDA 11 REQUIRED)
set(CMAKE_CUDA_STANDARD 11)
set(CMAKE_CUDA_STANDARD_REQUIRED ON)
# !CUDA
# OpenCV
find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
# !OpenCV
set(PROJECT_SOURCES
main.cpp
inference.h
inference.cpp
)
add_executable(Yolov8CPPInference ${PROJECT_SOURCES})
target_link_libraries(Yolov8CPPInference ${OpenCV_LIBS})
# YOLOv8/YOLOv5 Inference C++
This example demonstrates how to perform inference using YOLOv8 and YOLOv5 models in C++ with OpenCV DNN API.
## Usage
```bash
git clone ultralytics
cd ultralytics
pip install .
cd examples/YOLOv8-CPP-Inference
# Add a **yolov8\_.onnx** and/or **yolov5\_.onnx** model(s) to the ultralytics folder.
# Edit the **main.cpp** to change the **projectBasePath** to match your user.
# Note that by default the CMake file will try to import the CUDA library to be used with the OpenCVs dnn (cuDNN) GPU Inference.
# If your OpenCV build does not use CUDA/cuDNN you can remove that import call and run the example on CPU.
mkdir build
cd build
cmake ..
make
./Yolov8CPPInference
```
## Exporting YOLOv8 and YOLOv5 Models
To export YOLOv8 models:
```bash
yolo export model=yolov8s.pt imgsz=480,640 format=onnx opset=12
```
To export YOLOv5 models:
```bash
python3 export.py --weights yolov5s.pt --img 480 640 --include onnx --opset 12
```
yolov8s.onnx:
![image](https://user-images.githubusercontent.com/40023722/217356132-a4cecf2e-2729-4acb-b80a-6559022d7707.png)
yolov5s.onnx:
![image](https://user-images.githubusercontent.com/40023722/217357005-07464492-d1da-42e3-98a7-fc753f87d5e6.png)
This repository utilizes OpenCV DNN API to run ONNX exported models of YOLOv5 and YOLOv8. In theory, it should work for YOLOv6 and YOLOv7 as well, but they have not been tested. Note that the example networks are exported with rectangular (640x480) resolutions, but any exported resolution will work. You may want to use the letterbox approach for square images, depending on your use case.
The **main** branch version uses Qt as a GUI wrapper. The primary focus here is the **Inference** class file, which demonstrates how to transpose YOLOv8 models to work as YOLOv5 models.
#include "inference.h"
Inference::Inference(const std::string &onnxModelPath, const cv::Size &modelInputShape, const std::string &classesTxtFile, const bool &runWithCuda)
{
modelPath = onnxModelPath;
modelShape = modelInputShape;
classesPath = classesTxtFile;
cudaEnabled = runWithCuda;
loadOnnxNetwork();
// loadClassesFromFile(); The classes are hard-coded for this example
}
std::vector<Detection> Inference::runInference(const cv::Mat &input)
{
cv::Mat modelInput = input;
if (letterBoxForSquare && modelShape.width == modelShape.height)
modelInput = formatToSquare(modelInput);
cv::Mat blob;
cv::dnn::blobFromImage(modelInput, blob, 1.0/255.0, modelShape, cv::Scalar(), true, false);
net.setInput(blob);
std::vector<cv::Mat> outputs;
net.forward(outputs, net.getUnconnectedOutLayersNames());
int rows = outputs[0].size[1];
int dimensions = outputs[0].size[2];
bool yolov8 = false;
// yolov5 has an output of shape (batchSize, 25200, 85) (Num classes + box[x,y,w,h] + confidence[c])
// yolov8 has an output of shape (batchSize, 84, 8400) (Num classes + box[x,y,w,h])
if (dimensions > rows) // Check if the shape[2] is more than shape[1] (yolov8)
{
yolov8 = true;
rows = outputs[0].size[2];
dimensions = outputs[0].size[1];
outputs[0] = outputs[0].reshape(1, dimensions);
cv::transpose(outputs[0], outputs[0]);
}
float *data = (float *)outputs[0].data;
float x_factor = modelInput.cols / modelShape.width;
float y_factor = modelInput.rows / modelShape.height;
std::vector<int> class_ids;
std::vector<float> confidences;
std::vector<cv::Rect> boxes;
for (int i = 0; i < rows; ++i)
{
if (yolov8)
{
float *classes_scores = data+4;
cv::Mat scores(1, classes.size(), CV_32FC1, classes_scores);
cv::Point class_id;
double maxClassScore;
minMaxLoc(scores, 0, &maxClassScore, 0, &class_id);
if (maxClassScore > modelScoreThreshold)
{
confidences.push_back(maxClassScore);
class_ids.push_back(class_id.x);
float x = data[0];
float y = data[1];
float w = data[2];
float h = data[3];
int left = int((x - 0.5 * w) * x_factor);
int top = int((y - 0.5 * h) * y_factor);
int width = int(w * x_factor);
int height = int(h * y_factor);
boxes.push_back(cv::Rect(left, top, width, height));
}
}
else // yolov5
{
float confidence = data[4];
if (confidence >= modelConfidenceThreshold)
{
float *classes_scores = data+5;
cv::Mat scores(1, classes.size(), CV_32FC1, classes_scores);
cv::Point class_id;
double max_class_score;
minMaxLoc(scores, 0, &max_class_score, 0, &class_id);
if (max_class_score > modelScoreThreshold)
{
confidences.push_back(confidence);
class_ids.push_back(class_id.x);
float x = data[0];
float y = data[1];
float w = data[2];
float h = data[3];
int left = int((x - 0.5 * w) * x_factor);
int top = int((y - 0.5 * h) * y_factor);
int width = int(w * x_factor);
int height = int(h * y_factor);
boxes.push_back(cv::Rect(left, top, width, height));
}
}
}
data += dimensions;
}
std::vector<int> nms_result;
cv::dnn::NMSBoxes(boxes, confidences, modelScoreThreshold, modelNMSThreshold, nms_result);
std::vector<Detection> detections{};
for (unsigned long i = 0; i < nms_result.size(); ++i)
{
int idx = nms_result[i];
Detection result;
result.class_id = class_ids[idx];
result.confidence = confidences[idx];
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<int> dis(100, 255);
result.color = cv::Scalar(dis(gen),
dis(gen),
dis(gen));
result.className = classes[result.class_id];
result.box = boxes[idx];
detections.push_back(result);
}
return detections;
}
void Inference::loadClassesFromFile()
{
std::ifstream inputFile(classesPath);
if (inputFile.is_open())
{
std::string classLine;
while (std::getline(inputFile, classLine))
classes.push_back(classLine);
inputFile.close();
}
}
void Inference::loadOnnxNetwork()
{
net = cv::dnn::readNetFromONNX(modelPath);
if (cudaEnabled)
{
std::cout << "\nRunning on CUDA" << std::endl;
net.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA);
net.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA);
}
else
{
std::cout << "\nRunning on CPU" << std::endl;
net.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV);
net.setPreferableTarget(cv::dnn::DNN_TARGET_CPU);
}
}
cv::Mat Inference::formatToSquare(const cv::Mat &source)
{
int col = source.cols;
int row = source.rows;
int _max = MAX(col, row);
cv::Mat result = cv::Mat::zeros(_max, _max, CV_8UC3);
source.copyTo(result(cv::Rect(0, 0, col, row)));
return result;
}
#ifndef INFERENCE_H
#define INFERENCE_H
// Cpp native
#include <fstream>
#include <vector>
#include <string>
#include <random>
// OpenCV / DNN / Inference
#include <opencv2/imgproc.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/dnn.hpp>
struct Detection
{
int class_id{0};
std::string className{};
float confidence{0.0};
cv::Scalar color{};
cv::Rect box{};
};
class Inference
{
public:
Inference(const std::string &onnxModelPath, const cv::Size &modelInputShape = {640, 640}, const std::string &classesTxtFile = "", const bool &runWithCuda = true);
std::vector<Detection> runInference(const cv::Mat &input);
private:
void loadClassesFromFile();
void loadOnnxNetwork();
cv::Mat formatToSquare(const cv::Mat &source);
std::string modelPath{};
std::string classesPath{};
bool cudaEnabled{};
std::vector<std::string> classes{"person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic light", "fire hydrant", "stop sign", "parking meter", "bench", "bird", "cat", "dog", "horse", "sheep", "cow", "elephant", "bear", "zebra", "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee", "skis", "snowboard", "sports ball", "kite", "baseball bat", "baseball glove", "skateboard", "surfboard", "tennis racket", "bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple", "sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut", "cake", "chair", "couch", "potted plant", "bed", "dining table", "toilet", "tv", "laptop", "mouse", "remote", "keyboard", "cell phone", "microwave", "oven", "toaster", "sink", "refrigerator", "book", "clock", "vase", "scissors", "teddy bear", "hair drier", "toothbrush"};
cv::Size2f modelShape{};
float modelConfidenceThreshold {0.25};
float modelScoreThreshold {0.45};
float modelNMSThreshold {0.50};
bool letterBoxForSquare = true;
cv::dnn::Net net;
};
#endif // INFERENCE_H
#include <iostream>
#include <vector>
#include <getopt.h>
#include <opencv2/opencv.hpp>
#include "inference.h"
using namespace std;
using namespace cv;
int main(int argc, char **argv)
{
std::string projectBasePath = "/home/user/ultralytics"; // Set your ultralytics base path
bool runOnGPU = true;
//
// Pass in either:
//
// "yolov8s.onnx" or "yolov5s.onnx"
//
// To run Inference with yolov8/yolov5 (ONNX)
//
// Note that in this example the classes are hard-coded and 'classes.txt' is a place holder.
Inference inf(projectBasePath + "/yolov8s.onnx", cv::Size(640, 640), "classes.txt", runOnGPU);
std::vector<std::string> imageNames;
imageNames.push_back(projectBasePath + "/ultralytics/assets/bus.jpg");
imageNames.push_back(projectBasePath + "/ultralytics/assets/zidane.jpg");
for (int i = 0; i < imageNames.size(); ++i)
{
cv::Mat frame = cv::imread(imageNames[i]);
// Inference starts here...
std::vector<Detection> output = inf.runInference(frame);
int detections = output.size();
std::cout << "Number of detections:" << detections << std::endl;
for (int i = 0; i < detections; ++i)
{
Detection detection = output[i];
cv::Rect box = detection.box;
cv::Scalar color = detection.color;
// Detection box
cv::rectangle(frame, box, color, 2);
// Detection box text
std::string classString = detection.className + ' ' + std::to_string(detection.confidence).substr(0, 4);
cv::Size textSize = cv::getTextSize(classString, cv::FONT_HERSHEY_DUPLEX, 1, 2, 0);
cv::Rect textBox(box.x, box.y - 40, textSize.width + 10, textSize.height + 20);
cv::rectangle(frame, textBox, color, cv::FILLED);
cv::putText(frame, classString, cv::Point(box.x + 5, box.y - 10), cv::FONT_HERSHEY_DUPLEX, 1, cv::Scalar(0, 0, 0), 2, 0);
}
// Inference ends here...
// This is only for preview purposes
float scale = 0.8;
cv::resize(frame, frame, cv::Size(frame.cols*scale, frame.rows*scale));
cv::imshow("Inference", frame);
cv::waitKey(-1);
}
}
cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
project(yolov8_libtorch_example)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# -------------- OpenCV --------------
set(OpenCV_DIR "/path/to/opencv/lib/cmake/opencv4")
find_package(OpenCV REQUIRED)
message(STATUS "OpenCV library status:")
message(STATUS " config: ${OpenCV_DIR}")
message(STATUS " version: ${OpenCV_VERSION}")
message(STATUS " libraries: ${OpenCV_LIBS}")
message(STATUS " include path: ${OpenCV_INCLUDE_DIRS}")
include_directories(${OpenCV_INCLUDE_DIRS})
# -------------- libtorch --------------
list(APPEND CMAKE_PREFIX_PATH "/path/to/libtorch")
set(Torch_DIR "/path/to/libtorch/share/cmake/Torch")
find_package(Torch REQUIRED)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${TORCH_CXX_FLAGS}")
message("${TORCH_LIBRARIES}")
message("${TORCH_INCLUDE_DIRS}")
# The following code block is suggested to be used on Windows.
# According to https://github.com/pytorch/pytorch/issues/25457,
# the DLLs need to be copied to avoid memory errors.
# if (MSVC)
# file(GLOB TORCH_DLLS "${TORCH_INSTALL_PREFIX}/lib/*.dll")
# add_custom_command(TARGET yolov8_libtorch_example
# POST_BUILD
# COMMAND ${CMAKE_COMMAND} -E copy_if_different
# ${TORCH_DLLS}
# $<TARGET_FILE_DIR:yolov8_libtorch_example>)
# endif (MSVC)
include_directories(${TORCH_INCLUDE_DIRS})
add_executable(yolov8_libtorch_inference "${CMAKE_CURRENT_SOURCE_DIR}/main.cc")
target_link_libraries(yolov8_libtorch_inference ${TORCH_LIBRARIES} ${OpenCV_LIBS})
set_property(TARGET yolov8_libtorch_inference PROPERTY CXX_STANDARD 17)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment