ofxPS3Eye is an openFrameworks addon that provides a driver for the PS3 Eye camera, enabling 10-bit RAW capture and long-exposure control.
README
ofxPS3Eye
openFrameworks addon / standalone driver for the Sony PlayStation 3 Eye camera (OV538/OV534 USB bridge + OV772x sensor), with genuine 10-bit RAW10 capture, long-exposure / slow-shutter control, and direct sensor-register access.
Based on
inspirit/PS3EYEDriver, extended with a true 10-bit RAW path, 16-bit exposure, and non-blocking long-exposure support.---
Why this fork
The PS3 Eye is a ~$5 global-shutter-free rolling-shutter webcam that is wildly over-spec'd for its price. Most drivers only expose the bridge's 8-bit processed output. This fork unlocks the sensor's raw 10-bit Bayer path (DSP bypassed), which is what makes it usable as a cheap scientific / astrophotography / experimental imaging sensor:
- True 10-bit RAW10 — 384000 bytes/frame (640×480 packed SGRBG10P), values 0..1023,
- 16-bit / long exposure — full AEC range for low-light and multi-second integration.
- Non-blocking capture —
isFrameNew()so seconds-long exposures don't freeze the host. - Direct register access — sensor and bridge controls for experimentation.
Hardware compatibility
The PS3 Eye ships with two different USB bridge chips. Identify yours on macOS with
system_profiler SPUSBDataType and read the USB Camera-Bx.xx version string:| Bridge | USB string | 10-bit status |
|---|---|---|
| OV538 | B4.xx (e.g. B4.09.24.1) | ✅ Confirmed working RAW10 |
| OV534 | B3.xx | ⚠️ Unverified — see roadmap; not a known limit |
The earlier assumption that the OV534 (B3) "truncates RAW10 to 8-bit" was neverReference recipe:
actually tested — that 307200-byte observation predates the true-10-bit register
fixes and was almost certainly the same software bug later fixed for the OV538.
The driver uses the same bridge recipe for both models, so OV534 10-bit is an
open question pending a retest (see Roadmap).
inspirit/PS3EYEDriver issue #45 (user mattipee) and openastroproject/openastro issue #205.---
Quick start
using namespace ps3eye;
// 1. Enumerate
auto devices = PS3EYECam::getDevices();
if (devices.empty()) return;
PS3EYECam::PS3EYERef eye = devices[0];
// 2. Init — VGA, 60fps, raw Bayer, 10-bit on
eye->init(640, 480, 60, PS3EYECam::EOutputFormat::Bayer, /use10Bit=/true);
eye->start();
// 3. Grab (non-blocking-friendly for long exposures)
std::vector<uint16_t> frame(eye->getWidth() eye->getHeight());
if (eye->isFrameNew()) {
eye->getFrame(frame.data()); // uint16 overload for 10-bit formats
}Output formats (EOutputFormat)
| Format | Buffer size | Notes |
|---|---|---|
Bayer | wh (8-bit) / wh2 (10-bit) | Raw mosaic; getFrame(uint16_t) when use10Bit |
RAW10 | wh2 (uint16) | Unpacked 10-bit, values 0..1023 |
BGR / RGB | wh3 | Bridge-debayered 8-bit |
Gray | wh | 8-bit grayscale |
---
Example
A ready-to-generate example lives in
example/. It's the simplest path — 8-bit BGR output (bridge-debayered), uploaded to a texture and drawn — a good first run to confirm the camera works before moving to the RAW path.Generate the project with ofGen (ofWorks project generator):
cd example
ofGen # reads of.yml, pulls in addons, emits the platform projectof.yml declares the dependencies:ofpath: ../../..
addons:
- ofxKinect # provides libusb (ps3eye.h includes libusb.h)
- ofxPS3Eye
templates: [macos, zed, chalet]Heads-up:The source below is what's inps3eye.hincludeslibusb.h, which is bundled by ofxKinect — hence
it's listed as a dependency even though the example doesn't use Kinect features.
example/src/:ofApp.h
#pragma once
include "ofMain.h"
include "ps3eye.h"
class ofApp : public ofBaseApp {
public:
void setup();
void update();
void draw();
ps3eye::PS3EYECam::PS3EYERef eye;
std::vector<uint8_t> pixels; // BGR, wh3
ofTexture tex;
};ofApp.cpp
#include "ofApp.h"
using namespace ps3eye;
void ofApp::setup() {
auto devices = PS3EYECam::getDevices();
if (devices.empty()) { ofLogError("ofApp") << "No PS3 Eye camera found"; return; }
eye = devices[0];
// VGA, 60fps, 8-bit BGR (the bridge does the debayer). The simplest path.
eye->init(640, 480, 60, PS3EYECam::EOutputFormat::BGR, /use10Bit=/false);
eye->start();
pixels.resize(eye->getWidth() eye->getHeight() 3);
tex.allocate(eye->getWidth(), eye->getHeight(), GL_RGB);
}
void ofApp::update() {
if (eye && eye->isFrameNew()) {
eye->getFrame(pixels.data());
tex.loadData(pixels.data(), eye->getWidth(), eye->getHeight(), GL_BGR);
}
}
void ofApp::draw() {
ofBackground(0);
if (tex.isAllocated()) tex.draw(0, 0);
}For the 10-bit RAW path, swap---init(...)toEOutputFormat::Bayer, use10Bit=trueand grab with thegetFrame(uint16_t)
overload (see Quick start). The raw mosaic then needs software debayer/WB/tone —
see Roadmap → GPU pipeline.
Roadmap
Forward-looking work. Nothing here is a promise of a date — it's the direction. The unifying idea: RAW10 hands you a single-channel
R16 Bayer texture with the DSP bypassed, so the whole image pipeline (debayer → color → tone) is ours to build, ideally on the GPU at full 10-bit precision.1. GPU pipeline (priority)
- [ ] Shader debayer — upload raw Bayer as an
R16texture, demosaic in a fragment
- [ ] GPU unpack — push the packed RAW10 (4px-in-5-bytes) straight to a texture via
UnpackRAW10 step (zero-copy capture). - [ ] Full post chain in one shader — black-level subtract → per-channel WB gains →
- [ ] Texture output — expose
ofTexture/ofFboso callersdraw()directly.
2. Astrophotography / exposure stacking
- [ ] Frame integration (mean stack) — accumulate N frames into a float FBO;
- [ ] Multi-exposure HDR merge — bracket via
setExposure(), merge short+long into
- [ ] Calibration frames — dark-frame subtract + flat-field divide (standard astro);
- [ ] Hot / dead-pixel map + correction — important for long exposures.
- [ ] Lucky imaging — keep best X% of frames, sub-pixel align + stack (planetary/lunar).
3. Capture & interchange
- [ ] FITS export — dump the uint16 Bayer to FITS for Siril / PixInsight. Likely the
- [ ] DNG / TIFF export with the CFA pattern tagged, so Lightroom / RawTherapee can
- [ ] Intervalometer / timelapse — timestamped sequence capture.
4. Addon ergonomics
- [ ] A real
ofxPS3Eyewrapper — threaded oF-style grabber (update(),
isFrameNew(), getPixels(), getTexture(), draw()). Today this is the raw driver. - [ ] Example projects — basic grab, 10-bit debayer shader, long-exposure astro.
5. Hardware & validation
- [ ] Verify 10-bit on OV534 (B3) — plug in the B3.xx camera, run 10-bit VGA, and
system_profiler SPUSBDataType→ confirmUSB Camera-B3.xx.- 384000 B/frame → 10-bit is going through; 307200 B → genuine bridge cap.
- If 384000, confirm with mattipee's saturate test: bright/saturated scene, LSB
0x00). - ✅ pass → drop the "B3 not confirmed" caveat; ❌ fail → document a real, tested limit.
- [ ] Startup bridge-model log — parse
B3/B4from the USB string and print it,
- [ ] 2×2 binning / ROI — if the sensor cooperates: better SNR and framerate for
- [ ] Multi-camera — synchronized capture across multiple PS3 Eyes (cheap array/stereo).
Changelog
All notable changes to ofxPS3Eye. Format loosely based on Keep a Changelog.
[Unreleased] — 2026-06-05
Focus of this session: getting genuine 10-bit RAW10 capture working from the PS3 Eye and exposing useful sensor-side controls. Verified on the OV538 bridge (USB product string
B4.xx, e.g. USB Camera-B4.09.24.1); the older OV534 (B3.xx) model is not yet confirmed for 10-bit (see Roadmap → Hardware & validation).Reference: register recipe from
inspirit/PS3EYEDriver issue #45 (user mattipee) and openastroproject/openastro issue #205.Added
- 16-bit exposure / long exposure.
setExposure()now takes auint16_tand
0x08 (AEC[15:8]) and 0x10 (AEC[7:0]) instead of the previous ~9-bit mapping. Large values let the sensor extend frame time (effective fps drops) for low-light / long-exposure use. exposure member widened to uint16_t. setShutterTiming(r11, r0d, re5)— writes the sensor clock + bridge timing
0x11 CLKRC, 0x0d, 0xe5) directly, for slow-shutter / long-exposure use (lower effective fps → longer integration). Values come from the frame-rate tables (incl. mattipee's sub-1fps entries). isFrameNew()— non-blocking check for a ready frame (viaFrameQueue::Available()),
Fixed
- 10-bit frames now actually arrive. The bridge start sequence wrote
0x1c = 0x40
0x1d FIFO writes so the frame-size never took and the bridge fell back to 8-bit (307200 bytes/frame). Restored to 0x1c = 0x00 (matching the working recipe and the 8-bit path) → full RAW10 frames (384000 bytes = 640×480 packed, SGRBG10P) at 60fps. Confirmed genuine 10-bit: ~98–99% of packed LSB bytes non-zero, LSB histogram spread across all four 2-bit values. - Manual gain/exposure now stick. In 10-bit mode the sensor's AGC/AEC was left on,
start() now writes COM8 0x13 = 0xf0 (AGC/AEC/AWB off) before applying manual gain/exposure. - Image orientation. The RAW sensor init set COM3
0x0C = 0x00, clearing the
0x0C = 0xC0 (normal orientation; low bits still 0 = scale/DCW off, required for RAW).Removed
- Per-frame
"Frame completed successfully"console spam inpkt_scan. - The rate-limited
[10bit?]LSB-verification diagnostic inUnpackRAW10(used to
Notes
- In RAW10 the sensor's DSP is bypassed, so DSP-domain controls (brightness, contrast,
0x42/0x43/0x44, and the mislabeled hue at 0x01) have no effect. White balance, tint, and black level must be done in software downstream. EOutputFormat::Bayerwithuse10Bit = trueyields uint16 values (0..1023) via
getFrame(uint16_t); packed format is 4 pixels in 5 bytes, low 2 bits of each pixel packed into the 5th byte (UnpackRAW10).---
Credits
- Original driver:
inspirit/PS3EYEDriver. - 10-bit RAW10 register recipe: mattipee —
inspirit/PS3EYEDriver#45,
openastroproject/openastro #205.
ofxAddons