bbox-visualizer draws labeled boxes from VOC, COCO or YOLO coordinates
Make drawing and labeling bounding boxes a piece of cake
At a glance
- What is it?
- A small OpenCV helper for putting boxes and labels on images, aimed at the label placement arithmetic nobody wants to redo. It accepts three coordinate formats, refuses to draw in place, and takes its version number from git tags, which bites anyone installing from a clone.
- Who is it for?
- Adopt bbox-visualizer if your pipeline already produces boxes in VOC, COCO or YOLO coordinates and you are tired of hand-placing labels on OpenCV frames; the three-format keyword and the strict no-in-place contract are the reasons to. Do not adopt it to reduce dependencies, since opencv-python and numpy are required either way, and skip it if you need batch annotation tooling, because the package draws and labels without producing annotations.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 86 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 11, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Label placement is the work, not the rectangle
A rectangle is four numbers and a `cv2.rectangle` call. Positioning the label so it does not fall off the image, collide with the box or cover the object is the fiddly arithmetic, and that is the part this package takes over. The audience is anyone holding boxes from a detector, or a hand-labelled set, who needs frames a human can look at: annotation reviewers, a debug view, a blog figure. The distribution name and the import name differ, so `pip install bbox-visualizer` gives you a module called `bbox_visualizer`. Coordinates default to Pascal VOC: four absolute pixel values ordered x_min, y_min, x_max, y_max, which is the order the quick start uses for a box of `[150, 100, 450, 300]`. The API carries two spellings for the same drawing call, `draw_box` and `draw_rectangle` as aliases, and `draw_multiple_boxes` and `draw_multiple_rectangles` as aliases, both working identically. One call in the README's style table also passes `is_opaque=True` to `draw_box`.
Three coordinate formats, all funnelled through Pascal VOC
The `bbox_format` keyword is the reason to pick this over writing four lines yourself. Three values are accepted. `"voc"` is the default and takes `[x_min, y_min, x_max, y_max]` in absolute pixels. `"coco"` takes `[x_min, y_min, width, height]`, also absolute pixels, and this is the switch most callers need, since detector output is usually expressed as width and height. `"yolo"` takes `[x_center, y_center, width, height]` normalized to `[0, 1]`, and the README notes that image dimensions are read from the image, so no extra arguments are needed. Internally every format is converted to Pascal VOC before drawing, so the label layout code only ever sees one coordinate system. That has a consequence worth stating plainly: on the YOLO path the meaning of the box depends on the shape of the array it travels with, so a normalized box paired with a resized copy of the original frame describes a different rectangle. The keyword works on the multiple-object variants as well.
Every call returns a new image, so the loop is the costly part
The contract is stated without hedging: all functions return a new image and never modify the input. That is why every line of the quick start reassigns `img`, and why the multi-box example chains two assignments instead of nesting calls. The benefit is that you keep the untouched array and re-render variants from it without reloading the file, which is the right shape for comparing label styles across the same frame. The cost shows up in the pattern the README recommends for full control, a loop around the single-box functions. Because no call writes in place, a loop over N boxes allocates N full width times height times channels arrays, and OpenCV arrays are not lazy. If you are drawing over thousands of boxes on a large frame, the multi-object helpers exist for that reason, and the next section is about what they cost.
Parallel lists, and the README's own advice to skip the helpers
The `draw_multiple_*` and `add_multiple_*` functions take parallel Python lists, one entry per box, and those lists have to stay index aligned:
bboxes = [[150, 100, 450, 300], [500, 50, 700, 250]]
labels = ["person", "dog"]
img = bbv.draw_multiple_boxes(img, bboxes)
img = bbv.add_multiple_labels(img, labels, bboxes)`draw_multiple_boxes` also accepts one colour per box as a list of tuples:
img = bbv.draw_multiple_boxes(img, bboxes, bbox_color=[(0, 255, 0), (0, 0, 255)])Neither helper is mandatory and the README says so directly: they are convenience helpers, and for full control over your visualizations you should call `draw_box` and `add_label` in a loop instead. Worth naming what is not written down: the README does not say what happens when `labels` and `bboxes` differ in length, and mismatched lists are the failure that shows up first when both are assembled from detector output, where the number of detections and the number of surviving labels routinely disagree. The alias note points the same way. If `draw_rectangle` and `draw_box` are the same function, then the choice between the multi-object helpers and the loop is a choice about how much of the per-box configuration you give up, not about which function you call.
A label that does not fit falls back and only tells you through logging
Long class names are the normal case in object detection, and this library handles them by moving the label rather than by failing. Fallback warnings, the README's example being a label that does not fit, travel through Python's `logging` module, and silencing them takes one line:
import logging
logging.getLogger("bbox_visualizer").setLevel(logging.ERROR)The logger name matches the import name, which is worth noting because getting that string wrong leaves the warnings on. The pattern underneath is a deliberate silent degradation: a label that does not fit is repositioned and still rendered, and the only record is a log line that disappears entirely at ERROR level. Raising the level does not make the label fit, it only removes the notice that the layout changed. If the output images feed a review process rather than a debug print, the rendered frame is the thing to inspect, not the console.
The version number comes from git tags, not from a file
`pyproject.toml` marks the version as dynamic and points hatch-vcs at version control, so the number in the wheel is derived from git tags rather than written anywhere in the tree. Tidy, and it has two consequences. Building from a clone that carries no tags yields a version that is not the release you expect, which is the usual explanation for a surprising string in a lockfile. And the release history is compressed: v1.0.0 and v1.0.1 were both published on 2026-07-17, the patch roughly 25 minutes after the first, with v0.3.0 back on 2026-01-29. Against that, the project classifiers claim Development Status :: 5 - Production/Stable while the dev extras pin `ty>=0.0.1a0`, an alpha pre-release of the type checker the checks run. Packaging is hatchling with hatch-fancy-pypi-readme, the licence is MIT, and the hosted documentation sits at bbox-visualizer.readthedocs.io.
make test needs uv, and the dev extras do not list it
The Makefile is the entry point for everything except consuming the package. `make lint`, `make format`, `make typecheck` and `make test` each shell out to `uv run`, invoking ruff, ty and pytest, and `make check` runs lint, typecheck and test in sequence. `make build` goes through `uv run python -m build` and `make install` through `uv pip install .`. The gap is that uv is not in the dev extra set. That list holds build, hatch, twine, pytest, ruff and ty, and uv appears nowhere in it, so a contributor who installs the extras and runs `make test` meets a missing tool rather than a failing test. Two smaller inconsistencies sit alongside it. The `help` target runs a bare `python -c` instead of `uv run`, so it can print help from a different interpreter than the one the checks use. And because the type checker is pinned to a pre-release line, `make typecheck` can break on a dependency bump alone.
The style gallery loses its pictures in the text rendering
The README's guide to label styles is a two-column table: a photograph on the left, the function call that produces it on the right. In the plain text form the left cells come through empty, so the table reads as a list of code snippets with nothing attached. The photos are credited to Unsplash photographers in a note beneath it, the image files sit in `images/` at the repository root, and the full gallery is on the documentation site. The runnable scripts cover the same ground as text: `examples/quickstart.py` works on a blank canvas, `single_object.py` walks every single-object label style including `add_label` with `top=True` and `top=False`, `add_T_label`, `draw_flag_with_label` and a `draw_bg=False` variant, `multiple_objects.py` does the multi-object set, and `label_stress.py` pushes awkward label strings through every style for eyeballing the layout. No rendered output for any of them is included, so the visual choice still has to be made by running the code.
Editorial conclusion
Adopt bbox-visualizer if your pipeline already produces boxes in VOC, COCO or YOLO coordinates and you are tired of hand-placing labels on OpenCV frames; the three-format keyword and the strict no-in-place contract are the reasons to. Do not adopt it to reduce dependencies, since opencv-python and numpy are required either way, and skip it if you need batch annotation tooling, because the package draws and labels without producing annotations. Verify first that the `bbox_format` you pass matches the coordinate order your detector emits, because a COCO width and height read as VOC corners yields a plausible rectangle around the wrong region.
Frequently asked questions
What coordinate format does bbox-visualizer expect by default?
Pascal VOC, ordered `[x_min, y_min, x_max, y_max]` in absolute pixels, given as `bbox_format="voc"` or left at its default. `"coco"` takes `[x_min, y_min, width, height]` in pixels and `"yolo"` takes `[x_center, y_center, width, height]` normalized to `[0, 1]`, with all three converted to Pascal VOC internally before drawing.
Does bbox-visualizer modify the image I pass in?
No. All functions return a new image and never modify the input image, which is why every line of the quick start reassigns `img` rather than relying on in-place changes. The practical consequence is that a loop over many boxes allocates one full image copy per call.
How do I install bbox-visualizer and import it?
Run `pip install bbox-visualizer`, then import it as `bbox_visualizer`, since the distribution name and the module name differ. Python 3.10 or newer is required, and the declared runtime dependencies are opencv-python at 4.8.1.78 or later and numpy at 1.19.0 or later.
How do I silence the bbox-visualizer label warnings?
Set the logger to ERROR, because the fallback warnings, including the case of a label that does not fit, go through Python's `logging` module under the name `bbox_visualizer`. Raising the level suppresses the notice, not the repositioning the library applies to the label.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/shoumikchow-bbox-visualizer)