Coordinate Systems
BlobBoards uses three types of coordinate specifications. Understanding them is essential for correctly extracting and using blob positions.
| Type | Values | Purpose |
|---|---|---|
| Image Origin | :colmap, :opencv, :julia | Pixel coordinate convention |
| Reference Frame | :pattern, :board, :canvas | Spatial origin on boards |
| Coordinate System | :logical, :physical, :world | Unit system for transforms |
Image Origins
Different software uses different conventions for where the "first pixel" is located. BlobBoards stores coordinates internally in :colmap convention.
| Origin | First Pixel Center | Common Usage |
|---|---|---|
:colmap | (0.5, 0.5) | COLMAP, SfM tools, BlobBoards internal |
:opencv | (0, 0) | OpenCV, Python, NumPy |
:julia | (1, 1) | Julia arrays, MATLAB |
Visual Representation
:colmap :opencv :julia
corner at (0,0) corner at (0,0) corner at (0.5,0.5)
↓ ↓ ↓
┌─────────┐ ┌─────────┐ ┌─────────┐
│(0.5,0.5)│ │ (0,0) │ │ (1,1) │
│ • │ │ • │ │ • │
│ pixel │ │ pixel │ │ pixel │
└─────────┘ └─────────┘ └─────────┘Converting Between Image Origins
using BlobBoardsusing CoordinateTransformations: Translation# Image-origin conversion is a Translation — composable, callable on points/Discs/blobsT = Translation(:colmap, :opencv) # Translation(Vec2(-0.5, -0.5) * px)b = blobs(bundle, :pattern) # always in :colmap conventionb_opencv = T.(b) # shift every blob in placeT = Translation(:colmap, :julia)julia_point = T(colmap_point)# Strip units when composing with plain-Float64 mapsunitless = ustrip(T)Reference Frames
For calibration boards, three nested reference frames define where the coordinate origin is located.
| Frame | Origin Location | Use Case |
|---|---|---|
:pattern | Top-left of pattern area | Blob detection, feature matching |
:board | Top-left of board (includes frame) | Full board image processing |
:canvas | Top-left of page (includes margins) | Page layout, printing |
Layout Diagram
◀──────────────────── canvas_size.width ────────────────────▶
┌───────────────────────────────────────────────────────────┐ ▲
│ ○ :canvas origin │ │
│ (0,0) │ │
│ ┌────────────────────────────────────────────────┐ │ │
│ │ ○ :board origin │ │ │
│ │ (0,0) │ │ │
│ │ ┌────────────────────────────────────┐ │ │ │
│ │ │ ○ :pattern origin │ │ │ │
│ │ │ (0,0) │ │ │ │
│ │ │ │ │ │ │
│◀─▶│◀──▶│ ┌───┐ │◀────▶│◀───▶│ │
│ m │ f │ │ ● │ blob │ f │ m │ c
│ a │ r │ └───┘ │ r │ a │ a
│ r │ a │ │ a │ r │ n
│ g │ m │ │ m │ g │ v
│ i │ e │ │ e │ i │ a
│ n │ │ pattern_size │ │ n │ s
│ │ │ │ │ │ _
│ │ │ │ │ │ s
│ │ └────────────────────────────────────┘ │ │ i
│ │ ◀────── pattern_size.width ─────────▶ │ │ z
│ │ │ │ e
│ └────────────────────────────────────────────────┘ │ .
│ ◀───────────── board_size.width ──────────────▶ │ h
│ │ e
└──────────────────────────────────────────────────────────┘ i
g
Legend: h
○ = coordinate origin (top-left corner) t
● = blob center ▼
m = margin (canvas edge to board edge)
f = frame_width (board edge to pattern edge)Converting Between Reference Frames
bundle, _ = load("blob_board.json")# Get Translation between framesT = Translation(board_config(bundle), :pattern, :board)board_point = T(pattern_point)# Or extract blobs directly in desired frameb_pattern = blobs(bundle, :pattern)b_canvas = blobs(bundle, :canvas)Coordinate Values Example
For a blob at pattern coordinates (80px, 120px) with frame_width=75px:
| Frame | Blob Center |
|---|---|
:pattern | (80px, 120px) |
:board | (155px, 195px) |
:canvas | (155px, 195px) (same if no margin) |
Coordinate Systems
For transforms between pixel and physical units, use :logical, :physical, and :world:
| System | Units | Description |
|---|---|---|
:logical | px | Pixel coordinates (requires image_origin) |
:physical | mm | Physical coordinates in pattern frame |
:world | unitless mm | Centered origin, y-axis up |
Logical to Physical Transform
bundle, _ = load("blob_board.json")# Get affine transform from pixels to mmH = AffineMap(bundle, :logical, :physical; image_origin=:julia)# Transform a pixel point to physical coordinatesphysical_pt = H(Point2(100.0, 200.0))Logical to World Transform
# Direct transform from pixels to world frameH = AffineMap(bundle, :logical, :world; image_origin=:opencv)world_pt = H(Point2(100.0, 200.0))Blob Extraction
Unified API
The blobs function takes a reference frame (positional) and optional units:
bundle, _ = load("blob_board.json")# Pixel units (default) in pattern frameb = blobs(bundle, :pattern)# Pixel units in canvas frameb = blobs(bundle, :canvas)# Physical unitsb = blobs(bundle, :pattern; units=mm)b = blobs(bundle, :canvas; units=inch)Saddle Points
For boards with checkerboard frames (:saddle style):
# Pixel unitssp = saddle_points(bundle, :pattern)# Physical unitssp = saddle_points(bundle, :pattern; units=mm)World Coordinates
For 3D calibration (centered origin, y-up, mm):
b = blobs(bundle, :world)# origin at pattern center# x-axis right, y-axis up# units: mm (stripped to Float64)# Lift to 3D points on z=0 planeusing GeometryBasics: Point3X = Point3.(vcat.(centroid.(b), 0.0))Bounding Rectangles
Get rectangles for different regions:
using GeometryBasics: Rectbundle, _ = load("blob_board.json")# Pattern bounds (origin at 0,0)rect = Rect(bundle, :pattern)# Board bounds (extends into negative in pattern coords)rect = Rect(bundle, :board)# rect.origin = (-frame_width, -frame_width) in pattern frameCommon Workflows
Feature Detection Pipeline
bundle, image = load("blob_board.json")# Detect blobs (your detector returns OpenCV convention)detected = detect_blobs(image)# Get ground truth, convert to OpenCV conventiongt = blobs(bundle, :canvas)gt_opencv = Translation(:colmap, :opencv).(gt)# Match and evaluatematches = match_blobs(detected, gt_opencv)Camera Calibration
bundle, image = load("blob_board.json")# Get 3D points (world frame, z=0)b = blobs(bundle, :world)X = Point3.(vcat.(centroid.(b), 0.0))# Get 2D image points (from detection)x = detect_and_match(image, b)# Estimate camera posecamera = estimate_pose(X, x)Unit Conversions
Convert between physical and logical units:
using BlobBoards # re-exports mm, px, dpiusing VisualGeometryCore: uconvertusing GeometryBasics: Point2bundle, _ = BlobBoards.load("blob_board.json")config = board_config(bundle)print_density = config.print_density # e.g., 300 dpi# Use helper functionsscale = px_per_mm(print_density) # pixels per mmscale = mm_per_px(print_density) # mm per pixel (with units)# Physical to logicalphysical_point = Point2(10.0mm, 20.0mm)logical_point = uconvert.(px, physical_point .* print_density)# Logical to physicallogical_point = Point2(100.0px, 200.0px)physical_point = uconvert.(mm, logical_point ./ print_density)Summary Table
| What | Keyword/Argument | Values | Default |
|---|---|---|---|
| Pixel convention | image_origin (keyword) | :colmap, :opencv, :julia | :colmap (internal) |
| Spatial origin | reference frame (positional) | :pattern, :board, :canvas | required |
| Unit system | units (keyword) | px, mm, inch, etc. | px |