Skip to content

jacksonfcs/Windows-Hello-IR-Camera-Python

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

WHIRCAM — Windows Hello IR Camera for Python

PyPI version Python License Windows

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 MediaFrameSourceGroup API 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 pip or uv (see below).
  • Optional: numpy + opencv-python — needed only at runtime (frame decoding, pixel conversion, and imshow()). Install via whircam[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, SkipCameraEnumeration semantics 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 and numpy both ship native win_arm64 wheels, but opencv-python does 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

About

For Interacting with Windows Hello IR Camera in Python using pywinrt, optionally using numpy and opencv-python

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages