Vision system for Very Small Size Soccer (VSSS). Captures from USB or FLIR Spinnaker cameras, detects robots and ball, and broadcasts positions over multicast UDP using the SSL-Vision protobuf schema.
This fork extends the original RoboCIn vss-vision with:
- FLIR Spinnaker camera support (alongside USB cameras and video files)
- ArUco-based robot detector (alternative to the original LUT/blob pipeline)
Qt5 (Core, Widgets, Gui, Network)
OpenCV 4.5+ with contrib (for cv::aruco)
SFML (network, system)
TBB
Protobuf
spdlog (vendored under include/)
FLIR Spinnaker SDK (optional but currently required by CMake — see Spinnaker section)
Install on Ubuntu 22.04:
./InstallDependenciesThe Spinnaker SDK is not in the apt repos and must be installed manually at
/opt/spinnaker/. Until that's optionalised in the build, even USB-only setups
need the SDK present for linking. See docs/ARUCO_PENDING.md.
mkdir -p build && cd build
cmake ..
make -j$(nproc)
cd ../src
./VSS-VISIONThe "Source" panel at the bottom-left of the main window has three tabs:
- Camera — USB camera. Lists
/dev/video*devices. Configurable fromCam Configbutton (usesv4l2-ctlunder the hood). - Video — playback from a file (XML path persisted in
Config/Video.xml). - Spinnaker — FLIR cameras detected via the Spinnaker SDK. Camera parameters (gain/exposure/etc) are not yet exposed in the UI for this source.
Click Capture after choosing a tab and source.
Two robot detection algorithms ship in this build:
| Algorithm | When | What it needs | Pipeline cost |
|---|---|---|---|
| BlobDetection (legacy) | Default | LUT segmentation + RLE compression | Heavier — runs full segmentation pipeline |
| ArUco (new) | Toggle from menu | ArUco markers on robots (DICT_ARUCO_ORIGINAL) | Lighter — skips segmentation/RLE |
Toggle: Configure → Use ArUco Detector (checkbox in the menu bar).
When ArUco is active, Vision::update() skips LUTSegmentation and
RunLengthEncoding, and runs ArucoDetection::runFromFrame() directly on the
BGR frame. The ball is detected via HSV in the same pass.
Each robot slot (3 per team) maps to a single ArUco marker ID. To assign IDs:
- Per-robot: click the gear (⚙) icon next to each robot in the right panel → input dialog asks for the ID.
- Bulk: edit
src/Config/ArucoConfig.jsondirectly. Format:
{
"ROBOT1": 256, "ROBOT2": 272, "ROBOT3": 273,
"ADV1": 771, "ADV2": 939, "ADV3": 955,
"ballHueLow": 6, "ballSatLow": 80, "ballValLow": 100,
"ballHueHigh": 30, "ballSatHigh": 255, "ballValHigh": 255,
"ballMinArea": 250
}Use -1 to disable a slot.
ArUco mode detects the ball by HSV thresholding plus a circularity filter (it rejects elongated/irregular shapes). Defaults are tuned for a bright orange ball but must be re-tuned for your lighting and ball color. To diagnose:
- Log packets (toggle
Configure → Log Network Packets (stdout)) and confirmballs=0is the issue. - Open
Config/ArucoConfig.jsonand adjust:ballHueLow / ballHueHigh— OpenCV uses Hue in[0, 180]. Orange typically lives in[5, 25]. Reds wrap around 0/180 so a red ball needs two ranges, not supported yet.ballSatLow / ballSatHigh—[80, 255]is conservative. Lower S if the ball looks washed-out under your lighting.ballValLow / ballValHigh— drop V floor if the ball is in shadow.ballMinArea— minimum pixel area to accept (defaults to 80). Lower if the ball appears tiny far from the camera.
- Save the file and re-launch (no rebuild needed).
If HSV alone isn't reliable enough (e.g. orange shoes/cables in the field of
view), see docs/ARUCO_PENDING.md — the planned fix is to add an interactive
HSV trackbar calibrator and Kalman-based ball prediction.
Toggle Configure → Log Network Packets (stdout). Every UDP packet sent by
VisionServer is printed to stdout in the format:
[VisionServer] pkt#123 bytes=87 balls=1 blue=3 yellow=0 -> 224.5.23.2:10015
ball x=42.3 y=-17.1 px=(423,-171)
blue id=0 x=10.2 y=5.4 ori=0.785 px=(102,54)
blue id=1 ...
x/y are in mm (cm * 10), pixel_x/pixel_y are in 0.1 px units (cm * 100), matching the SSL-Vision protobuf convention.
ArUco and BlobDetection produce identical UDP packets — the consumer (vsss
software) does not need any change. Both write to vss.setEntities(ball, players) with:
Entity::team()set toColor::BLUE(2) orColor::YELLOW(3)Entity::id()set to(team-1)*100 + localId→ 100..102 (BLUE) or 200..202 (YELLOW)Entity::position()in cm (converted viaUtils::convertPositionPixelToCm)Entity::angle()in radians
VisionServer::send() then serialises into SSL_DetectionRobot /
SSL_DetectionBall and broadcasts on 224.5.23.2:10015.
+------------------+
USB / Spinnaker → | CameraManager | (singleton; picks USB
/ Video | | or Spinnaker per tab)
+--------+---------+
|
cv::Mat | (TBB graph: camera node)
↓
+------------------+
| Vision::update() |
+--------+---------+
|
_useAruco? ← toggle from Configure menu
/ \
no yes
| |
+------------------+ +-----------------------+
| LUTSegmentation | | ArucoDetection |
| + RLE compression| | (cv::aruco + HSV ball)|
| + BlobDetection | | |
+--------+---------+ +-----------+-----------+
| |
+-------------+---------------+
↓
vss.setEntities(ball, players)
↓
VisionServer::send(...)
↓
multicast 224.5.23.2:10015
Singletons in play: Vision, CameraManager, GameInfo (aliased as vss).
Threading uses an Intel TBB flow graph driven by TBBThreadManager.
.
├── include/spdlog/ # vendored logging
├── src/
│ ├── main.cpp # entry point
│ ├── CameraManager/ # USB / video / Spinnaker abstraction
│ ├── Vision/
│ │ ├── Vision.{cpp,h} # facade + algorithm selector
│ │ ├── ImageProcessing/ # WarpCorrection, LUTSegmentation, MaggicSegmentation
│ │ └── PositionProcessing/
│ │ ├── BlobDetection.cpp # legacy LUT/blob detector
│ │ ├── ArucoDetection.cpp # new ArUco-based detector
│ │ ├── PositionProcessing.cpp# shared base for legacy detectors
│ │ └── runlengthencoding.cpp # RLE compression for blob path
│ ├── Network/
│ │ └── visionServer/ # protobuf + multicast UDP
│ ├── Windows/
│ │ ├── MainVSSWindow.{cpp,h,ui} # main window, source tabs, menu
│ │ └── RobotWidget.{cpp,h,ui} # per-robot info widget (gear button → ArUco ID)
│ ├── Entity/ # Entity (id, position, angle, team)
│ ├── GameInfo/ # global game state (vss singleton)
│ ├── Utils/ # geometry, kalman, types
│ ├── TBBThreadManager.{cpp,h} # camera/vision flow graph
│ └── Config/
│ ├── ArucoConfig.json # ArUco marker IDs + ball HSV
│ ├── LUTVideo.xml # LUT segmentation table
│ ├── FieldLimits.xml # perspective correction points
│ ├── CameraConfigD/L.json # USB camera defaults / last-used
│ └── ...
└── docs/
└── ARUCO_PENDING.md # outstanding work for ArUco integration
./docker_build # build image
./docker_run [video id] # run; pass /dev/video<id> for USB camera passthroughNote: the Docker image does not include the Spinnaker SDK, so Spinnaker cameras work only on bare-metal builds at the moment.
If you want to add a third detection algorithm:
- Create
src/Vision/PositionProcessing/MyDetector.{cpp,h}. It does not need to inherit fromPositionProcessingif your algorithm doesn't operate on RLE'd segmented data — seeArucoDetectionfor a standalone example. - Add the files to
CMakeLists.txtunderSOURCESandHEADERS. - In
Vision.h, add a new pointer member and a flag, mirroring_arucoDetector/_useAruco. - In
Vision::update(), branch on the flag and route to your detector. - Your detector must call
vss.setEntities(ball, players)withEntity::id()andEntity::team()matching the convention above (see "Network output"), so the wire format stays compatible. - Add a UI toggle in
MainVSSWindowconstructor (look for theUse ArUco Detectoraction — same pattern).
See docs/ARUCO_PENDING.md for the running list.