Color X-Rays using the color of points. (#185)
Colorless points are considered to be black. Creating assets with intensities will give now brighter X-Rays. I plan to fix this by adding intensities besides colors into PointBatch and having a PointsProcessor that converts intensities into colors.master
parent
92b89d12c8
commit
0fe51185be
|
@ -32,20 +32,18 @@ namespace cartographer {
|
||||||
namespace io {
|
namespace io {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
using Voxels = mapping_3d::HybridGridBase<bool>;
|
struct PixelData {
|
||||||
|
size_t num_occupied_cells_in_column = 0;
|
||||||
|
double mean_r = 0.;
|
||||||
|
double mean_g = 0.;
|
||||||
|
double mean_b = 0.;
|
||||||
|
};
|
||||||
|
|
||||||
// Takes the logarithm of each value in 'mat', clamping to 0 as smallest value.
|
using PixelDataMatrix =
|
||||||
void TakeLogarithm(Eigen::MatrixXf* mat) {
|
Eigen::Matrix<PixelData, Eigen::Dynamic, Eigen::Dynamic>;
|
||||||
for (int y = 0; y < mat->rows(); ++y) {
|
|
||||||
for (int x = 0; x < mat->cols(); ++x) {
|
double Mix(const double a, const double b, const double t) {
|
||||||
const float value = (*mat)(y, x);
|
return a * (1. - t) + t * b;
|
||||||
if (value == 0.f) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const float new_value = std::log(value);
|
|
||||||
(*mat)(y, x) = new_value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cairo_status_t CairoWriteCallback(void* const closure,
|
cairo_status_t CairoWriteCallback(void* const closure,
|
||||||
|
@ -59,19 +57,49 @@ cairo_status_t CairoWriteCallback(void* const closure,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write 'mat' as a pleasing-to-look-at PNG into 'filename'
|
// Write 'mat' as a pleasing-to-look-at PNG into 'filename'
|
||||||
void WritePng(const Eigen::MatrixXf& mat, FileWriter* const file_writer) {
|
void WritePng(const PixelDataMatrix& mat, FileWriter* const file_writer) {
|
||||||
const int stride =
|
const int stride =
|
||||||
cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, mat.cols());
|
cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, mat.cols());
|
||||||
CHECK_EQ(stride % 4, 0);
|
CHECK_EQ(stride % 4, 0);
|
||||||
std::vector<uint32_t> pixels(stride / 4 * mat.rows(), 0.);
|
std::vector<uint32_t> pixels(stride / 4 * mat.rows(), 0.);
|
||||||
|
|
||||||
const float max = mat.maxCoeff();
|
float max = std::numeric_limits<float>::min();
|
||||||
for (int y = 0; y < mat.rows(); ++y) {
|
for (int y = 0; y < mat.rows(); ++y) {
|
||||||
for (int x = 0; x < mat.cols(); ++x) {
|
for (int x = 0; x < mat.cols(); ++x) {
|
||||||
const float value = mat(y, x);
|
const PixelData& cell = mat(y, x);
|
||||||
uint8_t shade = common::RoundToInt(255.f * (1.f - value / max));
|
if (cell.num_occupied_cells_in_column == 0.) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
max = std::max<float>(max, std::log(cell.num_occupied_cells_in_column));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int y = 0; y < mat.rows(); ++y) {
|
||||||
|
for (int x = 0; x < mat.cols(); ++x) {
|
||||||
|
const PixelData& cell = mat(y, x);
|
||||||
|
if (cell.num_occupied_cells_in_column == 0.) {
|
||||||
pixels[y * stride / 4 + x] =
|
pixels[y * stride / 4 + x] =
|
||||||
(255 << 24) | (shade << 16) | (shade << 8) | shade;
|
(255 << 24) | (255 << 16) | (255 << 8) | 255;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We use a logarithmic weighting for how saturated a pixel will be. The
|
||||||
|
// basic idea here was that walls (full height) are fully saturated, but
|
||||||
|
// details like chairs and tables are still well visible.
|
||||||
|
const float saturation =
|
||||||
|
std::log(cell.num_occupied_cells_in_column) / max;
|
||||||
|
double mean_r_in_column = (cell.mean_r / 255.);
|
||||||
|
double mean_g_in_column = (cell.mean_g / 255.);
|
||||||
|
double mean_b_in_column = (cell.mean_b / 255.);
|
||||||
|
|
||||||
|
double mix_r = Mix(1., mean_r_in_column, saturation);
|
||||||
|
double mix_g = Mix(1., mean_g_in_column, saturation);
|
||||||
|
double mix_b = Mix(1., mean_b_in_column, saturation);
|
||||||
|
|
||||||
|
const int r = common::RoundToInt(mix_r * 255.);
|
||||||
|
const int g = common::RoundToInt(mix_g * 255.);
|
||||||
|
const int b = common::RoundToInt(mix_b * 255.);
|
||||||
|
pixels[y * stride / 4 + x] = (255 << 24) | (r << 16) | (g << 8) | b;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,40 +120,6 @@ void WritePng(const Eigen::MatrixXf& mat, FileWriter* const file_writer) {
|
||||||
CHECK(file_writer->Close());
|
CHECK(file_writer->Close());
|
||||||
}
|
}
|
||||||
|
|
||||||
void WriteVoxels(const Voxels& voxels, FileWriter* const file_writer) {
|
|
||||||
Eigen::Array3i min(std::numeric_limits<int>::max(),
|
|
||||||
std::numeric_limits<int>::max(),
|
|
||||||
std::numeric_limits<int>::max());
|
|
||||||
Eigen::Array3i max(std::numeric_limits<int>::min(),
|
|
||||||
std::numeric_limits<int>::min(),
|
|
||||||
std::numeric_limits<int>::min());
|
|
||||||
|
|
||||||
// Find the maximum and minimum cells.
|
|
||||||
for (Voxels::Iterator it(voxels); !it.Done(); it.Next()) {
|
|
||||||
const Eigen::Array3i idx = it.GetCellIndex();
|
|
||||||
min = min.min(idx);
|
|
||||||
max = max.max(idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the (x, y) pixel of the given 'index'.
|
|
||||||
const auto voxel_index_to_pixel = [&max, &min](const Eigen::Array3i& index) {
|
|
||||||
// We flip the y axis, since matrices rows are counted from the top.
|
|
||||||
return Eigen::Array2i(max[1] - index[1], max[2] - index[2]);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Hybrid grid uses X: forward, Y: left, Z: up.
|
|
||||||
// For the screen we are using. X: right, Y: up
|
|
||||||
const int xsize = max[1] - min[1] + 1;
|
|
||||||
const int ysize = max[2] - min[2] + 1;
|
|
||||||
Eigen::MatrixXf image = Eigen::MatrixXf::Zero(ysize, xsize);
|
|
||||||
for (Voxels::Iterator it(voxels); !it.Done(); it.Next()) {
|
|
||||||
const Eigen::Array2i pixel = voxel_index_to_pixel(it.GetCellIndex());
|
|
||||||
++image(pixel.y(), pixel.x());
|
|
||||||
}
|
|
||||||
TakeLogarithm(&image);
|
|
||||||
WritePng(image, file_writer);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ContainedIn(
|
bool ContainedIn(
|
||||||
const common::Time& time,
|
const common::Time& time,
|
||||||
const std::vector<common::Interval<common::Time>>& time_intervals) {
|
const std::vector<common::Interval<common::Time>>& time_intervals) {
|
||||||
|
@ -137,14 +131,6 @@ bool ContainedIn(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Insert(const PointsBatch& batch, const transform::Rigid3f& transform,
|
|
||||||
Voxels* voxels) {
|
|
||||||
for (const auto& point : batch.points) {
|
|
||||||
const Eigen::Vector3f camera_point = transform * point;
|
|
||||||
*voxels->mutable_value(voxels->GetCellIndex(camera_point)) = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
XRayPointsProcessor::XRayPointsProcessor(
|
XRayPointsProcessor::XRayPointsProcessor(
|
||||||
|
@ -157,7 +143,9 @@ XRayPointsProcessor::XRayPointsProcessor(
|
||||||
output_filename_(output_filename),
|
output_filename_(output_filename),
|
||||||
transform_(transform) {
|
transform_(transform) {
|
||||||
for (size_t i = 0; i < (floors_.empty() ? 1 : floors.size()); ++i) {
|
for (size_t i = 0; i < (floors_.empty() ? 1 : floors.size()); ++i) {
|
||||||
voxels_.emplace_back(voxel_size, Eigen::Vector3f::Zero());
|
aggregations_.emplace_back(Aggregation{
|
||||||
|
mapping_3d::HybridGridBase<bool>(voxel_size, Eigen::Vector3f::Zero()),
|
||||||
|
{}});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,16 +167,79 @@ std::unique_ptr<XRayPointsProcessor> XRayPointsProcessor::FromDictionary(
|
||||||
floors, dictionary->GetString("filename"), file_writer_factory, next);
|
floors, dictionary->GetString("filename"), file_writer_factory, next);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void XRayPointsProcessor::WriteVoxels(const Aggregation& aggregation,
|
||||||
|
FileWriter* const file_writer) {
|
||||||
|
Eigen::Array3i min(std::numeric_limits<int>::max(),
|
||||||
|
std::numeric_limits<int>::max(),
|
||||||
|
std::numeric_limits<int>::max());
|
||||||
|
Eigen::Array3i max(std::numeric_limits<int>::min(),
|
||||||
|
std::numeric_limits<int>::min(),
|
||||||
|
std::numeric_limits<int>::min());
|
||||||
|
|
||||||
|
// Find the maximum and minimum cells.
|
||||||
|
for (mapping_3d::HybridGridBase<bool>::Iterator it(aggregation.voxels);
|
||||||
|
!it.Done(); it.Next()) {
|
||||||
|
const Eigen::Array3i idx = it.GetCellIndex();
|
||||||
|
min = min.min(idx);
|
||||||
|
max = max.max(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the (x, y) pixel of the given 'index'.
|
||||||
|
const auto voxel_index_to_pixel = [&max, &min](const Eigen::Array3i& index) {
|
||||||
|
// We flip the y axis, since matrices rows are counted from the top.
|
||||||
|
return Eigen::Array2i(max[1] - index[1], max[2] - index[2]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Hybrid grid uses X: forward, Y: left, Z: up.
|
||||||
|
// For the screen we are using. X: right, Y: up
|
||||||
|
const int xsize = max[1] - min[1] + 1;
|
||||||
|
const int ysize = max[2] - min[2] + 1;
|
||||||
|
PixelDataMatrix image = PixelDataMatrix(ysize, xsize);
|
||||||
|
for (mapping_3d::HybridGridBase<bool>::Iterator it(aggregation.voxels);
|
||||||
|
!it.Done(); it.Next()) {
|
||||||
|
const Eigen::Array3i cell_index = it.GetCellIndex();
|
||||||
|
const Eigen::Array2i pixel = voxel_index_to_pixel(cell_index);
|
||||||
|
PixelData& pixel_data = image(pixel.y(), pixel.x());
|
||||||
|
const auto& column_data =
|
||||||
|
aggregation.column_data.at(std::make_pair(cell_index[1], cell_index[2]));
|
||||||
|
pixel_data.mean_r = column_data.sum_r / column_data.count;
|
||||||
|
pixel_data.mean_g = column_data.sum_g / column_data.count;
|
||||||
|
pixel_data.mean_b = column_data.sum_b / column_data.count;
|
||||||
|
++pixel_data.num_occupied_cells_in_column;
|
||||||
|
}
|
||||||
|
WritePng(image, file_writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void XRayPointsProcessor::Insert(const PointsBatch& batch,
|
||||||
|
const transform::Rigid3f& transform,
|
||||||
|
Aggregation* const aggregation) {
|
||||||
|
constexpr Color kDefaultColor = {{0, 0, 0}};
|
||||||
|
for (size_t i = 0; i < batch.points.size(); ++i) {
|
||||||
|
const Eigen::Vector3f camera_point = transform * batch.points[i];
|
||||||
|
const Eigen::Array3i cell_index =
|
||||||
|
aggregation->voxels.GetCellIndex(camera_point);
|
||||||
|
*aggregation->voxels.mutable_value(cell_index) = true;
|
||||||
|
ColumnData& column_data =
|
||||||
|
aggregation->column_data[std::make_pair(cell_index[1], cell_index[2])];
|
||||||
|
const auto& color =
|
||||||
|
batch.colors.empty() ? kDefaultColor : batch.colors.at(i);
|
||||||
|
column_data.sum_r += color[0];
|
||||||
|
column_data.sum_g += color[1];
|
||||||
|
column_data.sum_b += color[2];
|
||||||
|
++column_data.count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void XRayPointsProcessor::Process(std::unique_ptr<PointsBatch> batch) {
|
void XRayPointsProcessor::Process(std::unique_ptr<PointsBatch> batch) {
|
||||||
if (floors_.empty()) {
|
if (floors_.empty()) {
|
||||||
CHECK_EQ(voxels_.size(), 1);
|
CHECK_EQ(aggregations_.size(), 1);
|
||||||
Insert(*batch, transform_, &voxels_[0]);
|
Insert(*batch, transform_, &aggregations_[0]);
|
||||||
} else {
|
} else {
|
||||||
for (size_t i = 0; i < floors_.size(); ++i) {
|
for (size_t i = 0; i < floors_.size(); ++i) {
|
||||||
if (!ContainedIn(batch->time, floors_[i].timespans)) {
|
if (!ContainedIn(batch->time, floors_[i].timespans)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Insert(*batch, transform_, &voxels_[i]);
|
Insert(*batch, transform_, &aggregations_[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
next_->Process(std::move(batch));
|
next_->Process(std::move(batch));
|
||||||
|
@ -196,13 +247,13 @@ void XRayPointsProcessor::Process(std::unique_ptr<PointsBatch> batch) {
|
||||||
|
|
||||||
PointsProcessor::FlushResult XRayPointsProcessor::Flush() {
|
PointsProcessor::FlushResult XRayPointsProcessor::Flush() {
|
||||||
if (floors_.empty()) {
|
if (floors_.empty()) {
|
||||||
CHECK_EQ(voxels_.size(), 1);
|
CHECK_EQ(aggregations_.size(), 1);
|
||||||
WriteVoxels(voxels_[0],
|
WriteVoxels(aggregations_[0],
|
||||||
file_writer_factory_(output_filename_ + ".png").get());
|
file_writer_factory_(output_filename_ + ".png").get());
|
||||||
} else {
|
} else {
|
||||||
for (size_t i = 0; i < floors_.size(); ++i) {
|
for (size_t i = 0; i < floors_.size(); ++i) {
|
||||||
WriteVoxels(
|
WriteVoxels(
|
||||||
voxels_[i],
|
aggregations_[i],
|
||||||
file_writer_factory_(output_filename_ + std::to_string(i) + ".png")
|
file_writer_factory_(output_filename_ + std::to_string(i) + ".png")
|
||||||
.get());
|
.get());
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,9 @@
|
||||||
#ifndef CARTOGRAPHER_IO_XRAY_POINTS_PROCESSOR_H_
|
#ifndef CARTOGRAPHER_IO_XRAY_POINTS_PROCESSOR_H_
|
||||||
#define CARTOGRAPHER_IO_XRAY_POINTS_PROCESSOR_H_
|
#define CARTOGRAPHER_IO_XRAY_POINTS_PROCESSOR_H_
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#include "Eigen/Core"
|
||||||
#include "cartographer/common/lua_parameter_dictionary.h"
|
#include "cartographer/common/lua_parameter_dictionary.h"
|
||||||
#include "cartographer/io/file_writer.h"
|
#include "cartographer/io/file_writer.h"
|
||||||
#include "cartographer/io/points_processor.h"
|
#include "cartographer/io/points_processor.h"
|
||||||
|
@ -50,6 +53,22 @@ class XRayPointsProcessor : public PointsProcessor {
|
||||||
FlushResult Flush() override;
|
FlushResult Flush() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
struct ColumnData {
|
||||||
|
double sum_r = 0.;
|
||||||
|
double sum_g = 0.;
|
||||||
|
double sum_b = 0.;
|
||||||
|
uint32_t count = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Aggregation {
|
||||||
|
mapping_3d::HybridGridBase<bool> voxels;
|
||||||
|
std::map<std::pair<int, int>, ColumnData> column_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
void WriteVoxels(const Aggregation& aggregation, FileWriter* const file_writer);
|
||||||
|
void Insert(const PointsBatch& batch, const transform::Rigid3f& transform,
|
||||||
|
Aggregation* aggregation);
|
||||||
|
|
||||||
PointsProcessor* const next_;
|
PointsProcessor* const next_;
|
||||||
const FileWriterFactory& file_writer_factory_;
|
const FileWriterFactory& file_writer_factory_;
|
||||||
|
|
||||||
|
@ -60,7 +79,7 @@ class XRayPointsProcessor : public PointsProcessor {
|
||||||
const transform::Rigid3f transform_;
|
const transform::Rigid3f transform_;
|
||||||
|
|
||||||
// Only has one entry if we do not separate into floors.
|
// Only has one entry if we do not separate into floors.
|
||||||
std::vector<mapping_3d::HybridGridBase<bool>> voxels_;
|
std::vector<Aggregation> aggregations_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace io
|
} // namespace io
|
||||||
|
|
Loading…
Reference in New Issue