Skip to content
This repository was archived by the owner on Nov 13, 2025. It is now read-only.

Commit 030287d

Browse files
authored
Min brightness filter (#254)
* implemented min brightness filter in detect target * combined percentile and min brightness threshold by taking maximum value * implemented blob average brightness filter * deleted time printing and made other minor changes - deleted uncessary comments - slight annotation refactor - set integration test back to not saving image
1 parent 74e934e commit 030287d

File tree

6 files changed

+85
-24
lines changed

6 files changed

+85
-24
lines changed

config.yaml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,16 @@ detect_target:
3737
# min_circularity: 0.01
3838
# max_circularity: 1
3939
# filter_by_inertia: True
40-
# min_inertia_ratio: 0.2
40+
# min_inertia_ratio: 0.1
4141
# max_inertia_ratio: 1
4242
# filter_by_convexity: False
4343
# min_convexity: 0.01
4444
# max_convexity: 1
4545
# filter_by_area: True
46-
# min_area_pixels: 50
47-
# max_area_pixels: 640
46+
# min_area_pixels: 160
47+
# max_area_pixels: 2000
48+
# min_brightness_threshold: 50
49+
# min_average_brightness_threshold: 130
4850

4951
flight_interface:
5052
# Port 5762 connects directly to the simulated auto pilot, which is more realistic

modules/detect_target/detect_target_brightspot.py

Lines changed: 68 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ def __init__(
4343
filter_by_area: bool,
4444
min_area_pixels: int,
4545
max_area_pixels: int,
46+
min_brightness_threshold: int,
47+
min_average_brightness_threshold: int,
4648
) -> None:
4749
"""
4850
Initializes the configuration for DetectTargetBrightspot.
@@ -62,6 +64,8 @@ def __init__(
6264
filter_by_area: Whether to filter by area.
6365
min_area_pixels: Minimum area in pixels.
6466
max_area_pixels: Maximum area in pixels.
67+
min_brightness_threshold: Minimum brightness threshold for bright spots.
68+
min_average_brightness_threshold: Minimum absolute average brightness of detected blobs.
6569
"""
6670
self.brightspot_percentile_threshold = brightspot_percentile_threshold
6771
self.filter_by_color = filter_by_color
@@ -78,6 +82,8 @@ def __init__(
7882
self.filter_by_area = filter_by_area
7983
self.min_area_pixels = min_area_pixels
8084
self.max_area_pixels = max_area_pixels
85+
self.min_brightness_threshold = min_brightness_threshold
86+
self.min_average_brightness_threshold = min_average_brightness_threshold
8187

8288

8389
# pylint: enable=too-many-instance-attributes
@@ -128,20 +134,24 @@ def run(
128134
# pylint: disable-next=broad-exception-caught
129135
except Exception as exception:
130136
self.__local_logger.error(
131-
f"{time.time()}: Failed to convert to greyscale, exception: {exception}"
137+
f"Failed to convert to greyscale, exception: {exception}"
132138
)
133139
return False, None
134140

141+
# Calculate the percentile threshold for bright spots
135142
brightspot_threshold = np.percentile(
136143
grey_image, self.__config.brightspot_percentile_threshold
137144
)
138145

139-
# Apply thresholding to isolate bright spots
146+
# Compute the maximum of the percentile threshold and the minimum brightness threshold
147+
combined_threshold = max(brightspot_threshold, self.__config.min_brightness_threshold)
148+
149+
# Apply combined thresholding to isolate bright spots
140150
threshold_used, bw_image = cv2.threshold(
141-
grey_image, brightspot_threshold, 255, cv2.THRESH_BINARY
151+
grey_image, combined_threshold, 255, cv2.THRESH_BINARY
142152
)
143153
if threshold_used == 0:
144-
self.__local_logger.error(f"{time.time()}: Failed to threshold image.")
154+
self.__local_logger.error("Failed to percentile threshold image.")
145155
return False, None
146156

147157
# Set up SimpleBlobDetector
@@ -166,33 +176,69 @@ def run(
166176

167177
# A lack of detections is not an error, but should still not be forwarded
168178
if len(keypoints) == 0:
169-
self.__local_logger.info(f"{time.time()}: No brightspots detected.")
179+
self.__local_logger.info("No brightspots detected (before blob average filter).")
170180
return False, None
171181

172-
# Annotate the image (green circle) with detected keypoints
173-
image_annotated = cv2.drawKeypoints(
174-
image, keypoints, None, (0, 255, 0), cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS
175-
)
182+
# Compute the average brightness of each blob
183+
average_brightness_list = []
184+
185+
for i, keypoint in enumerate(keypoints):
186+
x, y = keypoint.pt # Center of the blob
187+
radius = keypoint.size / 2 # Radius of the blob
188+
189+
# Define a square region of interest (ROI) around the blob
190+
x_min = int(max(0, x - radius))
191+
x_max = int(min(grey_image.shape[1], x + radius))
192+
y_min = int(max(0, y - radius))
193+
y_max = int(min(grey_image.shape[0], y + radius))
194+
195+
# Create a circular mask for the blob
196+
mask = np.zeros((y_max - y_min, x_max - x_min), dtype=np.uint8)
197+
# Circle centered at middle of mask
198+
cv2.circle(mask, (int(radius), int(radius)), int(radius), 255, -1)
199+
200+
# Extract the ROI from the grayscale image
201+
roi = grey_image[y_min:y_max, x_min:x_max]
202+
203+
# Apply the mask to the ROI
204+
masked_roi = cv2.bitwise_and(roi, roi, mask=mask)
205+
206+
# Calculate the mean brightness of the blob
207+
mean_brightness = cv2.mean(masked_roi, mask=mask)[0]
208+
# append index into list to keep track of associated keypoint
209+
average_brightness_list.append((mean_brightness, i))
210+
211+
# filter the blobs by their average brightness
212+
filtered_keypoints = []
213+
for brightness, idx in average_brightness_list:
214+
# Only append associated keypoint if the blob average is bright enough
215+
if brightness >= self.__config.min_average_brightness_threshold:
216+
filtered_keypoints.append(keypoints[idx])
217+
218+
# A lack of detections is not an error, but should still not be forwarded
219+
if len(filtered_keypoints) == 0:
220+
self.__local_logger.info("No brightspots detected (after blob average filter).")
221+
return False, None
176222

177223
# Process bright spot detection
178224
result, detections = detections_and_time.DetectionsAndTime.create(data.timestamp)
179225
if not result:
180-
self.__local_logger.error(f"{time.time()}: Failed to create detections for image.")
226+
self.__local_logger.error("Failed to create detections for image.")
181227
return False, None
182228

183229
# Get Pylance to stop complaining
184230
assert detections is not None
185231

186232
# Draw bounding boxes around detected keypoints
187-
for keypoint in keypoints:
233+
for keypoint in filtered_keypoints:
188234
x, y = keypoint.pt
189235
size = keypoint.size
190236
bounds = np.array([x - size / 2, y - size / 2, x + size / 2, y + size / 2])
191237
result, detection = detections_and_time.Detection.create(
192238
bounds, DETECTION_LABEL, CONFIDENCE
193239
)
194240
if not result:
195-
self.__local_logger.error(f"{time.time()}: Failed to create bounding boxes.")
241+
self.__local_logger.error("Failed to create bounding boxes.")
196242
return False, None
197243

198244
# Get Pylance to stop complaining
@@ -206,12 +252,21 @@ def run(
206252

207253
# Logging
208254
self.__local_logger.info(
209-
f"{time.time()}: Count: {self.__counter}. Target detection took {end_time - start_time} seconds. Objects detected: {detections}."
255+
f"Count: {self.__counter}. Target detection took {end_time - start_time} seconds. Objects detected: {detections}."
210256
)
211257

212258
if self.__filename_prefix != "":
213259
filename = self.__filename_prefix + str(self.__counter)
214260

261+
# Annotate the image (green circle) with detected keypoints
262+
image_annotated = cv2.drawKeypoints(
263+
image,
264+
filtered_keypoints,
265+
None,
266+
(0, 255, 0),
267+
cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS,
268+
)
269+
215270
# Annotated image
216271
cv2.imwrite(filename + ".png", image_annotated) # type: ignore
217272

2.69 MB
Loading
2.7 MB
Loading

tests/integration/test_detect_target_worker.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@
2020

2121

2222
BRIGHTSPOT_TEST_PATH = pathlib.Path("tests", "brightspot_example")
23-
IMAGE_BRIGHTSPOT_0_PATH = pathlib.Path(BRIGHTSPOT_TEST_PATH, "ir_detections_0.png")
23+
IMAGE_BRIGHTSPOT_0_PATH = pathlib.Path(BRIGHTSPOT_TEST_PATH, "ir_detections_5.png")
2424
IMAGE_BRIGHTSPOT_1_PATH = pathlib.Path(BRIGHTSPOT_TEST_PATH, "ir_detections_1.png")
2525

2626
BRIGHTSPOT_OPTION = detect_target_factory.DetectTargetOption.CV_BRIGHTSPOT
2727
# Logging is identical to detect_target_ultralytics.py
2828
# pylint: disable=duplicate-code
2929
BRIGHTSPOT_CONFIG = detect_target_brightspot.DetectTargetBrightspotConfig(
30-
brightspot_percentile_threshold=99.9,
30+
brightspot_percentile_threshold=99.5,
3131
filter_by_color=True,
3232
blob_color=255,
3333
filter_by_circularity=False,
@@ -40,8 +40,10 @@
4040
min_convexity=0.01,
4141
max_convexity=1,
4242
filter_by_area=True,
43-
min_area_pixels=50,
44-
max_area_pixels=640,
43+
min_area_pixels=100,
44+
max_area_pixels=2000,
45+
min_brightness_threshold=50,
46+
min_average_brightness_threshold=120,
4547
)
4648
# pylint: enable=duplicate-code
4749

@@ -160,7 +162,7 @@ def main() -> int:
160162
Main function.
161163
"""
162164
run_worker(BRIGHTSPOT_OPTION, BRIGHTSPOT_CONFIG)
163-
run_worker(ULTRALYTICS_OPTION, ULTRALYTICS_CONFIG)
165+
# run_worker(ULTRALYTICS_OPTION, ULTRALYTICS_CONFIG)
164166

165167
return 0
166168

tests/unit/test_detect_target_brightspot.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
# Config is identical to test_detect_target_worker.py
3939
# pylint: disable=duplicate-code
4040
DETECT_TARGET_BRIGHTSPOT_CONFIG = detect_target_brightspot.DetectTargetBrightspotConfig(
41-
brightspot_percentile_threshold=99.9,
41+
brightspot_percentile_threshold=99.5,
4242
filter_by_color=True,
4343
blob_color=255,
4444
filter_by_circularity=False,
@@ -51,8 +51,10 @@
5151
min_convexity=0.01,
5252
max_convexity=1,
5353
filter_by_area=True,
54-
min_area_pixels=50,
55-
max_area_pixels=640,
54+
min_area_pixels=100,
55+
max_area_pixels=2000,
56+
min_brightness_threshold=50,
57+
min_average_brightness_threshold=120,
5658
)
5759
# pylint: enable=duplicate-code
5860

0 commit comments

Comments
 (0)