mirror of
https://github.com/introlab/rtabmap.git
synced 2026-10-09 19:39:50 +08:00
* Working rtabmap_lidar-mapping example (live and pcap) * finalizing merge, added some deprecated * fixed build * Working deskewing for Lidar + Camera/IMU (no camera pose correction yet) and Lidar + Odom Sensor in main UI. * backward compatibility * fixed some not used variable warnings, fixed qt build for lidar mapping example * Refactored CameraMobile, added AREngine background support, fixed LidarVPL16 build error with PCL 1.8 * ARCoreJava: buffer last depth image in case its stamp i higher than pose stamp. CameraMobile: added pose buffer. SensorCaptureThread: to get pose, odomSensor should be explicitly set, but can be same as lidar or camera inputs. * Working external lidar on iOS * util3d::commonFiltering()/adjustNormalsToViewPoint() added organized cloud support. MainWindow: updated odomSensor setup * fixed winsock include order * reverted camera tool * disable imu filtering when odom sensor is used * Updated package version * fixed windows build * fixing more windows build erros
379 lines
11 KiB
C++
379 lines
11 KiB
C++
/*
|
|
Copyright (c) 2010-2022, Mathieu Labbe
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are met:
|
|
* Redistributions of source code must retain the above copyright
|
|
notice, this list of conditions and the following disclaimer.
|
|
* Redistributions in binary form must reproduce the above copyright
|
|
notice, this list of conditions and the following disclaimer in the
|
|
documentation and/or other materials provided with the distribution.
|
|
* Neither the names of its contributors may be used to endorse or promote products
|
|
derived from this software without specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <rtabmap/core/lidar/LidarVLP16.h>
|
|
#include <rtabmap/utilite/UTimer.h>
|
|
#include <rtabmap/utilite/UThread.h>
|
|
#include <pcl/pcl_config.h>
|
|
|
|
#if PCL_VERSION_COMPARE(<, 1, 9, 0)
|
|
#define VLP_MAX_NUM_LASERS 16
|
|
#define VLP_DUAL_MODE 0x39
|
|
#endif
|
|
|
|
namespace rtabmap {
|
|
|
|
/** @brief Function used to check that hour assigned to timestamp in conversion is
|
|
* correct. Velodyne only returns time since the top of the hour, so if the computer clock
|
|
* and the velodyne clock (gps-synchronized) are a little off, there is a chance the wrong
|
|
* hour may be associated with the timestamp
|
|
*
|
|
* Original author: Copyright (C) 2019 Matthew Pitropov, Joshua Whitley
|
|
* Original license: BSD License 2.0
|
|
* Original code: https://github.com/ros-drivers/velodyne/blob/master/velodyne_driver/include/velodyne_driver/time_conversion.hpp
|
|
*
|
|
* @param stamp timestamp recovered from velodyne
|
|
* @param nominal_stamp time coming from computer's clock
|
|
* @return timestamp from velodyne, possibly shifted by 1 hour if the function arguments
|
|
* disagree by more than a half-hour.
|
|
*/
|
|
double resolveHourAmbiguity(const double &stamp, const double &nominal_stamp) {
|
|
const int HALFHOUR_TO_SEC = 1800;
|
|
double retval = stamp;
|
|
if (nominal_stamp > stamp) {
|
|
if (nominal_stamp - stamp > HALFHOUR_TO_SEC) {
|
|
retval = retval + 2*HALFHOUR_TO_SEC;
|
|
}
|
|
} else if (stamp - nominal_stamp > HALFHOUR_TO_SEC) {
|
|
retval = retval - 2*HALFHOUR_TO_SEC;
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* Original author: Copyright (C) 2019 Matthew Pitropov, Joshua Whitley
|
|
* Original license: BSD License 2.0
|
|
* Original code: https://github.com/ros-drivers/velodyne/blob/master/velodyne_driver/include/velodyne_driver/time_conversion.hpp
|
|
*/
|
|
double rosTimeFromGpsTimestamp(const uint32_t data) {
|
|
const int HOUR_TO_SEC = 3600;
|
|
// time for each packet is a 4 byte uint
|
|
// It is the number of microseconds from the top of the hour
|
|
double time_nom = UTimer::now();
|
|
uint32_t cur_hour = time_nom / HOUR_TO_SEC;
|
|
double stamp = double(cur_hour * HOUR_TO_SEC) + double(data) / 1000000;
|
|
stamp = resolveHourAmbiguity(stamp, time_nom);
|
|
return stamp;
|
|
}
|
|
|
|
LidarVLP16::LidarVLP16(
|
|
const std::string& pcapFile,
|
|
bool organized,
|
|
bool stampLast,
|
|
float frameRate,
|
|
Transform localTransform) :
|
|
Lidar(frameRate, localTransform),
|
|
pcl::VLPGrabber(pcapFile),
|
|
timingOffsetsDualMode_(false),
|
|
startSweepTime_(0),
|
|
startSweepTimeHost_(0),
|
|
organized_(organized),
|
|
useHostTime_(false),
|
|
stampLast_(stampLast)
|
|
{
|
|
UDEBUG("Using PCAP file \"%s\"", pcapFile.c_str());
|
|
}
|
|
LidarVLP16::LidarVLP16(
|
|
const boost::asio::ip::address& ipAddress,
|
|
const std::uint16_t port,
|
|
bool organized,
|
|
bool useHostTime,
|
|
bool stampLast,
|
|
float frameRate,
|
|
Transform localTransform) :
|
|
Lidar(frameRate, localTransform),
|
|
pcl::VLPGrabber(ipAddress, port),
|
|
timingOffsetsDualMode_(false),
|
|
startSweepTime_(0),
|
|
startSweepTimeHost_(0),
|
|
organized_(organized),
|
|
useHostTime_(useHostTime),
|
|
stampLast_(stampLast)
|
|
{
|
|
UDEBUG("Using network lidar with IP=%s port=%d", ipAddress.to_string().c_str(), port);
|
|
}
|
|
|
|
LidarVLP16::~LidarVLP16()
|
|
{
|
|
UDEBUG("Stopping lidar...");
|
|
stop();
|
|
scanReady_.release();
|
|
UDEBUG("Stopped lidar!");
|
|
}
|
|
|
|
void LidarVLP16::setOrganized(bool enable)
|
|
{
|
|
organized_ = true;
|
|
}
|
|
|
|
bool LidarVLP16::init(const std::string &, const std::string &)
|
|
{
|
|
UDEBUG("Init lidar");
|
|
if(isRunning())
|
|
{
|
|
UDEBUG("Stopping lidar...");
|
|
stop();
|
|
uSleep(2000); // make sure all callbacks are finished
|
|
UDEBUG("Stopped lidar!");
|
|
}
|
|
startSweepTime_ = 0.0;
|
|
startSweepTimeHost_ = 0.0;
|
|
accumulatedScans_.clear();
|
|
if(organized_)
|
|
{
|
|
accumulatedScans_.resize(16);
|
|
}
|
|
else
|
|
{
|
|
accumulatedScans_.resize(1);
|
|
}
|
|
buildTimings(false);
|
|
start();
|
|
UDEBUG("Lidar capture started");
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Build a timing table for each block/firing. Stores in timing_offsets vector
|
|
*/
|
|
void LidarVLP16::buildTimings(bool dualMode)
|
|
{
|
|
// vlp16
|
|
// timing table calculation, from velodyne user manual
|
|
timingOffsets_.resize(12);
|
|
for (size_t i=0; i < timingOffsets_.size(); ++i){
|
|
timingOffsets_[i].resize(32);
|
|
}
|
|
// constants
|
|
double full_firing_cycle = 55.296 * 1e-6; // seconds
|
|
double single_firing = 2.304 * 1e-6; // seconds
|
|
double dataBlockIndex, dataPointIndex;
|
|
// compute timing offsets
|
|
for (size_t x = 0; x < timingOffsets_.size(); ++x){
|
|
for (size_t y = 0; y < timingOffsets_[x].size(); ++y){
|
|
if (dualMode){
|
|
dataBlockIndex = (x - (x % 2)) + (y / 16);
|
|
}
|
|
else{
|
|
dataBlockIndex = (x * 2) + (y / 16);
|
|
}
|
|
dataPointIndex = y % 16;
|
|
//timing_offsets[block][firing]
|
|
timingOffsets_[x][y] = (full_firing_cycle * dataBlockIndex) + (single_firing * dataPointIndex);
|
|
}
|
|
}
|
|
timingOffsetsDualMode_ = dualMode;
|
|
}
|
|
|
|
void LidarVLP16::toPointClouds (HDLDataPacket *dataPacket)
|
|
{
|
|
if (sizeof(HDLLaserReturn) != 3)
|
|
return;
|
|
|
|
double receivedHostTime = UTimer::now();
|
|
double packetStamp = rosTimeFromGpsTimestamp(dataPacket->gpsTimestamp);
|
|
if(startSweepTime_==0)
|
|
{
|
|
startSweepTime_ = packetStamp;
|
|
startSweepTimeHost_ = receivedHostTime;
|
|
}
|
|
|
|
bool dualMode = dataPacket->mode == VLP_DUAL_MODE;
|
|
if(timingOffsets_.empty() || timingOffsetsDualMode_ != dualMode)
|
|
{
|
|
// reset everything
|
|
timingOffsets_.clear();
|
|
buildTimings(dualMode);
|
|
startSweepTime_ = packetStamp;
|
|
startSweepTimeHost_ = receivedHostTime;
|
|
for(size_t i=0; i<accumulatedScans_.size(); ++i)
|
|
{
|
|
accumulatedScans_[i].clear();
|
|
}
|
|
}
|
|
|
|
double interpolated_azimuth_delta;
|
|
std::uint8_t index = 1;
|
|
if (dualMode)
|
|
{
|
|
index = 2;
|
|
}
|
|
if (dataPacket->firingData[index].rotationalPosition < dataPacket->firingData[0].rotationalPosition)
|
|
{
|
|
interpolated_azimuth_delta = ((dataPacket->firingData[index].rotationalPosition + 36000) - dataPacket->firingData[0].rotationalPosition) / 2.0;
|
|
}
|
|
else
|
|
{
|
|
interpolated_azimuth_delta = (dataPacket->firingData[index].rotationalPosition - dataPacket->firingData[0].rotationalPosition) / 2.0;
|
|
}
|
|
|
|
for (std::uint8_t i = 0; i < HDL_FIRING_PER_PKT; ++i)
|
|
{
|
|
HDLFiringData firing_data = dataPacket->firingData[i];
|
|
|
|
for (std::uint8_t j = 0; j < HDL_LASER_PER_FIRING; j++)
|
|
{
|
|
double current_azimuth = firing_data.rotationalPosition;
|
|
if (j >= VLP_MAX_NUM_LASERS)
|
|
{
|
|
current_azimuth += interpolated_azimuth_delta;
|
|
}
|
|
if (current_azimuth > 36000)
|
|
{
|
|
current_azimuth -= 36000;
|
|
}
|
|
|
|
double t = 0;
|
|
if (timingOffsets_.size())
|
|
t = timingOffsets_[i][j];
|
|
|
|
if (current_azimuth < HDLGrabber::last_azimuth_)
|
|
{
|
|
if (!accumulatedScans_[0].empty())
|
|
{
|
|
UScopeMutex lock(lastScanMutex_);
|
|
bool notify = lastScan_.laserScanRaw().empty();
|
|
if(stampLast_)
|
|
{
|
|
double lastStamp = startSweepTime_ + accumulatedScans_[accumulatedScans_.size()-1].back().t;
|
|
double diff = lastStamp - startSweepTime_;
|
|
lastScan_.setStamp(useHostTime_?startSweepTimeHost_+diff:lastStamp);
|
|
for(size_t r=0; r<accumulatedScans_.size(); ++r)
|
|
{
|
|
for(size_t k=0; k<accumulatedScans_[r].size(); ++k)
|
|
{
|
|
accumulatedScans_[r][k].t -= diff;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lastScan_.setStamp(useHostTime_?startSweepTimeHost_:startSweepTime_);
|
|
}
|
|
if(accumulatedScans_.size() > 1)
|
|
{
|
|
cv::Mat organizedScan = cv::Mat(1, accumulatedScans_[0].size(), CV_32FC(5), accumulatedScans_[0].data()).clone();
|
|
for(size_t k=1; k<accumulatedScans_.size(); ++k)
|
|
{
|
|
UASSERT((int)accumulatedScans_[k].size() == organizedScan.cols);
|
|
organizedScan.push_back(cv::Mat(1, accumulatedScans_[k].size(), CV_32FC(5), accumulatedScans_[k].data()).clone());
|
|
}
|
|
lastScan_.setLaserScan(LaserScan(organizedScan, 0, 0, LaserScan::kXYZIT, getLocalTransform()));
|
|
}
|
|
else
|
|
{
|
|
lastScan_.setLaserScan(LaserScan(cv::Mat(1, accumulatedScans_[0].size(), CV_32FC(5), accumulatedScans_[0].data()).clone(), 0, 0, LaserScan::kXYZIT, getLocalTransform()));
|
|
}
|
|
if(notify)
|
|
{
|
|
scanReady_.release();
|
|
}
|
|
|
|
startSweepTime_ = packetStamp + t;
|
|
startSweepTimeHost_ = receivedHostTime + t;
|
|
}
|
|
for(size_t k=0; k<accumulatedScans_.size(); ++k)
|
|
{
|
|
accumulatedScans_[k].clear();
|
|
}
|
|
}
|
|
|
|
double timeSinceStartOfThisScan = packetStamp + t - startSweepTime_;
|
|
|
|
pcl::PointXYZI xyzi;
|
|
HDLGrabber::computeXYZI (xyzi, current_azimuth, firing_data.laserReturns[j], laser_corrections_[j % VLP_MAX_NUM_LASERS]);
|
|
PointXYZIT xyzit;
|
|
xyzit.x = xyzi.y;
|
|
xyzit.y = -xyzi.x;
|
|
xyzit.z = xyzi.z;
|
|
xyzit.i = xyzi.intensity;
|
|
xyzit.t = timeSinceStartOfThisScan;
|
|
|
|
if(accumulatedScans_.size()>1)
|
|
{
|
|
accumulatedScans_[j % VLP_MAX_NUM_LASERS].push_back(xyzit);
|
|
}
|
|
else if (! (std::isnan (xyzit.x) || std::isnan (xyzit.y) || std::isnan (xyzit.z)))
|
|
{
|
|
accumulatedScans_[0].push_back (xyzit);
|
|
}
|
|
last_azimuth_ = current_azimuth;
|
|
|
|
if (dualMode)
|
|
{
|
|
pcl::PointXYZI dual_xyzi;
|
|
HDLGrabber::computeXYZI (dual_xyzi, current_azimuth, dataPacket->firingData[i + 1].laserReturns[j], laser_corrections_[j % VLP_MAX_NUM_LASERS]);
|
|
|
|
if(accumulatedScans_.size()>1)
|
|
{
|
|
xyzit.x = dual_xyzi.y;
|
|
xyzit.y = -dual_xyzi.x;
|
|
xyzit.z = dual_xyzi.z;
|
|
xyzit.i = dual_xyzi.intensity;
|
|
xyzit.t = timeSinceStartOfThisScan;
|
|
accumulatedScans_[j % VLP_MAX_NUM_LASERS].push_back (xyzit);
|
|
}
|
|
else if ((dual_xyzi.x != xyzi.x || dual_xyzi.y != xyzi.y || dual_xyzi.z != xyzi.z)
|
|
&& ! (std::isnan (dual_xyzi.x) || std::isnan (dual_xyzi.y) || std::isnan (dual_xyzi.z)))
|
|
{
|
|
xyzit.x = dual_xyzi.y;
|
|
xyzit.y = -dual_xyzi.x;
|
|
xyzit.z = dual_xyzi.z;
|
|
xyzit.i = dual_xyzi.intensity;
|
|
xyzit.t = timeSinceStartOfThisScan;
|
|
accumulatedScans_[0].push_back (xyzit);
|
|
}
|
|
}
|
|
}
|
|
if (dualMode)
|
|
{
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
SensorData LidarVLP16::captureData(SensorCaptureInfo * info)
|
|
{
|
|
SensorData data;
|
|
if(scanReady_.acquire(1, 5000))
|
|
{
|
|
UScopeMutex lock(lastScanMutex_);
|
|
if(!lastScan_.laserScanRaw().empty())
|
|
{
|
|
data = lastScan_;
|
|
lastScan_ = SensorData();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UWARN("Did not receive any scans for the past 5 seconds.");
|
|
}
|
|
return data;
|
|
}
|
|
|
|
|
|
} /* namespace rtabmap */
|