600 lines
26 KiB
Python
600 lines
26 KiB
Python
# config.py
|
|
|
|
# ============================================================
|
|
# Screen
|
|
# ============================================================
|
|
|
|
SCREEN_WIDTH = 1530
|
|
SCREEN_HEIGHT = 920
|
|
|
|
|
|
# ============================================================
|
|
# Camera
|
|
# ============================================================
|
|
|
|
# CAMERA_URL = "http://172.30.2.62:8080/video"
|
|
# CAMERA_URL = "http://172.30.1.216:4747/video"
|
|
CAMERA_URL = "http://192.168.1.194:4747/video"
|
|
|
|
# How long to wait between attempts to (re)connect to the camera
|
|
# when it is unreachable - both on startup (phone app not open yet)
|
|
# and whenever a reconnect is needed mid-show. The game keeps
|
|
# retrying forever instead of crashing, since this runs unattended
|
|
# in a booth and nobody may be there to relaunch it.
|
|
CAMERA_CONNECT_RETRY_SECONDS = 2.0
|
|
|
|
|
|
# ============================================================
|
|
# Detection intervals
|
|
# ============================================================
|
|
|
|
# Both of these run on a background vision thread (see vision.py),
|
|
# never on the render loop, so they can no longer cause frame
|
|
# hitches. Obstacle detection works on a downscaled image (see
|
|
# DETECTION_SCALE) and is cheap enough to run 4x per second, which
|
|
# makes moving a sticker feel responsive.
|
|
MONITOR_DETECTION_INTERVAL = 1.0
|
|
OBSTACLE_DETECTION_INTERVAL = 0.25
|
|
|
|
|
|
# ============================================================
|
|
# Calibration - screen border pattern
|
|
# ============================================================
|
|
#
|
|
# Instead of four small corner markers, the monitor draws a
|
|
# magenta/pink outer ring with a white inner ring around the edge
|
|
# of the screen, permanently (during both calibration and normal
|
|
# gameplay - see Renderer.draw_calibration_border). This is much
|
|
# more robust in a busy, noisy exhibition hall than four small
|
|
# markers:
|
|
# - it's a single large, high-contrast shape instead of four
|
|
# tiny independent ones, so a person briefly walking in front
|
|
# of one corner doesn't break detection the way losing one out
|
|
# of four markers would;
|
|
# - requiring BOTH the pink AND the white ring to be present
|
|
# (in the right relative position) makes it very unlikely that
|
|
# a random pink or white object/reflection in the room could
|
|
# be mistaken for the screen.
|
|
|
|
# Colors drawn on the monitor itself (RGB, used by pygame).
|
|
#
|
|
# Why GREEN instead of magenta/pink: the outer ring only has to be
|
|
# the single easiest possible color for a CAMERA (not a human eye)
|
|
# to pick out of a crowded, oddly-lit exhibition hall, and green is
|
|
# the best pick available for three concrete reasons:
|
|
# 1. Every consumer camera sensor uses a Bayer filter with TWICE
|
|
# as many green photosites as red or blue (the pattern a human
|
|
# eye is most sensitive to), so the green channel is always
|
|
# the least noisy, highest-resolution channel the camera has -
|
|
# this is exactly why film/broadcast green-screens are green
|
|
# and not some other color.
|
|
# 2. Pink/magenta sits close to skin tones and to a lot of common
|
|
# clothing under warm (incandescent/halogen) exhibition
|
|
# lighting, which white-balance algorithms can shift toward
|
|
# orange/red - pushing it out of a fixed HSV window entirely.
|
|
# Saturated green has no such close neighbour in a crowd of
|
|
# people, and survives white-balance shifts far better (most
|
|
# auto white balance implementations anchor their correction
|
|
# around making genuinely neutral/gray areas look neutral,
|
|
# which leaves a strongly green-dominant pixel green-dominant
|
|
# under almost any color temperature).
|
|
# 3. It pairs well with the detection method below: "is the green
|
|
# channel clearly stronger than red AND blue" is a RELATIVE
|
|
# test, so it survives brightness/exposure changes (someone's
|
|
# shadow, a spotlight) that would otherwise shift an absolute
|
|
# HSV threshold out of range.
|
|
CALIBRATION_PATTERN_OUTER_COLOR = (40, 255, 60) # saturated green
|
|
CALIBRATION_PATTERN_INNER_COLOR = (255, 255, 255) # white
|
|
|
|
# Thickness of each ring, in screen pixels (at the SCREEN_WIDTH x
|
|
# SCREEN_HEIGHT design resolution above).
|
|
CALIBRATION_OUTER_BORDER_SIZE = 40
|
|
CALIBRATION_INNER_BORDER_SIZE = 26
|
|
|
|
# ------------------------------------------------------------
|
|
# Target-color detection
|
|
# ------------------------------------------------------------
|
|
#
|
|
# The exact color the camera records for CALIBRATION_PATTERN_OUTER_
|
|
# COLOR depends on the camera's white balance/exposure and the
|
|
# room's lighting, and can drift over the course of a show (the
|
|
# hall's lighting changing, the phone's camera re-adjusting
|
|
# exposure, etc.) - a single fixed HSV box, tuned once, is exactly
|
|
# what used to make detection "work great at setup time, then get
|
|
# flaky an hour into the exhibition". So the mask that finds the
|
|
# outer ring is now built from two cooperating, auto-adapting
|
|
# pieces instead of one fixed threshold (see Calibration.
|
|
# _make_target_mask for the implementation):
|
|
#
|
|
# 1. A wide, loose hue pre-filter (this section) that only rules
|
|
# out colors nowhere near green at all (reds, blues, skin,
|
|
# gray) - it does NOT try to pin down the exact shade.
|
|
# 2. Within whatever passes that pre-filter, a per-frame Otsu
|
|
# threshold on how green-dominant each pixel is (how far G
|
|
# exceeds R and B) - Otsu finds the split point that best
|
|
# separates "clearly green" from "not" IN THAT FRAME, so it
|
|
# re-adapts itself to the current lighting automatically,
|
|
# frame by frame, with no fixed brightness/saturation number
|
|
# to go stale.
|
|
#
|
|
# CALIBRATION_TARGET_HUE_DEGREES/TOLERANCE describe the loose
|
|
# pre-filter in standard 0-360 degree hue (green = 120 degrees).
|
|
CALIBRATION_TARGET_HUE_DEGREES = 120
|
|
CALIBRATION_TARGET_HUE_TOLERANCE_DEGREES = 45
|
|
|
|
# Loose floor for saturation/brightness in the pre-filter - just
|
|
# enough to exclude flat gray/black/white areas, not to pin an
|
|
# exact shade (that's Otsu's job).
|
|
CALIBRATION_MIN_TARGET_SATURATION = 50
|
|
CALIBRATION_MIN_TARGET_VALUE = 35
|
|
|
|
# Once locked on, the system also WATCHES the hue it is actually
|
|
# seeing inside the confirmed ring and slowly re-centers the
|
|
# pre-filter on it (see Calibration._update_learned_hue). This is
|
|
# what lets it track a slow lighting drift over the course of a
|
|
# multi-hour show instead of needing to be re-tuned on-site.
|
|
# ADAPTIVE_MAX_DRIFT_DEGREES bounds how far that learned center may
|
|
# ever wander from the true CALIBRATION_TARGET_HUE_DEGREES, so a
|
|
# run of bad detections can never "learn" its way onto some
|
|
# unrelated color.
|
|
CALIBRATION_ADAPTIVE_HUE_ENABLED = True
|
|
CALIBRATION_ADAPTIVE_HUE_BLEND = 0.08
|
|
CALIBRATION_ADAPTIVE_MAX_DRIFT_DEGREES = 30
|
|
|
|
# The white inner ring is found the same way detection.py finds
|
|
# white stickers (see config's "White obstacle detection" section
|
|
# for the full reasoning): a LOCAL adaptive brightness test rather
|
|
# than one fixed floor for the whole frame, and LAB chroma rather
|
|
# than HSV saturation for "is this actually neutral". A fixed HSV
|
|
# box here was just as fragile under real exhibition lighting as the
|
|
# old fixed box was for stickers.
|
|
CALIBRATION_WHITE_ADAPTIVE_OFFSET = 8
|
|
CALIBRATION_WHITE_MIN_ABSOLUTE_LIGHTNESS = 50
|
|
|
|
# One-sided: the only other bright thing nearby is the green ring
|
|
# itself, so it's enough to exclude clearly GREEN-dominant pixels
|
|
# rather than pin an exact "how white" number - see the comment in
|
|
# Calibration._make_white_mask for why a two-sided chroma ceiling
|
|
# can't reliably cover every possible lighting cast but this can.
|
|
CALIBRATION_WHITE_MAX_GREEN_DOMINANCE = 50
|
|
|
|
# The white inner ring also serves as a live "gray card": its mean
|
|
# LAB (a*, b*) as the camera currently records it is continuously
|
|
# learned (slow exponential blend) and handed to detection.py, which
|
|
# judges a candidate sticker's whiteness relative to THAT instead of
|
|
# absolute neutral - see Calibration.white_reference(). Colored
|
|
# ambient light shifts a genuinely white/neutral surface's recorded
|
|
# color for physical reasons no fixed threshold can see around; this
|
|
# is what lets the sticker detector stay correct anyway, and track
|
|
# the room's lighting changing over a long show without re-tuning.
|
|
CALIBRATION_WHITE_REFERENCE_BLEND = 0.05
|
|
|
|
# How far (LAB a/b units) the learned white point may ever drift
|
|
# from true neutral, so a run of bad samples can never teach the
|
|
# sticker detector a wildly wrong idea of white.
|
|
CALIBRATION_WHITE_REFERENCE_MAX_OFFSET = 40.0
|
|
|
|
# The detected quad's area, as a fraction of the full camera frame,
|
|
# must fall in this range. Filters out both tiny false positives
|
|
# (reflections, noise) and implausibly large ones.
|
|
CALIBRATION_MIN_SCREEN_AREA_RATIO = 0.10
|
|
CALIBRATION_MAX_SCREEN_AREA_RATIO = 0.90
|
|
|
|
# How much of the sampled outer/inner ring band must actually match
|
|
# its expected color for a candidate to be accepted.
|
|
CALIBRATION_MIN_OUTER_TARGET_RATIO = 0.55
|
|
CALIBRATION_MIN_INNER_WHITE_RATIO = 0.40
|
|
|
|
# Combined quality/color score (0-1) required before a detection is
|
|
# even considered as a calibration candidate.
|
|
CALIBRATION_MIN_QUALITY = 0.50
|
|
|
|
# While NOT yet calibrated, the whole screen is filled solid green
|
|
# (Renderer.draw_full_calibration_screen) instead of just the thin
|
|
# border ring. This gives the very first lock-on the simplest,
|
|
# most unambiguous target possible: one huge solid-color rectangle
|
|
# with nothing else on screen. This is the fraction of that
|
|
# candidate quad's own interior that must actually be green for it
|
|
# to count - it naturally stays low while someone is standing in
|
|
# front of part of the monitor, so a person walking through frame
|
|
# during calibration just delays the lock instead of causing a bad
|
|
# one.
|
|
CALIBRATION_MIN_FULL_TARGET_RATIO = 0.85
|
|
|
|
# Temporal stability: how many consecutive matching detections are
|
|
# required before (re)locking calibration, and how far (as a
|
|
# fraction of the camera frame's diagonal) the corners are allowed
|
|
# to drift between frames and still count as "the same" detection.
|
|
# This is what lets the system ignore a person briefly walking
|
|
# through frame instead of resetting calibration.
|
|
CALIBRATION_REQUIRED_STABLE_FRAMES = 5
|
|
CALIBRATION_MAX_CORNER_JUMP_RATIO = 0.035
|
|
|
|
# Exponential smoothing applied to the accepted corner positions on
|
|
# every successful re-detection once already calibrated, so small
|
|
# frame-to-frame jitter doesn't make the transform wobble.
|
|
CALIBRATION_SMOOTHING_ALPHA = 0.25
|
|
|
|
# Morphological closing kernel (pixels) used to bridge small gaps
|
|
# in the color mask caused by glare/reflections on the monitor
|
|
# glass. Bigger = more tolerant of glare, but also more likely to
|
|
# merge in nearby unrelated green/white blobs - tune together with
|
|
# the hue settings above.
|
|
CALIBRATION_MASK_CLOSE_KERNEL = 17
|
|
|
|
|
|
# ============================================================
|
|
# Camera feed health
|
|
# ============================================================
|
|
#
|
|
# If the raw camera feed barely changes for this long, treat it as
|
|
# a stuck/cached/frozen frame (phone screen locked, streaming app
|
|
# paused in the background, stale network buffer, dead wifi
|
|
# re-serving the last JPEG, lens covered, etc.) rather than a live
|
|
# view, and don't trust it for calibration or obstacle detection.
|
|
CAMERA_FROZEN_DIFF_THRESHOLD = 0.6
|
|
CAMERA_FROZEN_SECONDS = 2.0
|
|
|
|
|
|
# ============================================================
|
|
# Rendering
|
|
# ============================================================
|
|
|
|
BACKGROUND_COLOR = (0, 0, 0)
|
|
|
|
TARGET_FPS = 144
|
|
|
|
# Only repaint (and push to the display) the small screen regions
|
|
# that actually changed since the last frame, instead of redrawing
|
|
# and re-uploading all 1920x1080 pixels 144 times a second. On most
|
|
# machines this is the single biggest rendering speed-up. If you
|
|
# ever see stale "ghost" pixels on the monitor (a driver that
|
|
# ignores partial updates), set this to False - everything still
|
|
# works, just with more CPU/GPU load.
|
|
USE_DIRTY_RECTS = True
|
|
|
|
# Safety net for USE_DIRTY_RECTS: force one complete repaint every
|
|
# this many seconds, so even a misbehaving driver can't leave a
|
|
# smudge on the screen for long.
|
|
FULL_REPAINT_SECONDS = 10.0
|
|
|
|
|
|
# ============================================================
|
|
# Visual effects (purely cosmetic - never affect physics)
|
|
# ============================================================
|
|
#
|
|
# Master switch. Everything below is drawn ONLY with saturated
|
|
# colors (never white blobs) and only in small, dim glows, because
|
|
# the camera is looking at this same screen and must never mistake
|
|
# an effect for a white sticker. If detection ever misbehaves and
|
|
# you suspect the visuals, flip this to False first to find out.
|
|
VISUAL_EFFECTS_ENABLED = True
|
|
|
|
# Each ball gets its own hue (degrees, 0-360) from this range. The
|
|
# default skips magenta/pink (roughly 280-350) on purpose: that is
|
|
# the calibration color, and it must stay unique to the calibration
|
|
# border so the camera never confuses the two.
|
|
BALL_HUE_MIN_DEGREES = 15
|
|
BALL_HUE_MAX_DEGREES = 255
|
|
|
|
# Every time a ball bounces off a sticker its hue shifts by this
|
|
# many degrees (staying inside the range above) - a small, very
|
|
# visible "the sticker reacted to me" cue that people love.
|
|
BALL_HUE_SHIFT_ON_BOUNCE = 22
|
|
|
|
# Fading comet tail behind each ball: how many past positions it
|
|
# remembers, and how often (seconds) a new one is recorded.
|
|
BALL_TRAIL_LENGTH = 16
|
|
BALL_TRAIL_SAMPLE_SECONDS = 1.0 / 60.0
|
|
|
|
# Soft colored glow around each ball (brighter the faster it moves).
|
|
BALL_GLOW_ENABLED = True
|
|
|
|
# Springy squash-and-stretch: balls elongate along their direction
|
|
# of travel with speed, and squash flat for a moment on a hard hit.
|
|
BALL_SQUASH_STRETCH_ENABLED = True
|
|
BALL_SQUASH_DURATION_SECONDS = 0.16
|
|
|
|
# Sparks + expanding ring on a bounce whose impact speed (px/s)
|
|
# exceeds this; gentler taps and resting contact produce nothing,
|
|
# so a ball lying still on a sticker stays calm.
|
|
IMPACT_EFFECT_MIN_SPEED = 160.0
|
|
MAX_PARTICLES = 260
|
|
|
|
|
|
# ============================================================
|
|
# Detected obstacles (what is painted back onto the monitor)
|
|
# ============================================================
|
|
#
|
|
# When True, every obstacle the camera currently sees is also
|
|
# painted white on the game's own screen (i.e. on the physical
|
|
# monitor itself), where it was detected - as the real tilted
|
|
# rectangle, not an axis-aligned box. When False, nothing extra is
|
|
# drawn for it. Either way this is purely visual - balls always
|
|
# collide with every currently-detected obstacle regardless of this
|
|
# flag; it never affects physics.
|
|
SHOW_DETECTED_OBSTACLES = False
|
|
|
|
DETECTED_OBSTACLE_COLOR = (255, 255, 255)
|
|
DETECTED_OBSTACLE_BORDER_WIDTH = 0
|
|
|
|
# The painted white is deliberately drawn a little SMALLER than the
|
|
# sticker (this fraction of the sticker's short side is trimmed off
|
|
# each edge). Reason: the camera looks at this very screen. If the
|
|
# painted white were exactly sticker-sized, then after someone
|
|
# removes the sticker the camera would keep seeing the painted
|
|
# white and "detect" the (now nonexistent) sticker forever. Painting
|
|
# inset keeps it hidden under the real sticker, and the detector
|
|
# additionally blanks out exactly the area that was painted before
|
|
# looking for stickers (see detection.py), so a removed sticker
|
|
# really disappears.
|
|
DETECTED_OBSTACLE_PAINT_INSET_RATIO = 0.22
|
|
|
|
|
|
# ============================================================
|
|
# Ball physics
|
|
# ============================================================
|
|
|
|
BALL_RADIUS = 15
|
|
|
|
# Each ball's radius is randomly scaled by up to +/- this fraction
|
|
# (bigger balls are also heavier in ball-vs-ball collisions).
|
|
BALL_RADIUS_VARIATION = 0.25
|
|
|
|
BALL_GRAVITY = 900
|
|
|
|
# Fraction of normal speed kept after bouncing off a sticker or a
|
|
# screen edge (1.0 = perfectly elastic: with gravity, a ball just
|
|
# keeps bouncing at the same energy forever and never settles down,
|
|
# which is the default here). Lower it (e.g. ~0.75) for a "real
|
|
# rubber ball" feel that loses a little height on every bounce
|
|
# instead. Very slow contact against a STICKER is still always
|
|
# treated as fully inelastic (BALL_REST_SPEED) so a ball resting on
|
|
# one doesn't buzz at the pixel level; screen edges always bounce at
|
|
# the full BALL_RESTITUTION regardless of speed.
|
|
BALL_RESTITUTION = 1.0
|
|
BALL_REST_SPEED = 55.0
|
|
|
|
# Fraction of sliding (tangential) speed lost per bounce off a
|
|
# sticker - what makes a ball roll/slide along a tilted sticker
|
|
# instead of skating on ice. 0 = no energy lost this way either (the
|
|
# default, paired with BALL_RESTITUTION = 1.0 above); raise it for
|
|
# more of a "rolling to a stop on a ramp" feel.
|
|
BALL_FRICTION = 0.0
|
|
|
|
# Safety cap on speed (px/s), so a freak event can never launch a
|
|
# ball fast enough to tunnel through a sticker.
|
|
BALL_MAX_SPEED = 2000.0
|
|
|
|
# Balls collide with each other: overlapping balls push apart and
|
|
# exchange momentum (heavier balls push lighter ones around). 1.0 =
|
|
# no energy lost in the collision either, matching the walls/
|
|
# stickers above.
|
|
BALL_BALL_COLLISION_ENABLED = True
|
|
BALL_BALL_RESTITUTION = 1.0
|
|
|
|
# Physics runs in equal slices of at most this many seconds, so
|
|
# nothing tunnels even on a slow frame.
|
|
PHYSICS_MAX_STEP_SECONDS = 1.0 / 240.0
|
|
|
|
|
|
# ============================================================
|
|
# Ball spawning and stuck balls
|
|
# ============================================================
|
|
#
|
|
# The two settings that matter most, kept together and right up
|
|
# front so they're easy to find and tune:
|
|
#
|
|
# A new ball drops in every BALL_SPAWN_INTERVAL_SECONDS, on its own
|
|
# clock, completely independent of what earlier balls are doing or
|
|
# whether they've disappeared yet.
|
|
BALL_SPAWN_INTERVAL_SECONDS = 2.5
|
|
|
|
# ============================================================
|
|
# Launcher configuration
|
|
# ============================================================
|
|
#
|
|
# Attached to top-center of the screen.
|
|
# Angles are in degrees relative to straight down (0 = straight down,
|
|
# negative = left, positive = right).
|
|
LAUNCHER_MIN_ANGLE = -70.0
|
|
LAUNCHER_MAX_ANGLE = 70.0
|
|
|
|
# Dimensions of the launcher tube in screen pixels.
|
|
LAUNCHER_LENGTH = 75.0
|
|
LAUNCHER_WIDTH = 26.0
|
|
|
|
# Angular rotation speed (controls continuous smooth movement).
|
|
LAUNCHER_ROTATION_SPEED = 35.0
|
|
|
|
# Initial ball speed when exiting the nozzle (px/s).
|
|
LAUNCHER_BALL_SPAWN_SPEED = 280.0
|
|
|
|
# Every ball is removed (with a little pop effect, see effects.py)
|
|
# this many seconds after it was spawned, whatever else is
|
|
# happening to it. 0 = balls live forever (until/unless the stuck-
|
|
# timeout below removes them instead).
|
|
BALL_MAX_LIFETIME_SECONDS = 20.0
|
|
|
|
# --- Everything below is secondary detail for the two settings above ---
|
|
|
|
# Hard ceiling on balls alive at once - only a safety net so trapped
|
|
# balls can never accumulate without limit over a long show.
|
|
MAX_SIMULTANEOUS_BALLS = 12
|
|
|
|
# Random horizontal drop position. With BALL_SPAWN_RANDOM_X = False
|
|
# every ball drops from the middle of the screen (the old
|
|
# behavior). Margin keeps drops away from the side walls.
|
|
BALL_SPAWN_RANDOM_X = True
|
|
BALL_SPAWN_MARGIN = 120
|
|
BALL_SPAWN_VELOCITY_JITTER = 60.0
|
|
|
|
# A ball is "stuck" if it has stayed within BALL_STUCK_DISTANCE
|
|
# pixels of one spot for BALL_STUCK_TIMEOUT_SECONDS - usually a
|
|
# pocket between stickers, but with every screen edge now a solid
|
|
# wall (see physics.py) a ball resting on an empty floor with no
|
|
# obstacles nearby counts too. A stuck ball stays put for that long
|
|
# (so visitors can see it) and is then removed with a small pop,
|
|
# freeing its slot, same as the lifetime limit above. 0 = never
|
|
# remove stuck balls (BALL_MAX_LIFETIME_SECONDS still applies).
|
|
BALL_STUCK_DISTANCE = 30.0
|
|
BALL_STUCK_TIMEOUT_SECONDS = 8.0
|
|
|
|
|
|
# ============================================================
|
|
# White obstacle detection
|
|
# ============================================================
|
|
|
|
# The camera image is warped to screen space at this fraction of
|
|
# the real screen resolution before looking for stickers. 0.5 makes
|
|
# detection ~4x cheaper with no meaningful loss of precision (a
|
|
# sticker is still tens of pixels wide). All the pixel values below
|
|
# stay in real screen pixels - the code converts.
|
|
DETECTION_SCALE = 0.5
|
|
|
|
# Real sticker size limits, in screen pixels squared.
|
|
MIN_OBSTACLE_AREA = 1500
|
|
MAX_OBSTACLE_AREA = 200000
|
|
|
|
# --- Finding candidate blobs (deliberately loose) -------------
|
|
# Pass 1 finds anything vaguely bright and unsaturated. It is meant
|
|
# to be generous; the strict checks below decide what is a sticker.
|
|
#
|
|
# Both "bright" and "unsaturated" are judged in CIELAB, not HSV (see
|
|
# imaging.py): L (lightness) and chroma (how colorful a pixel is)
|
|
# are far more independent of each other there than HSV's V and S,
|
|
# which is what makes the two tests below behave consistently
|
|
# whether a sticker sits in a shadowed corner or under a spotlight.
|
|
#
|
|
# "Bright" is no longer one fixed brightness floor for the whole
|
|
# frame, either. A single global number means a sticker in dimmer
|
|
# ambient light can fall below it and never even reach the per-blob
|
|
# refinement step below - which is exactly what made otherwise
|
|
# identical stickers seem to be "detected by force" some of the
|
|
# time. Instead, pass 1 asks a strictly LOCAL question - "is this
|
|
# pixel clearly brighter than the small neighborhood around it?"
|
|
# (cv2.adaptiveThreshold) - the same question a human eye asks when
|
|
# picking out a pale sticker on a dim part of a black screen, and it
|
|
# holds regardless of the overall exposure level that frame happens
|
|
# to have. OBSTACLE_ADAPTIVE_OFFSET is how much brighter than that
|
|
# local neighborhood a pixel must be; OBSTACLE_MIN_ABSOLUTE_LIGHTNESS
|
|
# is a sanity floor underneath it, so camera sensor noise sitting in
|
|
# a totally dark area can never be mistaken for "locally brighter".
|
|
OBSTACLE_ADAPTIVE_OFFSET = 14
|
|
OBSTACLE_MIN_ABSOLUTE_LIGHTNESS = 55
|
|
|
|
# The neighborhood size (screen px) the adaptive threshold above
|
|
# looks at is tied to the sticker's own size - large enough that the
|
|
# neighborhood is mostly black screen around the sticker rather than
|
|
# the sticker's own bright pixels (which would otherwise drag the
|
|
# local average up and make the sticker fail to stand out against
|
|
# ITSELF). Used as a multiple of the short side once a size is
|
|
# known; OBSTACLE_ADAPTIVE_DEFAULT_SHORT_SIDE is only the starting
|
|
# guess used before any sticker has been seen yet.
|
|
OBSTACLE_ADAPTIVE_BLOCK_SPAN = 2.4
|
|
OBSTACLE_ADAPTIVE_DEFAULT_SHORT_SIDE = 90
|
|
|
|
# Loose chroma ceiling for pass 1 (and for the per-blob edge
|
|
# refinement below) - just needs to exclude obviously colored
|
|
# objects (clothing, skin, colored lighting splashed on the floor),
|
|
# not pin an exact "how white" number (that is MAX_OBSTACLE_CHROMA
|
|
# below).
|
|
OBSTACLE_COARSE_CHROMA_MAX = 45
|
|
|
|
# Each blob's edge is then re-drawn at the "half-way" LIGHTNESS
|
|
# between the sticker's own paper level and the dark screen next to
|
|
# it. That makes a dim sticker in a shadowy corner and an identical
|
|
# sticker under a spotlight come out exactly the same size, which is
|
|
# what makes all stickers look and behave alike.
|
|
OBSTACLE_EDGE_LEVEL = 0.5
|
|
|
|
# --- Deciding whether a blob really is a sticker --------------
|
|
# Average lightness/chroma INSIDE the blob (CIELAB - see above).
|
|
MIN_OBSTACLE_BRIGHTNESS = 130
|
|
MAX_OBSTACLE_CHROMA = 28
|
|
|
|
# Fraction of the blob's own best-fit rotated rectangle it fills.
|
|
# A disc (a ball's glow, a bulb) scores 0.785, so anything above
|
|
# ~0.84 rejects round things outright.
|
|
MIN_OBSTACLE_RECTANGULARITY = 0.86
|
|
|
|
# Fraction of its convex hull the blob fills (rejects L-shapes,
|
|
# blobs with bites out of them).
|
|
MIN_OBSTACLE_SOLIDITY = 0.90
|
|
|
|
# Longest side / shortest side. Squares up to index-card shapes.
|
|
MAX_OBSTACLE_ASPECT_RATIO = 4.0
|
|
|
|
# Paper is a uniform, diffuse surface. Reflections and hot-spots
|
|
# vary a lot in brightness across their surface.
|
|
MAX_OBSTACLE_BRIGHTNESS_STD = 30.0
|
|
|
|
# A real sticker sits on a black screen: this fraction of the ring
|
|
# of pixels just outside it must be clearly darker than the sticker.
|
|
MIN_OBSTACLE_DARK_SURROUND_RATIO = 0.55
|
|
|
|
# Only look for stickers inside the calibration border, and never
|
|
# closer than this many extra screen pixels to it (a sticker
|
|
# touching the bright border would merge with it).
|
|
OBSTACLE_BORDER_MASK_MARGIN = 4
|
|
OBSTACLE_EDGE_MARGIN = 5
|
|
|
|
# --- All stickers are identical: use that -------------------
|
|
# Once a few stickers have been seen the system learns their size
|
|
# and rejects blobs that don't match (within this fraction) - this
|
|
# is what kills "forced" detections of things that merely look
|
|
# roughly rectangular. Set OBSTACLE_EXPECTED_SIZE = (long, short)
|
|
# in screen pixels to pin the size instead of learning it (the
|
|
# learned size is printed to the console once it has settled).
|
|
OBSTACLE_SIZE_TOLERANCE = 0.22
|
|
OBSTACLE_EXPECTED_SIZE = None
|
|
|
|
# Two or three identical stickers glued edge to edge form one
|
|
# bigger rectangle; accept whole multiples of the sticker size.
|
|
# (For physics a merged rectangle behaves exactly like the parts.)
|
|
OBSTACLE_ALLOW_MERGED_STICKERS = True
|
|
|
|
# Draw every sticker at exactly the learned size when the measured
|
|
# size is within ~10% of it, so identical stickers give identical
|
|
# obstacles instead of wobbling by a pixel or two.
|
|
OBSTACLE_SNAP_TO_EXPECTED_SIZE = True
|
|
|
|
# Forget the learned size after this many seconds with no stickers
|
|
# at all, so a different sticker size can be used later.
|
|
OBSTACLE_SIZE_FORGET_SECONDS = 30.0
|
|
|
|
# Tracks that are already established are kept with slightly
|
|
# relaxed rules (this fraction of the strict thresholds) so a real
|
|
# sticker never flickers in and out on borderline frames, while NEW
|
|
# stickers still have to pass the full strict tests.
|
|
OBSTACLE_RELAXED_FACTOR = 0.90
|
|
|
|
# --- Following stickers over time (each one tracked separately) ---
|
|
# Consecutive detections needed before a NEW sticker counts.
|
|
OBSTACLE_REQUIRED_STABLE_FRAMES = 2
|
|
|
|
# An established sticker survives this many passes without being
|
|
# seen (someone's hand in the way). At 0.25 s per pass, 12 = 3 s.
|
|
OBSTACLE_MAX_MISSING_FRAMES = 12
|
|
|
|
# After this many passes without seeing an established sticker, the
|
|
# game stops PAINTING its white patch (and, a moment later, stops
|
|
# blanking that area for the detector). Without this, a sticker that
|
|
# was moved would be hidden by its own old painted patch until it
|
|
# timed out. If it was moved, it is found at the new spot within a
|
|
# pass or two; if it was removed, it is dropped at
|
|
# OBSTACLE_MAX_MISSING_FRAMES as before. Physics keeps using it
|
|
# either way until then.
|
|
OBSTACLE_STALE_MISSES = 3
|
|
|
|
# Not-yet-confirmed candidates are dropped after this many misses.
|
|
OBSTACLE_PENDING_MAX_MISSING_FRAMES = 1
|
|
|
|
# Max distance (screen px) a sticker may move between two passes
|
|
# and still be "the same" sticker.
|
|
OBSTACLE_TEMPORAL_MATCH_DISTANCE = 80
|
|
|
|
# Kernel sizes (screen pixels) for cleaning up the blob mask.
|
|
OBSTACLE_MASK_OPEN_KERNEL = 3
|
|
OBSTACLE_MASK_CLOSE_KERNEL = 5
|