Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
@@ -1 +1 @@
*.zip filter=lfs diff=lfs merge=lfs -text
* text=auto eol=lf
113 changes: 113 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
name: Build layers

on:
push:
tags: ["v*"]
pull_request:
workflow_dispatch:

env:
NUMPY_VERSION: "2.5.4"

jobs:
# cv2 is built with the Python Limited API, so one build per
# (OpenCV, profile, arch) serves every Python >= 3.12.
opencv:
strategy:
fail-fast: false
matrix:
opencv: ["4.14.0", "5.1.0"]
profile: [minimal, standard, dnn]
arch: [x86_64, arm64]
runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
steps:
- uses: actions/checkout@v4
- run: ./build.sh 3.12 ${{ matrix.arch }} opencv ${{ matrix.opencv }} ${{ matrix.profile }}
- uses: actions/upload-artifact@v4
with:
name: opencv-${{ matrix.opencv }}-${{ matrix.profile }}-${{ matrix.arch }}
path: artifacts/*.tar.gz
if-no-files-found: error

numpy:
strategy:
fail-fast: false
matrix:
python: ["3.12", "3.13", "3.14"]
arch: [x86_64, arm64]
runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
steps:
- uses: actions/checkout@v4
- run: ./build.sh ${{ matrix.python }} ${{ matrix.arch }} numpy ${{ env.NUMPY_VERSION }}
- uses: actions/upload-artifact@v4
with:
name: numpy-${{ matrix.python }}-${{ matrix.arch }}
path: artifacts/*.tar.gz
if-no-files-found: error

# Smoke-test and zip inside each target runtime.
package:
needs: [opencv, numpy]
strategy:
fail-fast: false
matrix:
python: ["3.12", "3.13", "3.14"]
arch: [x86_64, arm64]
runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
pattern: opencv-*-${{ matrix.arch }}
path: artifacts
merge-multiple: true
- uses: actions/download-artifact@v4
with:
name: numpy-${{ matrix.python }}-${{ matrix.arch }}
path: artifacts
- run: ./build.sh ${{ matrix.python }} ${{ matrix.arch }} package
- uses: actions/upload-artifact@v4
with:
name: layers-${{ matrix.python }}-${{ matrix.arch }}
path: dist/*
if-no-files-found: error

# Single required status check for branch protection. Fails if any job
# failed, was cancelled, or was skipped because an earlier stage failed.
result:
name: CI result
if: always()
needs: [opencv, numpy, package]
runs-on: ubuntu-24.04
steps:
- if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped')
run: exit 1
- run: echo "all jobs passed"

release:
if: startsWith(github.ref, 'refs/tags/v')
needs: package
runs-on: ubuntu-24.04
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
with:
pattern: layers-*
path: dist
merge-multiple: true
- name: Checksums and release notes
working-directory: dist
run: |
sha256sum *.zip > SHA256SUMS
{
echo "| File | Profile | Python | Arch | OpenCV | NumPy | Unzipped (MB) | Zipped (MB) |"
echo "|---|---|---|---|---|---|---|---|"
jq -rs 'sort_by(.file)[] | "| \(.file) | \(.profile) | \(.python) | \(.arch) | \(.opencv) | \(.numpy) | \(.unzipped_mb) | \(.zipped_mb) |"' *.json
} > ../notes.md
- uses: softprops/action-gh-release@v2
with:
files: |
dist/*.zip
dist/SHA256SUMS
body_path: notes.md
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
artifacts/
dist/
__pycache__/
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2013-2020 Michael Rose and contributors
Copyright (c) 2022-2026 Daehee Yun

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
3 changes: 0 additions & 3 deletions Python 3.7.10/opencv-python-headless-3.4.17.61.zip

This file was deleted.

3 changes: 0 additions & 3 deletions Python 3.7.10/opencv-python-headless-4.0.1.24.zip

This file was deleted.

3 changes: 0 additions & 3 deletions Python 3.7.10/opencv-python-headless-4.1.2.30.zip

This file was deleted.

3 changes: 0 additions & 3 deletions Python 3.7.10/opencv-python-headless-4.2.0.34.zip

This file was deleted.

3 changes: 0 additions & 3 deletions Python 3.7.10/opencv-python-headless-4.3.0.38.zip

This file was deleted.

3 changes: 0 additions & 3 deletions Python 3.7.10/opencv-python-headless-4.4.0.46.zip

This file was deleted.

3 changes: 0 additions & 3 deletions Python 3.7.10/opencv-python-headless-4.5.5.62.zip

This file was deleted.

208 changes: 155 additions & 53 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,85 +1,187 @@
## Introduction
<div align="center">

This repository provides `pre-built headless OpenCV` for AWS Lambda. Python OpenCV includes native code libraries, I compiled and build these libraries using a Linux development machine so that the binaries are compatible with Amazon Linux. Once this layer is applied, you can use the layer to host your own algorithms on lambda for Python OpenCV.
# OpenCV Layer for AWS Lambda

Minimal headless OpenCV layers for AWS Lambda, built from source.

## Python Version
[![Build](https://github.com/076923/aws-lambda-python-opencv-layer/actions/workflows/build.yml/badge.svg)](https://github.com/076923/aws-lambda-python-opencv-layer/actions/workflows/build.yml)
[![Release](https://img.shields.io/github/v/release/076923/aws-lambda-python-opencv-layer)](https://github.com/076923/aws-lambda-python-opencv-layer/releases/latest)
[![Downloads](https://img.shields.io/github/downloads/076923/aws-lambda-python-opencv-layer/total)](https://github.com/076923/aws-lambda-python-opencv-layer/releases)
![Python](https://img.shields.io/badge/python-3.12%20%7C%203.13%20%7C%203.14-blue)
![Arch](https://img.shields.io/badge/arch-x86__64%20%7C%20arm64-orange)
[![License](https://img.shields.io/github/license/076923/aws-lambda-python-opencv-layer)](LICENSE)

For each Python directory, it was created as follows:
</div>

### Support Pre-built Python 3.7.10
- **Small**: OpenCV and its codecs are linked statically into a single stripped `cv2.abi3.so`, without FFmpeg or OpenBLAS.
- **Fast cold start**: Precompiled bytecode is included, because Lambda cannot write a bytecode cache to `/opt`.
- **Runtime-matched**: Built and smoke-tested inside the official `public.ecr.aws/lambda/python` images.
- **Reproducible**: Deterministic zips with `SHA256SUMS`, built by GitHub Actions.

> `based on Numpy==1.21.5`

- opencv-python-headless-3.4.17.61
- opencv-python-headless-4.0.1.24
- opencv-python-headless-4.1.2.30
- opencv-python-headless-4.2.0.34
- opencv-python-headless-4.3.0.38
- opencv-python-headless-4.4.0.46
- opencv-python-headless-4.5.5.62
## Quick Start

### Support Pre-built Python 3.8.16
**1. Download** a zip from the [latest release][latest]. The asset URL is stable across releases.

> `based on Numpy==1.24.2`
```bash
curl -LO https://github.com/076923/aws-lambda-python-opencv-layer/releases/latest/download/opencv-4.14.0-minimal-python3.13-arm64.zip
```

**2. Publish** it as a layer. The runtime and architecture must match your function.

```bash
aws lambda publish-layer-version \
--layer-name opencv-minimal \
--zip-file fileb://opencv-4.14.0-minimal-python3.13-arm64.zip \
--compatible-runtimes python3.13 \
--compatible-architectures arm64
```

> [!NOTE]
> Every zip is under the 50 MB limit for direct upload. If your own build is larger, upload it to S3 first and pass `--content S3Bucket=<bucket>,S3Key=<key>`.

**3. Use** it in your function.

```python
import base64

import cv2
import numpy as np


def handler(event, context):
data = np.frombuffer(base64.b64decode(event["image"]), np.uint8)
img = cv2.imdecode(data, cv2.IMREAD_COLOR)
thumb = cv2.resize(img, (256, 256), interpolation=cv2.INTER_AREA)
ok, buf = cv2.imencode(".webp", thumb, [cv2.IMWRITE_WEBP_QUALITY, 80])
return {"image": base64.b64encode(buf).decode()}
```


## Choose a Variant

Asset name: `opencv-{opencv}-{profile}-python{python}-{arch}.zip`

| Option | Values |
|---|---|
| `opencv` | `4.14.0`, `5.1.0` |
| `profile` | `minimal`, `standard`, `dnn` |
| `python` | `3.12`, `3.13`, `3.14` |
| `arch` | `x86_64`, `arm64` |

| Profile | Modules | Use for |
|---|---|---|
| `minimal` | core, imgproc, imgcodecs | Resize, crop, color conversion, filters, encode and decode |
| `standard` | minimal + features2d, calib3d, flann, objdetect, photo, video, ml | Feature matching, homography, QR codes, inpainting, optical flow |
| `dnn` | standard + dnn | Running ONNX and other models with `cv2.dnn` |

Sizes for Python 3.13 with NumPy 2.5.4 (zipped / unzipped, MB). Other Python versions differ by less than 0.3 MB.

- opencv-python-headless-3.4.18.65
- opencv-python-headless-4.1.2.30
- opencv-python-headless-4.2.0.34
- opencv-python-headless-4.3.0.38
- opencv-python-headless-4.4.0.46
- opencv-python-headless-4.5.5.64
- opencv-python-headless-4.6.0.66
- opencv-python-headless-4.7.0.68
| Profile | 4.14.0 x86_64 | 4.14.0 arm64 | 5.1.0 x86_64 | 5.1.0 arm64 |
|---|---|---|---|---|
| `minimal` | 12.4 / 33.6 | 10.5 / 29.2 | 14.6 / 39.3 | 13.0 / 35.1 |
| `standard` | 15.7 / 41.7 | 13.4 / 36.0 | 17.0 / 44.9 | 15.0 / 39.8 |
| `dnn` | 18.3 / 48.6 | 15.7 / 42.0 | 20.9 / 57.0 | 18.4 / 50.1 |

### Support Pre-built Python 3.9.16
For comparison, the official `opencv-python-headless` 4.14.0.94 wheel with NumPy is about 72 MB zipped and 215 MB unzipped. Each release lists the size of every asset.

> `based on Numpy==1.24.2`
> [!TIP]
> `arm64` (Graviton) is about 20% cheaper per GB-second than `x86_64`.

- opencv-python-headless-3.4.18.65
- opencv-python-headless-4.4.0.46
- opencv-python-headless-4.5.5.64
- opencv-python-headless-4.6.0.66
- opencv-python-headless-4.7.0.68
> [!NOTE]
> OpenCV 5 renamed some modules (`features2d` → `features`, `calib3d` → `calib`, `geometry`, `stereo`). The `standard` profile includes the equivalent modules for each version.


## Apply Layer
## Limitations

1. Upload the `opencv-python-headless.zip` file to Amazon S3.
The layers keep only what image processing on Lambda needs.

2. Open the Layers page of the Lambda console.
| Not included | Impact |
|---|---|
| FFmpeg, GStreamer, `videoio`, `highgui` | `cv2.VideoCapture`, `cv2.VideoWriter`, and `cv2.imshow` are not available |
| TIFF, JPEG 2000, OpenEXR, AVIF, HDR, PNM codecs | Only JPEG, PNG, and WebP can be read and written |
| OpenBLAS (OpenCV and NumPy) | NumPy uses its bundled `lapack_lite`. Large `matmul` and `linalg` calls are slower |
| IPP, OpenCL, TBB, Eigen | OpenCV uses its own SIMD kernels and pthreads |
| Haar cascade XML files | Ship the XML files you need with your function. `cv2.CascadeClassifier` is only available with 4.14.0, because OpenCV 5 removed it from the main repository |
| `python3.11` and older | These runtimes run on Amazon Linux 2 and are not supported |

3. Choose Create layer.

4. Under Layer configuration, for Name, enter a name for your layer.
## How It Stays Small

5. To upload OpenCV Python Headless layer
| Technique | Effect |
|---|---|
| Static build into one `cv2.abi3.so` | No bundled `.libs` directories or duplicate runtimes |
| `CPU_BASELINE=AVX2` on x86_64 without runtime dispatch | No SSE4, AVX, or AVX-512 copies of the kernels. Every Lambda x86_64 host supports AVX2 |
| `-ffunction-sections`, `--gc-sections`, `strip` | Removes unused code and symbols |
| NumPy without BLAS | Removes about 27 MB of OpenBLAS and gfortran runtime |
| Remove NumPy tests, headers, static libs, `f2py`, `.pyi` stubs | Removes files that are only needed for development |
| `.pyc` compiled with `unchecked-hash` | Zip rounds file times to 2 seconds, so timestamp-checked bytecode would be ignored on every cold start |

6. Choose Upload a file from Amazon S3. Then, for Amazon S3 link URL, enter a link to the file.

7. Choose Create.
## Build from Source

Requires Docker. Building `arm64` on an `x86_64` machine uses QEMU emulation and is slow.

## Structure
```bash
# ./build.sh <python> <arch> all <opencv> <profile> [--numpy <version>]
./build.sh 3.13 arm64 all 4.14.0 minimal
```

Example file structure for the `opencv-python-headless` library:
```
opencv-python-headless.{major}.{minor}.{revision}.{package_version}.zip
└ python/bin
└ python/cv2
└ python/numpy
└ python/numpy.libs
└ python/numpy-1.21.5.dist-info
└ python/opencv_python_headless-{major}.{minor}.{revision}.{package_version}.dist-info
└ python/opencv_python_headless.libs
dist/opencv-4.14.0-minimal-python3.13-arm64.zip
dist/opencv-4.14.0-minimal-python3.13-arm64.json # versions and sizes
```

## Another layer
<details>
<summary>Build stages</summary>

The build has three stages, which CI runs as separate jobs.

| Stage | Runs once per | Output |
|---|---|---|
| `opencv` | OpenCV × profile × arch | `cv2` built with the Python Limited API, so one binary serves Python 3.12 and later |
| `numpy` | Python × arch | NumPy without BLAS |
| `package` | Python × arch | Every zip for that runtime, after a smoke test |

```bash
./build.sh 3.12 x86_64 opencv 4.14.0 standard # artifacts/opencv-4.14.0-standard-x86_64.tar.gz
./build.sh 3.13 x86_64 numpy # artifacts/numpy-2.5.4-python3.13-x86_64.tar.gz
./build.sh 3.13 x86_64 package # dist/*.zip for every built cv2
```

</details>

<details>
<summary>Layer structure</summary>

Lambda extracts the layer to `/opt`, and `/opt/python` is on `sys.path`.

```
opencv-{opencv}-{profile}-python{python}-{arch}.zip
└── python/
├── cv2/
│ ├── __init__.py
│ ├── python-3/cv2.abi3.so
│ └── licenses/
├── numpy/
└── numpy-{version}.dist-info/
```

</details>


## Release

Push a `v*` tag. GitHub Actions builds all 36 zips and attaches them to the release with `SHA256SUMS` and a size table.

```bash
git tag v2026.11.0
git push origin v2026.11.0
```

The following [link][lambda-link] show how you can structure the folders in your layer .zip archive.

[lambda-link]: https://docs.aws.amazon.com/en_es/lambda/latest/dg/configuration-layers.html
## Author

## Authors
Daehee Yun ([[email protected]](mailto:[email protected]))

Daehee Yun(s076923@gmail.com)
[latest]: https://github.com/076923/aws-lambda-python-opencv-layer/releases/latest
Loading
Loading