圆心拟合
终于到了这一步,这里是整车观测的一个核心,在这里你会知道我们如果去计算所有装甲板的位置,以及我们如何去选择击打装甲板,当然这里的功能还不够完善和稳定,同时消耗大部分的时间资源,期待你们的优化
ArmorObserve::Center_fitting 函数如下:
center_position = armor.camera_position; // 圆心坐标
center_position = {center_position[0],center_position[2],-center_position[1]}; //世界坐标系
Armor_distance = AS.World_projection(armor,0); // 获取装甲板4点距离差
#define A
#ifdef A
yaw = armor.R[1];
#else
yaw = AS.Armor_Angle(armor); // 获取装甲板yaw角度
yaw = yaw*CV_PI/180.0f; // 转换为弧度
#endif //A
#ifdef CENTER_FIT
cv::putText(_src,"yaw:"+ std::to_string(yaw*180.0f/CV_PI),cv::Point(0,40),cv::FONT_HERSHEY_SIMPLEX, 1,cv::Scalar(255, 255, 0),2,3);
cv::putText(_src,"Armor_Distance:"+ std::to_string(Armor_distance),cv::Point(0,80),cv::FONT_HERSHEY_SIMPLEX, 1,cv::Scalar(255, 255, 0),2,3);
#endif //CENTER_FIT
需要注意的是,这里我们做了一个坐标转换的操作,我们整车观测的所有操作都是基于相机坐标系的,相信在你学完坐标系转换的部分,应该已经可以深刻的理解坐标系转换的必要和魅力,如果我们在世界坐标系下,是固定的,我们的计算复杂度和难度都会非常高,你可以自己手动推导一下
这一段代码是在保存装甲板的Yaw朝向角,这里我们获取了装甲板的4点距离差,我们得到Yaw的方法有两种,一种是pnp自带的朝向角解算,另一种就世界反投影法,关于世界反投影法,就是将一个虚拟的装甲板的投影到当前实际装甲板的位置上,然后我们通过从-90°~90°的一个三分查找,找到匹配度最高的装甲板,那么当前投影角度就是实际装甲板的角度,关于世界反投影的方法,可以仔细看看 2023年上交的青工会分享,赞美方佬!
接下来是重头戏
/** 高低装甲板和长短轴参数 */
double OF_length; // 前后装甲板高度
double RL_length; // 左右装甲板高度
double OF_height; // 前后装甲板高度
double RL_height; // 左右装甲板高度
// 判断当前装甲板是什么轴
if(OB_Track.axesState == LONG) { // 长轴
OF_length = OB.Long_axes;
RL_length = OB.Short_axes;
}
else if(OB_Track.axesState == SHORT) { // 短轴
OF_length = OB.Short_axes;
RL_length = OB.Long_axes;
}
if(OB_Track.HeightState == HIGH){ // 高轴
RL_height = -OB.High_LOW_difference;
}
else if(OB_Track.HeightState == LOW) { // 矮轴
RL_height = OB.High_LOW_difference;
}
/** 计算装甲板中心点坐标参数 */
double OF_cos_r = cos(abs(spin_angle))*OF_length; // 半径在x轴方向投影
double RL_cos_r = cos(abs(spin_angle))*RL_length; // 半径在x轴方向投影
double OF_sin_r = sin(abs(spin_angle))*OF_length; // 半径在y轴方向投影
double RL_sin_r = sin(abs(spin_angle))*RL_length; // 半径在y轴方向投影
/** 计算圆心坐标 */ // TODO: 需要将在相机坐标系下操作再转换到世界坐标系
center_position[0] += sin(yaw)*axes_length; // 三角函数解圆心
center_position[1] += cos(abs(yaw))*axes_length; // 三角函数解圆心
center_position = {center_position[0],-center_position[2],center_position[1]}; //相机坐标系
center_position = AS.cam2imu(center_position);
Smooth_Filter.update(center_position,Smooth_position); // 更新数据,进行平滑
// 平滑
// Smooth_position = {Smooth_position[0],-Smooth_position[2],Smooth_position[1]}; //相机坐标系
// Smooth_position = AS.cam2imu(Smooth_position);
// Smooth_position[2] = z;
Smooth_position[2] = OB.center_Z;
cir = AS.imu2pixel(Smooth_position); // 转换像素坐标
计算装甲板中心点坐标参数这里有点小问题,这里的逻辑需要放到spin_angel计算的后面,不然会导致解算出来的其实是上一帧的位置,而不是当前的位置,我现在手里的代码是老旧的,实际代码已经修改了,这里的计算我不相信展开说明了,你可以自己拿上纸笔,画出4个装甲板的俯视图,然后手动推导一下,便可以知道如何进行计算,最后转换回像素坐标是为了可以调试可视化,可以删除
接下来是火控预测和数据平滑,数据平滑写的比较简陋,只是一个所有数据的平均滤波,可以考虑用其他更好的方法
if(Smooth_Filter.fit){
// TODO:注意顺逆时针 这里都是逆时针
Eigen::Vector3d center_temp; // 圆心世界坐标(temp)
cv::Point2f spin_Aromor_cir; // 陀螺装甲板
double FPS_size = 1; // 帧率倍率(待调整) TODO:不能调过大,会导致跳过角度检测,试试计算(距离/运动时间*角速度)进行补偿 慢速:10(展示)(已弃)
// 已进入调参阶段
double compensate; // 弹道时间角度补偿
double Dial_time; // 拨盘转动时间 (新增)(待调整)
double time; // 距离使用车中心试试?还是使用装甲板的距离? 现使用车中心看看
double Diff = sqrt(pow(Smooth_position[0],2)+pow(Smooth_position[1],2));
double bullet_speed = 25;
double compensate_size = 1; // 倍率 TODO:测试可行,有上升空间(待测试)
time = Diff/bullet_speed; // 单位: s
Dial_time = 80; // 单位: ms
compensate = compensate_size*(time*1000)*(Angle_Speed) + Dial_time*(Angle_Speed); // 计算补偿角度 (等待测试看效果)
std::cout << "compensate:" << compensate << std::endl;
// std::cout << "anglespend:" << Angle_Speed << std::endl;
// std::cout << "time:" << time << std::endl;
cv::putText(_src,"compensate:"+ std::to_string(compensate),cv::Point(0,600),cv::FONT_HERSHEY_SIMPLEX, 1,cv::Scalar(255, 255, 0),2,3);
// 计算预测角度 (帧数补偿设置为15ms)
spin_angle = yaw*(180.0f/CV_PI) - FPS_size*(Angle_Speed*15) - compensate; // 陀螺装甲板所在角度(逆时针)
spin_angle *= (CV_PI/180.0f);
center_temp = AS.imu2cam(Smooth_position);
center_temp = {center_temp[0],center_temp[2],-center_temp[1]}; // 世界坐标系
spin_Aromor[0] = center_temp[0] - sin(spin_angle)*OF_length;
spin_Aromor[1] = center_temp[1] - cos(spin_angle)*OF_length;
spin_Aromor[2] = center_temp[2]-(RL_height/2);
spin_Aromor = {spin_Aromor[0],-spin_Aromor[2],spin_Aromor[1]}; //相机坐标系
spin_Aromor = AS.cam2imu(spin_Aromor);
spin_Aromor_cir = AS.imu2pixel(spin_Aromor);
// 计算其他点位
Left_Armor_angle = spin_angle*(180.0f/CV_PI) + 90.0;
Right_Armor_angle = spin_angle*(180.0f/CV_PI) - 90.0;
std::cout << spin_angle*(180.0f/CV_PI) << std::endl;
std::cout << Left_Armor_angle << std::endl;
if(yaw > 0){
// Left_Armor = {center_temp[0] - RL_cos_r,center_temp[1] + RL_sin_r,center_temp[2]+(RL_height/2)};
Left_Armor = {center_temp[0] - RL_cos_r,center_temp[1] - RL_sin_r,center_temp[2]};//test
Right_Armor = {center_temp[0] + RL_cos_r,center_temp[1] - RL_sin_r,center_temp[2]+(RL_height/2)};
O_Armor = {center_temp[0] + OF_sin_r,center_temp[1] + OF_cos_r,center_temp[2]-(RL_height/2)};
}else{
// Left_Armor = {center_temp[0] - RL_cos_r,center_temp[1] - RL_sin_r,center_temp[2]+(RL_height/2)};
Left_Armor = {center_temp[0] - RL_cos_r,center_temp[1] - RL_sin_r,center_temp[2]};//test
Right_Armor = {center_temp[0] + RL_cos_r,center_temp[1] + RL_sin_r,center_temp[2]+(RL_height/2)};
O_Armor = {center_temp[0] - OF_sin_r,center_temp[1] + OF_cos_r,center_temp[2]-(RL_height/2)};
}
// 转换部分,待优化
Left_Armor = {Left_Armor[0],-Left_Armor[2],Left_Armor[1]}; //相机坐标系
Left_Armor = AS.cam2imu(Left_Armor);
Left_Armor_cir = AS.imu2pixel(Left_Armor);
// 转换部分,待优化
Right_Armor = {Right_Armor[0],-Right_Armor[2],Right_Armor[1]}; //相机坐标系
Right_Armor = AS.cam2imu(Right_Armor);
Right_Armor_cir = AS.imu2pixel(Right_Armor);
// 转换部分,待优化
O_Armor = {O_Armor[0],-O_Armor[2],O_Armor[1]}; //相机坐标系
O_Armor = AS.cam2imu(O_Armor);
O_Armor_cir = AS.imu2pixel(O_Armor);
// 计算左右30度(暂定,可调) TODO:长短轴高低装甲板等待计算处理
cv::Point2f left_target_Armor; // 左侧装甲板点位
cv::Point2f right_target_Armor; // 右侧装甲板点位
left_target[0] = center_temp[0] - sin(30*(CV_PI/180.0f))*RL_length;
left_target[1] = center_temp[1] - cos(30*(CV_PI/180.0f))*RL_length;
left_target[2] = center_temp[2]+(RL_height/2);
right_target[0] = center_temp[0] + sin(30*(CV_PI/180.0f))*RL_length;
right_target[1] = center_temp[1] - cos(30*(CV_PI/180.0f))*RL_length;
right_target[2] = center_temp[2]+(RL_height/2);
// 转换部分,待优化
left_target = {left_target[0],-left_target[2],left_target[1]}; //相机坐标系
left_target = AS.cam2imu(left_target);
left_target_Armor = AS.imu2pixel(left_target);
// 转换部分,待优化
right_target = {right_target[0],-right_target[2],right_target[1]}; //相机坐标系
right_target = AS.cam2imu(right_target);
right_target_Armor = AS.imu2pixel(right_target);
// 更新拟合状态
// Fit_OK = Smooth_Filter.fit && CKF.Track_OK;
Fit_OK = Smooth_Filter.fit;
// Fit_OK = true;
cv::Point2f Armor_cir;
cv::Point2f pre_Armor_cir;
Armor_cir = AS.imu2pixel(armor.world_position);
pre_Armor = {pre_Armor[0],-pre_Armor[2],pre_Armor[1]}; //相机坐标系
pre_Armor = AS.cam2imu(pre_Armor);
pre_Armor_cir = AS.imu2pixel(pre_Armor);
circle(_src,cir,5,cv::Scalar(0,0,255),-1);
// circle(_src,Armor_cir,5,cv::Scalar(0,255,255),-1);
circle(_src,spin_Aromor_cir,5,cv::Scalar(255,0,255),-1);
// 左右预测点位
// circle(_src,Left_Armor_cir,5,cv::Scalar(255,0,255),-1);
// circle(_src,Right_Armor_cir,5,cv::Scalar(255,0,255),-1);
// circle(_src,O_Armor_cir,5,cv::Scalar(255,0,255),-1);
// 移动点位
// circle(_src,left_target_Armor,5,cv::Scalar(0,255,0),-1);
circle(_src,right_target_Armor,5,cv::Scalar(0,255,0),-1);
// circle(_src,pre_Armor_cir,5,cv::Scalar(255,0,255),-1);
// circle(_src,pre_cir,5,cv::Scalar(0,255,0),-1);
// std::cout << pre_cir << std::endl;
// cv::imshow("Observe_src",_src);
}
这一段就有很明显的调试和开发痕迹了,比较凌乱,注意不要以这个代码为主,这个代码版本已经落后,只是为了辅助学习原初的逻辑和想法,很多地方后续都是优化过的
首先就是一个角度预测,这里固定15ms不是个好的选择,要根据实际处理时间调整,然后根据当前一个圆心,计算出所有的装甲板位置,计算方法比较简单,就是一些几何和三角函数,存在一定的优化空间,比如可以考虑用矩阵计算,这样同时可以同时计算多个数据,速度上矩阵也比自己写的计算要快
得到所有的装甲板位置和当前的一个预测角度,我们很自然的可以选择出我们需要击打的装甲板,发送开火命令,完成火控流程