Skip to content

Commit 08a7dbc

Browse files
committed
Update performance example with all changes this time
1 parent 15ffc29 commit 08a7dbc

File tree

1 file changed

+68
-15
lines changed

1 file changed

+68
-15
lines changed

examples/ex05_performance.py

Lines changed: 68 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,38 +12,63 @@
1212
import cv2 as cv
1313
from cv2_hardware_init import *
1414

15-
# Import NumPy
15+
# Import NumPy to create arrays
1616
from ulab import numpy as np
1717

1818
# Import time for frame rate calculation
1919
import time
2020

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)
2337

2438
# Open the camera
2539
camera.open()
2640

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()
3446

3547
# Prompt the user to press a key to continue
3648
print("Press any key to continue")
3749

3850
# Loop to continuously read frames from the camera and display them
3951
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
4261
if not success:
4362
print("Failed to read frame from camera")
4463
break
4564

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')
4772

4873
# It's a good idea to measure the frame rate of the main loop to see how
4974
# fast the entire pipeline is running. This will include not only the
@@ -53,10 +78,38 @@
5378
current_time = time.ticks_us()
5479
fps = 1_000_000 / (current_time - loop_time)
5580
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)
5783

5884
# 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()
60113

61114
# Check for key presses
62115
key = cv.waitKey(1)

0 commit comments

Comments
 (0)