-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add basic USB-Camera support (#10)
Signed-off-by: Patrick Gehrsitz <[email protected]>
- Loading branch information
Showing
13 changed files
with
374 additions
and
485 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -55,3 +55,5 @@ cover/ | |
.idea/ | ||
|
||
venv | ||
.venv | ||
.vscode |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
from picamera2 import Picamera2 | ||
|
||
from spyglass.camera.camera import Camera | ||
from spyglass.camera.csi import CSI | ||
from spyglass.camera.usb import USB | ||
|
||
def init_camera( | ||
camera_num: int, | ||
tuning_filter=None, | ||
tuning_filter_dir=None | ||
) -> Camera: | ||
tuning = None | ||
|
||
if tuning_filter: | ||
params = {'tuning_file': tuning_filter} | ||
if tuning_filter_dir: | ||
params['dir'] = tuning_filter_dir | ||
tuning = Picamera2.load_tuning_file(**params) | ||
|
||
picam2 = Picamera2(camera_num, tuning=tuning) | ||
if picam2._is_rpi_camera(): | ||
cam = CSI(picam2) | ||
else: | ||
cam = USB(picam2) | ||
return cam |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import libcamera | ||
|
||
from abc import ABC, abstractmethod | ||
from picamera2 import Picamera2 | ||
|
||
from spyglass import logger | ||
from spyglass.exif import create_exif_header | ||
from spyglass.camera_options import process_controls | ||
from spyglass.server import StreamingServer, StreamingHandler | ||
|
||
class Camera(ABC): | ||
def __init__(self, picam2: Picamera2): | ||
self.picam2 = picam2 | ||
|
||
def create_controls(self, fps: int, autofocus: str, lens_position: float, autofocus_speed: str): | ||
controls = {} | ||
|
||
if 'FrameRate' in self.picam2.camera_controls: | ||
controls['FrameRate'] = fps | ||
|
||
if 'AfMode' in self.picam2.camera_controls: | ||
controls['AfMode'] = autofocus | ||
controls['AfSpeed'] = autofocus_speed | ||
if autofocus == libcamera.controls.AfModeEnum.Manual: | ||
controls['LensPosition'] = lens_position | ||
else: | ||
logger.warning('Attached camera does not support autofocus') | ||
|
||
return controls | ||
|
||
def configure(self, | ||
width: int, | ||
height: int, | ||
fps: int, | ||
autofocus: str, | ||
lens_position: float, | ||
autofocus_speed: str, | ||
control_list: list[list[str]]=[], | ||
upsidedown=False, | ||
flip_horizontal=False, | ||
flip_vertical=False): | ||
controls = self.create_controls(fps, autofocus, lens_position, autofocus_speed) | ||
c = process_controls(self.picam2, [tuple(ctrl) for ctrl in control_list]) | ||
controls.update(c) | ||
|
||
transform = libcamera.Transform( | ||
hflip=int(flip_horizontal or upsidedown), | ||
vflip=int(flip_vertical or upsidedown) | ||
) | ||
|
||
self.picam2.configure( | ||
self.picam2.create_video_configuration( | ||
main={'size': (width, height)}, | ||
controls=controls, | ||
transform=transform | ||
) | ||
) | ||
|
||
def _run_server(self, | ||
bind_address, | ||
port, | ||
streaming_handler: StreamingHandler, | ||
get_frame, | ||
stream_url='/stream', | ||
snapshot_url='/snapshot', | ||
orientation_exif=0): | ||
logger.info('Server listening on %s:%d', bind_address, port) | ||
logger.info('Streaming endpoint: %s', stream_url) | ||
logger.info('Snapshot endpoint: %s', snapshot_url) | ||
logger.info('Controls endpoint: %s', '/controls') | ||
address = (bind_address, port) | ||
streaming_handler.picam2 = self.picam2 | ||
streaming_handler.get_frame = get_frame | ||
streaming_handler.stream_url = stream_url | ||
streaming_handler.snapshot_url = snapshot_url | ||
if orientation_exif > 0: | ||
streaming_handler.exif_header = create_exif_header(orientation_exif) | ||
else: | ||
streaming_handler.exif_header = None | ||
current_server = StreamingServer(address, streaming_handler) | ||
current_server.serve_forever() | ||
|
||
@abstractmethod | ||
def start_and_run_server(self, | ||
bind_address, | ||
port, | ||
stream_url='/stream', | ||
snapshot_url='/snapshot', | ||
orientation_exif=0): | ||
pass | ||
|
||
@abstractmethod | ||
def stop(self): | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import io | ||
|
||
from picamera2.encoders import MJPEGEncoder | ||
from picamera2.outputs import FileOutput | ||
from threading import Condition | ||
|
||
from spyglass import camera | ||
from spyglass.server import StreamingHandler | ||
|
||
class CSI(camera.Camera): | ||
def start_and_run_server(self, | ||
bind_address, | ||
port, | ||
stream_url='/stream', | ||
snapshot_url='/snapshot', | ||
orientation_exif=0): | ||
|
||
class StreamingOutput(io.BufferedIOBase): | ||
def __init__(self): | ||
self.frame = None | ||
self.condition = Condition() | ||
|
||
def write(self, buf): | ||
with self.condition: | ||
self.frame = buf | ||
self.condition.notify_all() | ||
output = StreamingOutput() | ||
def get_frame(inner_self): | ||
with output.condition: | ||
output.condition.wait() | ||
return output.frame | ||
|
||
self.picam2.start_recording(MJPEGEncoder(), FileOutput(output)) | ||
|
||
self._run_server( | ||
bind_address, | ||
port, | ||
StreamingHandler, | ||
get_frame, | ||
stream_url=stream_url, | ||
snapshot_url=snapshot_url, | ||
orientation_exif=orientation_exif | ||
) | ||
|
||
def stop(self): | ||
self.picam2.stop_recording() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
from spyglass import camera | ||
from spyglass.server import StreamingHandler | ||
|
||
class USB(camera.Camera): | ||
def start_and_run_server(self, | ||
bind_address, | ||
port, | ||
stream_url='/stream', | ||
snapshot_url='/snapshot', | ||
orientation_exif=0): | ||
def get_frame(inner_self): | ||
#TODO: Cuts framerate in 1/n with n streams open, add some kind of buffer | ||
return self.picam2.capture_buffer() | ||
|
||
self.picam2.start() | ||
|
||
self._run_server( | ||
bind_address, | ||
port, | ||
StreamingHandler, | ||
get_frame, | ||
stream_url=stream_url, | ||
snapshot_url=snapshot_url, | ||
orientation_exif=orientation_exif | ||
) | ||
|
||
def stop(self): | ||
self.picam2.stop() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.