8. Troubleshooting¶
The marker is not detected¶
Work through these in order:
- Right configuration? A
FRACTAL_4L_6detector does not see aFRACTAL_5L_6print. - Big enough? The external marker needs ~45 px of side in the image
(about 3 px per cell, after
detection_scale). Move closer, raise the resolution or removedetection_scale. - Quiet zone. The black border must be surrounded by white (≥ 1 cell). A marker printed edge-to-edge on a dark table, or touching the image border, is not found.
- Mirrored image? A mirrored marker reads as a different code; some front cameras and screen grabs mirror the image — flip it back.
- Contrast. Glare, very dim light or grey print: try
adaptive_c = 4. - Distortion. Wide-angle lenses bend the edges; detect on the raw frame
and try
approx_poly_rate = 0.07(or undistort first). - Blur. Motion blur breaks the cell pattern; shorten the exposure.
- Cut by the frame edge? At least one level must be whole in the frame. Close to the marker, if the camera looks past its centre, every level can be cut — then nothing is found (on a landing video this was 59 of the 62 frames without a detection).
- Flickers at the edge of range? Read in one frame, not in the next:
FractalTracker(detector, use_prior=True)ordetect(frame, prior=last)keeps it; those levels reportbit_errors > 0. - Found late on the approach? With 2.5–3 px per cell and an
overexposed print,
diagnoseshows the marker's square a few cells off for many frames before the first read:FractalTracker(detector, capture_frames=3), and, if the height is known,expected_side_px=(lo, hi). Also lower the exposure, or use a matte print — the white of a glossy sheet saturates and the black cells turn grey. - Very close? Cells of 60 px and more are far larger than the automatic
threshold block (11 px at 1280 wide); on a dark frame the contours then
fall apart.
params.close_pass(on by default) retries such frames on a shrunken copy; if it is off, or the frame is huge, setadaptive_block_sizeto about a cell (31–61).
Look at what the detector sees with the drawing helper:
import numpy as np
import nanofractal as nf
det = nf.FractalDetector("FRACTAL_5L_6")
frame = np.full((600, 600), 255, np.uint8)
frame[100:500, 100:500] = nf.generate_fractal("FRACTAL_5L_6", 400)
res = det.detect(frame)
overlay = det.draw(frame, res, inplace=False)
print(res, overlay.shape) # save `overlay` and inspect it
Why a candidate was dropped¶
diagnose runs the same detection and lists every convex quad it looked at,
with the reason it was kept or dropped and how close it came to a real code:
frame = np.full((600, 600), 255, np.uint8)
frame[100:500, 100:500] = nf.generate_fractal("FRACTAL_5L_6", 400)
frame[136:156, 186:206] ^= 255 # damage one cell of the outer level
d = det.diagnose(frame)
print(d) # counts by reason
for q in d.quads:
if q.reason in ("border", "bits") and q.bit_errors <= 3:
print(q.nearest_id, q.bit_errors, q.contrast) # nearly a marker
reason |
meaning |
|---|---|
"accepted" |
decoded and returned |
"duplicate" |
decoded, but a larger quad of the same level was kept (the same square traced twice, or a second copy of the marker) |
"border" |
some cells of the black frame read white |
"bits" |
the frame is black, but no code matches |
"inconsistent" |
decoded, but not where the other levels put it (dropped, see inconsistent_ids) |
A quad of the marker's size that misses its id by one to three bits
(bit_errors) is a reading problem: too few pixels per cell, blur, or a
shadow edge across it (low contrast). Quads with many wrong bits are other
squares in the scene. Contours shorter than min_contour_size are not
listed; n_not_quad counts the ones that are not a convex quad.
The pose jumps or is wrong¶
- Check
reproj_err: above ~2 px the correspondences or the intrinsics are off. Gate on it — or better, weigh the pose byPose.cov(sd_t) (estimate_pose(..., full=True)) and the levels'cell_px/bit_errors. reproj_err_altclose toreproj_err(full=True): the tilt is ambiguous (small, nearly frontal marker); pick the solution with an IMU or gravity.- Use
with_inner_points=True— four corners of a small marker leave the rotation poorly constrained (the classic planar flip ambiguity). - Make sure
marker_sizeis the printed black square side, in the unit you expect the translation in. - Wrong or missing distortion coefficients bend everything near the image edges; calibrate.
- Smooth with
PoseSmootherand reset it when the marker is lost.
Errors¶
| error | cause |
|---|---|
TypeError in detect |
not uint8, or not C-contiguous → np.ascontiguousarray(img, dtype=np.uint8) |
ValueError: ... shape |
4-channel (BGRA) or 1-D input → convert to gray or BGR |
ValueError: invalid config |
typo in the configuration name |
ValueError: adaptive_block_size ... (or another field) |
parameter out of range, see Tuning |
ValueError: roi ... |
ROI outside the image or empty |
ValueError in estimate_pose |
camera_matrix not 3×3, unsupported number of distortion coefficients, NaN/inf values, or fisheye=True without exactly 4 coefficients |
Performance is lower than expected¶
- Call
nf.get_num_threads(): something may have set it to 1. - Several streams at once? See Video.
- Running
python -m nanofractal.benchshows what your machine does on the reference scenes; compare with Performance.