Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3e79f4103 | ||
|
|
cb59f59277 | ||
|
|
2ea8e25ec4 | ||
|
|
f07070cbe3 | ||
|
|
ce707d2993 | ||
|
|
f8e71ca290 | ||
|
|
1b24c9c566 | ||
|
|
5a7e8425f6 | ||
|
|
9367f6e92a | ||
|
|
71476f1cf0 |
1
.rm_bringup.pid
Normal file
1
.rm_bringup.pid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
34927
|
||||||
11507
screen.output
Normal file
11507
screen.output
Normal file
File diff suppressed because it is too large
Load Diff
@@ -105,6 +105,9 @@ public:
|
|||||||
// Get current mode
|
// Get current mode
|
||||||
DetectorMode getMode() const { return mode_; }
|
DetectorMode getMode() const { return mode_; }
|
||||||
|
|
||||||
|
// Get latest YOLO objects (for auto exposure control)
|
||||||
|
const std::vector<YoloObject>& getYoloObjects() const { return yolo_objects_; }
|
||||||
|
|
||||||
// Set ROI update interval (frames between YOLO updates)
|
// Set ROI update interval (frames between YOLO updates)
|
||||||
void setRoiUpdateInterval(int interval);
|
void setRoiUpdateInterval(int interval);
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
#include <image_transport/image_transport.hpp>
|
#include <image_transport/image_transport.hpp>
|
||||||
#include <rm_interfaces/msg/armors.hpp>
|
#include <rm_interfaces/msg/armors.hpp>
|
||||||
#include <rm_interfaces/srv/set_mode.hpp>
|
#include <rm_interfaces/srv/set_mode.hpp>
|
||||||
|
#include <std_msgs/msg/float32.hpp>
|
||||||
|
|
||||||
#include <cv_bridge/cv_bridge.h>
|
#include <cv_bridge/cv_bridge.h>
|
||||||
#include <tf2_ros/buffer.h>
|
#include <tf2_ros/buffer.h>
|
||||||
@@ -95,6 +96,7 @@ private:
|
|||||||
rclcpp::Subscription<sensor_msgs::msg::CameraInfo>::SharedPtr cam_info_sub_;
|
rclcpp::Subscription<sensor_msgs::msg::CameraInfo>::SharedPtr cam_info_sub_;
|
||||||
rclcpp::Publisher<rm_interfaces::msg::Armors>::SharedPtr armors_pub_;
|
rclcpp::Publisher<rm_interfaces::msg::Armors>::SharedPtr armors_pub_;
|
||||||
rclcpp::Publisher<visualization_msgs::msg::MarkerArray>::SharedPtr marker_pub_;
|
rclcpp::Publisher<visualization_msgs::msg::MarkerArray>::SharedPtr marker_pub_;
|
||||||
|
rclcpp::Publisher<std_msgs::msg::Float32>::SharedPtr auto_exposure_pub_;
|
||||||
rclcpp::Service<rm_interfaces::srv::SetMode>::SharedPtr set_mode_srv_;
|
rclcpp::Service<rm_interfaces::srv::SetMode>::SharedPtr set_mode_srv_;
|
||||||
|
|
||||||
// Image transport for debug
|
// Image transport for debug
|
||||||
@@ -157,6 +159,21 @@ private:
|
|||||||
void performBinaryThresCalibration(const sensor_msgs::msg::Image::ConstSharedPtr& img_msg);
|
void performBinaryThresCalibration(const sensor_msgs::msg::Image::ConstSharedPtr& img_msg);
|
||||||
void saveBinaryThresToYaml(int binary_thres);
|
void saveBinaryThresToYaml(int binary_thres);
|
||||||
|
|
||||||
|
// Auto exposure control
|
||||||
|
bool auto_exposure_enable_ = false;
|
||||||
|
rclcpp::TimerBase::SharedPtr auto_exposure_timer_;
|
||||||
|
double current_exposure_time_ = 1000.0;
|
||||||
|
double target_brightness_ = 100.0;
|
||||||
|
double brightness_tolerance_ = 20.0;
|
||||||
|
double exposure_step_gain_ = 100.0;
|
||||||
|
double exposure_decay_step_ = 20.0;
|
||||||
|
double exposure_min_ = 600.0;
|
||||||
|
double exposure_max_ = 2000.0;
|
||||||
|
double auto_exposure_control_interval_ms_ = 100.0;
|
||||||
|
void autoExposureTimerCallback();
|
||||||
|
double computeBrightnessInRois(const cv::Mat& gray_img, const std::vector<YoloObject>& yolo_objects);
|
||||||
|
void callSetExposureService(double exposure_time);
|
||||||
|
|
||||||
// BA
|
// BA
|
||||||
bool use_ba_ = true;
|
bool use_ba_ = true;
|
||||||
|
|
||||||
|
|||||||
@@ -118,6 +118,10 @@ ArmorYoloDetectorNode::ArmorYoloDetectorNode(const rclcpp::NodeOptions& options)
|
|||||||
marker_pub_ =
|
marker_pub_ =
|
||||||
this->create_publisher<visualization_msgs::msg::MarkerArray>("armor_detector/marker", 10);
|
this->create_publisher<visualization_msgs::msg::MarkerArray>("armor_detector/marker", 10);
|
||||||
|
|
||||||
|
// Auto exposure publisher
|
||||||
|
auto_exposure_pub_ =
|
||||||
|
this->create_publisher<std_msgs::msg::Float32>("auto_exposure/set_exposure", 10);
|
||||||
|
|
||||||
// Debug
|
// Debug
|
||||||
debug_ = this->declare_parameter("debug", false);
|
debug_ = this->declare_parameter("debug", false);
|
||||||
debug_log_interval_frames_ =
|
debug_log_interval_frames_ =
|
||||||
@@ -415,6 +419,25 @@ std::unique_ptr<Detector> ArmorYoloDetectorNode::initDetector() {
|
|||||||
on_set_parameters_callback_handle_ = this->add_on_set_parameters_callback(
|
on_set_parameters_callback_handle_ = this->add_on_set_parameters_callback(
|
||||||
std::bind(&ArmorYoloDetectorNode::onSetParameters, this, std::placeholders::_1));
|
std::bind(&ArmorYoloDetectorNode::onSetParameters, this, std::placeholders::_1));
|
||||||
|
|
||||||
|
// Auto exposure parameters
|
||||||
|
auto_exposure_enable_ = this->declare_parameter("auto_exposure.enable", false);
|
||||||
|
target_brightness_ = this->declare_parameter("auto_exposure.target_brightness", 100.0);
|
||||||
|
brightness_tolerance_ = this->declare_parameter("auto_exposure.brightness_tolerance", 10.0);
|
||||||
|
exposure_step_gain_ = this->declare_parameter("auto_exposure.step_gain", 100.0);
|
||||||
|
exposure_decay_step_ = this->declare_parameter("auto_exposure.decay_step", 5.0);
|
||||||
|
exposure_min_ = this->declare_parameter("auto_exposure.exposure_min", 100.0);
|
||||||
|
exposure_max_ = this->declare_parameter("auto_exposure.exposure_max", 20000.0);
|
||||||
|
auto_exposure_control_interval_ms_ = this->declare_parameter("auto_exposure.control_interval_ms", 100.0);
|
||||||
|
current_exposure_time_ = this->declare_parameter("auto_exposure.init_exposure_time", 5000.0);
|
||||||
|
|
||||||
|
if (auto_exposure_enable_) {
|
||||||
|
auto_exposure_timer_ = this->create_wall_timer(
|
||||||
|
std::chrono::milliseconds(static_cast<int>(auto_exposure_control_interval_ms_)),
|
||||||
|
[this]() { this->autoExposureTimerCallback(); });
|
||||||
|
FYT_INFO("armor_yolo_detect", "Auto exposure enabled (target_brightness={}, interval={}ms)",
|
||||||
|
target_brightness_, auto_exposure_control_interval_ms_);
|
||||||
|
}
|
||||||
|
|
||||||
return detector;
|
return detector;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -543,6 +566,42 @@ rcl_interfaces::msg::SetParametersResult ArmorYoloDetectorNode::onSetParameters(
|
|||||||
debug_markers_ = param.as_bool();
|
debug_markers_ = param.as_bool();
|
||||||
} else if (param.get_name() == "debug.enable_result_img") {
|
} else if (param.get_name() == "debug.enable_result_img") {
|
||||||
debug_result_img_ = param.as_bool();
|
debug_result_img_ = param.as_bool();
|
||||||
|
} else if (param.get_name() == "auto_exposure.enable") {
|
||||||
|
bool new_enable = param.as_bool();
|
||||||
|
if (new_enable != auto_exposure_enable_) {
|
||||||
|
auto_exposure_enable_ = new_enable;
|
||||||
|
if (auto_exposure_enable_) {
|
||||||
|
auto_exposure_timer_ = this->create_wall_timer(
|
||||||
|
std::chrono::milliseconds(static_cast<int>(auto_exposure_control_interval_ms_)),
|
||||||
|
[this]() { this->autoExposureTimerCallback(); });
|
||||||
|
FYT_INFO("armor_yolo_detect", "Auto exposure enabled");
|
||||||
|
} else {
|
||||||
|
auto_exposure_timer_.reset();
|
||||||
|
FYT_INFO("armor_yolo_detect", "Auto exposure disabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (param.get_name() == "auto_exposure.target_brightness") {
|
||||||
|
target_brightness_ = param.as_double();
|
||||||
|
} else if (param.get_name() == "auto_exposure.brightness_tolerance") {
|
||||||
|
brightness_tolerance_ = param.as_double();
|
||||||
|
} else if (param.get_name() == "auto_exposure.step_gain") {
|
||||||
|
exposure_step_gain_ = param.as_double();
|
||||||
|
} else if (param.get_name() == "auto_exposure.decay_step") {
|
||||||
|
exposure_decay_step_ = param.as_double();
|
||||||
|
} else if (param.get_name() == "auto_exposure.exposure_min") {
|
||||||
|
exposure_min_ = param.as_double();
|
||||||
|
} else if (param.get_name() == "auto_exposure.exposure_max") {
|
||||||
|
exposure_max_ = param.as_double();
|
||||||
|
} else if (param.get_name() == "auto_exposure.control_interval_ms") {
|
||||||
|
auto_exposure_control_interval_ms_ = param.as_double();
|
||||||
|
if (auto_exposure_timer_ && auto_exposure_enable_) {
|
||||||
|
auto_exposure_timer_->cancel();
|
||||||
|
auto_exposure_timer_ = this->create_wall_timer(
|
||||||
|
std::chrono::milliseconds(static_cast<int>(auto_exposure_control_interval_ms_)),
|
||||||
|
[this]() { this->autoExposureTimerCallback(); });
|
||||||
|
}
|
||||||
|
} else if (param.get_name() == "auto_exposure.init_exposure_time") {
|
||||||
|
current_exposure_time_ = param.as_double();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -835,6 +894,123 @@ void ArmorYoloDetectorNode::saveBinaryThresToYaml(int binary_thres) {
|
|||||||
FYT_INFO("armor_yolo_detect", "Binary threshold saved to yaml: {}", calib_save_yaml_path_);
|
FYT_INFO("armor_yolo_detect", "Binary threshold saved to yaml: {}", calib_save_yaml_path_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ArmorYoloDetectorNode::autoExposureTimerCallback() {
|
||||||
|
if (!auto_exposure_enable_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(processing_mutex_);
|
||||||
|
|
||||||
|
if (detector_ == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the latest yolo objects from detector
|
||||||
|
const auto& yolo_objects = detector_->getYoloObjects();
|
||||||
|
if (yolo_objects.empty()) {
|
||||||
|
// No detection, apply decay step to move toward target brightness
|
||||||
|
double exposure_time = current_exposure_time_;
|
||||||
|
exposure_time -= exposure_decay_step_;
|
||||||
|
if (exposure_time < exposure_min_) {
|
||||||
|
exposure_time = exposure_min_;
|
||||||
|
}
|
||||||
|
if (exposure_time > exposure_max_) {
|
||||||
|
exposure_time = exposure_max_;
|
||||||
|
}
|
||||||
|
if (std::abs(exposure_time - current_exposure_time_) > 1.0) {
|
||||||
|
current_exposure_time_ = exposure_time;
|
||||||
|
callSetExposureService(current_exposure_time_);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get latest image from frame queue
|
||||||
|
cv::Mat gray_img;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock_queue(queue_mutex_);
|
||||||
|
if (frame_queue_.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto& img_msg = frame_queue_.back().img_msg;
|
||||||
|
auto img = cv_bridge::toCvShare(img_msg, "bgr8")->image;
|
||||||
|
cv::cvtColor(img, gray_img, cv::COLOR_BGR2GRAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute brightness in actual YOLO detected bbox regions (NOT expanded ROI)
|
||||||
|
double brightness = computeBrightnessInRois(gray_img, yolo_objects);
|
||||||
|
|
||||||
|
if (brightness < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const double diff = brightness - target_brightness_;
|
||||||
|
const double last_exposure_time = current_exposure_time_;
|
||||||
|
|
||||||
|
if (std::fabs(diff) > brightness_tolerance_) {
|
||||||
|
current_exposure_time_ -= diff * exposure_step_gain_;
|
||||||
|
} else {
|
||||||
|
current_exposure_time_ -= exposure_decay_step_;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_exposure_time_ < exposure_min_) {
|
||||||
|
current_exposure_time_ = exposure_min_;
|
||||||
|
}
|
||||||
|
if (current_exposure_time_ > exposure_max_) {
|
||||||
|
current_exposure_time_ = exposure_max_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only call service if exposure change is significant
|
||||||
|
if (std::abs(current_exposure_time_ - last_exposure_time) > 10.0) {
|
||||||
|
callSetExposureService(current_exposure_time_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double ArmorYoloDetectorNode::computeBrightnessInRois(
|
||||||
|
const cv::Mat& gray_img,
|
||||||
|
const std::vector<YoloObject>& yolo_objects) {
|
||||||
|
if (yolo_objects.empty() || gray_img.empty()) {
|
||||||
|
return -1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double total_brightness = 0.0;
|
||||||
|
int pixel_count = 0;
|
||||||
|
|
||||||
|
for (const auto& obj : yolo_objects) {
|
||||||
|
// Use actual YOLO detected bbox, NOT expanded ROI
|
||||||
|
cv::Rect bbox(
|
||||||
|
static_cast<int>(obj.rect.x),
|
||||||
|
static_cast<int>(obj.rect.y),
|
||||||
|
static_cast<int>(obj.rect.width),
|
||||||
|
static_cast<int>(obj.rect.height));
|
||||||
|
|
||||||
|
// Clamp to image bounds
|
||||||
|
bbox &= cv::Rect(0, 0, gray_img.cols, gray_img.rows);
|
||||||
|
|
||||||
|
if (bbox.area() < 10) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::Mat roi = gray_img(bbox);
|
||||||
|
cv::Scalar mean_val = cv::mean(roi);
|
||||||
|
total_brightness += mean_val[0] * roi.total();
|
||||||
|
pixel_count += static_cast<int>(roi.total());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pixel_count == 0) {
|
||||||
|
return -1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return total_brightness / pixel_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArmorYoloDetectorNode::callSetExposureService(double exposure_time) {
|
||||||
|
// Publish exposure time to camera driver via topic
|
||||||
|
std_msgs::msg::Float32 msg;
|
||||||
|
msg.data = exposure_time;
|
||||||
|
auto_exposure_pub_->publish(msg);
|
||||||
|
FYT_DEBUG("armor_yolo_detect", "Auto exposure set exposure_time: {:.1f}", exposure_time);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace armor_yolo_detect
|
} // namespace armor_yolo_detect
|
||||||
|
|
||||||
#include "rclcpp_components/register_node_macro.hpp"
|
#include "rclcpp_components/register_node_macro.hpp"
|
||||||
|
|||||||
@@ -36,8 +36,8 @@
|
|||||||
lost_time_thres: 1.0
|
lost_time_thres: 1.0
|
||||||
|
|
||||||
solver:
|
solver:
|
||||||
shoot_rate_min: 3
|
shoot_rate_min: 6
|
||||||
shoot_rate_max: 13
|
shoot_rate_max: 12
|
||||||
ekf_count_threshold: 30
|
ekf_count_threshold: 30
|
||||||
bullet_speed: 20.5
|
bullet_speed: 20.5
|
||||||
shooting_range_width: 0.10 #射击范围
|
shooting_range_width: 0.10 #射击范围
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
roi_expand_pixel: 160
|
roi_expand_pixel: 160
|
||||||
|
|
||||||
# Binary threshold for light detection
|
# Binary threshold for light detection
|
||||||
binary_thres: 80
|
binary_thres: 120
|
||||||
|
|
||||||
# Binary threshold calibration mode
|
# Binary threshold calibration mode
|
||||||
# When enabled, automatically calibrates binary_thres to match traditional detection size with YOLO
|
# When enabled, automatically calibrates binary_thres to match traditional detection size with YOLO
|
||||||
@@ -49,3 +49,16 @@
|
|||||||
debug.enable_terminal_log: true
|
debug.enable_terminal_log: true
|
||||||
debug.enable_markers: true
|
debug.enable_markers: true
|
||||||
debug.enable_result_img: true
|
debug.enable_result_img: true
|
||||||
|
|
||||||
|
# Auto exposure control
|
||||||
|
# When enabled, automatically adjusts camera exposure based on YOLO detected regions (actual bbox, not expanded ROI)
|
||||||
|
# Algorithm: if brightness diff > tolerance, adjust by diff * step_gain; else decay by decay_step toward target
|
||||||
|
auto_exposure.enable: true
|
||||||
|
auto_exposure.target_brightness: 100.0 # Target brightness for detected regions (0-255)
|
||||||
|
auto_exposure.brightness_tolerance: 20.0 # Brightness tolerance, no adjustment if within this range
|
||||||
|
auto_exposure.step_gain: 10.0 # Exposure adjustment gain when brightness diff exceeds tolerance
|
||||||
|
auto_exposure.decay_step: 20.0 # Exposure decay step when brightness is within tolerance
|
||||||
|
auto_exposure.exposure_min: 600.0 # Minimum exposure time (us)
|
||||||
|
auto_exposure.exposure_max: 2000.0 # Maximum exposure time (us)
|
||||||
|
auto_exposure.control_interval_ms: 100.0 # Auto exposure control loop interval (ms)
|
||||||
|
auto_exposure.init_exposure_time: 1000.0 # Initial exposure time for auto exposure mode (us)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/**:
|
/**:
|
||||||
ros__parameters:
|
ros__parameters:
|
||||||
camera_info_url: package://rm_bringup/config/camera_info.yaml
|
camera_info_url: package://rm_bringup/config/camera_info.yaml
|
||||||
exposure_time: 1000
|
exposure_time: 800
|
||||||
gain: 18.3
|
gain: 18.3
|
||||||
resolution_width: 1440
|
resolution_width: 1440
|
||||||
resolution_height: 1080
|
resolution_height: 1080
|
||||||
@@ -9,3 +9,4 @@
|
|||||||
offsetY: 0
|
offsetY: 0
|
||||||
camera_name: "hik"
|
camera_name: "hik"
|
||||||
recording: false #是否录制
|
recording: false #是否录制
|
||||||
|
open_delay_ms: 2000 # Camera open delay after previous instance closed (ms)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <rclcpp/rclcpp.hpp>
|
#include <rclcpp/rclcpp.hpp>
|
||||||
#include <sensor_msgs/msg/camera_info.hpp>
|
#include <sensor_msgs/msg/camera_info.hpp>
|
||||||
#include <sensor_msgs/msg/image.hpp>
|
#include <sensor_msgs/msg/image.hpp>
|
||||||
|
#include <std_msgs/msg/float32.hpp>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
@@ -65,6 +66,20 @@ public:
|
|||||||
// Heartbeat
|
// Heartbeat
|
||||||
heartbeat_ = fyt::HeartBeatPublisher::create(this);
|
heartbeat_ = fyt::HeartBeatPublisher::create(this);
|
||||||
|
|
||||||
|
// Auto exposure subscriber - receives target exposure time from armor detector
|
||||||
|
auto_exposure_sub_ = this->create_subscription<std_msgs::msg::Float32>(
|
||||||
|
"auto_exposure/set_exposure", rclcpp::SensorDataQoS(),
|
||||||
|
[this](const std_msgs::msg::Float32::SharedPtr msg) {
|
||||||
|
if (msg->data > 0) {
|
||||||
|
std::lock_guard<std::mutex> lock(camera_mutex_);
|
||||||
|
exposure_time_ = static_cast<int>(msg->data);
|
||||||
|
if (camera_handle_ != nullptr) {
|
||||||
|
(void)MV_CC_SetFloatValue(camera_handle_, "ExposureTime", static_cast<float>(exposure_time_));
|
||||||
|
FYT_DEBUG("camera_driver", "Auto exposure set exposure_time: {}", exposure_time_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Param set callback
|
// Param set callback
|
||||||
params_callback_handle_ = this->add_on_set_parameters_callback(
|
params_callback_handle_ = this->add_on_set_parameters_callback(
|
||||||
std::bind(&HikCameraNode::parametersCallback, this, std::placeholders::_1));
|
std::bind(&HikCameraNode::parametersCallback, this, std::placeholders::_1));
|
||||||
@@ -83,8 +98,10 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Watch dog timer (also handles initial open)
|
// Watch dog timer (also handles initial open)
|
||||||
|
// Add initial delay to allow previous camera instance to fully release resources
|
||||||
|
int open_delay_ms = this->declare_parameter("open_delay_ms", 1000);
|
||||||
timer_ = this->create_wall_timer(
|
timer_ = this->create_wall_timer(
|
||||||
std::chrono::milliseconds(1000), std::bind(&HikCameraNode::timerCallback, this));
|
std::chrono::milliseconds(open_delay_ms), std::bind(&HikCameraNode::timerCallback, this));
|
||||||
|
|
||||||
// Start capture thread
|
// Start capture thread
|
||||||
capture_thread_ = std::thread([this]() { this->captureLoop(); });
|
capture_thread_ = std::thread([this]() { this->captureLoop(); });
|
||||||
@@ -193,20 +210,29 @@ private:
|
|||||||
|
|
||||||
void timerCallback()
|
void timerCallback()
|
||||||
{
|
{
|
||||||
// Open camera
|
// Open camera with retry delay
|
||||||
while (!is_open_ && rclcpp::ok()) {
|
if (!is_open_) {
|
||||||
|
static int retry_count = 0;
|
||||||
if (!open()) {
|
if (!open()) {
|
||||||
FYT_ERROR("camera_driver", "open() failed");
|
retry_count++;
|
||||||
|
if (retry_count >= 3) {
|
||||||
|
FYT_ERROR("camera_driver", "open() failed {} times, will keep retrying", retry_count);
|
||||||
|
}
|
||||||
close();
|
close();
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
retry_count = 0;
|
||||||
|
is_open_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Watchdog: no frames for too long => reconnect
|
// Watchdog: no frames for too long => reconnect
|
||||||
double dt = (this->now() - latest_frame_stamp_).seconds();
|
double dt = (this->now() - latest_frame_stamp_).seconds();
|
||||||
if (dt > 5.0) {
|
if (dt > 5.0) {
|
||||||
FYT_WARN("camera_driver", "Camera is not alive! lost frame for {:.2f} seconds", dt);
|
FYT_WARN("camera_driver", "Camera is not alive! lost frame for {:.2f} seconds, reconnecting...", dt);
|
||||||
close();
|
close();
|
||||||
|
is_open_ = false;
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,10 +240,15 @@ private:
|
|||||||
{
|
{
|
||||||
if (is_open_) {
|
if (is_open_) {
|
||||||
close();
|
close();
|
||||||
|
// Wait for camera to fully release resources after close
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||||
}
|
}
|
||||||
|
|
||||||
FYT_INFO("camera_driver", "Opening Hik Camera Device!");
|
FYT_INFO("camera_driver", "Opening Hik Camera Device!");
|
||||||
|
|
||||||
|
// Additional wait before enumerating devices
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||||
|
|
||||||
MV_CC_DEVICE_INFO_LIST device_list;
|
MV_CC_DEVICE_INFO_LIST device_list;
|
||||||
int ret = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, &device_list);
|
int ret = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, &device_list);
|
||||||
if (ret != MV_OK || device_list.nDeviceNum == 0) {
|
if (ret != MV_OK || device_list.nDeviceNum == 0) {
|
||||||
@@ -249,6 +280,24 @@ private:
|
|||||||
// Apply ROI/offset before querying image info
|
// Apply ROI/offset before querying image info
|
||||||
applyRoiLocked();
|
applyRoiLocked();
|
||||||
|
|
||||||
|
// Set pixel format to RGB8 (try to force color output)
|
||||||
|
// PixelType_Gvsp_RGB8_Packed = 0x02100014
|
||||||
|
int set_ret = MV_CC_SetEnumValue(camera_handle_, "PixelFormat", PixelType_Gvsp_RGB8_Packed);
|
||||||
|
if (set_ret != MV_OK) {
|
||||||
|
FYT_WARN("camera_driver", "Failed to set PixelFormat to RGB8: [0x{:x}]", set_ret);
|
||||||
|
} else {
|
||||||
|
FYT_INFO("camera_driver", "PixelFormat set to RGB8");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for pixel format to take effect
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
|
||||||
|
// Verify pixel format
|
||||||
|
MVCC_ENUMVALUE enum_value = {0};
|
||||||
|
if (MV_CC_GetEnumValue(camera_handle_, "PixelFormat", &enum_value) == MV_OK) {
|
||||||
|
FYT_INFO("camera_driver", "Current PixelFormat: 0x{:x}", enum_value.nCurValue);
|
||||||
|
}
|
||||||
|
|
||||||
// Set exposure / gain
|
// Set exposure / gain
|
||||||
(void)MV_CC_SetFloatValue(camera_handle_, "ExposureTime", static_cast<float>(exposure_time_));
|
(void)MV_CC_SetFloatValue(camera_handle_, "ExposureTime", static_cast<float>(exposure_time_));
|
||||||
(void)MV_CC_SetFloatValue(camera_handle_, "Gain", static_cast<float>(gain_));
|
(void)MV_CC_SetFloatValue(camera_handle_, "Gain", static_cast<float>(gain_));
|
||||||
@@ -295,6 +344,9 @@ private:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait for camera to be ready after starting grabbing
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
|
||||||
is_open_ = true;
|
is_open_ = true;
|
||||||
FYT_INFO("camera_driver", "Hik Camera Device Open Success!");
|
FYT_INFO("camera_driver", "Hik Camera Device Open Success!");
|
||||||
return true;
|
return true;
|
||||||
@@ -343,36 +395,69 @@ private:
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure output buffer
|
// Handle different pixel formats
|
||||||
const size_t need_size =
|
// Mono8 (0x1080009) needs to be converted to RGB8 by duplicating the channel
|
||||||
static_cast<size_t>(out_frame.stFrameInfo.nWidth) * out_frame.stFrameInfo.nHeight * 3;
|
// RGB8 (PixelType_Gvsp_RGB8_Packed = 0x02100014) can be used directly
|
||||||
if (image_msg_.data.size() != need_size) {
|
const bool is_mono8 = (out_frame.stFrameInfo.enPixelType == 0x1080009);
|
||||||
image_msg_.data.resize(need_size);
|
const bool is_rgb8 = (out_frame.stFrameInfo.enPixelType == 0x02100014);
|
||||||
}
|
|
||||||
|
|
||||||
convert_param_.pDstBuffer = image_msg_.data.data();
|
if (is_mono8) {
|
||||||
convert_param_.nDstBufferSize = image_msg_.data.size();
|
// Convert Mono8 to RGB8 by duplicating the single channel
|
||||||
convert_param_.pSrcData = out_frame.pBufAddr;
|
const size_t mono_size = static_cast<size_t>(out_frame.stFrameInfo.nWidth) * out_frame.stFrameInfo.nHeight;
|
||||||
convert_param_.nSrcDataLen = out_frame.stFrameInfo.nFrameLen;
|
const size_t rgb_size = mono_size * 3;
|
||||||
convert_param_.enSrcPixelType = out_frame.stFrameInfo.enPixelType;
|
if (image_msg_.data.size() != rgb_size) {
|
||||||
|
image_msg_.data.resize(rgb_size);
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(camera_mutex_);
|
|
||||||
if (!is_open_ || camera_handle_ == nullptr) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
ret = MV_CC_ConvertPixelType(camera_handle_, &convert_param_);
|
uint8_t* src = static_cast<uint8_t*>(out_frame.pBufAddr);
|
||||||
}
|
uint8_t* dst = image_msg_.data.data();
|
||||||
if (ret != MV_OK) {
|
for (size_t i = 0; i < mono_size; i++) {
|
||||||
FYT_WARN("camera_driver", "ConvertPixelType failed: [0x{:x}]", ret);
|
dst[i * 3 + 0] = src[i]; // B
|
||||||
|
dst[i * 3 + 1] = src[i]; // G
|
||||||
|
dst[i * 3 + 2] = src[i]; // R
|
||||||
|
}
|
||||||
|
image_msg_.encoding = "rgb8";
|
||||||
|
} else if (is_rgb8) {
|
||||||
|
// Already RGB8, just copy directly
|
||||||
|
const size_t rgb_size = static_cast<size_t>(out_frame.stFrameInfo.nWidth) * out_frame.stFrameInfo.nHeight * 3;
|
||||||
|
if (image_msg_.data.size() != rgb_size) {
|
||||||
|
image_msg_.data.resize(rgb_size);
|
||||||
|
}
|
||||||
|
memcpy(image_msg_.data.data(), out_frame.pBufAddr, rgb_size);
|
||||||
|
image_msg_.encoding = "rgb8";
|
||||||
|
} else {
|
||||||
|
// Try SDK conversion for other formats
|
||||||
|
const size_t need_size =
|
||||||
|
static_cast<size_t>(out_frame.stFrameInfo.nWidth) * out_frame.stFrameInfo.nHeight * 3;
|
||||||
|
if (image_msg_.data.size() != need_size) {
|
||||||
|
image_msg_.data.resize(need_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
convert_param_.pDstBuffer = image_msg_.data.data();
|
||||||
|
convert_param_.nDstBufferSize = image_msg_.data.size();
|
||||||
|
convert_param_.pSrcData = out_frame.pBufAddr;
|
||||||
|
convert_param_.nSrcDataLen = out_frame.stFrameInfo.nFrameLen;
|
||||||
|
convert_param_.enSrcPixelType = out_frame.stFrameInfo.enPixelType;
|
||||||
|
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(camera_mutex_);
|
std::lock_guard<std::mutex> lock(camera_mutex_);
|
||||||
if (camera_handle_ != nullptr) {
|
if (!is_open_ || camera_handle_ == nullptr) {
|
||||||
(void)MV_CC_FreeImageBuffer(camera_handle_, &out_frame);
|
continue;
|
||||||
}
|
}
|
||||||
|
ret = MV_CC_ConvertPixelType(camera_handle_, &convert_param_);
|
||||||
}
|
}
|
||||||
fail_count_++;
|
if (ret != MV_OK) {
|
||||||
continue;
|
FYT_WARN("camera_driver", "ConvertPixelType failed: [0x{:x}], src_pixel_type=0x{:x}, width={}, height={}",
|
||||||
|
ret, out_frame.stFrameInfo.enPixelType, out_frame.stFrameInfo.nWidth, out_frame.stFrameInfo.nHeight);
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(camera_mutex_);
|
||||||
|
if (camera_handle_ != nullptr) {
|
||||||
|
(void)MV_CC_FreeImageBuffer(camera_handle_, &out_frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fail_count_++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
image_msg_.encoding = "rgb8";
|
||||||
}
|
}
|
||||||
|
|
||||||
auto stamp = this->now();
|
auto stamp = this->now();
|
||||||
@@ -482,6 +567,9 @@ private:
|
|||||||
// Heartbeat
|
// Heartbeat
|
||||||
fyt::HeartBeatPublisher::SharedPtr heartbeat_;
|
fyt::HeartBeatPublisher::SharedPtr heartbeat_;
|
||||||
|
|
||||||
|
// Auto exposure subscriber
|
||||||
|
rclcpp::Subscription<std_msgs::msg::Float32>::SharedPtr auto_exposure_sub_;
|
||||||
|
|
||||||
// Params
|
// Params
|
||||||
int frame_rate_{210};
|
int frame_rate_{210};
|
||||||
int exposure_time_{5000};
|
int exposure_time_{5000};
|
||||||
|
|||||||
Reference in New Issue
Block a user