English | 简体中文
The kornia crate is a low-level computer vision library for Rust 🦀
Fast, thread-safe image I/O and processing with a single API that runs on the CPU or an NVIDIA GPU — the same Image and operators dispatch on where the data lives. It hands results to PyTorch and TensorRT with no host copy (DLPack, CUDA Array Interface), and fuses a camera frame into a normalized model input in one CUDA kernel — built for real-time pipelines.
- Getting Started
- Features
- Installation
- Examples
- Python Usage
- GPU / CUDA
- Development
- Contributing
- Citation
The following example demonstrates how to read and display image information:
use kornia::image::Image;
use kornia::io::functional as F;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// read the image
let image: Image<u8, 3> = F::read_image_any_rgb8("tests/data/dog.jpeg")?;
println!("Hello, world! 🦀");
println!("Loaded Image size: {:?}", image.size());
println!("\nGoodbyte!");
Ok(())
}Hello, world! 🦀
Loaded Image size: ImageSize { width: 258, height: 195 }
Goodbyte!- 🦀 Written in Rust: memory- and thread-safe, no GIL — usable from the free-threaded Python build.
- ⚡ Fast image I/O and processing: libjpeg-turbo decoding and SIMD (NEON/AVX2) kernels.
- 🎯 One API, CPU or GPU: the same
Imageand operators dispatch on residency — no separate GPU types. - 🔌 Zero-copy ML interop: DLPack and
__cuda_array_interface__to and from PyTorch, plus numpy views. - 🎥 Real-time ready: V4L2 camera capture and a fused NV12/YUYV → normalized CHW CUDA kernel for inference.
- 🐍 Python bindings via PyO3/Maturin, packaged for Linux (amd64/arm64, incl. Jetson), macOS and Windows; the same wheel is CPU-only or activates CUDA when an NVIDIA GPU is present.
- Supported Python versions are 3.8 through 3.14, including the free-threaded (3.13t/3.14t) build.
- Read images from AVIF, BMP, DDS, Farbfeld, GIF, HDR, ICO, JPEG (libjpeg-turbo), OpenEXR, PNG, PNM, TGA, TIFF, WebP.
- Convert images to grayscale, resize, crop, rotate, flip, pad, normalize, denormalize, and other image processing operations.
- Capture video frames from a camera and video writers.
Add the following to your Cargo.toml:
[dependencies]
kornia = "0.1"Alternatively, you can use each sub-crate separately:
[dependencies]
kornia-tensor = "0.1"
kornia-tensor-ops = "0.1"
kornia-io = "0.1"
kornia-image = "0.1"
kornia-imgproc = "0.1"
kornia-3d = "0.1"
kornia-apriltag = "0.1"
kornia-vlm = "0.1"
kornia-bow = "0.1"
kornia-algebra = "0.1"pip install kornia-rsA subset of the full rust API is exposed. See the kornia documentation for more detail about the API for python functions and objects exposed by the kornia-rs Python module.
The kornia-rs library is thread-safe for use under the free-threaded Python build.
Depending on the features you want to use, you might need to install the following dependencies in your system:
sudo apt-get install clangsudo apt-get install nasmsudo apt-get install libgstreamer1.0-dev libgstreamer-plugins-base1.0-devNote: Check the gstreamer installation guide for more details.
The following example shows how to read an image, convert it to grayscale and resize it. The image is then logged to a rerun recording stream for visualization.
For more examples and use cases, check out the examples directory, which includes:
- Image processing operations (resize, rotate, normalize, filters)
- Video capture and processing
- AprilTag detection
- Feature detection (FAST)
- Visual language models (VLM) integration
- And more...
use kornia::{image::{Image, ImageSize}, imgproc};
use kornia::io::functional as F;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// read the image
let image: Image<u8, 3> = F::read_image_any_rgb8("tests/data/dog.jpeg")?;
let image_viz = image.clone();
let image_f32: Image<f32, 3> = image.cast_and_scale::<f32>(1.0 / 255.0)?;
// convert the image to grayscale
let mut gray = Image::<f32, 1>::from_size_val(image_f32.size(), 0.0)?;
imgproc::color::gray_from_rgb(&image_f32, &mut gray)?;
// resize the image
let new_size = ImageSize {
width: 128,
height: 128,
};
let mut gray_resized = Image::<f32, 1>::from_size_val(new_size, 0.0)?;
imgproc::resize::resize_native(
&gray, &mut gray_resized,
imgproc::interpolation::InterpolationMode::Bilinear,
)?;
println!("gray_resize: {:?}", gray_resized.size());
// create a Rerun recording stream
let rec = rerun::RecordingStreamBuilder::new("Kornia App").spawn()?;
rec.log(
"image",
&rerun::Image::from_elements(
image_viz.as_slice(),
image_viz.size().into(),
rerun::ColorModel::RGB,
),
)?;
rec.log(
"gray",
&rerun::Image::from_elements(gray.as_slice(), gray.size().into(), rerun::ColorModel::L),
)?;
rec.log(
"gray_resize",
&rerun::Image::from_elements(
gray_resized.as_slice(),
gray_resized.size().into(),
rerun::ColorModel::L,
),
)?;
Ok(())
}Load an image, which is converted directly to a numpy array to ease the integration with other libraries.
import kornia_rs as K
import numpy as np
import torch
# load a JPEG with libjpeg-turbo
img: np.ndarray = K.io.read_image_jpeg("dog.jpeg", "rgb")
# or read any supported format
# img: np.ndarray = K.io.read_image("dog.png")
assert img.shape == (195, 258, 3)
# convert to dlpack to import to torch
img_t = torch.from_dlpack(img)
assert img_t.shape == (195, 258, 3)Write an image to disk:
import kornia_rs as K
import numpy as np
# load a JPEG with libjpeg-turbo
img: np.ndarray = K.io.read_image_jpeg("dog.jpeg", "rgb")
# write the image to disk (mode, JPEG quality)
K.io.write_image_jpeg("dog_copy.jpeg", img, "rgb", 95)kornia_rs.image.Image mirrors PIL's fromarray / save / load / decode
and natively holds uint16 for depth maps and scientific imagery
(lossless via PNG-16):
import io
import numpy as np
from kornia_rs.image import Image
# Bit depth is auto-detected from the numpy dtype.
rgb = np.random.randint(0, 255, (480, 640, 3), dtype=np.uint8)
depth = np.full((480, 640), 1500, dtype=np.uint16) # mm
rgb_img = Image.fromarray(rgb)
depth_img = Image.fromarray(depth)
# In-memory encode for transit (Zenoh / MCAP / gRPC).
png16_bytes = depth_img.encode("png") # lossless on uint16
# Save to disk (format from extension), or to any file-like (PIL parity).
rgb_img.save("dog.png")
buf = io.BytesIO(); rgb_img.save(buf, format="jpeg")
# Decode auto-detects bit depth from the file header.
back = Image.decode(png16_bytes, mode="L")
assert back.dtype == np.uint16The original ImageEncoder/ImageDecoder pair is still available for
JPEG-only workflows that want the explicit turbojpeg backend object:
import kornia_rs as K
img = K.io.read_image_jpeg("dog.jpeg", "rgb")
image_encoder = K.io.ImageEncoder()
image_encoder.set_quality(95)
img_encoded: list[int] = image_encoder.encode(img)
image_decoder = K.io.ImageDecoder()
decoded_img: np.ndarray = image_decoder.decode(bytes(img_encoded))Resize an image using the kornia-rs backend with SIMD acceleration:
import kornia_rs as K
# load image with kornia-rs
img = K.io.read_image_jpeg("dog.jpeg", "rgb")
# resize the image
resized_img = K.imgproc.resize(img, (128, 128), interpolation="bilinear")
assert resized_img.shape == (128, 128, 3)The published wheels are GPU-capable but load CUDA lazily: the same wheel runs on
CPU when no GPU is present and uses the GPU when one is. The GPU path needs an
NVIDIA driver (libcuda) and nvrtc from the CUDA toolkit; without them the CPU
ops keep working.
Device pixels use the same Image type. .device reads "cpu" or "cuda:{id}",
.to_cuda(stream) uploads, .cpu() downloads. Color ops live under
kornia_rs.imgproc and dispatch on residency: a device Image runs the CUDA
kernel, a host Image or numpy array runs the CPU kernel.
import numpy as np
import kornia_rs as K
from kornia_rs.image import Image
from kornia_rs.cuda import Stream
if K.cuda.is_available():
rgb = np.random.randint(0, 255, (480, 640, 3), dtype=np.uint8)
img = Image.from_numpy(rgb).to_cuda(Stream.default()) # -> "cuda:0"
gray = K.imgproc.gray_from_rgb(img) # runs on the GPU
out = gray.cpu().numpy() # -> host, (480, 640, 1)GPU color conversions (gray_from_rgb, bgr_from_rgb, hsv_from_rgb,
lab_from_rgb, ycbcr_from_rgb, sepia_from_rgb, apply_colormap, …) and the
fused Preprocessor are the GPU entry points. Tensors cross to PyTorch with no
copy through DLPack (torch.from_dlpack) and __cuda_array_interface__.
Preprocessor fuses resize, normalize and HWC→CHW into one CUDA kernel per
frame. It emits a device tensor that feeds an inference engine with no host copy
— the path for real-time camera pipelines.
import torch
from kornia_rs import Preprocessor, IMAGENET_MEAN, IMAGENET_STD
from kornia_rs.cuda import Stream
# One kernel per frame: NV12 -> normalized fp16 [1, 3, 640, 640] on the GPU.
pre = Preprocessor(mode="letterbox", format="nv12", f16=True,
mean=IMAGENET_MEAN, std=IMAGENET_STD, stream=Stream.default(0))
t = pre.run(nv12_frame, 1920, 1080, 640, 640) # device Tensor
x = torch.from_dlpack(t) # zero-copy handoff to PyTorch
# TensorRT: ctx.set_tensor_address("images", t.data_ptr)The same one-call-per-residency model holds in Rust — convert picks CPU or GPU
from where the images live:
let stream = CudaContext::new(0)?.default_stream();
let rgb = Rgb8::from_size_vec(size, data)?.to_cuda(&stream)?; // device image
let mut gray = Gray8::zeros_cuda(size, &stream)?;
rgb.convert(&mut gray)?; // runs on the GPUFull pipelines: examples/cuda_camera_preprocess
(V4L2 camera → fused CUDA preprocess) and
kornia-py/examples/preprocess_to_inference.py
(NV12 → fused preprocess → ResNet-18 / TensorRT, GPU-resident end to end).
Before you begin, ensure you have rust and python3 installed on your system.
-
Install Rust using rustup:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
-
Install
pixifor package and environment management:curl -fsSL https://pixi.sh/install.sh | bash -
Clone the repository to your local directory:
git clone https://github.com/kornia/kornia-rs.git
-
Install dependencies using pixi:
pixi install
You can check all available development commands via pixi task list:
pixi run rust-check # Check Rust compilation (all targets)
pixi run rust-clippy # Run clippy (all targets, warnings as errors)
pixi run rust-fmt # Format Rust code
pixi run rust-fmt-check # Check Rust formatting
pixi run rust-lint # Run all Rust lints (fmt + clippy + check)
pixi run rust-test # Run Rust tests
pixi run rust-test-release # Run Rust tests (release mode)
pixi run rust-clean # Clean Rust build artifacts
pixi run py-build # Build kornia-py for development
pixi run py-build-release # Build kornia-py for release
pixi run py-test # Run pytest
pixi run cpp-build # Build C++ library (debug)
pixi run cpp-test # Build and run C++ testsThis project includes a development container configuration for a consistent development environment across different machines.
Using the Dev Container:
- Install the
Remote - Containersextension in Visual Studio Code - Open the project folder in VS Code
- Press
F1and selectRemote-Containers: Reopen in Container - VS Code will build and open the project in the containerized environment
The devcontainer includes all necessary dependencies and tools for building and testing kornia-rs.
Compile the project and run all tests:
pixi run rust-testTo run tests for a specific package:
pixi run rust-test-package <package-name>To run clippy linting:
pixi run rust-clippyBuild Python wheels using maturin:
pixi run py-buildRun Python tests:
pixi run py-testWe welcome contributions! Please read CONTRIBUTING.md for:
- Coding standards and style guidelines
- Development workflow
- How to run local checks before submitting PRs
Kornia-rs accepts AI-assisted code but strictly rejects AI-generated contributions where the submitter acts as a proxy. All contributors must be the Sole Responsible Author for every line of code. Please review our AI Policy before submitting pull requests. Key requirements include:
- Proof of Verification: PRs must include local test logs proving execution (e.g.,
pixi run rust-testorcargo test) - Pre-Discussion: All PRs must be discussed in Discord or via a GitHub issue before implementation
- Library References: Implementations must be based on existing library references (Rust crates, OpenCV, etc.)
- Use Existing Utilities: Use existing
kornia-rsutilities instead of reinventing the wheel - Error Handling: Use
Result<T, E>for error handling (avoidunwrap()/expect()in library code) - Explain It: You must be able to explain any code you submit
Automated AI reviewers (e.g., @copilot) will check PRs against these policies. See AI_POLICY.md for complete details.
This is a child project of Kornia.
- 💬 Join our community on Discord
- 💖 Support the project on OpenCollective
- 📖 Read the full documentation
- 🦀 Browse the Rust API docs
If you use kornia-rs in your research, please cite:
@misc{2505.12425,
Author = {Edgar Riba and Jian Shi and Aditya Kumar and Andrew Shen and Gary Bradski},
Title = {Kornia-rs: A Low-Level 3D Computer Vision Library In Rust},
Year = {2025},
Eprint = {arXiv:2505.12425},
}