Skip to content

Commit c46ab86

Browse files
authored
Contours (#37)
* Use contours to calculate bounding boxes * Fix bug * Add option to draw bounding boxes which is enabled by default * Add option to docs * Formatting
1 parent 04b8b63 commit c46ab86

File tree

4 files changed

+54
-8
lines changed

4 files changed

+54
-8
lines changed

docs/config.md

+2
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ cameras:
5252
# OPTIONAL: but highly recommended, setting the default url for a snapshot to be
5353
# processed by this camera. This is required for auto detection (Default: none).
5454
url: "http://ip.ad.dr.ess/jpg"
55+
# OPTIONAL: Whether or not to draw bounding boxes for confirmed objects in the snapshots (Default: shown below).
56+
bounding_box: true
5557
# OPTIONAL: Whether or not to save the snapshots of confirmed detections (Default: shown below).
5658
save_detections: true
5759
# OPTIONAL: Whether or not to save the snapshots of missed detections (Default: shown below).

swatch/config.py

+4
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ class SnapshotConfig(SwatchBaseModel):
2929

3030
url: str = Field(title="Camera Snapshot Url.", default=None)
3131
mode: SnapshotModeEnum = Field(title="Snapshot mode.", default=SnapshotModeEnum.ALL)
32+
bounding_box: bool = Field(
33+
title="Write bounding boxes for detected objects on the snapshot.",
34+
default=True,
35+
)
3236
save_detections: bool = Field(
3337
title="Save snapshots of detections that are found.", default=True
3438
)

swatch/detection.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,10 @@ def __handle_detections__(self, detection_result: Dict[str, Any]) -> None:
8383
self.obj_data[non_unique_id]["zone_name"] = zone_name
8484
self.obj_data[non_unique_id]["variant"] = object_result["variant"]
8585

86-
if object_result["area"] > self.obj_data[non_unique_id].get(
87-
"top_area", 0
88-
):
89-
self.obj_data[non_unique_id]["top_area"] = object_result["area"]
86+
top_area = max([d["area"] for d in object_result["objects"]])
87+
88+
if top_area > self.obj_data[non_unique_id].get("top_area", 0):
89+
self.obj_data[non_unique_id]["top_area"] = top_area
9090

9191
# save snapshot with best area
9292
self.snap_processor.save_snapshot(

swatch/image.py

+44-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""ImageProcessor for getting detectable info from images."""
22

33
import datetime
4-
from typing import Any, Dict, Optional, Tuple
4+
from typing import Any, Dict, Optional, Set, Tuple
55
from colorthief import ColorThief
66
import cv2
77
import numpy as np
@@ -42,6 +42,32 @@ def __mask_image__(crop: Any, color_variant: ColorVariantConfig) -> Tuple[Any, i
4242
return (output, matches)
4343

4444

45+
def __detect_objects__(mask: Any, object: ObjectConfig) -> Set[Dict[str, Any]]:
46+
"""Detect objects and return list of bounding boxes."""
47+
# get gray image
48+
gray = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
49+
50+
# calculate contours
51+
_, thresh = cv2.threshold(gray, 1, 255, 0)
52+
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
53+
54+
detected = []
55+
56+
for contour in contours:
57+
x, y, w, h = cv2.boundingRect(contour)
58+
area = w * h
59+
60+
if object.min_area < area < object.max_area:
61+
detected.append(
62+
{
63+
"box": [x, y, x + w, y + h],
64+
"area": area,
65+
}
66+
)
67+
68+
return detected
69+
70+
4571
class ImageProcessor:
4672
"""Processing images with swatch config data."""
4773

@@ -76,8 +102,22 @@ def __check_image__(
76102
continue
77103

78104
output, matches = __mask_image__(crop, color_variant)
79-
80-
if detectable.min_area < matches < detectable.max_area:
105+
detected_objects = __detect_objects__(crop, detectable)
106+
107+
if detected_objects:
108+
109+
# draw bounding boxes on image if enabled
110+
if snapshot.bounding_box:
111+
for obj in detected_objects:
112+
cv2.rectangle(
113+
output,
114+
(obj["box"][0], obj["box"][1]),
115+
(obj["box"][2], obj["box"][3]),
116+
(0, 255, 0),
117+
4,
118+
)
119+
120+
# save the snapshot if enabled
81121
if snapshot.save_detections and snapshot.mode in [
82122
SnapshotModeEnum.ALL,
83123
SnapshotModeEnum.MASK,
@@ -91,9 +131,9 @@ def __check_image__(
91131

92132
return {
93133
"result": True,
94-
"area": matches,
95134
"variant": variant_name,
96135
"camera_name": camera_name,
136+
"objects": detected_objects,
97137
}
98138

99139
if matches > best_fail.get("area", 0):

0 commit comments

Comments
 (0)