framesource 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- frame_processors/__init__.py +37 -0
- frame_source/__init__.py +50 -0
- framesource/__init__.py +93 -0
- framesource/_msmf_config.py +26 -0
- framesource/_version.py +24 -0
- framesource/discovery.py +109 -0
- framesource/errors.py +82 -0
- framesource/factory.py +290 -0
- framesource/processors/__init__.py +34 -0
- framesource/processors/equirectangular360_processor.py +577 -0
- framesource/processors/fisheye2equirectangular_processor.py +328 -0
- framesource/processors/frame_processor.py +30 -0
- framesource/processors/hyperspectral_processor.py +23 -0
- framesource/processors/realsense_depth_processor.py +90 -0
- framesource/py.typed +0 -0
- framesource/sources/__init__.py +40 -0
- framesource/sources/audiospectrogram_capture.py +1068 -0
- framesource/sources/basler_capture.py +477 -0
- framesource/sources/folder_capture.py +920 -0
- framesource/sources/genicam_capture.py +681 -0
- framesource/sources/huateng_capture.py +245 -0
- framesource/sources/ipcamera_capture.py +254 -0
- framesource/sources/mvsdk.py +2454 -0
- framesource/sources/realsense_capture.py +565 -0
- framesource/sources/screen_capture.py +800 -0
- framesource/sources/video_capture_base.py +560 -0
- framesource/sources/video_file_capture.py +259 -0
- framesource/sources/webcam_capture.py +511 -0
- framesource/sources/ximea_capture.py +299 -0
- framesource/threading_utils.py +790 -0
- framesource-0.3.0.dist-info/METADATA +787 -0
- framesource-0.3.0.dist-info/RECORD +37 -0
- framesource-0.3.0.dist-info/WHEEL +5 -0
- framesource-0.3.0.dist-info/licenses/LICENSE +21 -0
- framesource-0.3.0.dist-info/scm_file_list.json +276 -0
- framesource-0.3.0.dist-info/scm_version.json +8 -0
- framesource-0.3.0.dist-info/top_level.txt +3 -0
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import warnings
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from ..errors import MissingDependencyError
|
|
8
|
+
from .video_capture_base import VideoCaptureBase
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BaslerCapture(VideoCaptureBase):
|
|
14
|
+
has_discovery = True
|
|
15
|
+
supports_exposure = True
|
|
16
|
+
supports_gain = True
|
|
17
|
+
|
|
18
|
+
"""Basler camera capture using pypylon."""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
source: Any = None,
|
|
23
|
+
*,
|
|
24
|
+
is_mono: Optional[bool] = None,
|
|
25
|
+
exposure: Optional[float] = None,
|
|
26
|
+
gain: Optional[float] = None,
|
|
27
|
+
width: Optional[int] = None,
|
|
28
|
+
height: Optional[int] = None,
|
|
29
|
+
**kwargs,
|
|
30
|
+
):
|
|
31
|
+
"""Initialize the Basler capture.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
source: Camera serial number (str) or device index (int,
|
|
35
|
+
default: 0).
|
|
36
|
+
is_mono: Configure the camera for monochrome (``Mono8``) output
|
|
37
|
+
instead of BGR8 (default: False). Applied on :meth:`connect`.
|
|
38
|
+
exposure: Initial exposure time in microseconds. Applied on
|
|
39
|
+
:meth:`connect` when provided.
|
|
40
|
+
gain: Initial gain in dB. Applied on :meth:`connect` when
|
|
41
|
+
provided.
|
|
42
|
+
width: Desired frame width in pixels. Applied on :meth:`connect`
|
|
43
|
+
(together with ``height``) when provided.
|
|
44
|
+
height: Desired frame height in pixels. Applied on
|
|
45
|
+
:meth:`connect` (together with ``width``) when provided.
|
|
46
|
+
**kwargs: Additional passthrough options stored on ``self.config``.
|
|
47
|
+
|
|
48
|
+
Raises:
|
|
49
|
+
MissingDependencyError: If the ``pypylon`` package is not
|
|
50
|
+
installed.
|
|
51
|
+
"""
|
|
52
|
+
# Preserve the historical ``self.config`` contents: only forward
|
|
53
|
+
# explicitly-provided values so the ``'width' in self.config`` style
|
|
54
|
+
# presence checks in connect() keep their old meaning.
|
|
55
|
+
for _key, _val in (
|
|
56
|
+
("is_mono", is_mono),
|
|
57
|
+
("exposure", exposure),
|
|
58
|
+
("gain", gain),
|
|
59
|
+
("width", width),
|
|
60
|
+
("height", height),
|
|
61
|
+
):
|
|
62
|
+
if _val is not None:
|
|
63
|
+
kwargs[_key] = _val
|
|
64
|
+
super().__init__(source, **kwargs)
|
|
65
|
+
self.camera = None
|
|
66
|
+
self.converter = None
|
|
67
|
+
try:
|
|
68
|
+
from pypylon import pylon
|
|
69
|
+
|
|
70
|
+
self.pylon = pylon
|
|
71
|
+
except ImportError as e:
|
|
72
|
+
raise MissingDependencyError("pypylon", extra="basler", details=str(e)) from e
|
|
73
|
+
|
|
74
|
+
self.is_mono = self.config.get("is_mono", False)
|
|
75
|
+
self.serial_number = source if isinstance(source, str) else None
|
|
76
|
+
self.device_index = source if isinstance(source, int) else 0
|
|
77
|
+
|
|
78
|
+
def connect(self) -> bool:
|
|
79
|
+
"""Connect to Basler camera."""
|
|
80
|
+
if self.pylon is None:
|
|
81
|
+
logger.error("pypylon not available")
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
# Get the transport layer factory
|
|
86
|
+
tlFactory = self.pylon.TlFactory.GetInstance()
|
|
87
|
+
|
|
88
|
+
# Get all attached devices and exit application if no device is found
|
|
89
|
+
devices = tlFactory.EnumerateDevices()
|
|
90
|
+
if len(devices) == 0:
|
|
91
|
+
logger.error("No Basler cameras found")
|
|
92
|
+
return False
|
|
93
|
+
|
|
94
|
+
# Create camera object
|
|
95
|
+
if self.serial_number:
|
|
96
|
+
# Connect by serial number
|
|
97
|
+
device_info = None
|
|
98
|
+
for device in devices:
|
|
99
|
+
if device.GetSerialNumber() == self.serial_number:
|
|
100
|
+
device_info = device
|
|
101
|
+
break
|
|
102
|
+
if device_info is None:
|
|
103
|
+
logger.error(f"Basler camera with serial number {self.serial_number} not found")
|
|
104
|
+
return False
|
|
105
|
+
self.camera = self.pylon.InstantCamera(tlFactory.CreateDevice(device_info))
|
|
106
|
+
else:
|
|
107
|
+
# Connect by index
|
|
108
|
+
if self.device_index >= len(devices):
|
|
109
|
+
logger.error(f"Basler camera index {self.device_index} out of range")
|
|
110
|
+
return False
|
|
111
|
+
self.camera = self.pylon.InstantCamera(
|
|
112
|
+
tlFactory.CreateDevice(devices[self.device_index])
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Open camera
|
|
116
|
+
self.camera.Open()
|
|
117
|
+
|
|
118
|
+
# Create image converter for color images
|
|
119
|
+
self.converter = self.pylon.ImageFormatConverter()
|
|
120
|
+
if self.is_mono:
|
|
121
|
+
self.converter.OutputPixelFormat = self.pylon.PixelType_Mono8
|
|
122
|
+
else:
|
|
123
|
+
self.converter.OutputPixelFormat = self.pylon.PixelType_BGR8packed
|
|
124
|
+
self.converter.OutputBitAlignment = self.pylon.OutputBitAlignment_MsbAligned
|
|
125
|
+
|
|
126
|
+
# Apply config parameters
|
|
127
|
+
if "exposure" in self.config:
|
|
128
|
+
self.set_exposure(self.config["exposure"])
|
|
129
|
+
if "gain" in self.config:
|
|
130
|
+
self.set_gain(self.config["gain"])
|
|
131
|
+
if "width" in self.config and "height" in self.config:
|
|
132
|
+
self.set_frame_size(self.config["width"], self.config["height"])
|
|
133
|
+
|
|
134
|
+
# Start grabbing
|
|
135
|
+
self.camera.StartGrabbing(self.pylon.GrabStrategy_LatestImageOnly)
|
|
136
|
+
|
|
137
|
+
self.is_connected = True
|
|
138
|
+
logger.info(f"Connected to Basler camera {self.camera.GetDeviceInfo().GetModelName()}")
|
|
139
|
+
return True
|
|
140
|
+
|
|
141
|
+
except Exception as e:
|
|
142
|
+
logger.error(f"Error connecting to Basler camera: {e}")
|
|
143
|
+
return False
|
|
144
|
+
|
|
145
|
+
def disconnect(self) -> bool:
|
|
146
|
+
"""Disconnect from Basler camera."""
|
|
147
|
+
try:
|
|
148
|
+
if self.camera is not None:
|
|
149
|
+
if self.camera.IsGrabbing():
|
|
150
|
+
self.camera.StopGrabbing()
|
|
151
|
+
self.camera.Close()
|
|
152
|
+
self.camera = None
|
|
153
|
+
self.converter = None
|
|
154
|
+
self.is_connected = False
|
|
155
|
+
logger.info("Disconnected from Basler camera")
|
|
156
|
+
return True
|
|
157
|
+
except Exception as e:
|
|
158
|
+
logger.error(f"Error disconnecting from Basler camera: {e}")
|
|
159
|
+
return False
|
|
160
|
+
|
|
161
|
+
def _read_implementation(self) -> tuple[bool, Optional[np.ndarray]]:
|
|
162
|
+
"""
|
|
163
|
+
Read a single frame from the Basler camera.
|
|
164
|
+
Returns:
|
|
165
|
+
Tuple[bool, Optional[np.ndarray]]: (success, frame)
|
|
166
|
+
"""
|
|
167
|
+
if not self.is_connected or self.camera is None:
|
|
168
|
+
return False, None
|
|
169
|
+
try:
|
|
170
|
+
grabResult = self.camera.RetrieveResult(5000, self.pylon.TimeoutHandling_ThrowException) # type: ignore
|
|
171
|
+
if grabResult.GrabSucceeded():
|
|
172
|
+
if self.converter:
|
|
173
|
+
image = self.converter.Convert(grabResult)
|
|
174
|
+
img_array = image.GetArray()
|
|
175
|
+
else:
|
|
176
|
+
img_array = grabResult.Array
|
|
177
|
+
grabResult.Release()
|
|
178
|
+
return True, img_array
|
|
179
|
+
else:
|
|
180
|
+
grabResult.Release()
|
|
181
|
+
return False, None
|
|
182
|
+
except Exception as e:
|
|
183
|
+
logger.error(f"Error reading from Basler camera: {e}")
|
|
184
|
+
return False, None
|
|
185
|
+
|
|
186
|
+
def get_exposure_range(self) -> tuple[float, float]:
|
|
187
|
+
"""Get exposure range in microseconds."""
|
|
188
|
+
if not self.is_connected or self.camera is None:
|
|
189
|
+
return (0.0, 0.0)
|
|
190
|
+
|
|
191
|
+
try:
|
|
192
|
+
min_exposure = self.camera.ExposureTime.GetMin()
|
|
193
|
+
max_exposure = self.camera.ExposureTime.GetMax()
|
|
194
|
+
return (min_exposure, max_exposure)
|
|
195
|
+
except Exception as e:
|
|
196
|
+
logger.error(f"Error getting exposure range: {e}")
|
|
197
|
+
return (0.0, 0.0)
|
|
198
|
+
|
|
199
|
+
def get_gain_range(self) -> tuple[float, float]:
|
|
200
|
+
"""Get gain range in dB."""
|
|
201
|
+
if not self.is_connected or self.camera is None:
|
|
202
|
+
return (0.0, 0.0)
|
|
203
|
+
|
|
204
|
+
try:
|
|
205
|
+
min_gain = self.camera.Gain.GetMin()
|
|
206
|
+
max_gain = self.camera.Gain.GetMax()
|
|
207
|
+
return (min_gain, max_gain)
|
|
208
|
+
except Exception as e:
|
|
209
|
+
logger.error(f"Error getting gain range: {e}")
|
|
210
|
+
return (0.0, 0.0)
|
|
211
|
+
|
|
212
|
+
def set_exposure(self, value: float) -> bool:
|
|
213
|
+
"""Set exposure in microseconds."""
|
|
214
|
+
if not self.is_connected or self.camera is None:
|
|
215
|
+
return False
|
|
216
|
+
|
|
217
|
+
try:
|
|
218
|
+
self.camera.ExposureTime.SetValue(value)
|
|
219
|
+
self._exposure = value
|
|
220
|
+
return True
|
|
221
|
+
except Exception as e:
|
|
222
|
+
logger.error(f"Error setting exposure: {e}")
|
|
223
|
+
return False
|
|
224
|
+
|
|
225
|
+
def get_exposure(self) -> Optional[float]:
|
|
226
|
+
"""Get exposure in microseconds."""
|
|
227
|
+
if not self.is_connected or self.camera is None:
|
|
228
|
+
return self._exposure
|
|
229
|
+
|
|
230
|
+
try:
|
|
231
|
+
return self.camera.ExposureTime()
|
|
232
|
+
except Exception:
|
|
233
|
+
return self._exposure
|
|
234
|
+
|
|
235
|
+
def set_gain(self, value: float) -> bool:
|
|
236
|
+
"""Set gain in dB."""
|
|
237
|
+
if not self.is_connected or self.camera is None:
|
|
238
|
+
return False
|
|
239
|
+
try:
|
|
240
|
+
self.camera.Gain.SetValue(value)
|
|
241
|
+
self._gain = value
|
|
242
|
+
return True
|
|
243
|
+
except Exception as e:
|
|
244
|
+
logger.error(f"Error setting gain: {e}")
|
|
245
|
+
return False
|
|
246
|
+
|
|
247
|
+
def get_gain(self) -> Optional[float]:
|
|
248
|
+
"""Get gain in dB."""
|
|
249
|
+
if not self.is_connected or self.camera is None:
|
|
250
|
+
return self._gain
|
|
251
|
+
|
|
252
|
+
try:
|
|
253
|
+
return self.camera.Gain.GetValue()
|
|
254
|
+
except Exception:
|
|
255
|
+
return self._gain
|
|
256
|
+
|
|
257
|
+
def enable_auto_exposure(self, enable: bool = True) -> bool:
|
|
258
|
+
"""Enable or disable auto exposure for Basler camera."""
|
|
259
|
+
if not self.is_connected or self.camera is None:
|
|
260
|
+
return False
|
|
261
|
+
try:
|
|
262
|
+
if enable:
|
|
263
|
+
self.camera.ExposureAuto.SetValue("Continuous")
|
|
264
|
+
else:
|
|
265
|
+
self.camera.ExposureAuto.SetValue("Off")
|
|
266
|
+
logger.info(f"Set Basler auto exposure to {enable}")
|
|
267
|
+
return True
|
|
268
|
+
except Exception as e:
|
|
269
|
+
logger.error(f"Error setting Basler auto exposure: {e}")
|
|
270
|
+
return False
|
|
271
|
+
|
|
272
|
+
def set_frame_size(self, width: int, height: int) -> bool:
|
|
273
|
+
"""Set frame size for Basler camera."""
|
|
274
|
+
if not self.is_connected or self.camera is None:
|
|
275
|
+
return False
|
|
276
|
+
try:
|
|
277
|
+
was_grabbing = self.camera.IsGrabbing()
|
|
278
|
+
if was_grabbing:
|
|
279
|
+
self.camera.StopGrabbing()
|
|
280
|
+
self.camera.Width.SetValue(width)
|
|
281
|
+
self.camera.Height.SetValue(height)
|
|
282
|
+
if was_grabbing:
|
|
283
|
+
self.camera.StartGrabbing(self.pylon.GrabStrategy_LatestImageOnly)
|
|
284
|
+
logger.info(f"Set Basler camera resolution to {width}x{height}")
|
|
285
|
+
return True
|
|
286
|
+
except Exception as e:
|
|
287
|
+
logger.error(f"Error setting Basler camera resolution: {e}")
|
|
288
|
+
return False
|
|
289
|
+
|
|
290
|
+
def get_frame_size(self) -> Optional[tuple[int, int]]:
|
|
291
|
+
"""Get frame size."""
|
|
292
|
+
if not self.is_connected or self.camera is None:
|
|
293
|
+
return None
|
|
294
|
+
|
|
295
|
+
try:
|
|
296
|
+
width = self.camera.Width.GetValue()
|
|
297
|
+
height = self.camera.Height.GetValue()
|
|
298
|
+
return (width, height)
|
|
299
|
+
except Exception:
|
|
300
|
+
return None
|
|
301
|
+
|
|
302
|
+
def set_fps(self, fps: float) -> bool:
|
|
303
|
+
"""Set FPS for Basler camera."""
|
|
304
|
+
if not self.is_connected or self.camera is None:
|
|
305
|
+
return False
|
|
306
|
+
try:
|
|
307
|
+
self.camera.AcquisitionFrameRateEnable.SetValue(True)
|
|
308
|
+
self.camera.AcquisitionFrameRate.SetValue(fps)
|
|
309
|
+
logger.info(f"Set Basler camera FPS to {fps}")
|
|
310
|
+
return True
|
|
311
|
+
except Exception as e:
|
|
312
|
+
logger.error(f"Error setting Basler camera FPS: {e}")
|
|
313
|
+
return False
|
|
314
|
+
|
|
315
|
+
def get_fps(self) -> Optional[float]:
|
|
316
|
+
"""Get FPS."""
|
|
317
|
+
if not self.is_connected or self.camera is None:
|
|
318
|
+
return None
|
|
319
|
+
|
|
320
|
+
try:
|
|
321
|
+
return self.camera.AcquisitionFrameRate.GetValue()
|
|
322
|
+
except Exception:
|
|
323
|
+
return None
|
|
324
|
+
|
|
325
|
+
@classmethod
|
|
326
|
+
def discover(cls) -> list:
|
|
327
|
+
"""
|
|
328
|
+
Discover available Basler cameras.
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
list: List of dictionaries containing Basler camera information.
|
|
332
|
+
Each dict contains: {'index': int, 'serial_number': str, 'name': str,
|
|
333
|
+
'device_class': str}
|
|
334
|
+
"""
|
|
335
|
+
devices = []
|
|
336
|
+
|
|
337
|
+
try:
|
|
338
|
+
from pypylon import pylon
|
|
339
|
+
except ImportError:
|
|
340
|
+
logger.warning("pypylon module not available. Cannot discover Basler cameras.")
|
|
341
|
+
return []
|
|
342
|
+
|
|
343
|
+
try:
|
|
344
|
+
# Get the transport layer factory
|
|
345
|
+
tlFactory = pylon.TlFactory.GetInstance()
|
|
346
|
+
|
|
347
|
+
# Get all attached devices
|
|
348
|
+
device_list = tlFactory.EnumerateDevices()
|
|
349
|
+
|
|
350
|
+
for index, device_info in enumerate(device_list):
|
|
351
|
+
try:
|
|
352
|
+
device_data = {
|
|
353
|
+
"index": index,
|
|
354
|
+
"id": index,
|
|
355
|
+
"serial_number": device_info.GetSerialNumber(),
|
|
356
|
+
"name": device_info.GetFriendlyName(),
|
|
357
|
+
"device_class": device_info.GetDeviceClass(),
|
|
358
|
+
}
|
|
359
|
+
devices.append(device_data)
|
|
360
|
+
logger.info(f"Found Basler camera: {device_data}")
|
|
361
|
+
|
|
362
|
+
except Exception as e:
|
|
363
|
+
logger.warning(f"Could not get info for Basler device {index}: {e}")
|
|
364
|
+
continue
|
|
365
|
+
|
|
366
|
+
except Exception as e:
|
|
367
|
+
logger.error(f"Error discovering Basler cameras: {e}")
|
|
368
|
+
|
|
369
|
+
return devices
|
|
370
|
+
|
|
371
|
+
@classmethod
|
|
372
|
+
def get_config_schema(cls) -> dict[str, Any]:
|
|
373
|
+
"""Get configuration schema for Basler camera capture"""
|
|
374
|
+
warnings.warn(
|
|
375
|
+
"get_config_schema() is deprecated and will be removed in a future release; "
|
|
376
|
+
"UI form schemas belong in the consuming application.",
|
|
377
|
+
DeprecationWarning,
|
|
378
|
+
stacklevel=2,
|
|
379
|
+
)
|
|
380
|
+
return {
|
|
381
|
+
"title": "Basler Camera Configuration",
|
|
382
|
+
"description": "Configure Basler industrial camera settings",
|
|
383
|
+
"fields": [
|
|
384
|
+
{
|
|
385
|
+
"name": "source",
|
|
386
|
+
"label": "Camera Source",
|
|
387
|
+
"type": "text",
|
|
388
|
+
"placeholder": "Serial number or device index (0, 1, 2...)",
|
|
389
|
+
"description": "Camera serial number or device index",
|
|
390
|
+
"required": False,
|
|
391
|
+
"default": "0",
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
"name": "exposure",
|
|
395
|
+
"label": "Exposure Time (µs)",
|
|
396
|
+
"type": "number",
|
|
397
|
+
"min": 1,
|
|
398
|
+
"max": 1000000,
|
|
399
|
+
"placeholder": "10000",
|
|
400
|
+
"description": "Exposure time in microseconds",
|
|
401
|
+
"required": False,
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
"name": "gain",
|
|
405
|
+
"label": "Gain (dB)",
|
|
406
|
+
"type": "number",
|
|
407
|
+
"min": 0,
|
|
408
|
+
"max": 40,
|
|
409
|
+
"step": 0.1,
|
|
410
|
+
"placeholder": "0.0",
|
|
411
|
+
"description": "Camera gain in decibels",
|
|
412
|
+
"required": False,
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
"name": "width",
|
|
416
|
+
"label": "Width",
|
|
417
|
+
"type": "number",
|
|
418
|
+
"min": 1,
|
|
419
|
+
"max": 10000,
|
|
420
|
+
"placeholder": "1920",
|
|
421
|
+
"description": "Frame width in pixels",
|
|
422
|
+
"required": False,
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
"name": "height",
|
|
426
|
+
"label": "Height",
|
|
427
|
+
"type": "number",
|
|
428
|
+
"min": 1,
|
|
429
|
+
"max": 10000,
|
|
430
|
+
"placeholder": "1080",
|
|
431
|
+
"description": "Frame height in pixels",
|
|
432
|
+
"required": False,
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
"name": "fps",
|
|
436
|
+
"label": "Frame Rate (FPS)",
|
|
437
|
+
"type": "number",
|
|
438
|
+
"min": 1,
|
|
439
|
+
"max": 1000,
|
|
440
|
+
"placeholder": "30",
|
|
441
|
+
"description": "Frames per second",
|
|
442
|
+
"required": False,
|
|
443
|
+
"default": 30,
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
"name": "is_mono",
|
|
447
|
+
"label": "Monochrome",
|
|
448
|
+
"type": "checkbox",
|
|
449
|
+
"description": "Camera outputs monochrome (grayscale) images",
|
|
450
|
+
"required": False,
|
|
451
|
+
"default": False,
|
|
452
|
+
},
|
|
453
|
+
],
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
if __name__ == "__main__":
|
|
458
|
+
# Example usage
|
|
459
|
+
import cv2
|
|
460
|
+
|
|
461
|
+
camera = BaslerCapture() # Replace with actual serial number or index
|
|
462
|
+
if camera.connect():
|
|
463
|
+
print("Webcam connected successfully.")
|
|
464
|
+
print(f"Exposure: {camera.get_exposure()}")
|
|
465
|
+
print(f"Gain: {camera.get_gain()}")
|
|
466
|
+
print(f"Frame size: {camera.get_frame_size()}")
|
|
467
|
+
|
|
468
|
+
# Read a few frames
|
|
469
|
+
while camera.is_connected:
|
|
470
|
+
ret, frame = camera.read()
|
|
471
|
+
if ret and frame is not None:
|
|
472
|
+
cv2.imshow("Webcam", frame)
|
|
473
|
+
if cv2.waitKey(1) & 0xFF == ord("q"):
|
|
474
|
+
break
|
|
475
|
+
camera.disconnect()
|
|
476
|
+
else:
|
|
477
|
+
print("Failed to connect to webcam.")
|