|
12 | 12 | import cv2 as cv
|
13 | 13 | from cv2_hardware_init import *
|
14 | 14 |
|
15 |
| -# Import NumPy |
| 15 | +# Import NumPy to create arrays |
16 | 16 | from ulab import numpy as np
|
17 | 17 |
|
18 | 18 | # Import time for frame rate calculation
|
19 | 19 | import time
|
20 | 20 |
|
21 |
| -# Initialize a loop timer to calculate processing speed in FPS |
22 |
| -loop_time = time.ticks_us() |
| 21 | +# Import garbage collector to measure memory usage |
| 22 | +import gc |
| 23 | + |
| 24 | +# Many OpenCV functions can take an optional output argument to store the result |
| 25 | +# of the operation. If it's not provided, OpenCV allocates a new array to store |
| 26 | +# the result, which can be slow and waste memory. When it is provided, OpenCV |
| 27 | +# instead writes the result to the provided array, reducing memory usage and |
| 28 | +# improving performance. The array must have the same shape and data type as the |
| 29 | +# expected output of the operation, otherwise a new array will be allocated. |
| 30 | +# |
| 31 | +# Here we preallocate arrays for the destination arguments of this example. If |
| 32 | +# the shapes or data types are incorrect, OpenCV will simply allocate new arrays |
| 33 | +# for each on the first loop iteration. The variables will then be re-assigned, |
| 34 | +# so this only negatively affects the first loop iteration. |
| 35 | +frame = np.zeros((240, 320, 3), dtype=np.uint8) |
| 36 | +result_image = np.zeros((240, 320, 3), dtype=np.uint8) |
23 | 37 |
|
24 | 38 | # Open the camera
|
25 | 39 | camera.open()
|
26 | 40 |
|
27 |
| -# The `read()` method of OpenCV camera drivers can optionally take an output |
28 |
| -# image as an argument. When it's not provided, the camera driver must allocate |
29 |
| -# a whole new image for the frame, which can be slow and waste memory. If the |
30 |
| -# image argument is provided, then the camera driver will write the data to the |
31 |
| -# provided image. The image must be a NumPy array with the same shape and data |
32 |
| -# type as the camera's |
33 |
| -success, frame = camera.read() |
| 41 | +# Initialize a loop timer to calculate processing speed in FPS |
| 42 | +loop_time = time.ticks_us() |
| 43 | + |
| 44 | +# Initialize a variable to track memory usage |
| 45 | +last_mem_free = gc.mem_free() |
34 | 46 |
|
35 | 47 | # Prompt the user to press a key to continue
|
36 | 48 | print("Press any key to continue")
|
37 | 49 |
|
38 | 50 | # Loop to continuously read frames from the camera and display them
|
39 | 51 | while True:
|
40 |
| - # Read a frame from the camera |
41 |
| - success, frame = camera.read() |
| 52 | + # Read a frame from the camera and measure how long it takes. Try running |
| 53 | + # this both with and without the preallocated `frame` array to see the |
| 54 | + # difference in performance |
| 55 | + t0 = time.ticks_us() |
| 56 | + success, frame = camera.read(frame) |
| 57 | + t1 = time.ticks_us() |
| 58 | + print("Read frame: %.2f ms" % ((t1 - t0) / 1_000), end='\t') |
| 59 | + |
| 60 | + # Check if the frame was read successfully |
42 | 61 | if not success:
|
43 | 62 | print("Failed to read frame from camera")
|
44 | 63 | break
|
45 | 64 |
|
46 |
| - # Now we'll |
| 65 | + # Now we'll do some processing on the frame. Try running this with and |
| 66 | + # without the preallocated `result_image` array, and try different OpenCV |
| 67 | + # functions to compare performance |
| 68 | + t0 = time.ticks_us() |
| 69 | + result_image = cv.cvtColor(frame, cv.COLOR_BGR2HSV, result_image) |
| 70 | + t1 = time.ticks_us() |
| 71 | + print("Processing: %.2f ms" % ((t1 - t0) / 1_000), end='\t') |
47 | 72 |
|
48 | 73 | # It's a good idea to measure the frame rate of the main loop to see how
|
49 | 74 | # fast the entire pipeline is running. This will include not only the
|
|
53 | 78 | current_time = time.ticks_us()
|
54 | 79 | fps = 1_000_000 / (current_time - loop_time)
|
55 | 80 | loop_time = current_time
|
56 |
| - frame = cv.putText(frame, f"FPS: {fps:.2f}", (10, 30), cv.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) |
| 81 | + print("FPS: %.2f" % fps, end='\t') |
| 82 | + result_image = cv.putText(result_image, f"FPS: {fps:.2f}", (10, 30), cv.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2) |
57 | 83 |
|
58 | 84 | # Display the frame
|
59 |
| - cv.imshow(display, frame) |
| 85 | + cv.imshow(display, result_image) |
| 86 | + |
| 87 | + # We can also measure memory usage to see how much RAM is being consumed by |
| 88 | + # this code. If you remove the output arguments from the functions above, |
| 89 | + # you'll see that the memory consumption increases significantly as new |
| 90 | + # arrays must be allocated each loop iteration |
| 91 | + mem_free = gc.mem_free() |
| 92 | + memory_used = last_mem_free - mem_free |
| 93 | + last_mem_free = mem_free |
| 94 | + print("Memory free: %d KiB" % (mem_free // 1024), end='\t') |
| 95 | + print("Memory consumed: %d KiB" % (memory_used // 1024), end='\n') |
| 96 | + |
| 97 | + # If the memory usage is negative, it means the garbage collector triggered |
| 98 | + # and freed some memory. Garbage collection can take some time, so you'll |
| 99 | + # notice a drop in FPS when it happens, and you may see a stutter in the |
| 100 | + # video stream on the display. This is another reason to preallocate arrays, |
| 101 | + # since it mitigates how frequently garbage collection is triggered |
| 102 | + if memory_used < 0: |
| 103 | + print("Garbage collection triggered!") |
| 104 | + |
| 105 | + # Something to try is triggering the garbage collector manually each loop |
| 106 | + # iteration to immediately free up memory. Garbage collection can be faster |
| 107 | + # if less memory has been allocated, so this can help avoid long stutters |
| 108 | + # from occasional garbage collection. However garbage collection will always |
| 109 | + # take *some* time, so this will lower the average FPS. You can choose to do |
| 110 | + # this if you prefer a consistent frame rate, or don't if you prefer maximum |
| 111 | + # frame rate and are okay with occasional stutters |
| 112 | + # gc.collect() |
60 | 113 |
|
61 | 114 | # Check for key presses
|
62 | 115 | key = cv.waitKey(1)
|
|
0 commit comments