Skip to content

Vision Detection System Improvements - #376

Open
thomas0829 wants to merge 20 commits into
huskyroboticsteam:masterfrom
thomas0829:ar-detection-improvements
Open

Vision Detection System Improvements#376
thomas0829 wants to merge 20 commits into
huskyroboticsteam:masterfrom
thomas0829:ar-detection-improvements

Conversation

@thomas0829

@thomas0829 thomas0829 commented Oct 24, 2025

Copy link
Copy Markdown
Member

This PR enhances the rover's vision detection capabilities with improvements to AR detection and integration of a new object detection system.

1. AR Detection Enhancements

Core Improvements:

  • Replace deprecated estimatePoseSingleMarkers API with solvePnP for better accuracy
  • Enable AR detection support in simulator mode with proper configuration
  • Fix CMakeLists.txt by removing non-existent test executables
  • Improve initialization and logging for better debugging

Runtime Control:

  • Add keyboard toggle control with R key (default: OFF)
  • Clear old landmark data when disabling detection
  • Background thread monitors enable/disable state
  • State change logging (STARTED/STOPPED)

2. Object Detection Integration (NEW ✨)

Model & Architecture:

  • Integrate OWL-ViT (Vision Transformer) model with LibTorch 2.1.0+
  • Support for custom object classes (configurable)
  • CUDA GPU acceleration for real-time performance
  • Model file: owlvit-cpp.pt (excluded from git, 586MB)

Features:

  • NMS (Non-Maximum Suppression) with IoU threshold 0.5 for duplicate removal
  • Configurable confidence threshold (default: 0.6)
  • Background detection thread with proper synchronization
  • Integration with camera system (Mast Camera)
  • Keyboard toggle control with O key (default: OFF)
  • Automatic CUDA JIT optimization disabling (fixes nvrtc compilation errors)

Testing & Visualization:

  • Side-by-side comparison test showing enabled/disabled states
  • Bounding box visualization with confidence scores
  • Detection count display
  • Smart model path detection (multiple fallback locations)

Code Structure:

src/object-detection/
├── ObjectDetector.h/cpp       # Core detection engine with NMS
├── DetectionResult.h/cpp      # Detection result data structure
├── read_objects.h/cpp         # Integration interface
├── ObjectDetectionTest.cpp    # Visualization test
├── CMakeLists.txt             # Build configuration
└── generate_tokens.py         # Token generation utility

3. Unified Keyboard Control Interface

Both detection systems can now be controlled via keyboard in real-time:

Key Function Default State
O Toggle object detection OFF
R Toggle ArUco detection OFF
Q Quit program -

Design Rationale:

  • Both systems disabled by default to reduce computational load
  • Operators can enable only what they need during missions
  • Clear console feedback for state changes
  • Logging at INFO level for visibility

4. Optional LibTorch Support

Flexibility:

  • Object detection is now optional - project builds without LibTorch
  • CMake gracefully falls back if LibTorch not found
  • Clear warning messages guide users to install LibTorch if desired
  • README includes complete LibTorch installation instructions

Installation:

# Optional: Install LibTorch for object detection
export LIBTORCH_PATH=~/libtorch
cmake ../src

Technical Details

Dependencies Added:

  • LibTorch 2.1.0+ with CUDA support (optional)
  • Environment variable: LIBTORCH_PATH for custom LibTorch location
  • Alternative: OWLVIT_MODEL_PATH for custom model location

Modified Files:

  • src/Globals.h/cpp - Added global enable flags
  • src/Rover.cpp - Keyboard control loop with conditional compilation
  • src/ar/read_landmarks.cpp - Enable/disable checking
  • src/network/MissionControlTasks.cpp - ArUco detection guard
  • src/CMakeLists.txt - Optional object detection module
  • src/object-detection/ObjectDetector.cpp - CUDA JIT fix
  • src/object-detection/read_objects.cpp - Smart model path detection
  • README.md - LibTorch installation guide
  • .gitignore - Ignore large model files

CUDA Compatibility:

  • Automatic JIT optimization disabling prevents nvrtc errors
  • No manual environment variable setup required
  • Fusion strategy set to avoid __ldg undefined identifier issues

Testing:

# Build (object detection included if LibTorch found)
cd build
cmake ../src
make Rover -j4

# Run rover
./Rover -p arm

# Toggle object detection at runtime with 'O' key
# Toggle ArUco detection at runtime with 'R' key

Without LibTorch:

# Build succeeds, object detection disabled
cd build
cmake ../src  # Shows warning: "LibTorch not found. Object detection will be DISABLED."
make Rover -j4

# Run rover (ArUco detection still available)
./Rover -p arm

# Pressing 'O' shows: "Object detection not available (LibTorch not found)"

@Sashmit29
Sashmit29 requested a review from imisaacwu October 31, 2025 00:53
@Sashmit29 Sashmit29 assigned Sashmit29 and unassigned Sashmit29 Nov 7, 2025

@imisaacwu imisaacwu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, couple small things 👍

Comment thread src/Rover.cpp Outdated
Comment on lines +158 to +161

// Open mast camera to load its configuration before initializing AR
LOG_F(INFO, "Opening mast camera...");
auto mastCam = robot::openCamera(Constants::MAST_CAMERA_ID);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to open the mast camera here? Could be wrong, but I believe the reason we don't initialize cameras on initialization is to wait until mission control is open and open cameras dynamically based on which cameras are displayed on the screen.

Comment thread src/network/MissionControlTasks.cpp Outdated
const auto& encoder = stream_data.encoder;

// Detect and log AR markers if this is the mast camera
if (cam == Constants::MAST_CAMERA_ID && AR::isLandmarkDetectionInitialized()) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could y'all generalize this to have detection on any camera?

Comment thread src/network/MissionControlTasks.cpp Outdated
std::vector<std::vector<cv::Point2f>> corners, rejectedPoints;
std::vector<int> ids;
cv::Ptr<cv::aruco::DetectorParameters> params = cv::aruco::DetectorParameters::create();
cv::aruco::detectMarkers(frame, AR::Markers::URC_MARKERS()->getDict(),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you also generalize the marker type?

Comment thread src/network/MissionControlTasks.cpp Outdated
// Detect markers using OpenCV's ArUco directly
std::vector<std::vector<cv::Point2f>> corners, rejectedPoints;
std::vector<int> ids;
cv::Ptr<cv::aruco::DetectorParameters> params = cv::aruco::DetectorParameters::create();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to create a new DetectorParameters every time?

Comment thread src/network/MissionControlTasks.cpp Outdated
Comment on lines +176 to +194
for (const auto& pair : uniqueMarkers) {
int id = pair.first;
const cv::Vec3d& tvec = pair.second;
double distance = cv::norm(tvec);

// Direct output to stdout with clear formatting
printf("\n");
printf("=================================================\n");
printf(" DETECTED ARUCO MARKER\n");
printf("=================================================\n");
printf(" Marker ID: %d\n", id);
printf(" Distance: %.2f m (%.0f cm)\n", distance, distance * 100.0);
printf(" Position (camera frame):\n");
printf(" X: %+.3f m\n", tvec[0]);
printf(" Y: %+.3f m\n", tvec[1]);
printf(" Z: %+.3f m\n", tvec[2]);
printf("=================================================\n");
printf("\n");
fflush(stdout);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, I think this looks good for now, eventually we will want to use the data for positioning, etc.
For now, could we simplify these print statements to use LOG_F(INFO, ...) instead of printf (and remove the stdio import at the top)?

@thomas0829
thomas0829 force-pushed the ar-detection-improvements branch 3 times, most recently from 690a255 to 7e4bedc Compare November 9, 2025 07:36
@thomas0829 thomas0829 changed the title feat(ar): Replace deprecated estimatePoseSingleMarkers with solvePnP and add AR detection toggle Vision Detection System Improvements Nov 14, 2025
@thomas0829
thomas0829 force-pushed the ar-detection-improvements branch from ed17303 to 4c1596c Compare November 14, 2025 08:47
Copilot AI review requested due to automatic review settings February 25, 2026 18:55
…and add AR detection toggle

- Replace cv::aruco::estimatePoseSingleMarkers with cv::solvePnP for pose estimation
  to ensure compatibility with future OpenCV versions
- Add toggle functionality for AR detection:
  * Added setEnabled() and isEnabled() methods to Detector class
  * Added keyboard shortcut 'A' in ARTester to toggle detection on/off
  * Display keyboard controls help message on startup
- Add new camera configuration for Global Shutter Camera (ELP-USBFHD01M-L21)
  * Complete camera calibration with intrinsic parameters
  * Reprojection error: 0.103 pixels
- Update calibration.cpp to save format, framerate, and image dimensions
  required by Camera::getGSTPipe()

Related tasks:
- [x] Switch to solvePnP
- [x] Make AR detection toggle-able
- [x] Update calibration.cpp to save new camera calibration
- Uncommented intrinsic parameters in MastCameraCalibration.yml for simulator
- Initialize AR detection at startup after opening mast camera in Rover.cpp
- Load camera config in simulator_interface to provide intrinsic parameters
- Add AR marker detection in camera stream with terminal output
- Use std::map to deduplicate markers (show only first occurrence per ID)
- Add formatted printf output with marker ID, distance, and position
- Remove code comment markers per code review feedback
- Add simulator-specific AR test executables to CMakeLists.txt

Fixes simulator AR detection by ensuring intrinsic parameters are available
before AR detector initialization.
Removed test_sim_camera and sim_ar_tester executables that don't have
corresponding source files, fixing CMake configuration error.
- Remove camera opening from Rover initialization
- Load AR intrinsic params directly from config file
- Fix openCamera_() to load parameters from config
- Generalize AR detection to work on any camera
- Use reusable DetectorParameters
- Replace printf with LOG_F for consistent logging
- Implemented ObjectDetector class with NMS and enable/disable functionality
- Added DetectionResult data structure for detection output
- Integrated object detection into Rover with keyboard control ('O' key)
- Added read_objects interface for background detection loop
- Created visualization test with side-by-side comparison
- Added Globals::objectDetectionEnabled flag for runtime control
- Updated CMakeLists.txt to include LibTorch dependencies
- Ignore large model files (.pt) and test images in gitignore
- Added Globals::arucoDetectionEnabled flag (default: false)
- Added 'R' key to toggle ArUco detection on/off
- Modified detectLandmarksLoop() to check enabled flag
- Clear old landmarks when disabling detection
- Modified MissionControlTasks to check enabled flag before detection
- Added state change logging (STARTED/STOPPED)
- readLandmarks() returns empty data when disabled
- Use LIBTORCH_PATH environment variable instead of absolute path
- Falls back to CMAKE_PREFIX_PATH or system installation
- Usage: export LIBTORCH_PATH=/path/to/libtorch or cmake -DCMAKE_PREFIX_PATH=/path/to/libtorch
- Changed find_package(Torch) from REQUIRED to QUIET
- Added ENABLE_OBJECT_DETECTION compile definition when Torch is found
- Added conditional compilation guards (#ifdef) in Rover.cpp
- Object detection features gracefully disabled without LibTorch
- 'O' keyboard command shows warning when feature unavailable
- Clear CMake warnings guide users to install LibTorch if needed
- Document object detection as an optional feature
- Provide step-by-step LibTorch installation guide
- Explain how to set LIBTORCH_PATH environment variable
- Clarify that build continues without LibTorch (feature disabled)
- Disable PyTorch JIT optimizations and fusion to prevent nvrtc errors
- Add automatic model path detection (checks multiple locations)
- Support OWLVIT_MODEL_PATH environment variable for custom paths
- Remove need for PYTORCH_JIT=0 environment variable
- Update README to reflect simplified setup
- Add ModelDownloader module for automatic model download from Hugging Face
- Integrate auto-download into object detection initialization
- Model is automatically downloaded if not found locally
- Uses wget/curl for pure C++ implementation
- Support OWLVIT_MODEL_URL environment variable for custom URLs
- Model hosted at thomas0829/OWL-ViT on Hugging Face
- Add RealSenseCamera class for Intel RealSense depth cameras
- Add camera calibration tool (CalibrateCamera.cpp)
- Add RealSense test program
- Update object detection with improved distance estimation
- Add calibrate_and_test.sh script
- Update world interface for object detection integration
@thomas0829
thomas0829 force-pushed the ar-detection-improvements branch from 64afedc to 5a4926b Compare February 25, 2026 19:02

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enhances the rover's vision detection system with two major components: improvements to ArUco marker detection and integration of a new OWL-ViT based object detection system. The ArUco detection now uses the modern solvePnP API instead of deprecated methods, supports simulator mode, and includes keyboard-controlled enable/disable functionality. The object detection system adds support for detecting specific objects (orange hammer, rock pick, water bottle) using a Vision Transformer model with LibTorch, including depth-based distance estimation via RealSense cameras. Both systems can be toggled on/off via keyboard controls and are disabled by default to reduce computational load.

Changes:

  • Replaced deprecated estimatePoseSingleMarkers with solvePnP for ArUco pose estimation
  • Added optional LibTorch-based object detection with OWL-ViT model, including NMS, depth support, and task-based detection
  • Implemented keyboard controls for runtime toggling of both detection systems with global enable flags

Reviewed changes

Copilot reviewed 40 out of 41 changed files in this pull request and generated 15 comments.

Show a summary per file
File Description
test_object_detection.sh Test script for object detection keyboard controls
src/world_interface/*.{h,cpp} Added depth frame reading capability for RealSense cameras
src/object-detection/* New object detection module with detector, model downloader, and test utilities
src/network/MissionControl*.{h,cpp} Added object detection enable/disable protocol handlers and ArUco logging
src/camera/*.{h,cpp} Added RealSenseCamera class and undistortion support
src/ar/*.{h,cpp} Updated to use solvePnP and added enable/disable state checking
src/Rover.cpp Added keyboard control loop for detection systems
src/Globals.{h,cpp} Added global atomic flags for detection enable states
src/CMakeLists.txt Added optional LibTorch support and RealSense configuration
camera-config/*.yml Uncommented calibration parameters for AR detection
calibrate_and_test.sh Testing commands for RealSense and camera calibration
README.md Added LibTorch installation instructions
Comments suppressed due to low confidence (1)

src/network/MissionControlProtocol.cpp:443

  • Functions are defined out of order compared to the header file. In the header, the order is: handleRequestObjectDetectionEnabled, setRequestedCmdVel, setRequestedTankCmdVel, then setObjectDetectionEnabled. Here they're implemented in a different order. While this doesn't affect functionality, maintaining the same order as the header improves code readability and maintenance.
void MissionControlProtocol::handleRequestObjectDetectionEnabled(const json& j) {
	bool enabled = j["enabled"];
	this->setObjectDetectionEnabled(enabled);
}

void MissionControlProtocol::setRequestedCmdVel(double dtheta, double dx) {
	_power_repeat_task.setCmdVel(dtheta, dx);
	robot::setCmdVel(dtheta, dx);
}

void MissionControlProtocol::setRequestedTankCmdVel(double left, double right) {
	_power_repeat_task.setTankCmdVel(left, right);
	robot::setTankCmdVel(left, right);
}

static bool validateJoint(const json& j) {
	return util::validateKey(j, "joint", val_t::string) &&
		   std::any_of(all_jointid_t.begin(), all_jointid_t.end(), [&](const auto& joint) {
			   return j["joint"].get<std::string>() == util::to_string(joint);
		   });
}

static bool validateJointPowerRequest(const json& j) {
	return validateJoint(j) && util::validateRange(j, "power", -1, 1);
}

void MissionControlProtocol::handleJointPowerRequest(const json& j) {
	// TODO: ignore this message if we are in autonomous mode.
	using robot::types::jointid_t;
	using robot::types::name_to_jointid;
	std::string joint = j["joint"];
	double power = j["power"];
	auto it = name_to_jointid.find(util::freezeStr(joint));
	if (it != name_to_jointid.end()) {
		jointid_t joint_id = it->second;
		setRequestedJointPower(joint_id, power);
	}
}

static bool validateJointPositionRequest(const json& j) {
	return validateJoint(j) && util::validateKey(j, "position", val_t::number_integer);
}

void MissionControlProtocol::handleJointPositionRequest([[maybe_unused]] const json& j) {
	// TODO: ignore this message if we are in autonomous mode.
	// std::string motor = j["joint"];
	// double position_deg = j["position"];
	// int32_t position_mdeg = std::round(position_deg * 1000);
	// TODO: actually implement joint position requests
	// setMotorPos(motor, position_mdeg);
}

static bool validateServoPositionRequest(const json& j) {
  return util::validateKey(j, "servo", val_t::string) && util::validateKey(j, "position", val_t::number_integer);
}

void MissionControlProtocol::handleServoPositionRequest(const json& j) {
  std::string servoName = j["servo"];
  int32_t position = j["position"];
  auto servo = name_to_servoid.find(util::freezeStr(servoName));
  if (servo != name_to_servoid.end()) {
	  robot::setServoPos(servo->second, position);
  }
}

static bool validateStepperTurnAngleRequest(const json& j) {
  return util::validateKey(j, "stepper", val_t::string) && util::validateKey(j, "angle", val_t::number_integer);
}

void MissionControlProtocol::handleStepperTurnAngleRequest(const json& j) {
  std::string stepperName = j["stepper"];
  int16_t angle = j["angle"];
  auto stepper = name_to_stepperid.find(util::freezeStr(stepperName));
  if (stepper != name_to_stepperid.end()) {
    robot::setRequestedStepperTurnAngle(stepper->second, angle);
  }
}

static bool validateWaypointNavRequest(const json& j) {
	bool lat_is_unsigned = util::validateKey(j, "latitude", val_t::number_unsigned);
	bool lon_is_unsigned = util::validateKey(j, "longitude", val_t::number_unsigned);
	return (lat_is_unsigned || util::validateKey(j, "latitude", val_t::number_float)) &&
		   (lon_is_unsigned || util::validateKey(j, "longitude", val_t::number_float)) &&
		   util::validateKey(j, "isApproximate", val_t::boolean) &&
		   util::validateKey(j, "isGate", val_t::boolean);
}

void MissionControlProtocol::handleWaypointNavRequest(const json& j) {
	float latitude = j["latitude"];
	float longitude = j["longitude"];
	bool isApproximate = j["isApproximate"];
	bool isGate = j["isGate"];
	if (Globals::AUTONOMOUS && !isApproximate && !isGate) {
		// gpsToMeters will not use altitude
		navtypes::gpscoords_t coords = {latitude, longitude, 0};
		auto target = robot::gpsToMeters(coords);
		if (target) {
			_autonomous_task.start(target.value());
		} else {
			LOG_F(WARNING, "No GPS converter initialized!");
		}
	}
}

static bool validateCameraStreamOpenRequest(const json& j) {
	return util::validateOneOf(j, "camera", Constants::CAMERA_SET);
}

void MissionControlProtocol::handleCameraStreamOpenRequest(const json& j) {
	CameraID cam = j["camera"];
	_camera_stream_task.openStream(cam, j["fps"]);
}

static bool validateCameraStreamCloseRequest(const json& j) {
	return util::validateOneOf(j, "camera", Constants::CAMERA_SET);
}

void MissionControlProtocol::handleCameraStreamCloseRequest(const json& j) {
	CameraID cam = j["camera"];
  	_camera_stream_task.closeStream(cam);
}

static bool validateCameraFrameRequest(const json& j) {
	return util::validateOneOf(j, "camera", Constants::CAMERA_SET);
}

void MissionControlProtocol::handleCameraFrameRequest(const json& j) {
	auto gps = gps::readGPSCoords();
	auto imu = robot::readIMU();
	CameraID cam = j["camera"];
	auto camDP = robot::readCamera(cam);

	Eigen::Quaterniond quat = imu.getData();
	double lon = 0, lat = 0, alt = 0;
	double w = 0, x = 0, y = 0, z = 0;
	if (gps.isValid()) {
		lon = gps.getData().lon;
		lat = gps.getData().lat;
		alt = gps.getData().alt;
    	w = quat.w();
    	x = quat.x();
    	y = quat.y();
    	z = quat.z();
	}

	if (camDP) {
		auto data = camDP.getData();
		cv::Mat frame = data.first;
		std::string b64_data = base64::encodeMat(frame, ".jpg");
		json msg = {{"type", CAMERA_FRAME_REP_TYPE}, {"camera", cam}, {"data", b64_data}, 
		{"orientW", w}, {"orientX", x}, {"orientY", y}, {"orientZ", z},
		{"lon", lon}, {"lat", lat}, {"alt", alt}};
		_server.sendJSON(Constants::MC_PROTOCOL_NAME, msg);
	}
}

void MissionControlProtocol::sendArmIKEnabledReport(bool enabled) {
	json msg = {{"type", ARM_IK_ENABLED_REP_TYPE}, {"enabled", enabled}};
	this->_server.sendJSON(Constants::MC_PROTOCOL_NAME, msg);
}

void MissionControlProtocol::sendObjectDetectionEnabledReport(bool enabled) {
	json msg = {{"type", OBJECT_DETECTION_ENABLED_REP_TYPE}, {"enabled", enabled}};
	this->_server.sendJSON(Constants::MC_PROTOCOL_NAME, msg);
}

void MissionControlProtocol::handleConnection() {
	// Turn off inverse kinematics on connection
	this->setArmIKEnabled(false);
	
	// Turn off object detection on connection
	this->setObjectDetectionEnabled(false);

	// TODO: send the actual mounted peripheral, as specified by the command-line parameter
	json j = {{"type", MOUNTED_PERIPHERAL_REP_TYPE}};

	if (Globals::mountedPeripheral == mountedperipheral_t::none) {
		j["peripheral"] = json(nullptr);
	} else {
		j["peripheral"] = util::to_string(Globals::mountedPeripheral);
	}

	this->_server.sendJSON(Constants::MC_PROTOCOL_NAME, j);

	if (!Globals::AUTONOMOUS) {
		// start power repeat thread (if not already running)
		_power_repeat_task.start();
	}
}

void MissionControlProtocol::handleHeartbeatTimedOut() {
	LOG_F(ERROR, "Heartbeat timed out! Emergency stopping.");
	this->stopAndShutdownPowerRepeat(true);
	robot::emergencyStop();
}

void MissionControlProtocol::stopAndShutdownPowerRepeat(bool sendDisableIK) {
	if (_power_repeat_task.isRunning()) {
		_power_repeat_task.stop();
		// explicitly set all joints to zero
		stopAllJoints();
		// explicitly stop chassis
		robot::setCmdVel(0, 0);
	}
	// Turn off inverse kinematics so that IK state will be in sync with mission control
	this->setArmIKEnabled(false, sendDisableIK);
}

MissionControlProtocol::MissionControlProtocol(SingleClientWSServer& server)
	: WebSocketProtocol(Constants::MC_PROTOCOL_NAME), _server(server),
	  _camera_stream_task(server), _telem_report_task(server), _arm_ik_task(server),
	  _autonomous_task() {
	// emergency stop and operation mode handlers need the class for context since they must
	// be able to access the methods to start and stop the power repeater thread
	this->addMessageHandler(
		EMERGENCY_STOP_REQ_TYPE,
		std::bind(&MissionControlProtocol::handleEmergencyStopRequest, this, _1),
		validateEmergencyStopRequest);
	this->addMessageHandler(
		OPERATION_MODE_REQ_TYPE,
		std::bind(&MissionControlProtocol::handleOperationModeRequest, this, _1),
		validateOperationModeRequest);
	// drive and joint power handlers need the class for context since they must modify
	// _last_joint_power and _last_cmd_vel (for the repeater thread)
	this->addMessageHandler(
		DRIVE_REQ_TYPE,
		std::bind(&MissionControlProtocol::handleDriveRequest, this, _1),
		validateDriveRequest);
	this->addMessageHandler(
		DRIVE_TANK_REQ_TYPE,
		std::bind(&MissionControlProtocol::handleTankDriveRequest, this, _1),
		validateTankDriveRequest);
	this->addMessageHandler(
		ARM_IK_ENABLED_TYPE,
		std::bind(&MissionControlProtocol::handleRequestArmIKEnabled, this, _1),
		validateArmIKEnable);
	this->addMessageHandler(
		OBJECT_DETECTION_ENABLED_TYPE,
		std::bind(&MissionControlProtocol::handleRequestObjectDetectionEnabled, this, _1),
		validateObjectDetectionEnable);
	this->addMessageHandler(
		JOINT_POWER_REQ_TYPE,
		std::bind(&MissionControlProtocol::handleJointPowerRequest, this, _1),
		validateJointPowerRequest);
	this->addMessageHandler(
		JOINT_POSITION_REQ_TYPE,
		std::bind(&MissionControlProtocol::handleJointPositionRequest, this, _1),
		validateJointPositionRequest);
	this->addMessageHandler(
		CAMERA_STREAM_OPEN_REQ_TYPE,
		std::bind(&MissionControlProtocol::handleCameraStreamOpenRequest, this, _1),
		validateCameraStreamOpenRequest);
	this->addMessageHandler(
		CAMERA_STREAM_CLOSE_REQ_TYPE,
		std::bind(&MissionControlProtocol::handleCameraStreamCloseRequest, this, _1),
		validateCameraStreamCloseRequest);
	this->addMessageHandler(
		CAMERA_FRAME_REQ_TYPE,
		std::bind(&MissionControlProtocol::handleCameraFrameRequest, this, _1),
		validateCameraFrameRequest);
	this->addMessageHandler(
		WAYPOINT_NAV_REQ_TYPE,
		std::bind(&MissionControlProtocol::handleWaypointNavRequest, this, _1),
		validateWaypointNavRequest);
	this->addMessageHandler(
    		SERVO_POSITION_REQ_TYPE,
    		std::bind(&MissionControlProtocol::handleServoPositionRequest, this, _1),
    		validateServoPositionRequest);
	this->addMessageHandler(
    		STEPPER_TURN_ANGLE_REQ_TYPE,
    		std::bind(&MissionControlProtocol::handleStepperTurnAngleRequest, this, _1),
    		validateStepperTurnAngleRequest);

	this->addConnectionHandler(std::bind(&MissionControlProtocol::handleConnection, this));

	this->addDisconnectionHandler(
		std::bind(&MissionControlProtocol::stopAndShutdownPowerRepeat, this, false));

	this->setHeartbeatTimedOutHandler(
		HEARTBEAT_TIMEOUT_PERIOD,
		std::bind(&MissionControlProtocol::handleHeartbeatTimedOut, this));

	_telem_report_task.start();
	_camera_stream_task.start();
}

MissionControlProtocol::~MissionControlProtocol() {
	this->stopAndShutdownPowerRepeat(true);
}

///// UTILITY FUNCTIONS //////
void MissionControlProtocol::setArmIKEnabled(bool enabled, bool sendReport) {
	Globals::armIKEnabled = enabled;
	if (enabled) {
		_arm_ik_task.start();
	} else {
		_arm_ik_task.stop();
	}


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +254 to +261

fs.writeComment("Video format: typically 'image/jpeg' or 'video/x-raw'.");
fs << "format" << "image/jpeg";
fs.writeComment("Framerate in frames per second.");
fs << "framerate" << 30;
fs.writeComment("Image dimensions (must match calibration resolution).");
fs << "image_width" << imageSize.width;
fs << "image_height" << imageSize.height;

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file modifies the camera calibration save functionality but the changes appear incomplete or unrelated to the PR's stated purpose. The PR description doesn't mention changes to camera calibration file format. These additional fields (format, framerate, dimensions) seem to duplicate information already stored elsewhere in the calibration file. Please verify this change is intentional and necessary.

Copilot uses AI. Check for mistakes.
Comment thread src/Rover.cpp
Comment on lines +40 to +86
void keyboardControlLoop() {
std::cout << "\n=== Keyboard Controls ===" << std::endl;
#ifdef ENABLE_OBJECT_DETECTION
std::cout << " O - Toggle object detection on/off" << std::endl;
#endif
std::cout << " R - Toggle ArUco detection on/off" << std::endl;
std::cout << " Q - Quit" << std::endl;
std::cout << "========================\n" << std::endl;

while (true) {
char input;
std::cin >> input;

switch (input) {
case 'o':
case 'O':
#ifdef ENABLE_OBJECT_DETECTION
Globals::objectDetectionEnabled = !Globals::objectDetectionEnabled;
LOG_F(INFO, "Object detection %s",
Globals::objectDetectionEnabled ? "ENABLED" : "DISABLED");
std::cout << "Object detection "
<< (Globals::objectDetectionEnabled ? "ON" : "OFF")
<< std::endl;
#else
LOG_F(WARNING, "Object detection not available (LibTorch not found)");
std::cout << "Object detection not available (LibTorch not found)" << std::endl;
#endif
break;
case 'r':
case 'R':
Globals::arucoDetectionEnabled = !Globals::arucoDetectionEnabled;
LOG_F(INFO, "ArUco detection %s",
Globals::arucoDetectionEnabled ? "ENABLED" : "DISABLED");
std::cout << "ArUco detection "
<< (Globals::arucoDetectionEnabled ? "ON" : "OFF")
<< std::endl;
break;
case 'q':
case 'Q':
LOG_F(INFO, "Quit requested from keyboard");
closeRover(0);
break;
default:
break;
}
}
}

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The keyboard input loop uses cin which is blocking and requires pressing Enter after each key. For a more responsive control interface, consider using non-blocking or immediate input methods (like ncurses or platform-specific APIs) so users can press a single key without needing to press Enter. This would improve the user experience significantly.

Copilot uses AI. Check for mistakes.
* @param[out] depth_scale Conversion factor: meters = raw_value * depth_scale
* @return true if depth data is available, false otherwise
*/
bool readDepthFrame(types::CameraID camera, cv::Mat& depth_frame, float& depth_scale);

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function signature in the header declares a bool return type, but the implementation in simulator_interface.cpp returns std::optional<std::pair<cv::Mat, float>> instead. This is a type mismatch that will cause compilation errors. The header and implementation signatures must match.

Copilot uses AI. Check for mistakes.
Comment on lines +229 to +235
std::optional<std::pair<cv::Mat, float>> readDepthFrame(CameraID cameraID) {
// TODO: Integrate with RealSenseCamera for real depth data
// For now, return nullopt (depth not available in basic camera interface)
// To use depth in real world, use RealSenseCamera directly in detection code
LOG_F(INFO, "readDepthFrame: Depth not available for camera %s in real_world_interface. Use RealSenseCamera directly.", cameraID.c_str());
return std::nullopt;
}

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The real_world_interface.cpp implementation returns std::optional but logs and returns immediately. This function should either match the header signature (bool return with out parameters) or the header should be updated to return std::optional. Currently there's a type mismatch between the header declaration and this implementation.

Copilot uses AI. Check for mistakes.
Comment on lines +558 to +575
// Limit to max 3 results (keep highest confidence)
const size_t MAX_DETECTIONS = 3;
if (results.size() > MAX_DETECTIONS) {
// Find indices of top 3 by confidence
std::vector<size_t> indices(results.size());
std::iota(indices.begin(), indices.end(), 0);
std::partial_sort(indices.begin(), indices.begin() + MAX_DETECTIONS, indices.end(),
[&results](size_t a, size_t b) {
return results[a].confidence > results[b].confidence;
});

std::vector<DetectionResult> top_results;
top_results.reserve(MAX_DETECTIONS);
for (size_t i = 0; i < MAX_DETECTIONS; ++i) {
top_results.push_back(std::move(results[indices[i]]));
}
results = std::move(top_results);
}

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment on line 557 says "Limit to max 3 results" but there's no clear explanation of why this limit exists or what happens to the other detections. If this is a performance optimization or a requirement, it should be documented more clearly. Consider adding this limit as a named constant with documentation explaining the reasoning.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +2
# Set LibTorch path
set(CMAKE_PREFIX_PATH "/home/thomas/libtorch")

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This hardcoded absolute path is specific to one user's machine and will fail in other development environments. The LibTorch path should be configurable via environment variable or CMake variable. Consider using: if(DEFINED ENV{LIBTORCH_PATH}) set(CMAKE_PREFIX_PATH "$ENV{LIBTORCH_PATH}") endif() at the start of this file, similar to what's done in the root CMakeLists.txt.

Suggested change
# Set LibTorch path
set(CMAKE_PREFIX_PATH "/home/thomas/libtorch")
# Set LibTorch path from environment variable if provided
if(DEFINED ENV{LIBTORCH_PATH})
set(CMAKE_PREFIX_PATH "$ENV{LIBTORCH_PATH}")
endif()

Copilot uses AI. Check for mistakes.
Comment on lines +94 to +97
detection_lock.lock();
current_detections = detections;
fresh_data = true;
detection_lock.unlock();

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mutex is locked and then unlocked manually, which is error-prone if an exception occurs between lock() and unlock(). Use std::lock_guard or std::scoped_lock instead for exception-safe RAII-style locking. For example: { std::lock_guard&lt;std::mutex&gt; lock(detection_lock); current_detections = detections; fresh_data = true; }

Copilot uses AI. Check for mistakes.
Comment thread src/ar/read_landmarks.cpp
Comment on lines +51 to +54
landmark_lock.lock();
current_landmarks = zero_landmarks;
fresh_data = false;
landmark_lock.unlock();

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Manual mutex locking pattern that's not exception-safe. Multiple instances in this file use lock()/unlock() manually. All should use std::lock_guard for RAII-style locking to prevent deadlocks if exceptions are thrown.

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +41
calib_info:
calibration_time: "Sat 01 May 2021 01:54:16 PM PDT"
board_width: 11
board_height: 8
square_size: 20.
flags: 0
avg_reprojection_error: 4.5744250014767379e-01
intrinsic_params:
image_width: 640
image_height: 480
camera_matrix: !!opencv-matrix
rows: 3
cols: 3
dt: d
data: [ 6.4936764773277400e+02, 0., 3.3546152305967058e+02, 0.,
6.4776804982533793e+02, 2.4785660889077488e+02, 0., 0., 1. ]
distortion_coefficients: !!opencv-matrix
rows: 5
cols: 1
dt: d
data: [ -4.5670452858419575e-01, 2.1512832208218641e-01,
-1.1179480533495888e-03, -5.7450430930239835e-04, 0. ]
extrinsic_params: !!opencv-matrix
rows: 4
cols: 4
dt: d
data: [ 0., 0., 1., -.50,
-1., 0., 0., -.25,
0., 0., 0., 1.,
0., 0., 0., 0.]

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The camera configuration file has intrinsic and extrinsic parameters uncommented (previously they were commented out). This is a significant change that affects how the camera system operates. The PR description mentions enabling AR detection in simulator mode, but doesn't clearly state that camera parameters would be uncommented. Verify this change is intentional and won't break existing camera configurations or AR detection behavior.

Copilot uses AI. Check for mistakes.
Comment on lines +164 to +166
output = current_detections;
fresh_data = false;
detection_lock.unlock();

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same mutex locking issue exists here - using manual lock()/unlock() instead of RAII lock guards. This pattern appears multiple times in this file (lines 94-97, 164-166). All should use std::lock_guard for exception safety.

Copilot uses AI. Check for mistakes.
- Add OWL-ViT fine-tuning scripts (train, export, dataset, capture)
- Update ObjectDetector to use CLIP normalization and vision-only model
- Export model with baked text embeddings (no text encoder at runtime)
- Lower default confidence threshold to 0.75
- Add __pycache__ and dataset artifacts to .gitignore
@thomas0829
thomas0829 force-pushed the ar-detection-improvements branch from a62c13b to cd202ce Compare March 19, 2026 21:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants