mirror of
https://github.com/introlab/rtabmap.git
synced 2026-10-05 01:27:46 +08:00
Improved test coverage for this branch
This commit is contained in:
@@ -114,6 +114,15 @@ private:
|
|||||||
* 0 is invalid and v>0 is the depth depthQuantA/(v-depthQuantB).
|
* 0 is invalid and v>0 is the depth depthQuantA/(v-depthQuantB).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/** @brief Signature of the ".rvl" layout (8 bytes, not null-terminated). */
|
||||||
|
const char kCompressedDepthRvlSignature[8] = {'D', 'E', 'P', 'T', 'H', 'R', 'V', 'L'};
|
||||||
|
/** @brief Size of the ".rvl" header: signature, uint32 cols, uint32 rows. */
|
||||||
|
const size_t kCompressedDepthRvlHeaderSize = 16;
|
||||||
|
/** @brief Signature of the inverse depth layout (8 bytes, not null-terminated). */
|
||||||
|
const char kCompressedDepthInvSignature[8] = {'D', 'E', 'P', 'T', 'H', 'I', 'N', 'V'};
|
||||||
|
/** @brief Size of the inverse depth header: signature, float depthQuantA, float depthQuantB. */
|
||||||
|
const size_t kCompressedDepthInvHeaderSize = 16;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Parses an image compression format "<codec>[:<maxDepth>[:<quantization>]]".
|
* @brief Parses an image compression format "<codec>[:<maxDepth>[:<quantization>]]".
|
||||||
*
|
*
|
||||||
|
|||||||
+19
-28
@@ -70,19 +70,6 @@ int deserializeMatType(int serializedType)
|
|||||||
((serializedType >> kSerializedCnShift) & 511) + 1);
|
((serializedType >> kSerializedCnShift) & 511) + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signatures at the start of rtabmap's own depth formats. Anything else is
|
|
||||||
// assumed to be a standard image format (PNG, JPG) decoded by OpenCV.
|
|
||||||
const char kRvlSignature[8] = {'D', 'E', 'P', 'T', 'H', 'R', 'V', 'L'};
|
|
||||||
const size_t kRvlHeaderSize = 16; // signature, uint32 cols, uint32 rows
|
|
||||||
|
|
||||||
// Quantized inverse depth of a 32FC1 depth image (same quantization as
|
|
||||||
// ROS's compressed_depth_image_transport): signature, float depthQuantA,
|
|
||||||
// float depthQuantB, followed by the 16UC1 inverse depth image compressed
|
|
||||||
// in one of the formats above (PNG, or RVL with its own signature).
|
|
||||||
// See the layouts documented in Compression.h, rtabmap_ros relies on them.
|
|
||||||
const char kInvDepthSignature[8] = {'D', 'E', 'P', 'T', 'H', 'I', 'N', 'V'};
|
|
||||||
const size_t kInvDepthHeaderSize = 16;
|
|
||||||
|
|
||||||
// Default quantization when only the maximum depth is set in the format.
|
// Default quantization when only the maximum depth is set in the format.
|
||||||
const float kDefaultDepthQuantization = 100.0f;
|
const float kDefaultDepthQuantization = 100.0f;
|
||||||
|
|
||||||
@@ -269,11 +256,11 @@ std::vector<unsigned char> compressImage(const cv::Mat & image, const std::strin
|
|||||||
std::vector<unsigned char> invDepthBytes = compressImage(invDepth, codec);
|
std::vector<unsigned char> invDepthBytes = compressImage(invDepth, codec);
|
||||||
if(!invDepthBytes.empty())
|
if(!invDepthBytes.empty())
|
||||||
{
|
{
|
||||||
bytes.resize(kInvDepthHeaderSize + invDepthBytes.size());
|
bytes.resize(kCompressedDepthInvHeaderSize + invDepthBytes.size());
|
||||||
memcpy(&bytes[0], kInvDepthSignature, 8);
|
memcpy(&bytes[0], kCompressedDepthInvSignature, 8);
|
||||||
memcpy(&bytes[8], &depthQuantA, 4);
|
memcpy(&bytes[8], &depthQuantA, 4);
|
||||||
memcpy(&bytes[12], &depthQuantB, 4);
|
memcpy(&bytes[12], &depthQuantB, 4);
|
||||||
memcpy(&bytes[kInvDepthHeaderSize], invDepthBytes.data(), invDepthBytes.size());
|
memcpy(&bytes[kCompressedDepthInvHeaderSize], invDepthBytes.data(), invDepthBytes.size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(image.type() == CV_32FC1)
|
else if(image.type() == CV_32FC1)
|
||||||
@@ -284,7 +271,7 @@ std::vector<unsigned char> compressImage(const cv::Mat & image, const std::strin
|
|||||||
}
|
}
|
||||||
else if(codec == ".rvl")
|
else if(codec == ".rvl")
|
||||||
{
|
{
|
||||||
bytes.assign(kRvlSignature, kRvlSignature+8);
|
bytes.assign(kCompressedDepthRvlSignature, kCompressedDepthRvlSignature+8);
|
||||||
int numPixels = image.rows * image.cols;
|
int numPixels = image.rows * image.cols;
|
||||||
// In the worst case, RVL compression results in ~1.5x larger data.
|
// In the worst case, RVL compression results in ~1.5x larger data.
|
||||||
bytes.resize(3 * numPixels + 20);
|
bytes.resize(3 * numPixels + 20);
|
||||||
@@ -293,8 +280,8 @@ std::vector<unsigned char> compressImage(const cv::Mat & image, const std::strin
|
|||||||
memcpy(&bytes[8], &cols, 4);
|
memcpy(&bytes[8], &cols, 4);
|
||||||
memcpy(&bytes[12], &rows, 4);
|
memcpy(&bytes[12], &rows, 4);
|
||||||
RvlCodec rvl;
|
RvlCodec rvl;
|
||||||
int compressedSize = rvl.CompressRVL(image.ptr<uint16_t>(), &bytes[kRvlHeaderSize], numPixels);
|
int compressedSize = rvl.CompressRVL(image.ptr<uint16_t>(), &bytes[kCompressedDepthRvlHeaderSize], numPixels);
|
||||||
bytes.resize(kRvlHeaderSize + compressedSize);
|
bytes.resize(kCompressedDepthRvlHeaderSize + compressedSize);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -317,6 +304,10 @@ cv::Mat compressImage2(const cv::Mat & image, const std::string & format)
|
|||||||
|
|
||||||
cv::Mat uncompressImage(const cv::Mat & bytes)
|
cv::Mat uncompressImage(const cv::Mat & bytes)
|
||||||
{
|
{
|
||||||
|
if(bytes.empty())
|
||||||
|
{
|
||||||
|
return cv::Mat();
|
||||||
|
}
|
||||||
return uncompressImage(bytes.data, bytes.total()*bytes.elemSize());
|
return uncompressImage(bytes.data, bytes.total()*bytes.elemSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,9 +321,9 @@ cv::Mat uncompressImage(const unsigned char * bytes, size_t size)
|
|||||||
cv::Mat image;
|
cv::Mat image;
|
||||||
if(bytes && size)
|
if(bytes && size)
|
||||||
{
|
{
|
||||||
if(hasSignature(bytes, size, kInvDepthSignature))
|
if(hasSignature(bytes, size, kCompressedDepthInvSignature))
|
||||||
{
|
{
|
||||||
if(size <= kInvDepthHeaderSize)
|
if(size <= kCompressedDepthInvHeaderSize)
|
||||||
{
|
{
|
||||||
UERROR("Inverse depth image is truncated (%d bytes).", (int)size);
|
UERROR("Inverse depth image is truncated (%d bytes).", (int)size);
|
||||||
return image;
|
return image;
|
||||||
@@ -340,7 +331,7 @@ cv::Mat uncompressImage(const unsigned char * bytes, size_t size)
|
|||||||
float depthQuantA, depthQuantB;
|
float depthQuantA, depthQuantB;
|
||||||
memcpy(&depthQuantA, &bytes[8], 4);
|
memcpy(&depthQuantA, &bytes[8], 4);
|
||||||
memcpy(&depthQuantB, &bytes[12], 4);
|
memcpy(&depthQuantB, &bytes[12], 4);
|
||||||
cv::Mat invDepth = uncompressImage(&bytes[kInvDepthHeaderSize], size - kInvDepthHeaderSize);
|
cv::Mat invDepth = uncompressImage(&bytes[kCompressedDepthInvHeaderSize], size - kCompressedDepthInvHeaderSize);
|
||||||
if(invDepth.type() == CV_16UC1)
|
if(invDepth.type() == CV_16UC1)
|
||||||
{
|
{
|
||||||
image = invDepthToDepth(invDepth, depthQuantA, depthQuantB);
|
image = invDepthToDepth(invDepth, depthQuantA, depthQuantB);
|
||||||
@@ -350,9 +341,9 @@ cv::Mat uncompressImage(const unsigned char * bytes, size_t size)
|
|||||||
UERROR("Inverse depth image should be 16UC1 (type=%d).", invDepth.type());
|
UERROR("Inverse depth image should be 16UC1 (type=%d).", invDepth.type());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(hasSignature(bytes, size, kRvlSignature))
|
else if(hasSignature(bytes, size, kCompressedDepthRvlSignature))
|
||||||
{
|
{
|
||||||
if(size < kRvlHeaderSize)
|
if(size < kCompressedDepthRvlHeaderSize)
|
||||||
{
|
{
|
||||||
UERROR("RVL depth image is truncated (%d bytes).", (int)size);
|
UERROR("RVL depth image is truncated (%d bytes).", (int)size);
|
||||||
return image;
|
return image;
|
||||||
@@ -362,7 +353,7 @@ cv::Mat uncompressImage(const unsigned char * bytes, size_t size)
|
|||||||
memcpy(&rows, &bytes[12], 4);
|
memcpy(&rows, &bytes[12], 4);
|
||||||
image = cv::Mat(rows, cols, CV_16UC1);
|
image = cv::Mat(rows, cols, CV_16UC1);
|
||||||
RvlCodec rvl;
|
RvlCodec rvl;
|
||||||
rvl.DecompressRVL(&bytes[kRvlHeaderSize], image.ptr<uint16_t>(), cols * rows);
|
rvl.DecompressRVL(&bytes[kCompressedDepthRvlHeaderSize], image.ptr<uint16_t>(), cols * rows);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -526,17 +517,17 @@ std::string compressedDepthFormat(const unsigned char * bytes, size_t size)
|
|||||||
std::string format;
|
std::string format;
|
||||||
if(bytes && size)
|
if(bytes && size)
|
||||||
{
|
{
|
||||||
if(hasSignature(bytes, size, kInvDepthSignature) && size > kInvDepthHeaderSize)
|
if(hasSignature(bytes, size, kCompressedDepthInvSignature) && size > kCompressedDepthInvHeaderSize)
|
||||||
{
|
{
|
||||||
float depthQuantA, depthQuantB, maxDepth, quantization;
|
float depthQuantA, depthQuantB, maxDepth, quantization;
|
||||||
memcpy(&depthQuantA, &bytes[8], 4);
|
memcpy(&depthQuantA, &bytes[8], 4);
|
||||||
memcpy(&depthQuantB, &bytes[12], 4);
|
memcpy(&depthQuantB, &bytes[12], 4);
|
||||||
invDepthParameters(depthQuantA, depthQuantB, maxDepth, quantization);
|
invDepthParameters(depthQuantA, depthQuantB, maxDepth, quantization);
|
||||||
format = uFormat("%s:%g:%g",
|
format = uFormat("%s:%g:%g",
|
||||||
compressedDepthFormat(&bytes[kInvDepthHeaderSize], size - kInvDepthHeaderSize).c_str(),
|
compressedDepthFormat(&bytes[kCompressedDepthInvHeaderSize], size - kCompressedDepthInvHeaderSize).c_str(),
|
||||||
maxDepth, quantization);
|
maxDepth, quantization);
|
||||||
}
|
}
|
||||||
else if(hasSignature(bytes, size, kRvlSignature))
|
else if(hasSignature(bytes, size, kCompressedDepthRvlSignature))
|
||||||
{
|
{
|
||||||
format = ".rvl";
|
format = ".rvl";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
#include <rtabmap/core/Compression.h>
|
#include <rtabmap/core/Compression.h>
|
||||||
|
#include <rtabmap/utilite/UException.h>
|
||||||
#include <opencv2/core.hpp>
|
#include <opencv2/core.hpp>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
@@ -380,3 +381,33 @@ TEST(CompressionTest, LegacyFloatDepthIsLossless)
|
|||||||
EXPECT_EQ(memcmp(restored.data, depth.data, depth.total() * depth.elemSize()), 0);
|
EXPECT_EQ(memcmp(restored.data, depth.data, depth.total() * depth.elemSize()), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(CompressionTest, MalformedDepthFormatsDecodeToEmpty)
|
||||||
|
{
|
||||||
|
// Signature and header only, no payload
|
||||||
|
std::vector<unsigned char> invDepth = {'D', 'E', 'P', 'T', 'H', 'I', 'N', 'V'};
|
||||||
|
invDepth.resize(16, 0);
|
||||||
|
EXPECT_TRUE(uncompressImage(invDepth).empty());
|
||||||
|
EXPECT_EQ(compressedDepthFormat(invDepth), ".png") << "too short to be inverse depth";
|
||||||
|
|
||||||
|
// Inverse depth header followed by an 8 bits image instead of a 16 bits one
|
||||||
|
const std::vector<unsigned char> png8 = compressImage(cv::Mat(4, 4, CV_8UC1, cv::Scalar(1)), ".png");
|
||||||
|
invDepth.insert(invDepth.end(), png8.begin(), png8.end());
|
||||||
|
EXPECT_TRUE(uncompressImage(invDepth).empty());
|
||||||
|
|
||||||
|
// RVL signature without its size
|
||||||
|
const std::vector<unsigned char> rvl = {'D', 'E', 'P', 'T', 'H', 'R', 'V', 'L', 4, 0};
|
||||||
|
EXPECT_TRUE(uncompressImage(rvl).empty());
|
||||||
|
EXPECT_EQ(compressedDepthFormat(rvl), ".rvl");
|
||||||
|
|
||||||
|
EXPECT_TRUE(uncompressImage(nullptr, 0).empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(CompressionTest, CompressionThreadRejectsInvalidFormat)
|
||||||
|
{
|
||||||
|
// std::string: a string literal would select the (bytes, isImage) constructor
|
||||||
|
const cv::Mat depth(4, 4, CV_32FC1, cv::Scalar(1.0f));
|
||||||
|
EXPECT_THROW(CompressionThread(depth, std::string(".jpg:10")), UException);
|
||||||
|
EXPECT_THROW(CompressionThread(depth, std::string(".bmp")), UException);
|
||||||
|
EXPECT_NO_THROW(CompressionThread(depth, std::string(".rvl:10:100")));
|
||||||
|
}
|
||||||
|
|||||||
@@ -4690,10 +4690,19 @@ TEST(MemoryTest, CreateSignatureRecompressesStereoPairAfterRectification)
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
enum DepthInput
|
||||||
|
{
|
||||||
|
kRawDepth,
|
||||||
|
kCompressedDepthWithRaw, // e.g., received from ROS and decoded
|
||||||
|
kCompressedDepthOnly // raw depth not needed (no features extracted here)
|
||||||
|
};
|
||||||
|
|
||||||
struct InverseDepthCase
|
struct InverseDepthCase
|
||||||
{
|
{
|
||||||
const char * targetVersion;
|
const char * targetVersion;
|
||||||
bool precompressed; // depth already compressed as inverse depth, e.g., from ROS
|
DepthInput input;
|
||||||
|
const char * depthCompressionFormat;
|
||||||
|
bool parallelCompression;
|
||||||
const char * expectedFormat;
|
const char * expectedFormat;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -4706,7 +4715,8 @@ TEST_P(MemoryInverseDepthTest, StoredDepthFormatFollowsDatabaseVersion)
|
|||||||
const InverseDepthCase & cs = GetParam();
|
const InverseDepthCase & cs = GetParam();
|
||||||
ParametersMap params = defaultMemoryParams();
|
ParametersMap params = defaultMemoryParams();
|
||||||
params[Parameters::kMemBinDataKept()] = "true";
|
params[Parameters::kMemBinDataKept()] = "true";
|
||||||
params[Parameters::kMemDepthCompressionFormat()] = ".rvl:10:100";
|
params[Parameters::kMemDepthCompressionFormat()] = cs.depthCompressionFormat;
|
||||||
|
params[Parameters::kMemCompressionParallelized()] = cs.parallelCompression ? "true" : "false";
|
||||||
params[Parameters::kDbTargetVersion()] = cs.targetVersion;
|
params[Parameters::kDbTargetVersion()] = cs.targetVersion;
|
||||||
Memory memory(params);
|
Memory memory(params);
|
||||||
const std::string dbPath = uniqueDbPath();
|
const std::string dbPath = uniqueDbPath();
|
||||||
@@ -4717,15 +4727,18 @@ TEST_P(MemoryInverseDepthTest, StoredDepthFormatFollowsDatabaseVersion)
|
|||||||
cv::randu(depth, 0.5f, 8.0f);
|
cv::randu(depth, 0.5f, 8.0f);
|
||||||
const CameraModel model(10.0, 10.0, 8.0, 8.0, CameraModel::opticalRotation());
|
const CameraModel model(10.0, 10.0, 8.0, 8.0, CameraModel::opticalRotation());
|
||||||
SensorData data;
|
SensorData data;
|
||||||
if(cs.precompressed)
|
if(cs.input == kRawDepth)
|
||||||
{
|
{
|
||||||
data = SensorData(compressImage2(rgb, ".png"), compressImage2(depth, ".png:10:100"), model);
|
data = SensorData(rgb, depth, model);
|
||||||
data.uncompressData(); // raw images are also there, as when received from ROS
|
|
||||||
ASSERT_FALSE(data.depthRaw().empty());
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
data = SensorData(rgb, depth, model);
|
data = SensorData(compressImage2(rgb, ".png"), compressImage2(depth, ".png:10:100"), model);
|
||||||
|
if(cs.input == kCompressedDepthWithRaw)
|
||||||
|
{
|
||||||
|
data.uncompressData();
|
||||||
|
ASSERT_FALSE(data.depthRaw().empty());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ASSERT_TRUE(memory.update(data, Transform(0, 0, 0, 0, 0, 0), cv::Mat::eye(6, 6, CV_64FC1) * 0.01));
|
ASSERT_TRUE(memory.update(data, Transform(0, 0, 0, 0, 0, 0), cv::Mat::eye(6, 6, CV_64FC1) * 0.01));
|
||||||
@@ -4748,7 +4761,12 @@ INSTANTIATE_TEST_SUITE_P(
|
|||||||
DatabaseVersions,
|
DatabaseVersions,
|
||||||
MemoryInverseDepthTest,
|
MemoryInverseDepthTest,
|
||||||
::testing::Values(
|
::testing::Values(
|
||||||
InverseDepthCase{"", false, ".rvl:10:100"},
|
InverseDepthCase{"", kRawDepth, ".rvl:10:100", true, ".rvl:10:100"},
|
||||||
InverseDepthCase{"", true, ".png:10:100"}, // reused as is
|
InverseDepthCase{"", kRawDepth, ".rvl:10:100", false, ".rvl:10:100"},
|
||||||
InverseDepthCase{"0.23.0", false, ".png"}, // legacy 32FC1 format
|
InverseDepthCase{"", kCompressedDepthWithRaw, ".rvl:10:100", true, ".png:10:100"}, // reused as is
|
||||||
InverseDepthCase{"0.23.0", true, ".png"})); // re-compressed
|
InverseDepthCase{"", kCompressedDepthOnly, ".rvl:10:100", true, ".png:10:100"}, // reused as is
|
||||||
|
InverseDepthCase{"0.23.0", kRawDepth, ".rvl:10:100", true, ".png"}, // legacy 32FC1 format
|
||||||
|
InverseDepthCase{"0.23.0", kCompressedDepthWithRaw, ".rvl:10:100", true, ".png"}, // re-compressed
|
||||||
|
InverseDepthCase{"0.23.0", kCompressedDepthOnly, ".rvl:10:100", true, ".png"}, // decompressed, re-compressed
|
||||||
|
InverseDepthCase{"", kRawDepth, ".rvl", true, ".png"}, // RVL is 16UC1 only: legacy
|
||||||
|
InverseDepthCase{"", kRawDepth, ".jpg", true, ".png"})); // invalid: default ".rvl"
|
||||||
|
|||||||
Reference in New Issue
Block a user