Lightweight cv2-like image processing library powered by Pillow
Project description
llowCV
A lightweight, cv2-like image processing library powered by Pillow.
OpenCV + NumPy is too heavy for edge devices and PyInstaller executables. llowCV uses only Pillow as its core dependency and mirrors cv2's API names, argument order, and coordinate system — minimizing migration cost.
import llowcv as lcv
img = lcv.imread("input.jpg")
img = lcv.resize(img, (640, 480))
img = lcv.put_text(img, "Hello World", (10, 30), font="Arial.ttf", size=24, color=(255, 255, 255))
lcv.imwrite("out.jpg", img)
Why llowCV?
| OpenCV | llowCV | |
|---|---|---|
| Core dependency | OpenCV + NumPy | Pillow only |
| Install size | ~100 MB | ~10 MB |
| PyInstaller exe | Painful | Easy |
| Raspberry Pi / Edge | Heavy | Lightweight |
| cv2-compatible API | — | ✅ |
| Unicode text rendering | Requires config | Just pass a TTF path |
| Real-time / Video | ✅ | Out of scope |
Installation
pip install llowcv
For Matplotlib display support:
pip install llowcv[mpl]
For NumPy / OpenCV interop:
pip install llowcv[cv2]
Color Formats
llowCV uses RGB as its internal format — the opposite of cv2's BGR.
| mode | Use case | Channel order |
|---|---|---|
"RGB" |
Color image (default) | R, G, B |
"RGBA" |
Color with transparency | R, G, B, A |
"L" |
Grayscale | Luminance only |
img = lcv.imread("input.jpg") # → mode='RGB'
img = lcv.imread("input.png", mode="RGBA") # → mode='RGBA' (transparency preserved)
img = lcv.imread("input.jpg", mode="L") # → mode='L' (grayscale)
BGR and BGRA are not directly supported. Use the interop API to exchange data with cv2:
# cv2 (BGR ndarray) → llowcv (RGB PIL.Image)
img = lcv.from_cv2(cv2_bgr_array) # BGR → RGB conversion happens automatically
# llowcv (RGB PIL.Image) → cv2 (BGR ndarray)
arr = lcv.as_cv2(img) # RGB → BGR conversion happens automatically
alpha_composite requires both images to be mode='RGBA'.
blend and composite expect mode='RGB'.
Channel operations
# Swap R and B channels (fix a BGR-ordered PIL.Image, or convert back)
img = lcv.bgr2rgb(img) # equivalent to cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = lcv.rgb2bgr(img) # same operation (alias)
# Split and merge channels
r, g, b = lcv.split(img) # like cv2.split — each channel is mode='L'
img = lcv.merge([r, g, b]) # like cv2.merge
img = lcv.merge([r, g, b, a], mode="RGBA") # works for RGBA too
# Extract a single channel
r_channel = lcv.split(img)[0] # R channel only, mode='L'
API Reference
Image I/O
img = lcv.imread("input.jpg") # Read as RGB
img = lcv.imread("input.jpg", mode="L") # Read as grayscale
lcv.imwrite("out.png", img) # Format inferred from extension
lcv.imshow(img) # Open with OS default viewer
lcv.imshow(img, backend="mpl") # Display via Matplotlib (pip install llowcv[mpl])
lcv.imshow(img, backend="mpl", block=False) # Non-blocking display (mpl only)
Transforms
img = lcv.resize(img, (640, 480)) # Resize
img = lcv.resize(img, (640, 480), interpolation="cubic") # With interpolation
img = lcv.resize(img, img.size, silent=True) # Suppress same-size warning
img = lcv.crop(img, (x, y, w, h)) # Crop (cv2-style xywh)
img = lcv.rotate(img, 90) # Rotate counter-clockwise
img = lcv.rotate(img, 45, expand=True) # Expand canvas to fit
img = lcv.flip(img, 0) # Flip vertical
img = lcv.flip(img, 1) # Flip horizontal
img = lcv.flip(img, -1) # Flip both axes
Interpolation options: "nearest" / "linear" (default) / "bilinear" / "cubic" / "bicubic" / "lanczos"
Filters & Color
img = lcv.blur(img, (5, 5)) # Box blur
img = lcv.blur(img, (1, 1)) # radius=0 — no-op, raises UserWarning
img = lcv.blur(img, (1, 1), silent=True) # Suppress the warning
img = lcv.sharpen(img, amount=1.5) # Sharpen (1.0 = no change)
img = lcv.to_gray(img) # Convert to grayscale (mode='L')
Text Drawing
img = lcv.put_text(
img,
text="Hello, World!",
org=(10, 40), # Bottom-left origin (cv2-compatible)
font="path/to/font.ttf",
size=32,
color=(255, 255, 255),
)
org uses the same bottom-left origin as cv2.putText.
font accepts any TTF or OTF file path — Unicode text (CJK, emoji, etc.) works out of the box.
Compositing
out = lcv.blend(img1, img2, alpha=0.5) # Alpha blend (0.0=img1, 1.0=img2)
out = lcv.composite(img1, img2, mask) # Mask composite (mask=0→img1, 255→img2)
out = lcv.alpha_composite(dst, src) # RGBA alpha compositing
NumPy / OpenCV Interop (opt-in)
Available after pip install llowcv[cv2]:
arr = lcv.as_cv2(img) # PIL.Image (RGB) → ndarray (BGR)
img = lcv.from_cv2(arr) # ndarray (BGR) → PIL.Image (RGB)
arr = lcv.as_numpy(img) # PIL.Image → RGB ndarray
arr = lcv.to_bgr(arr) # RGB ndarray → BGR ndarray
Camera
with lcv.Camera(index=0) as cam:
frame = cam.capture() # Returns PIL.Image (RGB)
lcv.imwrite("frame.jpg", frame)
Uses Windows Media Foundation on Windows and v4l2 on Linux.
Global Configuration
# Suppress all no-op warnings at once
lcv.config.warn_noop = False
# Or control via environment variable (0 / false / no / off to disable)
# LLOWCV_WARN_NOOP=0 python main.py
When warn_noop is enabled, a UserWarning is raised for:
resize:dsizematches the input image sizeblur:ksizeresults in no blurring (e.g.(1, 1))imshow:block=Falseused with thepillowbackend
Design Principles
- Pillow-only core — NumPy and OpenCV are strictly opt-in
- Immutable returns — every API returns a new
PIL.Image; no in-place mutation, no thread-safety concerns - cv2-compatible API — same names, argument order, and coordinate conventions as OpenCV
- No Video / GUI / ML — for real-time processing, use OpenCV
Migrating from cv2
# Before (cv2)
import cv2
img = cv2.imread("input.jpg")
img = cv2.resize(img, (640, 480))
cv2.imwrite("out.jpg", img)
# After (llowcv)
import llowcv as lcv
img = lcv.imread("input.jpg")
img = lcv.resize(img, (640, 480))
lcv.imwrite("out.jpg", img)
Key differences:
| cv2 | llowcv | |
|---|---|---|
| Image type | numpy.ndarray (BGR) |
PIL.Image (RGB) |
| In-place ops | Common | None — always returns new image |
waitKey / GUI loop |
Required | Not needed |
Requirements
- Python >= 3.10
- Pillow (core)
- matplotlib (
[mpl]optional) - numpy + opencv-python (
[cv2]optional) - numpy + opencv-python (
[cv2]extra, optional)
License
MIT License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file llowcv-0.1.1.tar.gz.
File metadata
- Download URL: llowcv-0.1.1.tar.gz
- Upload date:
- Size: 92.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b55b287daf2ff0aa30b868ba84fb50faff94c10f77cf46ab9b704a45a18b45ae
|
|
| MD5 |
f079c9a7bad3f183c779204d25671082
|
|
| BLAKE2b-256 |
864455e89eb9c29ebf9c3c220c657444e123f31d35015b0074e6122792d46ae1
|
Provenance
The following attestation bundles were made for llowcv-0.1.1.tar.gz:
Publisher:
publish.yml on Moge800/llowCV
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
llowcv-0.1.1.tar.gz -
Subject digest:
b55b287daf2ff0aa30b868ba84fb50faff94c10f77cf46ab9b704a45a18b45ae - Sigstore transparency entry: 1215827520
- Sigstore integration time:
-
Permalink:
Moge800/llowCV@cd6b268311225de0125f6dd35c4b431c7d3f6073 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/Moge800
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@cd6b268311225de0125f6dd35c4b431c7d3f6073 -
Trigger Event:
push
-
Statement type:
File details
Details for the file llowcv-0.1.1-py3-none-any.whl.
File metadata
- Download URL: llowcv-0.1.1-py3-none-any.whl
- Upload date:
- Size: 21.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b4b6b1b39f369cda538f329ffca70dc8e85ff4ddbadc084723dbee45354bdec3
|
|
| MD5 |
b7db808fca65d5afb3ff816ffd1c9700
|
|
| BLAKE2b-256 |
4c403494fbf05c4a8007b062280973cd398efe64c5294c7f2c2b9717e1695776
|
Provenance
The following attestation bundles were made for llowcv-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on Moge800/llowCV
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
llowcv-0.1.1-py3-none-any.whl -
Subject digest:
b4b6b1b39f369cda538f329ffca70dc8e85ff4ddbadc084723dbee45354bdec3 - Sigstore transparency entry: 1215827669
- Sigstore integration time:
-
Permalink:
Moge800/llowCV@cd6b268311225de0125f6dd35c4b431c7d3f6073 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/Moge800
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@cd6b268311225de0125f6dd35c4b431c7d3f6073 -
Trigger Event:
push
-
Statement type: