Vision Detection System Improvements - #376
Conversation
imisaacwu
left a comment
There was a problem hiding this comment.
Looks good, couple small things 👍
|
|
||
| // Open mast camera to load its configuration before initializing AR | ||
| LOG_F(INFO, "Opening mast camera..."); | ||
| auto mastCam = robot::openCamera(Constants::MAST_CAMERA_ID); |
There was a problem hiding this comment.
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.
| 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()) { |
There was a problem hiding this comment.
Could y'all generalize this to have detection on any camera?
| 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(), |
There was a problem hiding this comment.
Could you also generalize the marker type?
| // 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(); |
There was a problem hiding this comment.
Do we need to create a new DetectorParameters every time?
| 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); |
There was a problem hiding this comment.
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)?
690a255 to
7e4bedc
Compare
ed17303 to
4c1596c
Compare
…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
64afedc to
5a4926b
Compare
There was a problem hiding this comment.
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
estimatePoseSingleMarkerswithsolvePnPfor 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.
|
|
||
| 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; |
There was a problem hiding this comment.
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.
| 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; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
| * @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); |
There was a problem hiding this comment.
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.
| 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; | ||
| } |
There was a problem hiding this comment.
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.
| // 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); | ||
| } |
There was a problem hiding this comment.
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.
| # Set LibTorch path | ||
| set(CMAKE_PREFIX_PATH "/home/thomas/libtorch") |
There was a problem hiding this comment.
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.
| # 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() |
| detection_lock.lock(); | ||
| current_detections = detections; | ||
| fresh_data = true; | ||
| detection_lock.unlock(); |
There was a problem hiding this comment.
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<std::mutex> lock(detection_lock); current_detections = detections; fresh_data = true; }
| landmark_lock.lock(); | ||
| current_landmarks = zero_landmarks; | ||
| fresh_data = false; | ||
| landmark_lock.unlock(); |
There was a problem hiding this comment.
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.
| 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.] |
There was a problem hiding this comment.
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.
| output = current_detections; | ||
| fresh_data = false; | ||
| detection_lock.unlock(); |
There was a problem hiding this comment.
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.
- 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
a62c13b to
cd202ce
Compare
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:
estimatePoseSingleMarkersAPI withsolvePnPfor better accuracyRuntime Control:
Rkey (default: OFF)2. Object Detection Integration (NEW ✨)
Model & Architecture:
owlvit-cpp.pt(excluded from git, 586MB)Features:
Okey (default: OFF)Testing & Visualization:
Code Structure:
3. Unified Keyboard Control Interface
Both detection systems can now be controlled via keyboard in real-time:
ORQDesign Rationale:
4. Optional LibTorch Support
Flexibility:
Installation:
Technical Details
Dependencies Added:
LIBTORCH_PATHfor custom LibTorch locationOWLVIT_MODEL_PATHfor custom model locationModified Files:
src/Globals.h/cpp- Added global enable flagssrc/Rover.cpp- Keyboard control loop with conditional compilationsrc/ar/read_landmarks.cpp- Enable/disable checkingsrc/network/MissionControlTasks.cpp- ArUco detection guardsrc/CMakeLists.txt- Optional object detection modulesrc/object-detection/ObjectDetector.cpp- CUDA JIT fixsrc/object-detection/read_objects.cpp- Smart model path detectionREADME.md- LibTorch installation guide.gitignore- Ignore large model filesCUDA Compatibility:
__ldgundefined identifier issuesTesting:
Without LibTorch: