VEX V5 PYTHON | 08.2 AI VISION DATA
Guide objective
Configure AI Vision, select a detection mode and inspect bounding boxes, classification IDs and confidence scores before controlling movement.
01 | SEPARATE SETUP, OBSERVATION AND ACTION
In 08.1 you used a coloured target to steer. Here, keep the robot stationary and examine exactly what the AI Vision Sensor reports.
Build three layers: configure what to detect, read and validate observations, then decide what a robot should do. A diagnostic monitor tests the first two without hiding errors behind movement.
The complete program below has no motor commands. Use it as a standalone sensor project, not a second loop pasted into an autonomous movement program.
02 | CONNECT AND NAME THE DEVICE
- Use desktop VEXcode V5 and a Python project.
- Connect AI Vision to an available Brain smart port; this guide uses port 15.
- Add AI Vision in Devices and select that port.
- Name it ai_vision_1, or adapt every example to your generated name.
- Open the sensor utility using the connection workflow shown by VEXcode; use the official setup guide below if prompted for a direct sensor connection.
- Follow any required firmware-update prompts, then save the device configuration.
Mount the sensor rigidly and upright. Aim it at the region your task needs, with the lens unobstructed. Keep the mounting consistent while comparing trials.
Changing the project name is not the same as changing a generated device variable. Check the declarations that VEXcode actually creates.
03 | CHOOSE A DETECTION MODE
| Mode | Configure first | Snapshot selector |
|---|---|---|
| Colour signature | Sample and save TARGET in the utility | ai_vision_1__TARGET |
| AI classification | Enable AI detection and select an available model | AiVision.ALL_AIOBJS |
| AprilTags | Enable tag detection | AiVision.ALL_TAGS |
Colour matching asks whether the scene contains your sampled appearance. Classification asks whether it resembles a class supported by the selected model. A tag provides a marker ID.
Do not assume enabling AI means every classroom object becomes recognizable. Check the classes provided by the installed configuration. Tag-specific selection and missing detections are covered in 08.3.
04 | SAVE A COLOUR SIGNATURE
For the first run, use MODE = COLOUR. Present a distinctive, solid-coloured target under representative lighting, select a clean region and save its signature as TARGET.
Adjust the permitted colour variation while checking target and background detections. Test shadows and highlights rather than tuning against one ideal image.
VEXcode generates names such as ai_vision_1__TARGET. Retain the utility-generated camera and signature configuration. Write your own functions below # Begin project code, outside the generated region.
A Color Code combines configured colour signatures into a pattern. It is useful when one colour alone is ambiguous, but still needs suitable target layout and configuration.
05 | READ A SNAPSHOT SAFELY
objects = ai_vision_1.take_snapshot(ai_vision_1__TARGET)
if not objects:
print("No matching detections")
else:
for index, obj in enumerate(objects):
if obj.exists:
print(index, obj.centerX, obj.centerY, obj.width, obj.height)Never index objects[0] until you have checked that the tuple is non-empty. An empty result is a normal observation, while a disconnected device or API exception is a separate failure.
The API’s returned order is by width. The first entry is not necessarily the largest by area, the desired class or the same physical object as the previous first entry.
The inspector requests at most eight detections. That is a result cap, not proof that only eight or fewer objects exist in the scene.
06 | UNDERSTAND PIXELS AND THE BOX
| Field | Interpretation |
|---|---|
| originX / originY | Top-left of bounding box |
| centerX / centerY | Box centre |
| width / height | Box dimensions |
| exists | Whether the returned entry represents a detection |
| id | Classification or tag identifier, interpreted by mode |
| score | AI classification confidence score; not a colour-quality score |
In the worked example, box area is 80 × 60 = 4,800 pixel². Horizontal centre error is 220 − 160 = +60 pixels. Neither gives a physical distance without additional calibration.
A box can include background and can change when part of the target leaves view. Do not treat rectangle area as the exact object silhouette.
07 | CLASS IDs DEPEND ON THE MODEL
A classification ID is a label within the chosen model. It is not a permanent identity assigned to one physical object.
Inspect the enabled model and its class list before setting TARGET_CLASS_ID. Different models can assign the same numerical ID to different classes.
The inspector prints IDs in AI mode and can filter to one configured class. TARGET_CLASS_ID = None means inspect all returned classes.
A confidence score helps rank evidence, but a score of 80 should not be treated as a guaranteed 80% real-world correctness rate. Compare scores with actual test outcomes.
08 | SELECT A CANDIDATE EXPLICITLY
The inspector separates all validated records from candidates that pass the area and optional classification filters. Among candidates, it selects the largest box area.
| Example record | Area | Possible outcome |
|---|---|---|
| A: 80 × 20 | 1,600 pixel² | Wider but smaller area |
| B: 50 × 50 | 2,500 pixel² | Selected by area if both pass filters |
In AI mode, the starter also requires score ≥ 70 and, if configured, a matching class ID. These are teaching settings to test, not universal thresholds.
CANDIDATE means a record passed this policy. It does not mean it is safe to drive toward, unique or consistently tracked across snapshots.
09 | COMPLETE STATIONARY DATA INSPECTOR
Configure ai_vision_1 on port 15. For the default COLOUR mode, save TARGET first. Keep generated VEX imports, Brain and device configuration above this code.
The program declares a Controller for B cancellation; use your existing generated controller instead if one is already configured. It does not need drive motors, odometry or inertial calibration.
# =============================================================================
# TEAM [MY TEAM NAME / NUMBER] | STATIONARY AI VISION INSPECTOR
# =============================================================================
# Configure AI Vision in Devices on port 15, named ai_vision_1.
# For COLOUR mode, configure a colour signature named TARGET.
# Keep generated VEX imports, brain and camera setup above '# Begin project code'.
# This standalone program has NO motor commands or competition movement callbacks.
import math
controller = Controller(PRIMARY) # Do not duplicate a generated Controller.
# =============================================================================
# INSPECTION SETTINGS
# =============================================================================
MODE = "COLOUR" # COLOUR, AI or TAGS; enable the corresponding mode in Devices
RUN_MS = 20000
POLL_MS = 250
MAX_SNAPSHOT_MS = 500
MAX_OBJECTS = 8 # Requested result cap, not total objects present in the scene
MIN_AREA_PX2 = 150
MIN_AI_SCORE = 70 # Teaching threshold; test rather than treating as probability
TARGET_CLASS_ID = None # Optional AI class filter; use the configured model's IDs
IMAGE_WIDTH = 320.0
IMAGE_HEIGHT = 240.0
def selected_signature():
if MODE == "COLOUR":
return ai_vision_1__TARGET
if MODE == "AI":
return AiVision.ALL_AIOBJS
if MODE == "TAGS":
return AiVision.ALL_TAGS
raise ValueError("Choose COLOUR, AI or TAGS")
def object_record(obj, mode, index):
if not obj.exists:
return None
values = (obj.originX, obj.originY, obj.centerX, obj.centerY,
obj.width, obj.height)
if not all(math.isfinite(v) for v in values):
raise ValueError("Non-finite box data")
ox, oy, cx, cy, width, height = values
if not (0 <= ox <= IMAGE_WIDTH and 0 <= oy <= IMAGE_HEIGHT
and 0 <= cx <= IMAGE_WIDTH and 0 <= cy <= IMAGE_HEIGHT
and 0 < width <= IMAGE_WIDTH and 0 < height <= IMAGE_HEIGHT):
raise ValueError("Box data outside image bounds")
record = {"index": index, "origin_x": ox, "origin_y": oy,
"x": cx, "y": cy, "width": width, "height": height,
"area": width*height, "error_x": cx-IMAGE_WIDTH/2,
"class_or_tag_id": None, "score": None}
if mode in ("AI", "TAGS"):
identity = obj.id
if not math.isfinite(identity) or identity < 0 or int(identity) != identity:
raise ValueError("Invalid classification/tag ID")
record["class_or_tag_id"] = int(identity)
if mode == "AI":
score = obj.score
if not math.isfinite(score) or not 1 <= score <= 100:
raise ValueError("Invalid AI score")
record["score"] = score
return record
def analyse_objects(objects, mode):
records = []
for index, obj in enumerate(objects):
record = object_record(obj, mode, index)
if record is not None:
records.append(record)
candidates = []
for r in records:
if r["area"] < MIN_AREA_PX2:
continue
if mode == "AI":
if r["score"] < MIN_AI_SCORE:
continue
if TARGET_CLASS_ID is not None and r["class_or_tag_id"] != TARGET_CLASS_ID:
continue
candidates.append(r)
if not records:
return "NO_DETECTIONS", records, None
if not candidates:
return "FILTERED_OUT", records, None
# Deliberate area selection; snapshot order is not persistent object identity.
chosen = max(candidates, key=lambda r: r["area"])
return "CANDIDATE", records, chosen
def print_frame(status, records, chosen, snapshot_ms):
print("MODE", MODE, "STATUS", status, "VALID", len(records),
"SNAPSHOT_MS", snapshot_ms)
for r in records:
print("INDEX", r["index"], "ID", r["class_or_tag_id"], "SCORE", r["score"],
"ORIGIN", r["origin_x"], r["origin_y"], "CENTRE", r["x"], r["y"],
"SIZE", r["width"], r["height"], "AREA", r["area"])
brain.screen.clear_screen()
brain.screen.set_cursor(1, 1)
brain.screen.print(MODE, " ", status)
brain.screen.set_cursor(2, 1)
brain.screen.print("Valid: ", len(records))
if chosen is not None:
brain.screen.set_cursor(3, 1)
brain.screen.print("X/Y: ", chosen["x"], "/", chosen["y"])
brain.screen.set_cursor(4, 1)
brain.screen.print("X error: ", chosen["error_x"], " px")
brain.screen.set_cursor(5, 1)
brain.screen.print("Area: ", chosen["area"])
print("SELECTED_INDEX", chosen["index"], "ERROR_X", chosen["error_x"])
def inspect_ai_vision():
try:
if (MODE not in ("COLOUR", "AI", "TAGS")
or not all(math.isfinite(v) for v in
(RUN_MS, POLL_MS, MAX_SNAPSHOT_MS, MAX_OBJECTS,
MIN_AREA_PX2, MIN_AI_SCORE))
or RUN_MS <= 0 or POLL_MS <= 0 or MAX_SNAPSHOT_MS <= 0
or not 1 <= MAX_OBJECTS <= 24 or int(MAX_OBJECTS) != MAX_OBJECTS
or MIN_AREA_PX2 < 0 or not 1 <= MIN_AI_SCORE <= 100
or (TARGET_CLASS_ID is not None and
(not math.isfinite(TARGET_CLASS_ID) or TARGET_CLASS_ID < 0
or int(TARGET_CLASS_ID) != TARGET_CLASS_ID))):
return "INVALID_SETTINGS"
signature = selected_signature()
started = brain.timer.time(MSEC)
while brain.timer.time(MSEC)-started < RUN_MS:
if controller.buttonB.pressing():
return "CANCELLED"
if not ai_vision_1.installed():
return "SENSOR_NOT_INSTALLED"
before = brain.timer.time(MSEC)
objects = ai_vision_1.take_snapshot(signature, int(MAX_OBJECTS))
after = brain.timer.time(MSEC)
if controller.buttonB.pressing():
return "CANCELLED"
if after-started >= RUN_MS:
return "FINISHED"
if after-before > MAX_SNAPSHOT_MS:
return "SLOW_SNAPSHOT"
status, records, chosen = analyse_objects(objects, MODE)
print_frame(status, records, chosen, after-before)
wait(POLL_MS, MSEC)
return "FINISHED"
except Exception:
return "CONFIGURATION_OR_DATA_FAULT"
result = inspect_ai_vision()
print("INSPECTOR_END", result)
brain.screen.clear_screen() # Remove the last candidate when monitoring ends.
brain.screen.set_cursor(1, 1)
brain.screen.print("Inspector: ", result)
Open the VEXcode console for every validated record. The Brain shows the current candidate summary. The screen is cleared each observation, and cleared again when the inspector ends, so an old candidate is not left displayed as current.
RUN_MS bounds the observation session. The measured snapshot duration is host-call duration, not a sensor-supplied exposure timestamp. A slow-call check runs after the API returns; it cannot interrupt a blocked API call.
10 | DISTINGUISH OBSERVATION FROM FAILURE
| Status | Meaning |
|---|---|
| NO_DETECTIONS | No existing entries in the current result |
| FILTERED_OUT | Records exist, but none pass the chosen filters |
| CANDIDATE | One record selected by this policy |
| SENSOR_NOT_INSTALLED | Device connection check failed |
| SLOW_SNAPSHOT | The snapshot call exceeded the timing policy |
| CONFIGURATION_OR_DATA_FAULT | Generated name, API access or returned data failed |
| CANCELLED / FINISHED | User stopped the inspector or the session ended |
If changing MODE to AI yields no detections, check the model and enabled feature before changing the colour signature. They are different detection mechanisms.
If records appear but FILTERED_OUT persists, inspect size, score and class filtering independently. Do not loosen every setting at once.
11 | BUILD A SMALL TEST MATRIX
Keep the camera fixed and record results while changing one factor at a time.
| Trial | Record |
|---|---|
| Target left, centre, right | Centre coordinates and horizontal error |
| Near and far | Width, height and area |
| Bright and dim light | Detection count and stability |
| Target partially hidden | Box changes and missed detections |
| Two similar targets | Returned order and selected index |
| Wrong class or distractor | ID, score and filtering result |
| Target removed | NO_DETECTIONS without stale selection |
Use repeated trials. One correct detection does not establish reliability, and the selected index can change when boxes change width.
12 | COMMON MISTAKES
- Reading before a snapshot: properties describe a captured observation.
- Using an old generated name: match the actual device and signature declarations.
- Hard-coding a universal class ID: check the selected model.
- Treating tuple index as identity: detection ordering can change.
- Using score for a colour signature: score belongs to AI classification.
- Confusing image Y and field Y: image Y increases downward.
- Turning a diagnostic candidate into movement immediately: add selection, freshness and loss policies first.
CHALLENGES | CONFIGURE, INSPECT AND EXPLAIN
- Configure: save a signature and identify its generated Python name.
- Read: report all boxes from one snapshot without indexing an empty tuple.
- Calculate: find area and horizontal error for a 40 × 30 box centred at X = 110.
- Compare: show a scene where width order and area selection choose different entries.
- Filter: inspect a class filter using the selected model’s actual IDs.
- Mock: distinguish no detections, filtered detections and malformed data.
- Measure: complete a test matrix under three lighting conditions.
- Evaluate: explain why a high score does not guarantee object identity or a collision-free approach.
Calculation check
Area = 40 × 30 = 1,200 pixel². Horizontal error = 110 − 160 = −50 pixels, so the centre is left of the image midpoint.
OFFICIAL REFERENCES
- AI Vision Python API and model-dependent class IDs
- Getting started with AI Vision on V5
- Configure colour signatures
- Configure AI classifications
- VEX AI Vision Python guidance
The inspector and filtering rules are independently written teaching examples. Verify the generated names and behaviour with your own sensor configuration.