Windows Hello IR camera module — cv2.VideoCapture-like access to hidden IR cameras
Project description
WHIRCAM — Windows Hello IR Camera for Python
Access Windows Hello IR cameras from Python like cv2.VideoCapture. These
cameras are hidden from legacy APIs (DirectShow, OpenCV, Media Foundation) by
the driver's SkipCameraEnumeration and SensorCameraMode flags. This module
talks to them through the MediaFrameSourceGroup API instead.
This is a raw camera access library, not an authentication tool. It reads pixel data and depth maps from the IR camera sensor. It does not perform face recognition, Windows Hello sign-in, or any biometric processing.
Try it in one shot (requires an IR camera and opencv-python):
uvx --from whircam[full] whircam --imshow
Or with pip:
pip install whircam[full] && whircam --imshow
See your IR camera live in Python:
import whircam
with whircam.IRCam() as cap:
while cap.imshow() != 27: # ESC to exit
pass
whircam.IRCam.destroy_all_windows()
Full API reference:
groups = whircam.find_groups()
cap = whircam.IRCam(format_output="gray") # or "bgr" (default)
ret, frame = cap.read() # (bool, 640×360 BGR or grayscale ndarray)
ret, depth = cap.read_depth() # (bool, 640×360 uint16 depth in mm)
ret, frame, depth = cap.read_both()
cap.release()
# or with whircam.IRCam() as cap: ...
Requirements
- Windows 10 1809+ (build 17763) — the
MediaFrameSourceGroupAPI is not available on earlier versions. - A Windows Hello IR camera (most laptops with Windows Hello have one).
- winrt-* — Per-namespace Python bindings for
the WinRT APIs this module uses (14 namespace packages — Foundation,
Foundation.Collections, Media, MediaProperties, Media.Capture,
Media.Capture.Frames, Devices, Devices.Enumeration, Storage.Streams,
Graphics, Imaging, DirectX, Direct3D11, plus the runtime).
Required for all functionality. Automatically installed via
piporuv(see below). - Optional:
numpy+opencv-python— needed only at runtime (frame decoding, pixel conversion, andimshow()). Install viawhircam[full].
Install
pip install whircam # core only (find_groups, GroupInfo)
pip install whircam[full] # + numpy + opencv-python (read, imshow)
With uv:
uv add whircam # core only
uv add whircam[full] # + numpy + opencv-python
Try it once without installing:
uvx --from whircam[full] whircam --imshow
The core winrt-* dependencies are pulled in automatically. If you copy
just whircam.py into your project manually, install the 14 namespace
packages separately:
uv add winrt-runtime winrt-Windows.Foundation winrt-Windows.Foundation.Collections winrt-Windows.Media winrt-Windows.Media.MediaProperties winrt-Windows.Media.Capture winrt-Windows.Media.Capture.Frames winrt-Windows.Devices winrt-Windows.Devices.Enumeration winrt-Windows.Storage.Streams winrt-Windows.Graphics winrt-Windows.Graphics.Imaging winrt-Windows.Graphics.DirectX winrt-Windows.Graphics.DirectX.Direct3D11
NumPy and OpenCV (the [full] extra) are needed only at runtime —
find_groups() works with the winrt-* packages alone.
Contributing
Issues and pull requests are welcome. I maintain this project in my free time and review contributions as I can — there are no guarantees on response or release cadence.
Dev setup (uv):
uv sync --group dev
uv run pytest -k "not test_does_not_crash and not test_entry_point_script_help and not test_help_exits_ok"
Dev setup (pip):
pip install -e ".[dev]"
pytest -k "not test_does_not_crash and not test_entry_point_script_help and not test_help_exits_ok"
Both commands above install the winrt-* packages, numpy, and opencv-python
automatically via the project's pyproject.toml dependencies.
The -k filter excludes the hardware-dependent and CLI-entry-point
tests that require a camera or an installed script. Run without the filter
if you have an IR camera and whircam installed.
API
Discovery
whircam.find_groups()
Returns a list of camera groups that contain an IR source:
[
{"index": 0, "name": "Rts-DMFT-Group",
"sources": [{"kind": "Color", ...}, {"kind": "Infrared", ...}]},
{"index": 1, "name": "USB2.0 IR UVC WebCam",
"sources": [{"kind": "Infrared", ...}]},
]
import whircam and whircam.find_groups() only require the winrt-* packages. NumPy and OpenCV
(optional) are only needed when you open a camera via IRCam(),
and are imported lazily at that point.
Opening a camera
cap = whircam.IRCam() # auto-selects first IR camera
cap = whircam.IRCam(group=0) # by find_groups() index
cap = whircam.IRCam(group="USB2.0 IR UVC WebCam") # by display name
cap = whircam.IRCam(group=groups[0]) # by find_groups() dict
cap = whircam.IRCam(format_index=1) # select format by index
cap = whircam.IRCam(format_output="gray") # skip BGR conversion (returns 2-D frames)
The camera opens in SharedReadOnly mode — it will not interfere with Windows
Hello face authentication.
Reading frames
| Method | Returns | Behavior |
|---|---|---|
cap.read() |
(bool, ndarray) |
Blocks until a new IR frame is received. Shape: (h, w, 3) for BGR, (h, w) for grayscale |
cap.read_depth() |
(bool, uint16_ndarray) |
Blocks until a new depth frame is received |
cap.read_both() |
(bool, ndarray, uint16) |
Blocks until a new frame pair is received |
cap.imshow(mode=) |
int |
Read next frame and show in window. mode= controls frame filtering: "illuminated" (default, IR LED on), "ambient", or "both". Returns cv2.waitKey key code (-1 if no frame) |
IRCam.destroy_all_windows() |
— | Close all OpenCV windows created by imshow() — no import cv2 needed |
The read methods block until a new frame is available. The internal pump reads
the most recent frame from the camera each cycle — if frames arrive faster
than the consumer processes them, some are silently dropped (analogous to
cv2.VideoCapture.read with a ring-buffer backend). Depth is in
millimeters (1–65535). Returns (False, None) on timeout or error.
Properties
cap.formats # [{"index":0, "width":640, "height":360, "fps":30.0, "subtype":"L8"}, ...]
cap.info # {"group_name":..., "format_index":..., "depth_available":..., "format_applied":...}
cap.fps # current frame rate
cap.width # frame width in pixels
cap.height # frame height in pixels
cap.shape # (height, width) — OpenCV convention
cap.is_opened # True while pump is running
Context manager
with whircam.IRCam() as cap:
ret, frame = cap.read()
CLI
# Live IR camera preview (ESC to exit)
whircam --imshow
whircam --imshow 0 # select by index
whircam --imshow "USB Camera" # select by name
whircam --imshow --mode both # show every frame (unfiltered)
whircam --imshow --mode ambient # show only ambient frames
# List IR cameras
whircam --list
# Check version
whircam --version
Limitations
-
This is a near-IR depth camera, not thermal IR. Windows Hello cameras use 850 nm near-infrared structured light or time-of-flight for face authentication. They do not detect heat/thermal radiation — the output is a grayscale intensity image of the scene under IR illumination (and optionally a depth map in mm).
-
~15 fps effective rate: The Windows Hello camera driver alternates illuminated and ambient frames.
try_acquire_latest_frame()takes ~15–16ms per call regardless. You get ~7.5 illuminated + 7.5 ambient frames per second regardless of the advertised format. -
Format switches may fail: Some camera drivers reject
set_format_async()while the pipeline is held by the Windows FrameServer. Format selection is best-effort and falls back to the default. -
Windows-only: Relies on APIs unavailable on other platforms.
-
Depends on WinRT APIs that Microsoft may change. This library reaches into
Windows.Media.Capture.Frames,Windows.Graphics.Imaging, and related WinRT namespaces — undocumented internal behaviours that Microsoft has changed between Windows versions without notice (e.g., frame delivery timing in 22H2,SkipCameraEnumerationsemantics in 24H2). A future Windows Update can break this library without warning. No guarantees, no SLAs. -
ARM64 (Surface etc.) — opencv-python has no official ARM64 wheel. The
winrt-*packages andnumpyboth ship nativewin_arm64wheels, butopencv-pythondoes not yet (PR #1143). If you need native ARM64 Python, use an unofficial wheel from cgohlke/win_arm64-wheels or run x64 Python under emulation (all major Surface devices support this). -
Python free-threading (--disable-gil, 3.13+) — untested. This library uses
threading,asyncio, and WinRT COM interop via the winrt-* packages. The free-threaded build has not been tested and may not work. The primary obstacle is likely the PyWinRT C++/WinRT object model rather than Python threading primitives.
Design Notes
These are intentional trade-offs worth documenting, not bugs.
Single-file module, not a package. whircam.py is one file you can copy into
any project with no build step. Splitting into _types.py, _frame_store.py,
etc. would require a directory install and break the zero-dependency drop-in
use case.
CI runs camera-free tests on every push. A .github/workflows/ci.yml
workflow installs the package and runs pytest -k "not test_does_not_crash"
on Windows Server 2022 for Python 3.10–3.13. Hardware-dependent tests
(those that instantiate IRCam()) are excluded.
test/ has no __init__.py. The pyproject.toml sets
--import-mode=importlib explicitly. Running pytest from the project root
(with the config present) works. Running pytest test/ from a sibling directory
without the config would fail — but so would any other config-dependent invocation.
_PROJECT_URL. The _PROJECT_URL constant at the top of whircam.py
points to the GitHub repo (used in the untested-build warning). Install
instructions now use pip install whircam — the git URL is no longer the
primary install path.
No WinRT mocking in tests. The MediaFrameSourceGroup, MediaCapture,
and related types are native C++/WinRT objects with no pure-Python interface
to implement. fresh_whircam() (evict + re-import) is the pragmatic substitute
for test isolation.
release() and WinRT call timeouts. WinRT APIs like
try_acquire_latest_frame() have no timeout mechanism — a driver hang can
block the pump thread indefinitely. The daemon thread and force-close via
loop.stop() are best-effort. At process exit, WinRT cleans up native
resources automatically.
Global winrt- state vs test isolation.* Module-level singletons
(_MediaFrameSourceGroup, etc.) are lazy-loaded once. Test isolation uses
fresh_whircam() to evict the module from sys.modules and re-import.
A class-namespace approach would be cleaner but adds complexity without
practical benefit at this scale.
Dependencies
| Package | Min version | Required for | Lazy? |
|---|---|---|---|
winrt-runtime + winrt-Windows.* (14 namespace packages) |
3.2 | WinRT projection — async ops, MediaCapture, MediaFrameSourceGroup, SoftwareBitmap, Direct3D | no |
numpy |
1.24 | frame buffer conversion | optional — only for IRCam() |
opencv-python |
4.6 | pixel format conversion (GRAY→BGR) | optional — only for IRCam() |
Version ranges are intentionally broad. The APIs whircam uses (frombuffer,
reshape, cvtColor, GRAY2BGR, RGBA2BGR) are stable across all listed
minimum versions.
License
MIT
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 whircam-0.1.0.tar.gz.
File metadata
- Download URL: whircam-0.1.0.tar.gz
- Upload date:
- Size: 30.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ce3f74859b0d26b6ba5ba1369eec594c595a478070d328d9ad34fd7f30b6bff0
|
|
| MD5 |
63507475ce20beb56e2132ce8a95aaad
|
|
| BLAKE2b-256 |
43555f62aa15d732a179df9ac94cebbccc287cecc5bf6a899c7fec5976b97d77
|
File details
Details for the file whircam-0.1.0-py3-none-any.whl.
File metadata
- Download URL: whircam-0.1.0-py3-none-any.whl
- Upload date:
- Size: 22.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0292918ea32665d9a79d5ddc6db4f748c0112c15cf69862d03d3677789a06380
|
|
| MD5 |
307c2e196c25e12890772cf530d8ca2c
|
|
| BLAKE2b-256 |
e84b09aeba0251e4523bb3f7687e8d9ad3e369ce1f16d53069388b304d2a9c71
|