-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Multicam Refinements + GigE Support (#7)
* fixing speed issues with RTSP stream * adding serial_number to config if user doesn't provide serial_number * adjusting name of method to _find_webcam_serial_numbers * various refinements * some gige troubleshooting * Update README.md * Update README.md * adding functionality to assign Basler properties by name * removing explicit log level setting * Update README.md * Update README.md * Update README.md * Automatically reformatting code with black and isort * cleaning up the poetry file and downgrading the opencv-python version to help Raspberry Pi users * adding automated tests and better handling for optional dependency import failures * Update README.md * Automatically reformatting code with black and isort --------- Co-authored-by: Tim Huff <[email protected]> Co-authored-by: Leo Dirac <[email protected]> Co-authored-by: Auto-format Bot <[email protected]>
- Loading branch information
1 parent
fe70ad1
commit 844a5d3
Showing
9 changed files
with
645 additions
and
175 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 |
---|---|---|
@@ -1,7 +1,7 @@ | ||
# FrameGrab by Groundlight | ||
## A user-friendly library for grabbing images from cameras or streams | ||
|
||
FrameGrab is an open-source Python library designed to make it easy to grab frames (images) from cameras or streams. The library supports webcams, RTSP streams, Basler USB cameras and Intel RealSense depth cameras. | ||
FrameGrab is an open-source Python library designed to make it easy to grab frames (images) from cameras or streams. The library supports generic USB cameras (such as webcams), RTSP streams, Basler USB cameras, Basler GigE cameras, and Intel RealSense depth cameras. | ||
|
||
FrameGrab also provides basic motion detection functionality. FrameGrab requires Python 3.7 or higher. | ||
|
||
|
@@ -20,29 +20,36 @@ To install the FrameGrab library, simply run: | |
pip install framegrab | ||
``` | ||
|
||
## Optional Dependencies | ||
To use a Basler USB or GigE camera, you must separately install the `pypylon` package. | ||
|
||
Similarly, to use Intel RealSense cameras, you must install `pyrealsense2`. | ||
|
||
If you don't intend to use these camera types, you don't need to install these extra packages. | ||
|
||
## Usage | ||
|
||
### Frame Grabbing | ||
|
||
Simple usage with a single webcam would look something like the following: | ||
Simple usage with a single USB camera would look something like the following: | ||
|
||
``` | ||
from framegrab import FrameGrabber | ||
config = { | ||
'input_type': 'webcam', | ||
'input_type': 'generic_usb', | ||
} | ||
grabber = FrameGrabber.create_grabber(config) | ||
``` | ||
`config` can contain many details and settings about your camera, but only `input_type` is required. Available `input_type` options are: `webcam`, `rtsp`, `realsense` and `basler_usb`. | ||
`config` can contain many details and settings about your camera, but only `input_type` is required. Available `input_type` options are: `generic_usb`, `rtsp`, `realsense` and `basler`. | ||
|
||
Here's an example of a single webcam configured with several options: | ||
Here's an example of a single USB camera configured with several options: | ||
``` | ||
config = { | ||
'name': 'front door camera', | ||
'input_type': 'webcam', | ||
'name': 'Front Door Camera', | ||
'input_type': 'generic_usb', | ||
'id': { | ||
'serial_number': 23432570 | ||
}, | ||
|
@@ -92,7 +99,7 @@ grabber.release() | |
|
||
You might have several cameras that you want to use in the same application. In this case, you can load the configurations from a yaml file and use `FrameGrabber.create_grabbers`. | ||
|
||
If you have multiple cameras of the same type plugged in, it's recommended to provide serial numbers in the configurations; this ensures that each configuration is paired with the correct camera. If you don't provide serial numbers in your configurations, configurations will be paired with cameras in a sequential manner. | ||
If you have multiple cameras of the same type plugged in, it's recommended that you include serial numbers in the configurations; this ensures that each configuration is paired with the correct camera. If you don't provide serial numbers in your configurations, configurations will be paired with cameras in a sequential manner. | ||
|
||
Below is a sample yaml file containing configurations for three different cameras. | ||
``` | ||
|
@@ -107,17 +114,17 @@ GL_CAMERAS: | | |
right: .8 | ||
- name: conference room | ||
input_type: rtsp | ||
address: | ||
id: | ||
rtsp_url: rtsp://admin:[email protected]/cam/realmonitor?channel=1&subtype=0 | ||
options: | ||
crop: | ||
absolute: | ||
pixels: | ||
top: 350 | ||
bottom: 1100 | ||
left: 1100 | ||
right: 2000 | ||
- name: workshop | ||
input_type: webcam | ||
input_type: generic_usb | ||
id: | ||
serial_number: B77D3A8F | ||
``` | ||
|
@@ -138,12 +145,44 @@ grabbers = FrameGrabber.create_grabbers(configs) | |
for grabber in grabbers.values(): | ||
print(grabber.config) | ||
frame = grabber.grab() | ||
display_image(frame) | ||
display_image(frame) # substitute this line for your preferred method of displaying images, such as cv2.imshow | ||
grabber.release() | ||
``` | ||
It is also possible to 'autodiscover' cameras. This will automatically connect to all cameras that are plugged into your machine, such as `webcam`, `realsense` and `basler_usb` cameras. Default configurations will be loaded for each camera. Please note that RTSP streams cannot be discovered in this manner; RTSP URLs must be specified in the configurations. | ||
### Configurations | ||
The table below shows all available configurations and the cameras to which they apply. | ||
| Configuration Name | Example | Webcam | RTSP | Basler | Realsense | | ||
|----------------------------|-----------------|------------|-----------|-----------|-----------| | ||
| name | On Robot Arm | optional | optional | optional | optional | | ||
| input_type | generic_usb | required | required | required | required | | ||
| id.serial_number | 23458234 | optional | - | optional | optional | | ||
| id.rtsp_url | rtsp://… | - | required | - | - | | ||
| options.resolution.height | 480 | optional | - | - | optional | | ||
| options.resolution.width | 640 | optional | - | - | optional | | ||
| options.zoom.digital | 1.3 | optional | optional | optional | optional | | ||
| options.crop.pixels.top | 100 | optional | optional | optional | optional | | ||
| options.crop.pixels.bottom | 400 | optional | optional | optional | optional | | ||
| options.crop.pixels.left | 100 | optional | optional | optional | optional | | ||
| options.crop.pixels.right | 400 | optional | optional | optional | optional | | ||
| options.crop.relative.top | 0.1 | optional | optional | optional | optional | | ||
| options.crop.relative.bottom | 0.9 | optional | optional | optional | optional | | ||
| options.crop.relative.left | 0.1 | optional | optional | optional | optional | | ||
| options.crop.relative.right | 0.9 | optional | optional | optional | optional | | ||
| options.depth.side_by_side | 1 | - | - | - | optional | | ||
|
||
In addition to the configurations in the table above, you can set any Basler camera property by including `options.basler.<BASLER PROPERTY NAME>`. For example, it's common to set `options.basler.PixelFormat` to `RGB8`. | ||
|
||
### Autodiscovery | ||
Autodiscovery automatically connects to all cameras that are plugged into your machine or discoverable on the network, including `generic_usb`, `realsense` and `basler` cameras. Default configurations will be loaded for each camera. Please note that RTSP streams cannot be discovered in this manner; RTSP URLs must be specified in the configurations. | ||
|
||
Autodiscovery is great for simple applications where you don't need to set any special options on your cameras. It's also a convenient method for finding the serial numbers of your cameras (if the serial number isn't printed on the camera). | ||
``` | ||
grabbers = FrameGrabber.autodiscover() | ||
# Print some information about the discovered cameras | ||
for grabber in grabbers.values(): | ||
print(grabber.config) | ||
grabber.release() | ||
``` | ||
|
||
### Motion Detection | ||
|
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 |
---|---|---|
@@ -1,22 +1,20 @@ | ||
[tool.poetry] | ||
name = "framegrab" | ||
version = "0.2.1" | ||
version = "0.3" | ||
description = "Easily grab frames from cameras or streams" | ||
authors = ["Groundlight <[email protected]>"] | ||
license = "MIT" | ||
readme = "README.md" | ||
|
||
[tool.poetry.dependencies] | ||
python = "^3.7" | ||
opencv-python = "^4.7.0.72" | ||
youtube-dl = "^2021.12.17" | ||
pillow = "^9.5.0" | ||
pafy = "^0.5.5" | ||
opencv-python = "^4.4.0.46" | ||
pyyaml = "^6.0.1" | ||
|
||
[tool.poetry.group.dev.dependencies] | ||
black = "^23.3.0" | ||
pytest = "^7.0.1" | ||
|
||
[build-system] | ||
requires = ["poetry-core"] | ||
build-backend = "poetry.core.masonry.api" | ||
build-backend = "poetry.core.masonry.api" |
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,19 @@ | ||
from framegrab import FrameGrabber | ||
|
||
print('Autodiscovering cameras...') | ||
|
||
grabbers = FrameGrabber.autodiscover() | ||
|
||
print('-' * 100) | ||
print(f'Found {len(grabbers)} camera(s): {list(grabbers.keys())}') | ||
|
||
# Get a frame from each camera | ||
for camera_name, grabber in grabbers.items(): | ||
frame = grabber.grab() | ||
|
||
print(f'Grabbed frame from {camera_name} with shape {frame.shape}') | ||
print(grabber.config) | ||
|
||
grabber.release() | ||
|
||
print('Autodiscover demo complete.') |
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,33 @@ | ||
from framegrab import FrameGrabber | ||
import yaml | ||
import cv2 | ||
|
||
# load the configurations from yaml | ||
config_path = 'sample_config.yaml' | ||
with open(config_path, 'r') as f: | ||
data = yaml.safe_load(f) | ||
configs = yaml.safe_load(data['GL_CAMERAS']) | ||
|
||
print('Loaded the following configurations from yaml:') | ||
print(configs) | ||
|
||
# Create the grabbers | ||
grabbers = FrameGrabber.create_grabbers(configs) | ||
|
||
while True: | ||
# Get a frame from each camera | ||
for camera_name, grabber in grabbers.items(): | ||
frame = grabber.grab() | ||
|
||
cv2.imshow(camera_name, frame) | ||
|
||
key = cv2.waitKey(30) | ||
|
||
if key == ord('q'): | ||
break | ||
|
||
cv2.destroyAllWindows() | ||
|
||
for grabber in grabbers.values(): | ||
grabber.release() | ||
|
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,24 @@ | ||
GL_CAMERAS: | | ||
- name: Front Door | ||
input_type: generic_usb | ||
options: | ||
zoom: | ||
digital: 1.5 | ||
- name: Conference Room | ||
input_type: rtsp | ||
id: | ||
rtsp_url: rtsp://admin:[email protected]/cam/realmonitor?channel=1&subtype=0 | ||
options: | ||
crop: | ||
relative: | ||
top: .1 | ||
bottom: .9 | ||
left: .1 | ||
right: .9 | ||
- name: Workshop | ||
input_type: basler | ||
id: | ||
serial_number: 12345678 | ||
options: | ||
basler: | ||
ExposureTime: 60000 |
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,26 @@ | ||
"""Finds a single USB camera (or built-in webcam) and displays its feed in a window. | ||
Press 'q' to quit. | ||
""" | ||
|
||
import cv2 | ||
from framegrab import FrameGrabber | ||
|
||
config = { | ||
'name': 'My Camera', | ||
'input_type': 'generic_usb', | ||
} | ||
|
||
grabber = FrameGrabber.create_grabber(config) | ||
|
||
while True: | ||
frame = grabber.grab() | ||
|
||
cv2.imshow('FrameGrab Single-Camera Demo', frame) | ||
|
||
key = cv2.waitKey(30) | ||
if key == ord('q'): | ||
break | ||
|
||
cv2.destroyAllWindows() | ||
|
||
grabber.release() |
Oops, something went wrong.