Files
odin_ros_driver1/src/cloud_reprojector.cpp
T

194 lines
6.3 KiB
C++

/*
Copyright 2025 Manifold Tech Ltd.(www.manifoldtech.com.co)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include "cloud_reprojector.hpp"
#include <cmath>
#include <limits>
CloudReprojector::CloudReprojector()
{
}
bool CloudReprojector::initialize(const CameraParams& cam_params, const ExtrinsicParams& ext_params)
{
camera_params_ = cam_params;
extrinsic_params_ = ext_params;
if (camera_params_.A11 < 1e-6 || camera_params_.A22 < 1e-6 ||
camera_params_.u0 < 1e-6 || camera_params_.v0 < 1e-6)
{
return false;
}
camera_model_ = std::make_unique<mini_vikit::PolynomialCamera>(
camera_params_.image_width, camera_params_.image_height,
camera_params_.A11, camera_params_.A22,
camera_params_.u0, camera_params_.v0,
camera_params_.A12,
camera_params_.k2, camera_params_.k3, camera_params_.k4,
camera_params_.k5, camera_params_.k6, camera_params_.k7
);
initialized_ = true;
return true;
}
Eigen::Matrix4d CloudReprojector::calculateTic(const Eigen::Matrix4d& Tcl, const Eigen::Matrix4d& Til)
{
// Tic = Til * Tlc = Til * Tcl.inverse()
Eigen::Matrix4d Tlc = Tcl.inverse();
return Til * Tlc;
}
Eigen::Matrix4d CloudReprojector::odomPoseToMatrix(const OdomPose& odom) const
{
Eigen::Matrix4d T = Eigen::Matrix4d::Identity();
T.block<3, 3>(0, 0) = odom.orientation.toRotationMatrix();
T(0, 3) = odom.position.x();
T(1, 3) = odom.position.y();
T(2, 3) = odom.position.z();
return T;
}
cv::Mat CloudReprojector::reprojectCloud(const pcl::PointCloud<pcl::PointXYZRGB>& cloud_odom,
const OdomPose& odom_pose)
{
if (!initialized_)
{
return cv::Mat();
}
// T_odom_imu: imu pose in odom frame
Eigen::Matrix4d T_odom_imu = odomPoseToMatrix(odom_pose);
// T_imu_odom: transforms points from odom frame to imu frame
Eigen::Matrix4d T_imu_odom = T_odom_imu.inverse();
// T_cam_imu = Tic.inverse(): transforms from imu to camera
Eigen::Matrix4d T_cam_imu = extrinsic_params_.Tic.inverse();
// T_cam_odom: transforms points from odom frame to camera frame
Eigen::Matrix4d T_cam_odom = T_cam_imu * T_imu_odom;
pcl::PointCloud<pcl::PointXYZRGB> cloud_in_cam;
pcl::transformPointCloud(cloud_odom, cloud_in_cam, T_cam_odom);
return projectCloudToImage(cloud_in_cam);
}
cv::Mat CloudReprojector::reprojectCloudDepth(const pcl::PointCloud<pcl::PointXYZRGB>& cloud_odom,
const OdomPose& odom_pose)
{
if (!initialized_)
{
return cv::Mat();
}
Eigen::Matrix4d T_odom_imu = odomPoseToMatrix(odom_pose);
Eigen::Matrix4d T_imu_odom = T_odom_imu.inverse();
Eigen::Matrix4d T_cam_imu = extrinsic_params_.Tic.inverse();
Eigen::Matrix4d T_cam_odom = T_cam_imu * T_imu_odom;
pcl::PointCloud<pcl::PointXYZRGB> cloud_in_cam;
pcl::transformPointCloud(cloud_odom, cloud_in_cam, T_cam_odom);
return projectCloudToImageDepth(cloud_in_cam);
}
cv::Mat CloudReprojector::projectCloudToImage(const pcl::PointCloud<pcl::PointXYZRGB>& cloud_in_cam) const
{
cv::Mat img = cv::Mat::zeros(camera_params_.image_height, camera_params_.image_width, CV_8UC3);
img.setTo(cv::Scalar(255, 255, 255));
const double fx = camera_model_->fx();
const double fy = camera_model_->fy();
const double cx = camera_model_->cx();
const double cy = camera_model_->cy();
for (const auto& pt : cloud_in_cam)
{
if (pt.z <= 0.01)
continue;
int u_int, v_int;
if (0)
{
// Pinhole projection (undistorted image)
u_int = static_cast<int>(std::round(fx * pt.x / pt.z + cx));
v_int = static_cast<int>(std::round(fy * pt.y / pt.z + cy));
}
else
{
// Distorted projection (original image)
Eigen::Vector3d pt_cam(pt.x, pt.y, pt.z);
Eigen::Vector2d uv = camera_model_->world2cam(pt_cam);
u_int = static_cast<int>(std::round(uv[0]));
v_int = static_cast<int>(std::round(uv[1]));
}
if (u_int >= 0 && u_int < camera_params_.image_width &&
v_int >= 0 && v_int < camera_params_.image_height)
{
cv::circle(img, cv::Point(u_int, v_int), point_radius_,
cv::Scalar(pt.b, pt.g, pt.r), -1);
}
}
return img;
}
cv::Mat CloudReprojector::projectCloudToImageDepth(const pcl::PointCloud<pcl::PointXYZRGB>& cloud_in_cam) const
{
cv::Mat img = cv::Mat::zeros(camera_params_.image_height, camera_params_.image_width, CV_8UC3);
img.setTo(cv::Scalar(255, 255, 255));
float z_min = std::numeric_limits<float>::max();
float z_max = 0.f;
for (const auto& pt : cloud_in_cam)
{
if (pt.z > 0.01f)
{
z_min = std::min(z_min, static_cast<float>(pt.z));
z_max = std::max(z_max, static_cast<float>(pt.z));
}
}
// dynamic z range, only for visualization, not accurate for depth calculation
float z_rng = z_max - z_min;
if (z_rng < 1e-4f)
{
z_rng = 1.f;
}
for (const auto& pt : cloud_in_cam)
{
if (pt.z <= 0.01)
continue;
int u_int, v_int;
Eigen::Vector3d pt_cam(pt.x, pt.y, pt.z);
Eigen::Vector2d uv = camera_model_->world2cam(pt_cam);
u_int = static_cast<int>(std::round(uv[0]));
v_int = static_cast<int>(std::round(uv[1]));
if (u_int >= 0 && u_int < camera_params_.image_width &&
v_int >= 0 && v_int < camera_params_.image_height)
{
const uchar g = static_cast<uchar>(
255.0f * (static_cast<float>(pt.z) - z_min) / z_rng);
cv::circle(img, cv::Point(u_int, v_int), point_radius_,
cv::Scalar(g, g, g), -1);
}
}
return img;
}