@camera.ui/camera-ui-opencv 0.0.19 → 0.0.21

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "displayName": "OpenCV",
3
3
  "name": "@camera.ui/camera-ui-opencv",
4
- "version": "0.0.19",
4
+ "version": "0.0.21",
5
5
  "description": "camera.ui opencv plugin",
6
6
  "author": "seydx (https://github.com/seydx/camera.ui)",
7
7
  "main": "./src/main.py",
@@ -21,7 +21,7 @@
21
21
  "url": "https://github.com/seydx/camera.ui/issues"
22
22
  },
23
23
  "engines": {
24
- "camera.ui": ">=0.0.31-alpha.1",
24
+ "camera.ui": ">=0.0.34-alpha.2",
25
25
  "node": ">=20.17.0"
26
26
  },
27
27
  "homepage": "https://github.com/seydx/camera.ui#readme",
@@ -39,6 +39,9 @@
39
39
  "camera.ui": {
40
40
  "pythonVersion": "3.11",
41
41
  "extension": "motionDetection",
42
- "dependencies": []
42
+ "dependencies": [],
43
+ "options": {
44
+ "extendedMotionDetection": true
45
+ }
43
46
  }
44
47
  }
package/requirements.txt CHANGED
@@ -1,3 +1,3 @@
1
1
  numpy==1.26.4
2
2
  opencv-python-headless==4.10.0.84
3
- camera-ui-python-types==0.1.91
3
+ camera-ui-python-types==0.1.96
@@ -8,9 +8,9 @@ from camera_ui_python_types import (
8
8
  CameraDevice,
9
9
  CameraStorage,
10
10
  Detection,
11
- LoggerService,
12
11
  PluginAPI,
13
12
  )
13
+
14
14
  from detector_defaults import (
15
15
  available_models,
16
16
  default_area,
@@ -26,175 +26,214 @@ from detector_defaults import (
26
26
  from opencv_utils import get_detections, get_detections_bs, get_detections_fd
27
27
  from plugin_typings import CameraStorageValues
28
28
 
29
- background_tasks = set[asyncio.Task[Any]]()
30
-
31
29
 
32
30
  class CameraDetector:
33
- def __init__(self, api: PluginAPI, camera: CameraDevice, logger: LoggerService) -> None:
31
+ def __init__(self, api: PluginAPI, camera_device: CameraDevice) -> None:
34
32
  super().__init__()
35
33
 
36
34
  self.api = api
37
- self.camera = camera
38
- self.logger = logger
35
+ self.camera_device = camera_device
36
+ self.camera_logger = camera_device.logger
39
37
 
40
38
  self.started = False
41
39
  self.closed = False
42
- self.frame_generation_task: Optional[asyncio.Task[Any]] = None
40
+ self.restarting = False
41
+
42
+ self.initialized = False
43
+
43
44
  self.executor = None
44
45
  self.previous_frame: Optional[np.ndarray[Any, Any]] = None
45
46
 
46
47
  self.camera_storage: CameraStorage[CameraStorageValues] = self.__create_camera_storage()
47
- self.camera.on_connected.subscribe(lambda connected: self.start() if connected else self.close())
48
48
 
49
- def start(self) -> None:
49
+ async def initialize(self) -> None:
50
+ if self.initialized:
51
+ return
52
+
53
+ self.initialized = True
54
+
55
+ async def on_connected(connected: bool) -> None:
56
+ if connected:
57
+ await self.start()
58
+ else:
59
+ self.close()
60
+
61
+ await self.camera_device.on_connected.asubscribe(on_connected)
62
+
63
+ async def start(self) -> None:
50
64
  if not self.started:
51
65
  self.started = True
52
66
  self.closed = False
53
67
 
54
68
  self.executor = ThreadPoolExecutor(max_workers=2, thread_name_prefix="OpenCV")
55
69
 
56
- self.logger.log(
57
- f"Starting motion detection for camera {self.camera.name} ({self.__detector_model()})..."
58
- )
59
-
60
- self.frame_generation_task = asyncio.create_task(
61
- self.__detect(), name=f"MotionDetection-{self.camera.name}"
62
- )
70
+ self.camera_logger.log(f"Starting motion detection ({self.__detector_model()})...")
63
71
 
64
- background_tasks.add(self.frame_generation_task)
65
- self.frame_generation_task.add_done_callback(
66
- lambda _: background_tasks.remove(self.frame_generation_task)
67
- if self.frame_generation_task in background_tasks
68
- else None
69
- )
72
+ await self.__start_detection()
70
73
 
71
74
  def close(self) -> None:
72
- if self.started and not self.closed:
75
+ if not self.closed:
73
76
  self.started = False
74
77
  self.closed = True
75
78
 
76
- self.logger.log(f"Stopping motion detection for camera {self.camera.name}...")
77
-
78
- if self.frame_generation_task is not None:
79
- self.frame_generation_task.cancel()
80
- self.frame_generation_task = None
79
+ self.camera_logger.log("Stopping motion detection")
81
80
 
82
81
  if self.executor is not None:
83
82
  self.executor.shutdown(wait=False)
84
83
  self.executor = None
85
84
 
86
- def restart(self) -> None:
87
- self.logger.log(f"Restarting motion detection for camera {self.camera.name}...")
85
+ async def __start_detection(self) -> None:
86
+ try:
87
+ await self.__detect()
88
+ except asyncio.CancelledError:
89
+ pass
90
+ except Exception as error:
91
+ self.camera_logger.error("Error generating frames", error)
92
+ await self.__restart()
93
+
94
+ async def __restart(self) -> None:
95
+ if self.restarting or self.closed:
96
+ return
88
97
 
89
- self.close()
98
+ self.restarting = True
90
99
 
91
- if self.camera.connected:
92
- self.start()
100
+ self.camera_logger.log("Restarting motion detection in 2s...")
101
+ await asyncio.sleep(2)
102
+
103
+ self.restarting = False
104
+
105
+ if self.camera_device.connected and not self.closed:
106
+ await self.__start_detection()
93
107
 
94
108
  async def __detect(self) -> None:
95
- while not self.closed:
96
- try:
97
- backSub: Optional[cv2.BackgroundSubtractorMOG2] = None
98
-
99
- async for frame in self.camera.get_frames():
100
- if self.closed:
101
- break
102
-
103
- frame_image = await frame.to_image({"format": {"to": "gray"}})
104
- image_array = np.array(frame_image["image"])
105
-
106
- detector_model = self.__detector_model()
107
-
108
- if detector_model == "Frame Difference":
109
- area = self.camera_storage.values["frame_difference"]["area"]
110
-
111
- if self.previous_frame is None:
112
- self.previous_frame = image_array
113
- continue
114
-
115
- dets = await asyncio.get_event_loop().run_in_executor(
116
- self.executor,
117
- get_detections_fd,
118
- self.previous_frame,
119
- image_array,
120
- area,
121
- )
122
-
123
- self.previous_frame = image_array
124
-
125
- elif detector_model == "Background Substraction":
126
- if backSub is None:
127
- backSub = cv2.createBackgroundSubtractorMOG2(varThreshold=18, detectShadows=False)
128
-
129
- threshold = self.camera_storage.values["background_substraction"]["threshold"]
130
- area = self.camera_storage.values["background_substraction"]["area"]
131
- learning_rate = self.camera_storage.values["background_substraction"]["learning_rate"]
132
-
133
- dets = await asyncio.get_event_loop().run_in_executor(
134
- self.executor,
135
- get_detections_bs,
136
- image_array,
137
- backSub,
138
- threshold,
139
- area,
140
- learning_rate,
141
- )
142
-
143
- else: # default
144
- blur = self.camera_storage.values["default"]["blur"]
145
- threshold = self.camera_storage.values["default"]["threshold"]
146
- area = self.camera_storage.values["default"]["area"]
147
- image_array = cv2.stackBlur(image_array, (blur, blur))
148
-
149
- if self.previous_frame is None:
150
- self.previous_frame = image_array
151
- continue
152
-
153
- dets = await asyncio.get_event_loop().run_in_executor(
154
- self.executor,
155
- get_detections,
156
- self.previous_frame,
157
- image_array,
158
- threshold,
159
- area,
160
- )
161
-
162
- self.previous_frame = image_array
163
-
164
- detections: list[Detection] = []
165
-
166
- for det in dets:
167
- (x1, y1, x2, y2) = det
168
-
169
- detection: Detection = {
170
- "label": "motion",
171
- "confidence": 1,
172
- "boundingBox": (x1, y1, x2, y2),
173
- "inputWidth": frame_image["info"]["width"],
174
- "inputHeight": frame_image["info"]["height"],
175
- "origWidth": frame.metadata["origWidth"],
176
- "origHeight": frame.metadata["origHeight"],
177
- }
178
-
179
- detections.append(detection)
180
-
181
- await self.camera.update_state("motion", {"detections": detections}, frame)
182
- except asyncio.CancelledError:
109
+ backSub: Optional[cv2.BackgroundSubtractorMOG2] = None
110
+
111
+ async for frame in self.camera_device.get_frames():
112
+ if self.closed:
183
113
  break
184
- except Exception as error:
185
- self.logger.error(self.camera.name, "Error generating frames", error)
186
- self.logger.log(self.camera.name, "Restarting motion detection in 5 seconds...")
187
- await asyncio.sleep(5)
188
- continue
114
+
115
+ frame_image = await frame.to_image(
116
+ {"format": {"to": "gray"}, "resize": {"width": 640, "height": 0}}
117
+ )
118
+
119
+ image_array = np.array(frame_image["image"])
120
+
121
+ detector_model = self.__detector_model()
122
+
123
+ if detector_model == "Frame Difference":
124
+ area = self.camera_storage.values["frame_difference"]["area"]
125
+
126
+ if self.previous_frame is None:
127
+ self.previous_frame = image_array
128
+ continue
129
+
130
+ dets = await asyncio.get_event_loop().run_in_executor(
131
+ self.executor,
132
+ get_detections_fd,
133
+ self.previous_frame,
134
+ image_array,
135
+ area,
136
+ )
137
+
138
+ self.previous_frame = image_array
139
+
140
+ elif detector_model == "Background Substraction":
141
+ if backSub is None:
142
+ backSub = cv2.createBackgroundSubtractorMOG2(varThreshold=18, detectShadows=False)
143
+
144
+ threshold = self.camera_storage.values["background_substraction"]["threshold"]
145
+ area = self.camera_storage.values["background_substraction"]["area"]
146
+ learning_rate = self.camera_storage.values["background_substraction"]["learning_rate"]
147
+
148
+ dets = await asyncio.get_event_loop().run_in_executor(
149
+ self.executor,
150
+ get_detections_bs,
151
+ image_array,
152
+ backSub,
153
+ threshold,
154
+ area,
155
+ learning_rate,
156
+ )
157
+
158
+ else: # default
159
+ blur = self.camera_storage.values["default"]["blur"]
160
+ threshold = self.camera_storage.values["default"]["threshold"]
161
+ area = self.camera_storage.values["default"]["area"]
162
+ image_array = cv2.stackBlur(image_array, (blur, blur))
163
+
164
+ if self.previous_frame is None:
165
+ self.previous_frame = image_array
166
+ continue
167
+
168
+ dets = await asyncio.get_event_loop().run_in_executor(
169
+ self.executor,
170
+ get_detections,
171
+ self.previous_frame,
172
+ image_array,
173
+ threshold,
174
+ area,
175
+ )
176
+
177
+ self.previous_frame = image_array
178
+
179
+ detections: list[Detection] = []
180
+
181
+ for det in dets:
182
+ (x1, y1, x2, y2) = det
183
+
184
+ detection: Detection = {
185
+ "label": "motion",
186
+ "confidence": 1,
187
+ "boundingBox": (x1, y1, x2, y2),
188
+ "inputWidth": frame_image["info"]["width"],
189
+ "inputHeight": frame_image["info"]["height"],
190
+ "origWidth": frame.metadata["origWidth"],
191
+ "origHeight": frame.metadata["origHeight"],
192
+ }
193
+
194
+ detections.append(detection)
195
+
196
+ await self.camera_device.update_state("motion", {"detections": detections}, frame)
189
197
 
190
198
  def __detector_model(self) -> Literal["Frame Difference", "Background Substraction", "Default"]:
191
199
  return self.camera_storage.values["motion_detector"]
192
200
 
201
+ async def __default_settings(self) -> None:
202
+ if self.camera_storage.values["motion_detector"] != default_model:
203
+ await self.camera_storage.setValue("motion_detector", default_model)
204
+
205
+ async def __default_d_settings(self) -> None:
206
+ if self.camera_storage.values["default"]["area"] != default_area:
207
+ await self.camera_storage.setValue("default.area", default_area)
208
+
209
+ if self.camera_storage.values["default"]["threshold"] != default_threshold:
210
+ await self.camera_storage.setValue("default.threshold", default_threshold)
211
+
212
+ if self.camera_storage.values["default"]["blur"] != default_blur:
213
+ await self.camera_storage.setValue("default.blur", default_blur)
214
+
215
+ if self.camera_storage.values["default"]["dilation"] != default_dilt:
216
+ await self.camera_storage.setValue("default.dilation", default_dilt)
217
+
218
+ async def __default_bs_settings(self) -> None:
219
+ if self.camera_storage.values["background_substraction"]["area"] != default_area_bs:
220
+ await self.camera_storage.setValue("background_substraction.area", default_area_bs)
221
+
222
+ if self.camera_storage.values["background_substraction"]["threshold"] != default_threshold_bs:
223
+ await self.camera_storage.setValue("background_substraction.threshold", default_threshold_bs)
224
+
225
+ if self.camera_storage.values["background_substraction"]["learning_rate"] != default_learning_rate:
226
+ await self.camera_storage.setValue("background_substraction.learning_rate", default_learning_rate)
227
+
228
+ async def __default_fd_settings(self) -> None:
229
+ if self.camera_storage.values["frame_difference"]["area"] != default_area_fd:
230
+ await self.camera_storage.setValue("frame_difference.area", default_area_fd)
231
+
193
232
  def __create_camera_storage(self) -> CameraStorage[CameraStorageValues]:
194
233
  camera_storage: CameraStorage[CameraStorageValues] = (
195
234
  self.api.storage_controller.create_camera_storage(
196
235
  self,
197
- self.camera.id,
236
+ self.camera_device.id,
198
237
  {
199
238
  "motion_detector": {
200
239
  "type": "string",
@@ -206,7 +245,9 @@ class CameraDetector:
206
245
  "store": True,
207
246
  "defaultValue": default_model,
208
247
  "required": True,
209
- "onSet": self.__change_model,
248
+ "onSet": lambda new, old: self.camera_logger.log(
249
+ f"Motion detection model changed from {old} to {new}"
250
+ ),
210
251
  },
211
252
  "default": {
212
253
  "type": "object",
@@ -215,7 +256,6 @@ class CameraDetector:
215
256
  "description": "Settings for the default model",
216
257
  "group": "Default",
217
258
  "opened": True,
218
- "hidden": True,
219
259
  "properties": {
220
260
  "area": {
221
261
  "type": "number",
@@ -228,8 +268,8 @@ class CameraDetector:
228
268
  "maximum": 1000,
229
269
  "step": 1,
230
270
  "required": True,
231
- "onSet": lambda old, new: self.logger.log(
232
- self.camera.name, f"Default model motion area changed from {old} to {new}"
271
+ "onSet": lambda new, old: self.camera_logger.log(
272
+ f"Default model motion area changed from {old} to {new}",
233
273
  ),
234
274
  },
235
275
  "threshold": {
@@ -243,8 +283,8 @@ class CameraDetector:
243
283
  "maximum": 255,
244
284
  "step": 1,
245
285
  "required": True,
246
- "onSet": lambda old, new: self.logger.log(
247
- self.camera.name, f"Default model threshold changed from {old} to {new}"
286
+ "onSet": lambda new, old: self.camera_logger.log(
287
+ f"Default model threshold changed from {old} to {new}",
248
288
  ),
249
289
  },
250
290
  "blur": {
@@ -258,8 +298,8 @@ class CameraDetector:
258
298
  "maximum": 21,
259
299
  "step": 1,
260
300
  "required": True,
261
- "onSet": lambda old, new: self.logger.log(
262
- self.camera.name, f"Default model blur radius changed from {old} to {new}"
301
+ "onSet": lambda new, old: self.camera_logger.log(
302
+ f"Default model blur radius changed from {old} to {new}",
263
303
  ),
264
304
  },
265
305
  "dilation": {
@@ -273,11 +313,17 @@ class CameraDetector:
273
313
  "maximum": 21,
274
314
  "step": 1,
275
315
  "required": True,
276
- "onSet": lambda old, new: self.logger.log(
277
- self.camera.name,
316
+ "onSet": lambda new, old: self.camera_logger.log(
278
317
  f"Default model dilation size changed from {old} to {new}",
279
318
  ),
280
319
  },
320
+ "reset": {
321
+ "type": "button",
322
+ "title": "Default Settings",
323
+ "key": "default_d",
324
+ "description": "Reset motion detection settings to default",
325
+ "onSet": lambda new, old: self.__default_d_settings(),
326
+ },
281
327
  },
282
328
  },
283
329
  "background_substraction": {
@@ -287,7 +333,6 @@ class CameraDetector:
287
333
  "description": "Settings for the background substraction model",
288
334
  "group": "Background Substraction",
289
335
  "opened": True,
290
- "hidden": True,
291
336
  "properties": {
292
337
  "area": {
293
338
  "type": "number",
@@ -300,8 +345,7 @@ class CameraDetector:
300
345
  "maximum": 1000,
301
346
  "step": 1,
302
347
  "required": True,
303
- "onSet": lambda old, new: self.logger.log(
304
- self.camera.name,
348
+ "onSet": lambda new, old: self.camera_logger.log(
305
349
  f"Background substraction model area changed from {old} to {new}",
306
350
  ),
307
351
  },
@@ -316,8 +360,7 @@ class CameraDetector:
316
360
  "maximum": 255,
317
361
  "step": 1,
318
362
  "required": True,
319
- "onSet": lambda old, new: self.logger.log(
320
- self.camera.name,
363
+ "onSet": lambda new, old: self.camera_logger.log(
321
364
  f"Background substraction model threshold changed from {old} to {new}",
322
365
  ),
323
366
  },
@@ -332,11 +375,17 @@ class CameraDetector:
332
375
  "maximum": 1,
333
376
  "step": 0.01,
334
377
  "required": True,
335
- "onSet": lambda old, new: self.logger.log(
336
- self.camera.name,
378
+ "onSet": lambda new, old: self.camera_logger.log(
337
379
  f"Background substraction model threshold changed from {old} to {new}",
338
380
  ),
339
381
  },
382
+ "reset": {
383
+ "type": "button",
384
+ "title": "Default Settings",
385
+ "key": "default_bs",
386
+ "description": "Reset motion detection settings to default",
387
+ "onSet": lambda new, old: self.__default_bs_settings(),
388
+ },
340
389
  },
341
390
  },
342
391
  "frame_difference": {
@@ -346,7 +395,6 @@ class CameraDetector:
346
395
  "description": "Settings for the frame difference model",
347
396
  "group": "Frame Difference",
348
397
  "opened": True,
349
- "hidden": True,
350
398
  "properties": {
351
399
  "area": {
352
400
  "type": "number",
@@ -359,55 +407,28 @@ class CameraDetector:
359
407
  "maximum": 1000,
360
408
  "step": 1,
361
409
  "required": True,
362
- "onSet": lambda old, new: self.logger.log(
363
- self.camera.name,
410
+ "onSet": lambda new, old: self.camera_logger.log(
364
411
  f"Frame difference model area changed from {old} to {new}",
365
412
  ),
366
- }
413
+ },
414
+ "reset": {
415
+ "type": "button",
416
+ "title": "Default Settings",
417
+ "key": "default_fd",
418
+ "description": "Reset motion detection settings to default",
419
+ "onSet": lambda new, old: self.__default_fd_settings(),
420
+ },
367
421
  },
368
422
  },
423
+ "reset": {
424
+ "type": "button",
425
+ "title": "Default Settings",
426
+ "key": "default_settings",
427
+ "description": "Reset motion detection settings to default",
428
+ "onSet": lambda new, old: self.__default_settings(),
429
+ },
369
430
  },
370
431
  )
371
432
  )
372
433
 
373
- current_model = camera_storage.values["motion_detector"]
374
- current_model_schema = None
375
-
376
- if current_model == "Default":
377
- current_model_schema = camera_storage.getSchema("default")
378
- elif current_model == "Background Substraction":
379
- current_model_schema = camera_storage.getSchema("background_substraction")
380
- elif current_model == "Frame Difference":
381
- current_model_schema = camera_storage.getSchema("frame_difference")
382
-
383
- if current_model_schema:
384
- current_model_schema["hidden"] = False
385
-
386
434
  return camera_storage
387
-
388
- def __change_model(self, old_model: str, new_model: str) -> None:
389
- self.logger.log(self.camera.name, f"Motion detection model changed from {old_model} to {new_model}")
390
-
391
- old_schema = None
392
- new_schema = None
393
- self.previous_frame = None
394
-
395
- if old_model == "Default":
396
- old_schema = self.camera_storage.getSchema("default")
397
- elif old_model == "Background Substraction":
398
- old_schema = self.camera_storage.getSchema("background_substraction")
399
- elif old_model == "Frame Difference":
400
- old_schema = self.camera_storage.getSchema("frame_difference")
401
-
402
- if new_model == "Default":
403
- new_schema = self.camera_storage.getSchema("default")
404
- elif new_model == "Background Substraction":
405
- new_schema = self.camera_storage.getSchema("background_substraction")
406
- elif new_model == "Frame Difference":
407
- new_schema = self.camera_storage.getSchema("frame_difference")
408
-
409
- if old_schema:
410
- old_schema["hidden"] = True
411
-
412
- if new_schema:
413
- new_schema["hidden"] = False
@@ -8,7 +8,7 @@ default_threshold_bs = 50
8
8
  default_blur = 11
9
9
  default_dilt = 15
10
10
 
11
- default_learning_rate = -1
11
+ default_learning_rate = 0.9
12
12
 
13
13
  available_models = ["Frame Difference", "Background Substraction", "Default"]
14
14
 
package/src/main.py CHANGED
@@ -5,7 +5,6 @@ from typing import Any, Optional, Union
5
5
 
6
6
  import cv2
7
7
  import numpy as np
8
- from camera_detector import CameraDetector
9
8
  from camera_ui_python_types import (
10
9
  CameraDevice,
11
10
  CameraExtension,
@@ -16,6 +15,8 @@ from camera_ui_python_types import (
16
15
  PluginAPI,
17
16
  RootSchema,
18
17
  )
18
+
19
+ from camera_detector import CameraDetector
19
20
  from detector_defaults import (
20
21
  default_area,
21
22
  default_area_bs,
@@ -46,7 +47,7 @@ class OpenCV(MotionDetectionPlugin):
46
47
  async def camera_selected(self, camera: CameraDevice, extension: CameraExtension) -> None:
47
48
  self.logger.log(f"New camera added ({extension}): {camera.name}")
48
49
  self.cameras[camera.id] = camera
49
- self.create_detector(camera)
50
+ await self.create_detector(camera)
50
51
 
51
52
  def camera_deselected(self, id: str, extension: CameraExtension) -> None:
52
53
  camera = self.cameras.get(id)
@@ -56,11 +57,10 @@ class OpenCV(MotionDetectionPlugin):
56
57
  self.remove_detector(self.cameras[id])
57
58
  del self.cameras[id]
58
59
 
59
- def start(self) -> None:
60
+ async def start(self) -> None:
60
61
  self.logger.log("Plugin Started")
61
62
 
62
- for camera_device in self.cameras.values():
63
- self.create_detector(camera_device)
63
+ await asyncio.gather(*[self.create_detector(camera) for camera in self.cameras.values()])
64
64
 
65
65
  def stop(self) -> None:
66
66
  self.logger.log("Plugin Stopped")
@@ -68,7 +68,7 @@ class OpenCV(MotionDetectionPlugin):
68
68
  detectors_copy = list(self.detectors.values())
69
69
 
70
70
  for detector in detectors_copy:
71
- self.remove_detector(detector.camera)
71
+ self.remove_detector(detector.camera_device)
72
72
 
73
73
  async def onFormSubmit(self, action_id: str, payload: Any) -> Union[FormSubmitResponse, None]:
74
74
  print("Form submitted", action_id, payload)
@@ -77,9 +77,10 @@ class OpenCV(MotionDetectionPlugin):
77
77
  for camera in cameras:
78
78
  self.cameras[camera.id] = camera
79
79
 
80
- def create_detector(self, camera: CameraDevice) -> None:
81
- detector = CameraDetector(self.api, camera, self.logger)
80
+ async def create_detector(self, camera: CameraDevice) -> None:
81
+ detector = CameraDetector(self.api, camera)
82
82
  self.detectors[camera.id] = detector
83
+ await detector.initialize()
83
84
 
84
85
  def remove_detector(self, camera: CameraDevice) -> None:
85
86
  detector = self.detectors[camera.id]