Skip to content

deps: OpenCV leaves the default install, and nothing it did goes with it - #56

Merged
rokbenko merged 4 commits into
mainfrom
deps/no-opencv
Nov 2, 2026
Merged

rokbenko merged 4 commits into
mainfrom
deps/no-opencv

Conversation

@rokbenko

@rokbenko rokbenko commented Nov 1, 2026 •

Copy link
Copy Markdown
Owner

OpenCV was half of a default install on disk and most of its download, for a colour threshold and a QR code. Stacked on #55's branch.

Measured, a clean install from built wheels on Windows:

With OpenCV Without
On disk 230 MB 118 MB
Cold install, empty cache 30.1 s 22.5 s
Peak memory of a run 68.9 MB 64.3 MB

Download by platform, from the wheel sizes in uv.lock: Linux x86-64 92.4 to 36.4 MB, macOS arm64 67.4 to 21.2 MB, Windows 61.0 to 30.2 MB, Linux arm64 66.8 to 33.1 MB.

Nothing it did is gone.

  • The default detector does the same arithmetic with numpy, in quackd/perception/blobs.py: OpenCV's 8 bit HSV with its own fixed point tables, its 8 connected outer contours, its polygon areas, and the order it gives ties in, because that decides which ball a robot sees
  • The QR code quackd ui shows a phone comes from segno, pure Python, about 350 KB, nothing under it. It is a base dependency so the code survives any upgrade path
  • quackd[yolo] brings the headless wheel itself, because ultralytics brings the build that wants a display's libraries
  • LeRobot's opencv:// cameras get OpenCV from LeRobot, and the daemons under bridge/ use the robot's own, as they always did

Exact on x86, and what Arm does. tests/test_color_blob_parity.py runs against OpenCV itself, which stays in dev as the oracle: all 16,777,216 colours, 1,500 awkward masks with rings, islands, pinched corners and ties, and every field of detect() on simulator, noisy and saturated frames. It passes on both versions CI locks (OpenCV 5.0 on 3.11, 4.13 on 3.12), and I also ran it on Windows at the floors pyproject.toml allows: numpy 1.26, OpenCV 4.9, Pillow 10.3.

I first wrote that this was the same everywhere to the last bit. macOS CI said otherwise, so I measured every platform GitHub has, with OpenCV 4.13 and 5.0:

numpy against OpenCV
Linux, Windows, Intel Mac (x86) 0 of 16,777,216 colours differ, at any frame width
Apple Silicon, Linux on Arm, a full row 6,582 colours, saturation one step apart, numpy the higher
Same, the last columns of a frame whose width 8 does not divide 123,444 colours, saturation one step either way

OpenCV's Arm build disagrees with itself: rgb(1,2,1) reads saturation 127 in a row of 16 and 128 in the last columns of a row of 17. numpy gives one table on every machine, the x86 one, checked by hash on all five platforms that could run it. Windows on Arm could not run it: OpenCV 4.13 has no wheel there, and cryptography would not build.

What that reaches:

  • Hue and value never differ
  • Every colour was checked on both code paths: no colour crosses the threshold of any target quackd ships, and a mask is made of nothing else, so those detections are the same to the last bit on every machine
  • 670 frames of normal widths and 720 of odd widths on Arm: 0 detections changed on the shipped targets
  • A target tuned by hand on Arm can gain up to 1,158 colours if its saturation floor is one of 11 values, and more in the last few columns of an odd width. At floors I picked to be the worst, 1 frame in 720 changed, by 0.16% of a blob's area and a millimetre of distance, and nothing appeared or vanished

The test that reads every colour stays exact off Arm, at three widths now. A new test holds the above on macOS CI.

Speed per frame, seven targets, OpenCV 4.13 beside numpy:

Frame OpenCV numpy
clean, 256 px 1.57 ms 2.70 ms
clean, 640x480 7.20 ms 8.85 ms
camera noise, 640x480 30.5 ms 29.9 ms
saturated noise 20% 231 ms 72 ms
saturated noise 100% 685 ms 341 ms

Detection runs on the event loop that feeds the deadman, so the worst frames are the ones that matter, and those got quicker. On Apple Silicon, Linux on Arm and Intel Macs, a clean or noisy 640x480 frame costs 1.4 to 1.7 times what OpenCV took, and saturated frames take 0.3 to 0.7 times.

What changes for a reader: quackd doctor has one row fewer, opencv, in its table and in --json. That is the only difference in every command's help, the MCP tools, every verb and duck, doctor's report and eleven scripted runs against 0.29.1. A clean install with no OpenCV ran find-and-kick to a pass on the simulator.

🤖 Generated with Claude Code

rokbenko and others added 3 commits November 1, 2026 19:12
OpenCV was half of a default install on disk and most of its download, for a
colour threshold and a QR code. A clean install goes from 230 MB to 118 MB and a
cold one from 30 s to 22 s on Windows, and the download falls 50 to 69% by
platform.

quackd/perception/blobs.py does the detector's arithmetic with numpy, to the last
bit: OpenCV's 8-bit HSV with its own fixed-point tables, its 8-connected outer
contours, its polygon areas, and the order it gives ties in, because that decides
which ball a robot sees. tests/test_color_blob_parity.py holds all of it against
OpenCV itself, every colour there is included, on 5.0 and 4.13, and it passed at
the declared floors of numpy, OpenCV and Pillow too. segno, pure Python and about
350 KB, draws quackd ui's QR code, and the codes read back as before.

quackd[yolo] brings the headless wheel itself, because ultralytics brings the build
that wants a display's libraries. dev carries OpenCV as the oracle. doctor loses
its opencv row, the one difference in every command's help, the MCP tools, every
verb and duck, doctor's report and eleven scripted runs against 0.29.1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…wo before it

doctor.py: this branch's words about the walk over the other's cached walk. It
names PIL and zmq outright now, so the comment's example is yaml. Both added
under [Unreleased] and both are kept, Changed with Changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…race, fixed below

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ests and the changelog say so

macOS CI went red on the parity tests, and I had written that a detection is the same to the last
bit. Measured on Apple Silicon, Intel Macs, Linux and Windows, on x86 and Arm, with OpenCV 4.13 and
5.0: numpy gives one table on all of them and x86 OpenCV gives the same table at any frame width.
On Arm OpenCV reads 6,582 of the 16,777,216 colours a saturation step lower through its NEON code,
and 123,444 a step either way through the code for what is left of a row. Hue and value never move.

No colour crosses the threshold of a target quackd ships on either path, so the masks are the same
and so is everything made from them. A target tuned by hand on Arm can gain up to 1,158 colours if
its saturation floor is one of 11 values, and more in the last few columns of a frame whose width 8
does not divide. On 720 frames of odd widths at floors picked to be the worst, one detection moved,
by 0.16% of its area and a millimetre of distance. Nothing appeared or vanished.

So the test that reads every colour stays exact off Arm, at three widths now, and a new one holds
what the difference can reach on Arm. The prefilter and range tests were never about OpenCV's
rounding, so they read numpy's conversion, which the first test holds to OpenCV.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rokbenko
rokbenko changed the base branch from perf/doctor-and-start to main November 2, 2026 06:12
@rokbenko
rokbenko merged commit 9f3222d into main Nov 2, 2026
12 checks passed
rokbenko added a commit that referenced this pull request Nov 2, 2026
…s an outline in Python

The review before tagging 0.30.0 found what the five frames I had timed could not: a long edge is what
a walk in Python pays for. A comb of one pixel stripes took 417 ms where OpenCV took 8, past the line
PR #56 set for itself, on the event loop that feeds a robot's deadman.

An outer border's polygon needs no walk. With what a blob encloses filled in, it holds a whole 2 x 2
window of pixels where all 4 are set and half of one where 3 are, and each such window belongs to one
pair of overlapping runs, which labelling finds anyway. Filling in also drops a blob inside another's
hole, which the walk was also there to find. The labelling itself now drops a pair once its two runs
are together.

This takes the worst of it and not all of it. A frame of many short runs still costs several times what
OpenCV took, because a mask and its background are each labelled run by run.

Held to OpenCV by every mask up to 4 x 3 and 2 x 6 in the suite, by frame-sized masks of nothing but
edge, and once outside it by every mask of 4 x 5 and 5 x 4, 2,097,152 of them, none different.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
rokbenko added a commit that referenced this pull request Nov 2, 2026
A cleanup. A default install is half the size with OpenCV out of it, doctor and help answer sooner, CI's
test jobs take 5 to 11 minutes, and four holes in the robot daemons are closed. A minor, by its Added and
Changed.

The ten versions and their windows are 0.30.0 and <0.31, by scripts/set_version.py, and the window's
copies in CONTRIBUTING.md, the adapter guide and ADR-0037, the policy lines quoted in the LeRobot guide
and the README's Status line follow by hand.

The read against the code took two reviews and changed more than a release usually does. They found
frames far past the speed line PR #56 set itself, which the first of the two commits before this one
answers as far as it can: the entry on what a frame costs is new and says what stays slower. They also
found that the nightly had never passed and the count quoted for it was from before this release's code,
so that count is its first run. The rest are sentences that said more than was measured: which requests
were compared, which frames are the worst, which installs have no advisory, four presets not five, and
over a third of help's wait, not most. Every shipped section is as v0.29.1 has it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rokbenko
rokbenko deleted the deps/no-opencv branch November 2, 2026 13:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant