Compare commits

10 Commits

Author SHA1 Message Date
cyy_mac
d3e79f4103 try fix gray 2026-03-28 05:07:34 +08:00
cyy_mac
cb59f59277 try fix gray 2026-03-28 05:03:46 +08:00
cyy_mac
2ea8e25ec4 fix camera 2026-03-28 04:59:54 +08:00
cyy_mac
f07070cbe3 尝试修复相机 2026-03-28 04:56:19 +08:00
fengsh
ce707d2993 打开自动曝光 2026-03-28 04:52:18 +08:00
cyy_mac
f8e71ca290 尝试修复相机报错 2026-03-28 04:51:19 +08:00
cyy_mac
1b24c9c566 使用服务控制相机曝光 2026-03-28 04:46:33 +08:00
cyy_mac
5a7e8425f6 修复quequ报错 2026-03-28 04:39:02 +08:00
cyy_mac
9367f6e92a 更新自动曝光 2026-03-28 04:32:34 +08:00
fengsh
71476f1cf0 应对高曝光,比赛现场 2026-03-27 20:06:48 +08:00
10 changed files with 11839 additions and 33 deletions

BIN
.DS_Store vendored

Binary file not shown.

1
.rm_bringup.pid Normal file
View File

@@ -0,0 +1 @@
34927

11507
screen.output Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -105,6 +105,9 @@ public:
// Get current 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)
void setRoiUpdateInterval(int interval);

View File

@@ -34,6 +34,7 @@
#include <image_transport/image_transport.hpp>
#include <rm_interfaces/msg/armors.hpp>
#include <rm_interfaces/srv/set_mode.hpp>
#include <std_msgs/msg/float32.hpp>
#include <cv_bridge/cv_bridge.h>
#include <tf2_ros/buffer.h>
@@ -95,6 +96,7 @@ private:
rclcpp::Subscription<sensor_msgs::msg::CameraInfo>::SharedPtr cam_info_sub_;
rclcpp::Publisher<rm_interfaces::msg::Armors>::SharedPtr armors_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_;
// Image transport for debug
@@ -157,6 +159,21 @@ private:
void performBinaryThresCalibration(const sensor_msgs::msg::Image::ConstSharedPtr& img_msg);
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
bool use_ba_ = true;

View File

@@ -118,6 +118,10 @@ ArmorYoloDetectorNode::ArmorYoloDetectorNode(const rclcpp::NodeOptions& options)
marker_pub_ =
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_ = this->declare_parameter("debug", false);
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(
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;
}
@@ -543,6 +566,42 @@ rcl_interfaces::msg::SetParametersResult ArmorYoloDetectorNode::onSetParameters(
debug_markers_ = param.as_bool();
} else if (param.get_name() == "debug.enable_result_img") {
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_);
}
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
#include "rclcpp_components/register_node_macro.hpp"

View File

@@ -36,8 +36,8 @@
lost_time_thres: 1.0
solver:
shoot_rate_min: 3
shoot_rate_max: 13
shoot_rate_min: 6
shoot_rate_max: 12
ekf_count_threshold: 30
bullet_speed: 20.5
shooting_range_width: 0.10 #射击范围

View File

@@ -13,7 +13,7 @@
roi_expand_pixel: 160
# Binary threshold for light detection
binary_thres: 80
binary_thres: 120
# Binary threshold calibration mode
# When enabled, automatically calibrates binary_thres to match traditional detection size with YOLO
@@ -49,3 +49,16 @@
debug.enable_terminal_log: true
debug.enable_markers: 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)

View File

@@ -1,7 +1,7 @@
/**:
ros__parameters:
camera_info_url: package://rm_bringup/config/camera_info.yaml
exposure_time: 1000
exposure_time: 800
gain: 18.3
resolution_width: 1440
resolution_height: 1080
@@ -9,3 +9,4 @@
offsetY: 0
camera_name: "hik"
recording: false #是否录制
open_delay_ms: 2000 # Camera open delay after previous instance closed (ms)

View File

@@ -11,6 +11,7 @@
#include <rclcpp/rclcpp.hpp>
#include <sensor_msgs/msg/camera_info.hpp>
#include <sensor_msgs/msg/image.hpp>
#include <std_msgs/msg/float32.hpp>
#include <string>
#include <thread>
@@ -65,6 +66,20 @@ public:
// Heartbeat
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
params_callback_handle_ = this->add_on_set_parameters_callback(
std::bind(&HikCameraNode::parametersCallback, this, std::placeholders::_1));
@@ -83,8 +98,10 @@ public:
}
// 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(
std::chrono::milliseconds(1000), std::bind(&HikCameraNode::timerCallback, this));
std::chrono::milliseconds(open_delay_ms), std::bind(&HikCameraNode::timerCallback, this));
// Start capture thread
capture_thread_ = std::thread([this]() { this->captureLoop(); });
@@ -193,20 +210,29 @@ private:
void timerCallback()
{
// Open camera
while (!is_open_ && rclcpp::ok()) {
// Open camera with retry delay
if (!is_open_) {
static int retry_count = 0;
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();
std::this_thread::sleep_for(std::chrono::milliseconds(500));
return;
}
retry_count = 0;
is_open_ = true;
}
// Watchdog: no frames for too long => reconnect
double dt = (this->now() - latest_frame_stamp_).seconds();
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();
is_open_ = false;
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
}
@@ -214,10 +240,15 @@ private:
{
if (is_open_) {
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!");
// Additional wait before enumerating devices
std::this_thread::sleep_for(std::chrono::milliseconds(200));
MV_CC_DEVICE_INFO_LIST device_list;
int ret = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, &device_list);
if (ret != MV_OK || device_list.nDeviceNum == 0) {
@@ -249,6 +280,24 @@ private:
// Apply ROI/offset before querying image info
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
(void)MV_CC_SetFloatValue(camera_handle_, "ExposureTime", static_cast<float>(exposure_time_));
(void)MV_CC_SetFloatValue(camera_handle_, "Gain", static_cast<float>(gain_));
@@ -295,6 +344,9 @@ private:
return false;
}
// Wait for camera to be ready after starting grabbing
std::this_thread::sleep_for(std::chrono::milliseconds(100));
is_open_ = true;
FYT_INFO("camera_driver", "Hik Camera Device Open Success!");
return true;
@@ -343,36 +395,69 @@ private:
continue;
}
// Ensure output buffer
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);
}
// Handle different pixel formats
// Mono8 (0x1080009) needs to be converted to RGB8 by duplicating the channel
// RGB8 (PixelType_Gvsp_RGB8_Packed = 0x02100014) can be used directly
const bool is_mono8 = (out_frame.stFrameInfo.enPixelType == 0x1080009);
const bool is_rgb8 = (out_frame.stFrameInfo.enPixelType == 0x02100014);
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_);
if (!is_open_ || camera_handle_ == nullptr) {
continue;
if (is_mono8) {
// Convert Mono8 to RGB8 by duplicating the single channel
const size_t mono_size = static_cast<size_t>(out_frame.stFrameInfo.nWidth) * out_frame.stFrameInfo.nHeight;
const size_t rgb_size = mono_size * 3;
if (image_msg_.data.size() != rgb_size) {
image_msg_.data.resize(rgb_size);
}
ret = MV_CC_ConvertPixelType(camera_handle_, &convert_param_);
}
if (ret != MV_OK) {
FYT_WARN("camera_driver", "ConvertPixelType failed: [0x{:x}]", ret);
uint8_t* src = static_cast<uint8_t*>(out_frame.pBufAddr);
uint8_t* dst = image_msg_.data.data();
for (size_t i = 0; i < mono_size; i++) {
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_);
if (camera_handle_ != nullptr) {
(void)MV_CC_FreeImageBuffer(camera_handle_, &out_frame);
if (!is_open_ || camera_handle_ == nullptr) {
continue;
}
ret = MV_CC_ConvertPixelType(camera_handle_, &convert_param_);
}
fail_count_++;
continue;
if (ret != MV_OK) {
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();
@@ -482,6 +567,9 @@ private:
// Heartbeat
fyt::HeartBeatPublisher::SharedPtr heartbeat_;
// Auto exposure subscriber
rclcpp::Subscription<std_msgs::msg::Float32>::SharedPtr auto_exposure_sub_;
// Params
int frame_rate_{210};
int exposure_time_{5000};