|
5 | 5 | #------------------------------------------------------------------------------- |
6 | 6 | # ex05_detect_sfe_logo.py |
7 | 7 | # |
8 | | -# This example demonstrates a basic vision processing pipeline. It reads frames |
9 | | -# from the camera, finds contours in the image, and compares them to a reference |
10 | | -# contour to detect the SparkFun flame logo. Below is some (bad) ASCII art of |
11 | | -# the logo for reference. The example draws the actual contour that it's looking |
12 | | -# for in the top left corner of the display. |
13 | | -# |
| 8 | +# This example demonstrates a basic vision processing pipeline. A pipeline is |
| 9 | +# just a sequence of steps used to extract meaningful data from an image. The |
| 10 | +# pipeline in this example attempts to detect the SparkFun flame logo using |
| 11 | +# contour matching. If it's detected, it will be outlined on the display for |
| 12 | +# visualization. The bounding box and center of the logo will also be drawn, |
| 13 | +# demonstrating how to acquire useful numerical data from an image (eg. the |
| 14 | +# position and size of an object). |
| 15 | +# |
| 16 | +# Note that this pipeline is very simple and does not include many of the steps |
| 17 | +# that would typically be included in more robust pipelines. This was done for |
| 18 | +# simplicity and performance, so it may produce false positives or miss the logo |
| 19 | +# entirely sometimes. |
| 20 | +#------------------------------------------------------------------------------- |
| 21 | + |
| 22 | +# Import OpenCV and hardware initialization module |
| 23 | +import cv2 as cv |
| 24 | +from cv2_hardware_init import * |
| 25 | + |
| 26 | +# Import NumPy |
| 27 | +from ulab import numpy as np |
| 28 | + |
| 29 | +# Import time for frame rate calculation |
| 30 | +import time |
| 31 | + |
| 32 | +# Here we define a reference contour for the SparkFun flame logo. This was |
| 33 | +# created manually by picking points on the boundary of a small image of the |
| 34 | +# logo in an image editor. Below is also ASCII art of the logo for reference, |
| 35 | +# but the actual contour is drawn in the top left corner of the display. |
14 | 36 | # ___ |
15 | 37 | # / _\ |
16 | 38 | # \ \ |
|
21 | 43 | # | _____/ |
22 | 44 | # | / |
23 | 45 | # |/ |
24 | | -# |
25 | | -# If the logo is detected, it will be highlighted in red on the display. Note |
26 | | -# that this vision pipeline is very simple and does not include many of the |
27 | | -# steps that would typically be included in more robust pipelines for the sake |
28 | | -# of simplicity and performance. So it may produce false positives or miss the |
29 | | -# logo entirely in some cases. |
30 | | -#------------------------------------------------------------------------------- |
31 | | - |
32 | | -# Import OpenCV |
33 | | -import cv2 as cv |
34 | | -from cv2_hardware_init import * |
35 | | -from ulab import numpy as np |
36 | | -import time |
37 | | - |
38 | | -# Here we define a reference contour for the SparkFun flame logo. This was |
39 | | -# created manually by picking points on the boundary of a small image of the |
40 | | -# logo in an image editor. This gets drawn in the top left corner of the |
41 | | -# display for reference |
42 | 46 | logo_contour = np.array( |
43 | 47 | [[[0,48]], |
44 | 48 | [[0,22]], |
|
65 | 69 | [[20,36]], |
66 | 70 | [[12,36]]], dtype=np.float) |
67 | 71 |
|
68 | | -# Initialize a loop timer to calculate processing speed in FPS |
69 | | -loop_time = time.ticks_us() |
70 | | - |
71 | | -# Open the camera |
72 | | -camera.open() |
73 | | - |
74 | | -# Prompt the user to press a key to continue |
75 | | -print("Press any key to continue") |
76 | | - |
77 | | -# Loop to continuously read frames from the camera and display them |
78 | | -while True: |
79 | | - # Read a frame from the camera |
80 | | - success, frame = camera.read() |
81 | | - |
| 72 | +# This is the pipeline implementation. This gets called for each frame captured |
| 73 | +# by the camera in the main loop |
| 74 | +def my_pipeline(frame): |
82 | 75 | # Here we binarize the image. There are many ways to do this, but here we |
83 | 76 | # simply convert the image to grayscale and then apply Otsu's thresholding |
84 | 77 | # method to create a binary image. This means it will only detect a dark |
|
87 | 80 | gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY) |
88 | 81 | ret, thresh = cv.threshold(gray, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU) |
89 | 82 |
|
90 | | - # Find contours in the binary image, which represent the boundaries of |
91 | | - # shapes. Contours are a powerful tool in OpenCV for shape analysis and |
92 | | - # object detection |
| 83 | + # Find contours in the binary image, which are simply lists of points around |
| 84 | + # the boundaries of shapes. Contours are a powerful tool in OpenCV for shape |
| 85 | + # analysis and object detection |
93 | 86 | contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE) |
94 | 87 |
|
95 | 88 | # It's possible that no contours were found, so first check if any were |
|
130 | 123 | # that good matches are usually around 0.5, so we'll use a slightly |
131 | 124 | # higher threshold of 1.0 |
132 | 125 | if best_similarity < 1.0: |
133 | | - # Now we'll draw the best contour found on the original image |
| 126 | + # The best contour found is a good match, so we'll draw it on the |
| 127 | + # frame to outline the detected logo for visualization |
134 | 128 | frame = cv.drawContours(frame, [best_contour], -1, (0, 0, 255), 2) |
135 | 129 |
|
| 130 | + # Visualization is great, but the purpose of most real pipelines is |
| 131 | + # to extract useful data from the image. For example, suppose we |
| 132 | + # want to know where the logo is located in the image and how large |
| 133 | + # it is. We can use the bounding rectangle of the contour to get the |
| 134 | + # position and size of the logo |
| 135 | + left, top, width, height = cv.boundingRect(best_contour) |
| 136 | + center_x = left + width // 2 |
| 137 | + center_y = top + height // 2 |
| 138 | + |
| 139 | + # Now we could use this data for some task! For example, if we had |
| 140 | + # a robot that needed to drive up to the logo, we could turn to face |
| 141 | + # the logo with the center point, then drive towards it until the |
| 142 | + # size is big enough. |
| 143 | + # |
| 144 | + # This example doesn't actually make use of the data, so we'll just |
| 145 | + # draw the bounding box and center of the logo for visualization |
| 146 | + frame = cv.rectangle(frame, (left, top), (left + width, top + height), (255, 0, 0), 2) |
| 147 | + frame = cv.circle(frame, (center_x, center_y), 5, (0, 255, 0), -1) |
| 148 | + |
| 149 | +# Initialize a loop timer to calculate processing speed in FPS |
| 150 | +loop_time = time.ticks_us() |
| 151 | + |
| 152 | +# Open the camera |
| 153 | +camera.open() |
| 154 | + |
| 155 | +# Prompt the user to press a key to continue |
| 156 | +print("Press any key to continue") |
| 157 | + |
| 158 | +# Loop to continuously read frames from the camera and display them |
| 159 | +while True: |
| 160 | + # Read a frame from the camera |
| 161 | + success, frame = camera.read() |
| 162 | + if not success: |
| 163 | + print("Failed to read frame from camera") |
| 164 | + break |
| 165 | + |
| 166 | + # Call the pipeline function to process the frame |
| 167 | + my_pipeline(frame) |
| 168 | + |
136 | 169 | # All processing is done! Calculate the frame rate and display it |
137 | 170 | current_time = time.ticks_us() |
138 | 171 | fps = 1000000 / (current_time - loop_time) |
|
0 commit comments