c4dynamics 2.0.3__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.
Files changed (50) hide show
  1. c4dynamics/__init__.py +240 -0
  2. c4dynamics/datasets/__init__.py +95 -0
  3. c4dynamics/datasets/_manager.py +596 -0
  4. c4dynamics/datasets/_registry.py +80 -0
  5. c4dynamics/detectors/__init__.py +37 -0
  6. c4dynamics/detectors/yolo3_opencv.py +686 -0
  7. c4dynamics/detectors/yolo3_tf.py +124 -0
  8. c4dynamics/eqm/__init__.py +324 -0
  9. c4dynamics/eqm/derivs.py +212 -0
  10. c4dynamics/eqm/integrate.py +359 -0
  11. c4dynamics/filters/__init__.py +1373 -0
  12. c4dynamics/filters/a.py +48 -0
  13. c4dynamics/filters/ekf.py +320 -0
  14. c4dynamics/filters/kalman.py +725 -0
  15. c4dynamics/filters/kalman_v0.py +1071 -0
  16. c4dynamics/filters/kalman_v1.py +821 -0
  17. c4dynamics/filters/lowpass.py +123 -0
  18. c4dynamics/filters/luenberger.py +97 -0
  19. c4dynamics/rotmat/__init__.py +141 -0
  20. c4dynamics/rotmat/animate.py +465 -0
  21. c4dynamics/rotmat/rotmat.py +351 -0
  22. c4dynamics/sensors/__init__.py +72 -0
  23. c4dynamics/sensors/lineofsight.py +78 -0
  24. c4dynamics/sensors/radar.py +740 -0
  25. c4dynamics/sensors/seeker.py +1030 -0
  26. c4dynamics/states/__init__.py +327 -0
  27. c4dynamics/states/lib/__init__.py +320 -0
  28. c4dynamics/states/lib/datapoint.py +660 -0
  29. c4dynamics/states/lib/pixelpoint.py +776 -0
  30. c4dynamics/states/lib/rigidbody.py +677 -0
  31. c4dynamics/states/state.py +1486 -0
  32. c4dynamics/utils/__init__.py +44 -0
  33. c4dynamics/utils/_struct.py +6 -0
  34. c4dynamics/utils/const.py +130 -0
  35. c4dynamics/utils/cprint.py +80 -0
  36. c4dynamics/utils/gen_gif.py +142 -0
  37. c4dynamics/utils/idx2keys.py +4 -0
  38. c4dynamics/utils/images_loader.py +63 -0
  39. c4dynamics/utils/math.py +136 -0
  40. c4dynamics/utils/plottools.py +140 -0
  41. c4dynamics/utils/plottracks.py +304 -0
  42. c4dynamics/utils/printpts.py +36 -0
  43. c4dynamics/utils/slides_gen.py +64 -0
  44. c4dynamics/utils/tictoc.py +167 -0
  45. c4dynamics/utils/video_gen.py +300 -0
  46. c4dynamics/utils/vidgen.py +182 -0
  47. c4dynamics-2.0.3.dist-info/METADATA +242 -0
  48. c4dynamics-2.0.3.dist-info/RECORD +50 -0
  49. c4dynamics-2.0.3.dist-info/WHEEL +5 -0
  50. c4dynamics-2.0.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,686 @@
1
+ import os, sys
2
+ import cv2
3
+
4
+ import numpy as np
5
+ sys.path.append('.')
6
+ from c4dynamics import c4d
7
+ from c4dynamics import pixelpoint
8
+ from typing import Optional
9
+
10
+ MODEL_SIZE = (416, 416, 3)
11
+
12
+
13
+ class yolov3:
14
+ '''
15
+ YOLO: Real-Time Object Detection
16
+
17
+
18
+ :class:`yolov3` is a YOLOv3 (You Only Look Once) object detection model.
19
+ Though it is no longer the most accurate object detection algorithm,
20
+ YOLOv3 is still a very good choice when you need real-time detection
21
+ while maintaining excellent accuracy.
22
+
23
+
24
+
25
+ YOLOv3 processes an entire image in a single forward pass,
26
+ making it efficient for dynamic scenes.
27
+ Its key strength lies the ability to simultaneously
28
+ predict bounding box coordinates and class probabilities
29
+ for multiple objects within an image.
30
+
31
+
32
+ Parameters
33
+ ==========
34
+ weights_path : str, optional
35
+ Path to the YOLOv3 weights file. Defaults None.
36
+
37
+
38
+ See Also
39
+ ========
40
+ .filters
41
+ .pixelpoint
42
+
43
+
44
+
45
+ **Classes**
46
+
47
+
48
+ Using YOLOv3 means
49
+ object detection capability with the 80 pre-trained
50
+ classes that come with the COCO dataset.
51
+
52
+
53
+ The following 80 classes are available using COCO's pre-trained weights:
54
+
55
+ .. admonition:: COCO dataset
56
+
57
+ person, bicycle, car, motorcycle, airplane, bus, train, truck, boat,
58
+ traffic light, fire hydrant, stop sign, parking meter, bench, bird, cat,
59
+ dog, horse, sheep, cow, elephant, bear, zebra, giraffe, backpack,
60
+ umbrella, handbag, tie, suitcase, frisbee, skis,snowboard, sports ball,
61
+ kite, baseball bat, baseball glove, skateboard, surfboard, tennis racket,
62
+ bottle, wine glass, cup, fork, knife, spoon, bowl, banana, apple,
63
+ sandwich, orange, broccoli, carrot, hot dog, pizza, donut, cake, chair,
64
+ couch, potted plant, bed, dining table, toilet, tv, laptop, mouse, remote,
65
+ keyboard, cell phone, microwave, oven, toaster, sink, refrigerator, book,
66
+ clock, vase, scissors, teddy bear, hair drier, toothbrush
67
+
68
+
69
+
70
+ .. figure:: /_architecture/yolo-object-detection.jpg
71
+
72
+ *Figure*
73
+ Object Detection with YOLO using COCO pre-trained classes 'dog', 'bicycle', 'truck'.
74
+ Read more at: `darknet-yolo <https://pjreddie.com/darknet/yolo/>`_ .
75
+
76
+
77
+ **Implementation (c4dynamics)**
78
+
79
+ The :class:`yolov3` class abstracts the complexities of model initialization,
80
+ input preprocessing, and output parsing.
81
+ The :meth:`detect` method returns a
82
+ :class:`pixelpoint <c4dynamics.states.lib.pixelpoint.pixelpoint>`
83
+ for each detected object.
84
+ The `pixelpoint` is a :mod:`predefined state class <c4dynamics.states.lib>`
85
+ representing a data point in a video frame with an associated bounding box.
86
+ Its methods and properties enhance the YOLOv3 output structure,
87
+ providing a convenient data structure for handling tracking missions.
88
+
89
+
90
+
91
+
92
+ **Installation**
93
+
94
+ C4dynamics downloads
95
+ the YOLOv3' weights file
96
+ once at first call to :class:`yolov3` and saves the cache.
97
+ For further details see :mod:`datasets <c4dynamics.datasets>`.
98
+ Alternatively, the user can provide a path to his
99
+ own weights file using the parameter `weights_path`.
100
+
101
+
102
+ **Construction**
103
+
104
+ A YOLOv3 detector instance is created by making a direct call
105
+ to the `yolov3` constructor:
106
+
107
+ .. code::
108
+
109
+ >>> from c4dynamics.detectors import yolov3
110
+ >>> yolo3 = yolov3()
111
+ Fetched successfully
112
+
113
+
114
+ Initialization of the instance does not require any mandatory parameters.
115
+
116
+
117
+
118
+ Example
119
+ =======
120
+
121
+ The following snippet initializes the YOLOv3 model and
122
+ runs the `detect()` method on an image containing four airplanes.
123
+ The example uses the `datasets` module from `c4dynamics` to fetch an image.
124
+ For further details, see :mod:`c4dynamics.datasets`.
125
+
126
+
127
+ Import required packages:
128
+
129
+ .. code::
130
+
131
+ >>> import cv2
132
+ >>> import c4dynamics as c4d
133
+ >>> from matplotlib import pyplot as plt
134
+
135
+ Load YOLOv3 detector:
136
+
137
+ .. code::
138
+
139
+ >>> yolo3 = c4d.detectors.yolov3()
140
+ Fetched successfully
141
+
142
+ Fetch and read the image:
143
+
144
+ .. code::
145
+
146
+ >>> imagepath = c4d.datasets.image('planes')
147
+ Fetched successfully
148
+ >>> img = cv2.imread(imagepath)
149
+
150
+
151
+ Run YOLOv3 detector on an image:
152
+
153
+ .. code::
154
+
155
+ >>> pts = yolo3.detect(img)
156
+
157
+
158
+ Now `pts` consists of
159
+ :class:`pixelpoint <c4dynamics.states.lib.pixelpoint.pixelpoint>`
160
+ instances for each object detected in the frame.
161
+ Let's use the properties and methods of the `pixelpoint` class to
162
+ view the attributes of the detected objects:
163
+
164
+
165
+ .. code::
166
+
167
+ >>> def ptup(n): return '(' + str(n[0]) + ', ' + str(n[1]) + ')'
168
+ >>> print('{:^10} | {:^10} | {:^16} | {:^16} | {:^10} | {:^14}'.format('center x', 'center y', 'box top-left', 'box bottom-right', 'class', 'frame size')) # doctest: +IGNORE_OUTPUT
169
+ >>> for p in pts:
170
+ ... print('{:^10d} | {:^10d} | {:^16} | {:^16} | {:^10} | {:^14}'.format(p.x, p.y, ptup(p.box[0]), ptup(p.box[1]), p.class_id, ptup(p.fsize))) # doctest: +IGNORE_OUTPUT
171
+ ... cv2.rectangle(img, p.box[0], p.box[1], [0, 0, 0], 2) # +IGNORE_OUTPUT
172
+ ... point = (int((p.box[0][0] + p.box[1][0]) / 2 - 75), p.box[1][1] + 22) # doctest: +IGNORE_OUTPUT
173
+ ... cv2.putText(img, p.class_id, point, cv2.FONT_HERSHEY_SIMPLEX, 1, [0, 0, 0], 2) # doctest: +IGNORE_OUTPUT
174
+ center x | center y | box top-left | box bottom-right | class | frame size
175
+ 615 | 295 | (562, 259) | (668, 331) | aeroplane | (1280, 720)
176
+ 779 | 233 | (720, 199) | (838, 267) | aeroplane | (1280, 720)
177
+ 635 | 189 | (578, 153) | (692, 225) | aeroplane | (1280, 720)
178
+ 793 | 575 | (742, 540) | (844, 610) | aeroplane | (1280, 720)
179
+
180
+
181
+ .. code::
182
+
183
+ >>> plt.figure() # doctest: +IGNORE_OUTPUT
184
+ >>> plt.axis(False) # doctest: +IGNORE_OUTPUT
185
+ >>> plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) # doctest: +IGNORE_OUTPUT
186
+
187
+ .. figure:: /_examples/yolov3/intro.png
188
+
189
+
190
+ '''
191
+
192
+ class_names = (
193
+ 'person', 'bicycle', 'car', 'motorbike', 'aeroplane',
194
+ 'bus', 'train', 'truck', 'boat', 'traffic',
195
+ 'light', 'fire', 'hydrant', 'stop', 'sign',
196
+ 'parking', 'meter', 'bench', 'bird', 'cat',
197
+ 'dog', 'horse', 'sheep', 'cow', 'elephant',
198
+ 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella',
199
+ 'handbag', 'tie', 'suitcase', 'frisbee', 'skis',
200
+ 'snowboard', 'sports', 'ball', 'kite', 'baseball',
201
+ 'bat', 'baseball', 'glove', 'skateboard', 'surfboard',
202
+ 'tennis', 'racket', 'bottle', 'wine', 'glass',
203
+ 'cup', 'fork', 'knife', 'spoon', 'bowl',
204
+ 'banana', 'apple', 'sandwich', 'orange', 'broccoli',
205
+ 'carrot', 'hot', 'dog', 'pizza', 'donut',
206
+ 'cake', 'chair', 'sofa', 'pottedplant', 'bed',
207
+ 'diningtable', 'toilet', 'tvmonitor', 'laptop', 'mouse',
208
+ 'remote', 'keyboard', 'cell', 'phone', 'microwave')
209
+
210
+ _nms_th = 0.5
211
+ _confidence_th = 0.5
212
+
213
+ def __init__(self, weights_path: Optional[str] = None) -> None:
214
+
215
+ errormsg = ''
216
+ if weights_path is None:
217
+ weights_path = c4d.datasets.nn_model('YOLOv3')
218
+ errormsg = "Try to clear the cache by 'c4dynamics.datasets.clear_cache()'"
219
+
220
+
221
+
222
+ if not os.path.exists(weights_path):
223
+ raise FileNotFoundError(f"The file 'yolov3.weights' does not "
224
+ f"exist in: '{weights_path}'. {errormsg}")
225
+
226
+
227
+ cfg_path = os.path.join(os.path.dirname(__file__), 'yolov3.cfg')
228
+ # cfg_path = 'yolov3.cfg'
229
+ # coconames = os.path.join(yolodir, 'coco.names')
230
+
231
+ self.net = cv2.dnn.readNetFromDarknet(cfg_path, weights_path)
232
+ self.net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV)
233
+ ln = self.net.getLayerNames()
234
+ self.ln = [ln[i - 1] for i in self.net.getUnconnectedOutLayers()]
235
+
236
+ # with open(coconames, 'r') as f:
237
+ # self.class_names = f.read().strip().split('\n')
238
+
239
+ # self.__dict__.update(kwargs)
240
+
241
+
242
+
243
+ @property
244
+ def nms_th(self) -> float:
245
+ '''
246
+ Gets and sets the Non-Maximum Suppression (NMS) threshold.
247
+
248
+ Objects with confidence scores below this threshold are suppressed.
249
+
250
+
251
+
252
+ Parameters
253
+ ----------
254
+ nms_th : float
255
+ The new threshold value for NMS during object detection.
256
+ Defaults: `nms_th = 0.5`.
257
+
258
+ Returns
259
+ -------
260
+ nms_th : float
261
+ The threshold value used for NMS during object detection.
262
+ Objects with confidence scores below this threshold are suppressed.
263
+
264
+
265
+
266
+ Example
267
+ -------
268
+
269
+ Import required packages:
270
+
271
+ .. code::
272
+
273
+ >>> import c4dynamics as c4d
274
+ >>> from matplotlib import pyplot as plt
275
+ >>> import cv2
276
+
277
+
278
+ Fetch 'planes.png' using the c4dynamics' datasets module (see :mod:`c4dynamics.datasets`):
279
+
280
+ .. code::
281
+
282
+ >>> impath = c4d.datasets.image('planes')
283
+ Fetched successfully
284
+
285
+
286
+ Load YOLOv3 detector and set 3 NMS threshold values to compare:
287
+
288
+ .. code::
289
+
290
+ >>> yolo3 = c4d.detectors.yolov3()
291
+ Fetched successfully
292
+ >>> nms_thresholds = [0.1, 0.5, 0.9]
293
+
294
+
295
+ Run the detector on each threshold:
296
+
297
+ .. code::
298
+
299
+ >>> _, axs = plt.subplots(1, 3)
300
+ >>> for i, nms_threshold in enumerate(nms_thresholds):
301
+ ... yolo3.nms_th = nms_threshold
302
+ ... img = cv2.imread(impath)
303
+ ... pts = yolo3.detect(img)
304
+ ... for p in pts:
305
+ ... cv2.rectangle(img, p.box[0], p.box[1], [0, 255, 0], 2) # doctest: +IGNORE_OUTPUT
306
+ ... axs[i].imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) # doctest: +IGNORE_OUTPUT
307
+ ... axs[i].set_title(f"NMS Threshold: {nms_threshold}", fontsize = 6)
308
+ ... axs[i].axis('off') # doctest: +IGNORE_OUTPUT
309
+
310
+
311
+ .. figure:: /_examples/yolov3/nms_th.png
312
+
313
+
314
+ A high value (0.9) for the Non-Maximum Suppression (NMS) threshold here
315
+ leads to an increased number of bounding boxes around a single object.
316
+ When the NMS threshold is high, it means that a significant overlap is
317
+ required for two bounding boxes to be considered redundant,
318
+ and one of them will be suppressed.
319
+ To address this issue, it's essential to choose an appropriate
320
+ NMS threshold based on the characteristics of your dataset and the
321
+ level of overlap between objects.
322
+ A lower NMS threshold (e.g., 0.4 or 0.5)
323
+ is commonly used to suppress redundant boxes effectively
324
+ while retaining accurate detections.
325
+ Experimenting with different
326
+ threshold values and observing their impact on the results is crucial
327
+ for optimizing the performance of object detection models.
328
+
329
+
330
+ '''
331
+ return self._nms_th
332
+
333
+ @nms_th.setter
334
+ def nms_th(self, val: float) -> None:
335
+ self._nms_th = val
336
+
337
+ @property
338
+ def confidence_th(self) -> float:
339
+ '''
340
+ Gets and sets the confidence threshold used in the object detection.
341
+
342
+ Detected objects with confidence scores below this threshold are filtered out.
343
+
344
+
345
+
346
+ Parameters
347
+ ----------
348
+ confidence_th : float
349
+ The new confidence threshold for object detection.
350
+ Defaults: `confidence_th = 0.5`.
351
+
352
+ Returns
353
+ -------
354
+ confidence_th : float
355
+ The confidence threshold for object detection.
356
+ Detected objects with confidence scores below this threshold are filtered out.
357
+
358
+
359
+ Example
360
+ -------
361
+
362
+ Import required packages:
363
+
364
+ .. code::
365
+
366
+ >>> import c4dynamics as c4d
367
+ >>> from matplotlib import pyplot as plt
368
+ >>> import cv2
369
+
370
+
371
+ Fetch 'planes.png' using the c4dynamics' datasets module (see :mod:`c4dynamics.datasets`):
372
+
373
+ .. code::
374
+
375
+ >>> impath = c4d.datasets.image('planes')
376
+ Fetched successfully
377
+
378
+
379
+ Load YOLOv3 detector and set 3 confidence threshold values to compare:
380
+
381
+ .. code::
382
+
383
+ >>> yolo3 = c4d.detectors.yolov3()
384
+ Fetched successfully
385
+ >>> confidence_thresholds = [0.9, 0.95, 0.99]
386
+
387
+
388
+ Run the detector on each threshold:
389
+
390
+ .. code::
391
+
392
+ >>> _, axs = plt.subplots(1, 3)
393
+ >>> for i, confidence_threshold in enumerate(confidence_thresholds):
394
+ ... yolo3.confidence_th = confidence_threshold
395
+ ... img = cv2.imread(impath)
396
+ ... pts = yolo3.detect(img)
397
+ ... for p in pts:
398
+ ... cv2.rectangle(img, p.box[0], p.box[1], [0, 255, 0], 2) # doctest: +IGNORE_OUTPUT
399
+ ... axs[i].imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) # doctest: +IGNORE_OUTPUT
400
+ ... axs[i].set_title(f"Confidence Threshold: {confidence_threshold}", fontsize = 6)
401
+ ... axs[i].axis('off') # doctest: +IGNORE_OUTPUT
402
+
403
+
404
+ .. figure:: /_examples/yolov3/confidence_th.png
405
+
406
+
407
+ A single object being missed, particularly when setting the confidence threshold to 0.99,
408
+ suggests that the model is highly confident in its predictions.
409
+ This level of performance is typically achievable when the model
410
+ has been trained on a diverse and representative dataset,
411
+ encompassing a wide variety of object instances, backgrounds,
412
+ and conditions.
413
+
414
+
415
+ '''
416
+
417
+ return self._confidence_th
418
+
419
+ @confidence_th.setter
420
+ def confidence_th(self, val: float) -> None:
421
+ self._confidence_th = val
422
+
423
+
424
+
425
+ def detect(self, frame: np.ndarray) -> list[pixelpoint]:
426
+ '''
427
+ Detects objects in a frame using the YOLOv3 model.
428
+
429
+ At each call, the detector performs the following steps:
430
+
431
+ 1. Preprocesses the frame by creating a blob, normalizing pixel values, and swapping Red and Blue channels.
432
+
433
+ 2. Sets input to the YOLOv3 model and performs a forward pass to obtain detections.
434
+
435
+ 3. Extracts detected objects based on a confidence threshold, calculates bounding box coordinates, and filters results using Non-Maximum Suppression (NMS).
436
+
437
+
438
+ Parameters
439
+ ----------
440
+ frame : numpy.array
441
+ An input frame for object detection.
442
+
443
+ Returns
444
+ -------
445
+ out : list[pixelpoint]
446
+ A list of :class:`pixelpoint <c4dynamics.states.pixelpoint.pixelpoint>` objects representing detected objects,
447
+ each containing bounding box coordinates and class label.
448
+
449
+
450
+ Examples
451
+ --------
452
+
453
+ **Setup**
454
+
455
+ Import required packages:
456
+
457
+ .. code::
458
+
459
+ >>> import cv2 # opencv-python
460
+ >>> import c4dynamics as c4d
461
+ >>> from matplotlib import pyplot as plt
462
+
463
+
464
+
465
+ Fetch 'planes.png' and 'aerobatics.mp4' using the c4dynamics' datasets module (see :mod:`c4dynamics.datasets`):
466
+
467
+ .. code::
468
+
469
+ >>> impath = c4d.datasets.image('planes')
470
+ Fetched successfully
471
+ >>> vidpath = c4d.datasets.video('aerobatics')
472
+ Fetched successfully
473
+
474
+
475
+
476
+ Load YOLOv3 detector:
477
+
478
+ .. code::
479
+
480
+ >>> yolo3 = c4d.detectors.yolov3()
481
+ Fetched successfully
482
+
483
+
484
+
485
+
486
+ Let the auxiliary function:
487
+
488
+ .. code::
489
+
490
+ >>> def ptup(n): return '(' + str(n[0]) + ', ' + str(n[1]) + ')'
491
+
492
+
493
+
494
+ **Object detection in a single frame**
495
+
496
+
497
+ .. code::
498
+
499
+ >>> img = cv2.imread(impath)
500
+ >>> pts = yolo3.detect(img)
501
+ >>> for p in pts:
502
+ ... cv2.rectangle(img, p.box[0], p.box[1], [0, 255, 0], 2) # doctest: +IGNORE_OUTPUT
503
+
504
+ .. code::
505
+
506
+ >>> plt.figure() # doctest: +IGNORE_OUTPUT
507
+ >>> plt.axis(False) # doctest: +IGNORE_OUTPUT
508
+ >>> plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) # doctest: +IGNORE_OUTPUT
509
+
510
+
511
+ .. figure:: /_examples/yolov3/single_image.png
512
+
513
+
514
+
515
+ **Object detection in a video**
516
+
517
+ .. code::
518
+
519
+ >>> video_cap = cv2.VideoCapture(vidpath)
520
+ >>> while video_cap.isOpened():
521
+ ... ret, frame = video_cap.read()
522
+ ... if not ret: break
523
+ ... pts = yolo3.detect(frame)
524
+ ... for p in pts:
525
+ ... cv2.rectangle(frame, p.box[0], p.box[1], [0, 255, 0], 2) # doctest: +IGNORE_OUTPUT
526
+ ... cv2.imshow('YOLOv3', frame) # doctest: +IGNORE_OUTPUT
527
+ ... cv2.waitKey(10) # doctest: +IGNORE_OUTPUT
528
+
529
+ .. figure:: /_examples/yolov3/aerobatics.gif
530
+
531
+
532
+
533
+
534
+ **The output structure**
535
+
536
+
537
+ The output of the detect() function is a list of :class:`pixelpoint <c4dynamics.states.pixelpoint.pixelpoint>` object.
538
+ The :class:`pixelpoint <c4dynamics.states.pixelpoint.pixelpoint>` has unique attributes to manipulate the detected object class and
539
+ bounding box.
540
+
541
+ .. code::
542
+
543
+ >>> print('{:^10} | {:^10} | {:^10} | {:^16} | {:^16} | {:^10} | {:^14}' # doctest: +IGNORE_OUTPUT
544
+ ... .format('# object', 'center x', 'center y', 'box top-left', 'box bottom-right', 'class', 'frame size'))
545
+ >>> # main loop:
546
+ >>> for i, p in enumerate(pts):
547
+ ... print('{:^10d} | {:^10.3f} | {:^10.3f} | {:^16} | {:^16} | {:^10} | {:^14}'
548
+ ... .format(i, p.x, p.y, ptup(p.box[0]), ptup(p.box[1]), p.class_id, ptup(p.fsize)))
549
+ ... cv2.rectangle(img, p.box[0], p.box[1], [0, 0, 0], 2) # doctest: +IGNORE_OUTPUT
550
+ ... point = (int((p.box[0][0] + p.box[1][0]) / 2 - 75), p.box[1][1] + 22)
551
+ ... cv2.putText(img, p.class_id, point, cv2.FONT_HERSHEY_SIMPLEX, 1, [0, 0, 0], 2) # doctest: +IGNORE_OUTPUT
552
+ # object | center x | center y | box top-left | box bottom-right | class | frame size
553
+ 0 | 0.584 | 0.376 | (691, 234) | (802, 306) | aeroplane | (1280, 720)
554
+ 1 | 0.457 | 0.473 | (528, 305) | (642, 376) | aeroplane | (1280, 720)
555
+ 2 | 0.471 | 0.322 | (542, 196) | (661, 267) | aeroplane | (1280, 720)
556
+ 3 | 0.546 | 0.873 | (645, 588) | (752, 668) | aeroplane | (1280, 720)
557
+
558
+ .. code::
559
+
560
+ >>> plt.figure() # doctest: +IGNORE_OUTPUT
561
+ >>> plt.axis(False) # doctest: +IGNORE_OUTPUT
562
+ >>> plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) # doctest: +IGNORE_OUTPUT
563
+
564
+ .. figure:: /_examples/yolov3/outformat.png
565
+
566
+
567
+ '''
568
+ #
569
+ # Step 1: Preprocess the Frame
570
+ # - Create a blob (binary large object) from the input frame with the
571
+ # specified dimensions
572
+ # - Normalize pixel values to a range of 0 to 1
573
+ # - Specify the dimensions of the input layer of the YOLOv3 model
574
+ # - Swap Red and Blue channels (BGR to RGB)
575
+ # - Set crop to False to preserve the original aspect ratio
576
+ ##
577
+ blob = cv2.dnn.blobFromImage(frame, 1 / 255.0, (MODEL_SIZE[0], MODEL_SIZE[1]), swapRB = True, crop = False)
578
+
579
+ #
580
+ # Step 2: Set Input to the YOLOv3 Model and Perform Forward Pass
581
+ # - Set the blob as the input to the YOLOv3 model
582
+ # - Get the names of the output layers of the model
583
+ # - Perform a forward pass through the model to obtain detections
584
+ #
585
+ # The returning detection structure:
586
+ # 1) x_center
587
+ # 2) y_center
588
+ # 3) width
589
+ # 4) height
590
+ # 5) confidence score
591
+ # 6:end) probabilities for each class
592
+ ##
593
+ self.net.setInput(blob)
594
+ detections = self.net.forward(self.ln)
595
+
596
+ #
597
+ # Step 3: Extract Detected Objects
598
+ # - Iterate through the detected objects in the forward pass results
599
+ # - Filter objects based on confidence threshold
600
+ # - Calculate bounding box coordinates and convert to integers
601
+ # - Append bounding box coordinates and class labels to respective lists
602
+ ##
603
+ raw = [] # xc, yc, w, h
604
+ boxes = [] # top left x, top left y, width, height
605
+ classIDs = []
606
+ confidences = []
607
+ fheight, fwidth = frame.shape[:2]
608
+
609
+ for detection in detections:
610
+ for d in detection:
611
+
612
+ scores = d[5:]
613
+ classID = np.argmax(scores)
614
+ confidence = scores[classID]
615
+
616
+ if scores[classID] > self._confidence_th: # Adjust the confidence threshold as needed
617
+
618
+ box = d[:4] * [fwidth, fheight, fwidth, fheight] # relative (xc, yc, w, h) to pixels
619
+ # (center_x, center_y, width, height) = box.astype('int')
620
+
621
+ x = box[0] - box[2] / 2 # top left x
622
+ y = box[1] - box[3] / 2 # top left y
623
+
624
+ boxes.append([x, y, box[2], box[3]]) # top left x, top left y, width, height
625
+ confidences.append(float(confidence))
626
+ classIDs.append(classID)
627
+ raw.append(d[:4])
628
+
629
+
630
+ indices = np.array(cv2.dnn.NMSBoxes(boxes, confidences, self._confidence_th, self._nms_th))
631
+
632
+ # box_out = []
633
+ # class_out = []
634
+ points_out = []
635
+
636
+
637
+ if len(indices) > 0:
638
+ # for i in indices.flatten():
639
+ for i in indices.ravel():
640
+ # (x, y) = (boxes[i][0], boxes[i][1])
641
+ # (w, h) = (boxes[i][2], boxes[i][3])
642
+ # x top left, y top left, x bottom right, y bottom right
643
+ # box_out.append([boxes[i][0], boxes[i][1], boxes[i][0] + boxes[i][2], boxes[i][1] + boxes[i][3]])
644
+
645
+ # points_out.append(pixelpoint(raw[i], self.class_names[classIDs[i]], (w, h)))
646
+ pp = pixelpoint(x = int(raw[i][0] * fwidth), y = int(raw[i][1] * fheight), w = int(raw[i][2] * fwidth), h = int(raw[i][3] * fheight))
647
+ # pp.units = 'normalized'
648
+ pp.fsize = (fwidth, fheight)
649
+ pp.class_id = self.class_names[classIDs[i]]
650
+ points_out.append(pp)
651
+
652
+ # class_out.append(self.class_names[classIDs[i]])
653
+
654
+ # box_out = np.array(box_out)
655
+
656
+ return points_out # box_out, class_out,
657
+
658
+
659
+
660
+
661
+
662
+ if __name__ == "__main__":
663
+
664
+ import doctest, contextlib
665
+ from c4dynamics import IgnoreOutputChecker, cprint
666
+
667
+ # Register the custom OutputChecker
668
+ doctest.OutputChecker = IgnoreOutputChecker
669
+
670
+ tofile = False
671
+ optionflags = doctest.FAIL_FAST
672
+
673
+ if tofile:
674
+ with open(os.path.join('tests', '_out', 'output.txt'), 'w') as f:
675
+ with contextlib.redirect_stdout(f), contextlib.redirect_stderr(f):
676
+ result = doctest.testmod(optionflags = optionflags)
677
+ else:
678
+ result = doctest.testmod(optionflags = optionflags)
679
+
680
+ if result.failed == 0:
681
+ cprint(os.path.basename(__file__) + ": all tests passed!", 'g')
682
+ else:
683
+ print(f"{result.failed}")
684
+
685
+
686
+