From 537ac7faa95b005e1222b75938af936b326a6098 Mon Sep 17 00:00:00 2001 From: Peter van Lunteren Date: Thu, 23 Nov 2023 11:44:08 +0100 Subject: [PATCH 1/6] Set line width and label font size proportional to image size The line width of the bounding box will be set to 1/150th of the image width or height, whichever is smaller. If this value is smaller than the previous default value of 2, 2 will be chosen. Same for the label font size. This will be set to 1/40th of the image width or height, whichever is smaller. If this value is smaller than the previous default value of 15, 15 will be chosen. --- bounding_box/bounding_box.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/bounding_box/bounding_box.py b/bounding_box/bounding_box.py index d5f8850..453bdce 100644 --- a/bounding_box/bounding_box.py +++ b/bounding_box/bounding_box.py @@ -36,8 +36,6 @@ _DEFAULT_COLOR_NAME = "green" _FONT_PATH = _os.path.join(_LOC, "Ubuntu-B.ttf") -_FONT_HEIGHT = 15 -_FONT = ImageFont.truetype(_FONT_PATH, _FONT_HEIGHT) def _rgb_to_bgr(color): return list(reversed(color)) @@ -45,8 +43,8 @@ def _rgb_to_bgr(color): def _color_image(image, font_color, background_color): return background_color + (font_color - background_color) * image / 255 -def _get_label_image(text, font_color_tuple_bgr, background_color_tuple_bgr): - text_image = _FONT.getmask(text) +def _get_label_image(text, font_color_tuple_bgr, background_color_tuple_bgr, font): + text_image = font.getmask(text) shape = list(reversed(text_image.size)) bw_image = np.array(text_image).reshape(shape) @@ -87,12 +85,18 @@ def add(image, left, top, right, bottom, label=None, color=None): colors = [_rgb_to_bgr(item) for item in _COLOR_NAME_TO_RGB[color]] color, color_text = colors - _cv2.rectangle(image, (left, top), (right, bottom), color, 2) + image_height, image_width, _ = image.shape + line_width_box = max(round(min(image_height, image_width) / 150), 2) + + _cv2.rectangle(image, (left, top), (right, bottom), color, line_width_box) if label: - _, image_width, _ = image.shape - label_image = _get_label_image(label, color_text, color) + font_height = max(round(min(image_height, image_width) / 40), 15) + font = ImageFont.truetype(_FONT_PATH, font_height) + + label_image = _get_label_image(label, color_text, color, font) + label_height, label_width, _ = label_image.shape rectangle_height, rectangle_width = 1 + label_height, 1 + label_width From b883579065f5da1b5b0192104295cb38be96ea28 Mon Sep 17 00:00:00 2001 From: Peter van Lunteren Date: Mon, 27 Nov 2023 15:47:14 +0100 Subject: [PATCH 2/6] Rename bounding_box.py to visualise_detection.py --- .../bounding_box.py => visualise_detection/visualise_detection.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename bounding_box/bounding_box.py => visualise_detection/visualise_detection.py (100%) diff --git a/bounding_box/bounding_box.py b/visualise_detection/visualise_detection.py similarity index 100% rename from bounding_box/bounding_box.py rename to visualise_detection/visualise_detection.py From 5727c359bc23bce8978b3fc61081fdd92e67be59 Mon Sep 17 00:00:00 2001 From: Peter van Lunteren Date: Mon, 27 Nov 2023 15:48:41 +0100 Subject: [PATCH 3/6] Rename visualise_detection.py to bounding_box.py --- .../visualise_detection.py => bounding_box/bounding_box.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename visualise_detection/visualise_detection.py => bounding_box/bounding_box.py (100%) diff --git a/visualise_detection/visualise_detection.py b/bounding_box/bounding_box.py similarity index 100% rename from visualise_detection/visualise_detection.py rename to bounding_box/bounding_box.py From 6d2e5787ce82d34d44968d93e543a6b414f898d1 Mon Sep 17 00:00:00 2001 From: Peter van Lunteren Date: Mon, 27 Nov 2023 15:52:33 +0100 Subject: [PATCH 4/6] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 04e78e6..e681063 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ + +Fork from https://github.com/nalepae/bounding-box/tree/master. I've set the line width and label size proportional to the image size. Helpfull for large images. I've opened a pull request to the original repo, but it appears to be non-active. Hence I'll use my own fork. Original documentation below. + + # Bounding Box **Bounding Box** is a library to plot pretty bounding boxes with a simple Python API. From 7395153a30a9ef7b0598f7c2e734bd8ba8222525 Mon Sep 17 00:00:00 2001 From: Peter van Lunteren Date: Tue, 28 Jan 2025 07:04:29 +0100 Subject: [PATCH 5/6] added option to select for S, M, L size --- bounding_box/bounding_box.py | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/bounding_box/bounding_box.py b/bounding_box/bounding_box.py index 453bdce..89555e5 100644 --- a/bounding_box/bounding_box.py +++ b/bounding_box/bounding_box.py @@ -56,7 +56,7 @@ def _get_label_image(text, font_color_tuple_bgr, background_color_tuple_bgr, fon return np.concatenate(image).transpose(1, 2, 0) -def add(image, left, top, right, bottom, label=None, color=None): +def add(image, left, top, right, bottom, label=None, color=None, size=2): if type(image) is not _np.ndarray: raise TypeError("'image' parameter must be a numpy.ndarray") try: @@ -86,13 +86,33 @@ def add(image, left, top, right, bottom, label=None, color=None): color, color_text = colors image_height, image_width, _ = image.shape - line_width_box = max(round(min(image_height, image_width) / 150), 2) - + + # The line width of the box and the font size are calculated based on the image size and the vis_size + # vis_size is set by the user (0=XS, 1=S, 2=M, 3=L, 4=XL) + if size == 0: # XS + width_factor = 1 / 1200 + font_factor = width_factor * 10 + elif size == 1: # S + width_factor = 1 / 600 + font_factor = width_factor * 8 + elif size == 2: # M + width_factor = 1 / 300 + font_factor = width_factor * 6 + elif size == 3: # L + width_factor = 1 / 150 + font_factor = width_factor * 4 + elif size == 4: # XL + width_factor = 1 / 75 + font_factor = width_factor * 2 + + # calculate the line width of the box + line_width_box = max(round(min(image_height, image_width) * width_factor), 1) _cv2.rectangle(image, (left, top), (right, bottom), color, line_width_box) if label: - - font_height = max(round(min(image_height, image_width) / 40), 15) + + # caluculate the font size + font_height = round(min(image_height, image_width) * font_factor) font = ImageFont.truetype(_FONT_PATH, font_height) label_image = _get_label_image(label, color_text, color, font) From 7522e3d37d42a89241bb20035957bc054a211f8f Mon Sep 17 00:00:00 2001 From: Peter van Lunteren Date: Tue, 21 Oct 2025 10:47:21 +0200 Subject: [PATCH 6/6] Add color conversion functions for hex and contrast --- bounding_box/bounding_box.py | 40 ++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/bounding_box/bounding_box.py b/bounding_box/bounding_box.py index 89555e5..2a8046b 100644 --- a/bounding_box/bounding_box.py +++ b/bounding_box/bounding_box.py @@ -8,6 +8,7 @@ from PIL import ImageFont import numpy as _np from hashlib import md5 as _md5 +import string as _string _LOC = _path.realpath(_path.join(_os.getcwd(),_path.dirname(__file__))) @@ -37,6 +38,26 @@ _FONT_PATH = _os.path.join(_LOC, "Ubuntu-B.ttf") +def _hex_to_rgb(color): + if type(color) is not str: + return None + color = color.strip() + if color.startswith("#"): + color = color[1:] + if len(color) == 3: + color = "".join(ch * 2 for ch in color) + if len(color) != 6 or any(ch not in _string.hexdigits for ch in color): + return None + try: + return tuple(int(color[i:i + 2], 16) for i in (0, 2, 4)) + except ValueError: + return None + +def _get_contrasting_rgb(rgb): + r, g, b = rgb + luminance = 0.299 * r + 0.587 * g + 0.114 * b + return (255, 255, 255) if luminance < 186 else (0, 0, 0) + def _rgb_to_bgr(color): return list(reversed(color)) @@ -77,12 +98,19 @@ def add(image, left, top, right, bottom, label=None, color=None, size=2): if type(color) is not str: raise TypeError("'color' must be a str") - - if color not in _COLOR_NAME_TO_RGB: - msg = "'color' must be one of " + ", ".join(_COLOR_NAME_TO_RGB) - raise ValueError(msg) - - colors = [_rgb_to_bgr(item) for item in _COLOR_NAME_TO_RGB[color]] + color = color.strip() + + if color in _COLOR_NAME_TO_RGB: + box_rgb, text_rgb = _COLOR_NAME_TO_RGB[color] + else: + rgb_color = _hex_to_rgb(color) + if rgb_color is None: + msg = "'color' must be a recognised name or hex value such as #RRGGBB" + raise ValueError(msg) + box_rgb = rgb_color + text_rgb = _get_contrasting_rgb(rgb_color) + + colors = [_rgb_to_bgr(item) for item in (box_rgb, text_rgb)] color, color_text = colors image_height, image_width, _ = image.shape