mirror of
https://github.com/introlab/rtabmap.git
synced 2026-09-03 01:50:24 +08:00
add support for RVL depth image compression (#1409)
* add rvl_codec * add support for RVL deep image compression * DBViewer: edit depth re-using same compression format than the one in database. * Fixing windows ci * 💄 * missing dll export (windows) * typo --------- Co-authored-by: matlabbe <matlabbe@gmail.com>
This commit is contained in:
@@ -64,6 +64,8 @@ SET(SRC_FILES
|
||||
util3d_correspondences.cpp
|
||||
util3d_motion_estimation.cpp
|
||||
|
||||
rvl_codec.cpp
|
||||
|
||||
SensorData.cpp
|
||||
Graph.cpp
|
||||
Compression.cpp
|
||||
|
||||
@@ -34,14 +34,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
namespace rtabmap {
|
||||
|
||||
// format : ".png" ".jpg" "" (empty is general)
|
||||
// format : ".jpg" ".png" ".rvl" "" (empty is general)
|
||||
CompressionThread::CompressionThread(const cv::Mat & mat, const std::string & format) :
|
||||
uncompressedData_(mat),
|
||||
format_(format),
|
||||
image_(!format.empty()),
|
||||
compressMode_(true)
|
||||
{
|
||||
UASSERT(format.empty() || format.compare(".png") == 0 || format.compare(".jpg") == 0);
|
||||
UASSERT(format.empty() || format.compare(".jpg") == 0 || format.compare(".png") == 0 || format.compare(".rvl") == 0);
|
||||
}
|
||||
// assume image
|
||||
CompressionThread::CompressionThread(const cv::Mat & bytes, bool isImage) :
|
||||
@@ -96,7 +96,7 @@ void CompressionThread::mainLoop()
|
||||
this->kill();
|
||||
}
|
||||
|
||||
// ".png" or ".jpg"
|
||||
// ".jpg" or ".png" or ".rvl"
|
||||
std::vector<unsigned char> compressImage(const cv::Mat & image, const std::string & format)
|
||||
{
|
||||
std::vector<unsigned char> bytes;
|
||||
@@ -106,7 +106,21 @@ std::vector<unsigned char> compressImage(const cv::Mat & image, const std::strin
|
||||
{
|
||||
//save in 8bits-4channel
|
||||
cv::Mat bgra(image.size(), CV_8UC4, image.data);
|
||||
cv::imencode(format, bgra, bytes);
|
||||
cv::imencode(".png", bgra, bytes);
|
||||
}
|
||||
else if(format == ".rvl")
|
||||
{
|
||||
bytes = {'D', 'E', 'P', 'T', 'H', 'R', 'V', 'L'};
|
||||
int numPixels = image.rows * image.cols;
|
||||
// In the worst case, RVL compression results in ~1.5x larger data.
|
||||
bytes.resize(3 * numPixels + 20);
|
||||
uint32_t cols = image.cols;
|
||||
uint32_t rows = image.rows;
|
||||
memcpy(&bytes[8], &cols, 4);
|
||||
memcpy(&bytes[12], &rows, 4);
|
||||
RvlCodec rvl;
|
||||
int compressedSize = rvl.CompressRVL(image.ptr<uint16_t>(), &bytes[16], numPixels);
|
||||
bytes.resize(16 + compressedSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -116,7 +130,7 @@ std::vector<unsigned char> compressImage(const cv::Mat & image, const std::strin
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// ".png" or ".jpg"
|
||||
// ".jpg" or ".png" or ".rvl"
|
||||
cv::Mat compressImage2(const cv::Mat & image, const std::string & format)
|
||||
{
|
||||
std::vector<unsigned char> bytes = compressImage(image, format);
|
||||
@@ -129,21 +143,33 @@ cv::Mat compressImage2(const cv::Mat & image, const std::string & format)
|
||||
|
||||
cv::Mat uncompressImage(const cv::Mat & bytes)
|
||||
{
|
||||
cv::Mat image;
|
||||
cv::Mat image;
|
||||
if(!bytes.empty())
|
||||
{
|
||||
#if CV_MAJOR_VERSION>2 || (CV_MAJOR_VERSION >=2 && CV_MINOR_VERSION >=4)
|
||||
image = cv::imdecode(bytes, cv::IMREAD_UNCHANGED);
|
||||
#else
|
||||
image = cv::imdecode(bytes, -1);
|
||||
#endif
|
||||
if(image.type() == CV_8UC4)
|
||||
if (compressedDepthFormat(bytes) == ".rvl")
|
||||
{
|
||||
// Using clone() or copyTo() caused a memory leak !?!?
|
||||
// image = cv::Mat(image.size(), CV_32FC1, image.data).clone();
|
||||
cv::Mat depth(image.size(), CV_32FC1);
|
||||
memcpy(depth.data, image.data, image.total()*image.elemSize());
|
||||
image = depth;
|
||||
uint32_t cols, rows;
|
||||
memcpy(&cols, &bytes.data[8], 4);
|
||||
memcpy(&rows, &bytes.data[12], 4);
|
||||
image = cv::Mat(rows, cols, CV_16UC1);
|
||||
RvlCodec rvl;
|
||||
rvl.DecompressRVL(&bytes.data[16], image.ptr<uint16_t>(), cols * rows);
|
||||
}
|
||||
else
|
||||
{
|
||||
#if CV_MAJOR_VERSION>2 || (CV_MAJOR_VERSION >=2 && CV_MINOR_VERSION >=4)
|
||||
image = cv::imdecode(bytes, cv::IMREAD_UNCHANGED);
|
||||
#else
|
||||
image = cv::imdecode(bytes, -1);
|
||||
#endif
|
||||
if(image.type() == CV_8UC4)
|
||||
{
|
||||
// Using clone() or copyTo() caused a memory leak !?!?
|
||||
// image = cv::Mat(image.size(), CV_32FC1, image.data).clone();
|
||||
cv::Mat depth(image.size(), CV_32FC1);
|
||||
memcpy(depth.data, image.data, image.total()*image.elemSize());
|
||||
image = depth;
|
||||
}
|
||||
}
|
||||
}
|
||||
return image;
|
||||
@@ -151,17 +177,29 @@ cv::Mat uncompressImage(const cv::Mat & bytes)
|
||||
|
||||
cv::Mat uncompressImage(const std::vector<unsigned char> & bytes)
|
||||
{
|
||||
cv::Mat image;
|
||||
cv::Mat image;
|
||||
if(bytes.size())
|
||||
{
|
||||
#if CV_MAJOR_VERSION>2 || (CV_MAJOR_VERSION >=2 && CV_MINOR_VERSION >=4)
|
||||
image = cv::imdecode(bytes, cv::IMREAD_UNCHANGED);
|
||||
#else
|
||||
image = cv::imdecode(bytes, -1);
|
||||
#endif
|
||||
if(image.type() == CV_8UC4)
|
||||
if (compressedDepthFormat(bytes) == ".rvl")
|
||||
{
|
||||
image = cv::Mat(image.size(), CV_32FC1, image.data).clone();
|
||||
uint32_t cols, rows;
|
||||
memcpy(&cols, &bytes[8], 4);
|
||||
memcpy(&rows, &bytes[12], 4);
|
||||
image = cv::Mat(rows, cols, CV_16UC1);
|
||||
RvlCodec rvl;
|
||||
rvl.DecompressRVL(&bytes[16], image.ptr<uint16_t>(), cols * rows);
|
||||
}
|
||||
else
|
||||
{
|
||||
#if CV_MAJOR_VERSION>2 || (CV_MAJOR_VERSION >=2 && CV_MINOR_VERSION >=4)
|
||||
image = cv::imdecode(bytes, cv::IMREAD_UNCHANGED);
|
||||
#else
|
||||
image = cv::imdecode(bytes, -1);
|
||||
#endif
|
||||
if(image.type() == CV_8UC4)
|
||||
{
|
||||
image = cv::Mat(image.size(), CV_32FC1, image.data).clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
return image;
|
||||
@@ -291,4 +329,33 @@ std::string uncompressString(const cv::Mat & bytes)
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string compressedDepthFormat(const cv::Mat & bytes)
|
||||
{
|
||||
return compressedDepthFormat(bytes.data, bytes.rows * bytes.cols * bytes.elemSize());
|
||||
}
|
||||
std::string compressedDepthFormat(const std::vector<unsigned char> & bytes)
|
||||
{
|
||||
return compressedDepthFormat(bytes.data(), bytes.size());
|
||||
}
|
||||
std::string compressedDepthFormat(const unsigned char * bytes, size_t size)
|
||||
{
|
||||
std::string format;
|
||||
if(bytes && size)
|
||||
{
|
||||
size_t maxlen = std::min(size, size_t(8));
|
||||
std::vector<unsigned char> signature(maxlen);
|
||||
memcpy(&signature[0], bytes, maxlen);
|
||||
if (std::string(signature.begin(), signature.end()) == "DEPTHRVL")
|
||||
{
|
||||
format = ".rvl";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Assuming png by default
|
||||
format = ".png";
|
||||
}
|
||||
}
|
||||
return format;
|
||||
}
|
||||
|
||||
} /* namespace rtabmap */
|
||||
|
||||
@@ -512,12 +512,13 @@ void DBDriver::updateCalibration(int nodeId, const std::vector<CameraModel> & mo
|
||||
_dbSafeAccessMutex.unlock();
|
||||
}
|
||||
|
||||
void DBDriver::updateDepthImage(int nodeId, const cv::Mat & image)
|
||||
void DBDriver::updateDepthImage(int nodeId, const cv::Mat & image, const std::string & format)
|
||||
{
|
||||
_dbSafeAccessMutex.lock();
|
||||
this->updateDepthImageQuery(
|
||||
nodeId,
|
||||
image);
|
||||
image,
|
||||
format);
|
||||
_dbSafeAccessMutex.unlock();
|
||||
}
|
||||
|
||||
|
||||
@@ -4719,7 +4719,8 @@ void DBDriverSqlite3::updateCalibrationQuery(
|
||||
|
||||
void DBDriverSqlite3::updateDepthImageQuery(
|
||||
int nodeId,
|
||||
const cv::Mat & image) const
|
||||
const cv::Mat & image,
|
||||
const std::string & format) const
|
||||
{
|
||||
UDEBUG("");
|
||||
if(_ppDb)
|
||||
@@ -4738,7 +4739,8 @@ void DBDriverSqlite3::updateDepthImageQuery(
|
||||
// Save depth
|
||||
stepDepthUpdate(ppStmt,
|
||||
nodeId,
|
||||
image);
|
||||
image,
|
||||
format);
|
||||
|
||||
// Finalize (delete) the statement
|
||||
rc = sqlite3_finalize(ppStmt);
|
||||
@@ -6009,7 +6011,7 @@ std::string DBDriverSqlite3::queryStepDepthUpdate() const
|
||||
return "UPDATE Data SET depth=? WHERE id=?;";
|
||||
}
|
||||
}
|
||||
void DBDriverSqlite3::stepDepthUpdate(sqlite3_stmt * ppStmt, int nodeId, const cv::Mat & image) const
|
||||
void DBDriverSqlite3::stepDepthUpdate(sqlite3_stmt * ppStmt, int nodeId, const cv::Mat & image, const std::string & format) const
|
||||
{
|
||||
if(!ppStmt)
|
||||
{
|
||||
@@ -6023,7 +6025,7 @@ void DBDriverSqlite3::stepDepthUpdate(sqlite3_stmt * ppStmt, int nodeId, const c
|
||||
if(!image.empty() && (image.type()!=CV_8UC1 || image.rows > 1))
|
||||
{
|
||||
// compress
|
||||
imageCompressed = compressImage2(image, ".png");
|
||||
imageCompressed = compressImage2(image, format);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -80,6 +80,7 @@ Memory::Memory(const ParametersMap & parameters) :
|
||||
_notLinkedNodesKeptInDb(Parameters::defaultMemNotLinkedNodesKept()),
|
||||
_saveIntermediateNodeData(Parameters::defaultMemIntermediateNodeDataKept()),
|
||||
_rgbCompressionFormat(Parameters::defaultMemImageCompressionFormat()),
|
||||
_depthCompressionFormat(Parameters::defaultMemDepthCompressionFormat()),
|
||||
_incrementalMemory(Parameters::defaultMemIncrementalMemory()),
|
||||
_localizationDataSaved(Parameters::defaultMemLocalizationDataSaved()),
|
||||
_reduceGraph(Parameters::defaultMemReduceGraph()),
|
||||
@@ -568,6 +569,7 @@ void Memory::parseParameters(const ParametersMap & parameters)
|
||||
Parameters::parse(params, Parameters::kMemNotLinkedNodesKept(), _notLinkedNodesKeptInDb);
|
||||
Parameters::parse(params, Parameters::kMemIntermediateNodeDataKept(), _saveIntermediateNodeData);
|
||||
Parameters::parse(params, Parameters::kMemImageCompressionFormat(), _rgbCompressionFormat);
|
||||
Parameters::parse(params, Parameters::kMemDepthCompressionFormat(), _depthCompressionFormat);
|
||||
Parameters::parse(params, Parameters::kMemRehearsalIdUpdatedToNewOne(), _idUpdatedToNewOneRehearsal);
|
||||
Parameters::parse(params, Parameters::kMemGenerateIds(), _generateIds);
|
||||
Parameters::parse(params, Parameters::kMemBadSignaturesIgnored(), _badSignaturesIgnored);
|
||||
@@ -5808,10 +5810,42 @@ Signature * Memory::createSignature(const SensorData & inputData, const Transfor
|
||||
std::vector<unsigned char> imageBytes;
|
||||
std::vector<unsigned char> depthBytes;
|
||||
|
||||
if(_saveDepth16Format && !depthOrRightImage.empty() && depthOrRightImage.type() == CV_32FC1)
|
||||
if(!depthOrRightImage.empty() && depthOrRightImage.type() == CV_32FC1)
|
||||
{
|
||||
UWARN("Save depth data to 16 bits format: depth type detected is 32FC1, use 16UC1 depth format to avoid this conversion (or set parameter \"Mem/SaveDepth16Format\"=false to use 32bits format).");
|
||||
depthOrRightImage = util2d::cvtDepthFromFloat(depthOrRightImage);
|
||||
if(_saveDepth16Format)
|
||||
{
|
||||
static bool warned = false;
|
||||
if(!warned)
|
||||
{
|
||||
UWARN("Converting depth data to 16 bits format because depth type detected is 32FC1, "
|
||||
"feed 16UC1 depth format directly to avoid this conversion (or set parameter %s=false "
|
||||
"to save 32bits format). This warning is only printed once.",
|
||||
Parameters::kMemSaveDepth16Format().c_str());
|
||||
warned = true;
|
||||
}
|
||||
depthOrRightImage = util2d::cvtDepthFromFloat(depthOrRightImage);
|
||||
}
|
||||
else if(_depthCompressionFormat == ".rvl")
|
||||
{
|
||||
static bool warned = false;
|
||||
if(!warned)
|
||||
{
|
||||
UWARN("%s is set to false to use 32bits format but this is not "
|
||||
"compatible with the compressed depth format chosen (%s=\"%s\"), depth "
|
||||
"images will be compressed in \".png\" format instead. Explicitly "
|
||||
"set %s to true to keep using \"%s\" format and images will be "
|
||||
"converted to 16bits for convenience (warning: that would "
|
||||
"remove all depth values over 65 meters). Explicitly set %s=\".png\" "
|
||||
"to suppress this warning. This warning is only printed once.",
|
||||
Parameters::kMemSaveDepth16Format().c_str(),
|
||||
Parameters::kMemDepthCompressionFormat().c_str(),
|
||||
_depthCompressionFormat.c_str(),
|
||||
Parameters::kMemSaveDepth16Format().c_str(),
|
||||
_depthCompressionFormat.c_str(),
|
||||
Parameters::kMemDepthCompressionFormat().c_str());
|
||||
warned = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cv::Mat compressedImage;
|
||||
@@ -5821,7 +5855,7 @@ Signature * Memory::createSignature(const SensorData & inputData, const Transfor
|
||||
if(_compressionParallelized)
|
||||
{
|
||||
rtabmap::CompressionThread ctImage(image, _rgbCompressionFormat);
|
||||
rtabmap::CompressionThread ctDepth(depthOrRightImage, depthOrRightImage.type() == CV_32FC1 || depthOrRightImage.type() == CV_16UC1?std::string(".png"):_rgbCompressionFormat);
|
||||
rtabmap::CompressionThread ctDepth(depthOrRightImage, depthOrRightImage.type() == CV_32FC1 || depthOrRightImage.type() == CV_16UC1?_depthCompressionFormat:_rgbCompressionFormat);
|
||||
rtabmap::CompressionThread ctLaserScan(laserScan.data());
|
||||
rtabmap::CompressionThread ctUserData(data.userDataRaw());
|
||||
if(!image.empty())
|
||||
@@ -5853,7 +5887,7 @@ Signature * Memory::createSignature(const SensorData & inputData, const Transfor
|
||||
else
|
||||
{
|
||||
compressedImage = compressImage2(image, _rgbCompressionFormat);
|
||||
compressedDepth = compressImage2(depthOrRightImage, depthOrRightImage.type() == CV_32FC1 || depthOrRightImage.type() == CV_16UC1?std::string(".png"):_rgbCompressionFormat);
|
||||
compressedDepth = compressImage2(depthOrRightImage, depthOrRightImage.type() == CV_32FC1 || depthOrRightImage.type() == CV_16UC1?_depthCompressionFormat:_rgbCompressionFormat);
|
||||
compressedScan = compressData2(laserScan.data());
|
||||
compressedUserData = compressData2(data.userDataRaw());
|
||||
}
|
||||
|
||||
@@ -318,12 +318,13 @@ CameraStereoZed::CameraStereoZed(
|
||||
sl::RESOLUTION res = static_cast<sl::RESOLUTION>(resolution_);
|
||||
sl::DEPTH_MODE qual = static_cast<sl::DEPTH_MODE>(quality_);
|
||||
|
||||
UASSERT(res >= sl::RESOLUTION::HD2K && res < sl::RESOLUTION::LAST);
|
||||
UASSERT(qual >= sl::DEPTH_MODE::NONE && qual < sl::DEPTH_MODE::LAST);
|
||||
#if ZED_SDK_MAJOR_VERSION < 4
|
||||
UASSERT(res >= sl::RESOLUTION::HD2K && res < sl::RESOLUTION::LAST);
|
||||
sl::SENSING_MODE sens = static_cast<sl::SENSING_MODE>(sensingMode_);
|
||||
UASSERT(sens >= sl::SENSING_MODE::STANDARD && sens < sl::SENSING_MODE::LAST);
|
||||
#else
|
||||
UASSERT(res >= sl::RESOLUTION::HD4K && res < sl::RESOLUTION::LAST);
|
||||
UASSERT(sensingMode_ >= 0 && sensingMode_ < 2);
|
||||
#endif
|
||||
UASSERT(confidenceThr_ >= 0 && confidenceThr_ <=100);
|
||||
|
||||
102
corelib/src/rvl_codec.cpp
Normal file
102
corelib/src/rvl_codec.cpp
Normal file
@@ -0,0 +1,102 @@
|
||||
// The following code is a C++ wrapper of the code presented by
|
||||
// Andrew D. Wilson in "Fast Lossless Depth Image Compression" at SIGCHI'17.
|
||||
// The original code is licensed under the MIT License.
|
||||
|
||||
#include <rtabmap/core/rvl_codec.h>
|
||||
|
||||
namespace rtabmap
|
||||
{
|
||||
|
||||
RvlCodec::RvlCodec() {}
|
||||
|
||||
void RvlCodec::EncodeVLE(int value)
|
||||
{
|
||||
do
|
||||
{
|
||||
int nibble = value & 0x7; // lower 3 bits
|
||||
if (value >>= 3)
|
||||
nibble |= 0x8; // more to come
|
||||
word_ <<= 4;
|
||||
word_ |= nibble;
|
||||
if (++nibblesWritten_ == 8) // output word
|
||||
{
|
||||
*pBuffer_++ = word_;
|
||||
nibblesWritten_ = 0;
|
||||
word_ = 0;
|
||||
}
|
||||
} while (value);
|
||||
}
|
||||
|
||||
int RvlCodec::DecodeVLE()
|
||||
{
|
||||
unsigned int nibble;
|
||||
int value = 0, bits = 29;
|
||||
do
|
||||
{
|
||||
if (!nibblesWritten_)
|
||||
{
|
||||
word_ = *pBuffer_++; // load word
|
||||
nibblesWritten_ = 8;
|
||||
}
|
||||
nibble = word_ & 0xf0000000;
|
||||
value |= (nibble << 1) >> bits;
|
||||
word_ <<= 4;
|
||||
nibblesWritten_--;
|
||||
bits -= 3;
|
||||
} while (nibble & 0x80000000);
|
||||
return value;
|
||||
}
|
||||
|
||||
int RvlCodec::CompressRVL(const uint16_t * input, unsigned char * output, int numPixels)
|
||||
{
|
||||
buffer_ = pBuffer_ = reinterpret_cast<int *>(output);
|
||||
nibblesWritten_ = 0;
|
||||
const uint16_t * end = input + numPixels;
|
||||
uint16_t previous = 0;
|
||||
while (input != end)
|
||||
{
|
||||
int zeros = 0, nonzeros = 0;
|
||||
for (; (input != end) && !*input; input++, zeros++) {}
|
||||
EncodeVLE(zeros); // number of zeros
|
||||
for (const uint16_t * p = input; (p != end) && *p++; nonzeros++) {}
|
||||
EncodeVLE(nonzeros); // number of nonzeros
|
||||
for (int i = 0; i < nonzeros; i++)
|
||||
{
|
||||
uint16_t current = *input++;
|
||||
int delta = current - previous;
|
||||
int positive = (delta << 1) ^ (delta >> 31);
|
||||
EncodeVLE(positive); // nonzero value
|
||||
previous = current;
|
||||
}
|
||||
}
|
||||
if (nibblesWritten_) // last few values
|
||||
*pBuffer_++ = word_ << 4 * (8 - nibblesWritten_);
|
||||
return static_cast<int>((unsigned char *)pBuffer_ - (unsigned char *)buffer_); // num bytes
|
||||
}
|
||||
|
||||
void RvlCodec::DecompressRVL(const unsigned char * input, uint16_t * output, int numPixels)
|
||||
{
|
||||
buffer_ = pBuffer_ = const_cast<int *>(reinterpret_cast<const int *>(input));
|
||||
nibblesWritten_ = 0;
|
||||
uint16_t current, previous = 0;
|
||||
int numPixelsToDecode = numPixels;
|
||||
while (numPixelsToDecode)
|
||||
{
|
||||
int zeros = DecodeVLE(); // number of zeros
|
||||
numPixelsToDecode -= zeros;
|
||||
for (; zeros; zeros--)
|
||||
*output++ = 0;
|
||||
int nonzeros = DecodeVLE(); // number of nonzeros
|
||||
numPixelsToDecode -= nonzeros;
|
||||
for (; nonzeros; nonzeros--)
|
||||
{
|
||||
int positive = DecodeVLE(); // nonzero value
|
||||
int delta = (positive >> 1) ^ -(positive & 1);
|
||||
current = previous + delta;
|
||||
*output++ = current;
|
||||
previous = current;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rtabmap
|
||||
Reference in New Issue
Block a user