2021SC@SDUSC
open3D Image和RGBDImage
本篇对open3D中的image、RGBDImage和ImageIO的源码进行分析。
Image
image类存储具有可自定义的宽度、高度、通道数和每个通道的字节数的图像。
-
注意点:
- 由于需要解析的代码较多,为使代码解读更加清晰,我将代码分析的详细过程写在代码段的注释中。
- 中文部分是源码解读,包含代码分析和遇到的问题。
-
类中部分关键函数和相关知识:
- FloatValueAt:用于访问一个(单通道)浮点图像的双线性插值浮点值的函数。
- CreateDepthToCameraDistanceMultiplierFloatImage:函数,创建一个浮点型图像组成的乘数,将深度值转换为相机距离。
乘数函数 M ( u , v ) M(u,v) M(u,v)定义为: M ( u , v ) = 1 + ( ( u − c x ) / f x ) 2 + ( ( v − c y ) / f y ) 2 M(u, v) = \sqrt{1 + ((u - cx) / fx) ^ 2 + ((v - cy) / fy) ^ 2} M(u,v)=1+((u−cx)/fx)2+((v−cy)/fy)2。
#pragma once
#include <Eigen/Core>
#include <memory>
#include <vector>
#include "open3d/geometry/Geometry2D.h"
#include "open3d/utility/Logging.h"
namespace open3d {
namespace camera {
class PinholeCameraIntrinsic;
}
namespace geometry {
class Image;
/// Typedef and functions for ImagePyramid.
typedef std::vector<std::shared_ptr<Image>> ImagePyramid;
/// Image类存储的图像具有可定制的宽度,高度,通道数量和每个通道的字节数。
class Image : public Geometry2D {
public:
/// \enum ColorToIntensityConversionType
/// 指定R、G、B通道转换为强度时权重是否相同。只用于图像与3通道。
/// 当使用“加权”时,R、G、B通道根据ITU BT.601数字标准加权:I = 0.299 * R + 0.587 * G + 0.114 * B。
enum class ColorToIntensityConversionType {
///R, G, B信道的权值相等。
Equal,
///加权R, G, B通道:I = 0.299 * R + 0.587 * G + 0.114 * B。
Weighted,
};
/// \enum FilterType
///
/// 指定图像过滤器类型。
enum class FilterType {
///高斯滤波器的大小为3 x 3。
Gaussian3,
///高斯滤波器的大小为5 x 5。
Gaussian5,
///高斯滤波器的大小为7 x 7。
Gaussian7,
///沿x轴的Sobel滤波器。
Sobel3Dx,
///沿y轴的Sobel滤波器。
Sobel3Dy
};
public:
///默认构造函数。
Image() : Geometry2D(Geometry::GeometryType::Image) {}
~Image() override {}
public:
Image &Clear() override;
bool IsEmpty() const override;
Eigen::Vector2d GetMinBound() const override;
Eigen::Vector2d GetMaxBound() const override;
/// 测试坐标(u, v)是否位于图像的inner_marge中。
///
/// 参数u 沿宽度维度的坐标。
/// 参数v 沿着高度维度坐标。
/// 参数inner_margin inner_margin图像边界的内缘。
/// 如果坐标(u, v)位于图像的inner_marge中,返回true。
bool TestImageBoundary(double u, double v, double inner_margin = 0.0) const;
public:
///如果图像有有效数据,返回true。
virtual bool HasData() const {
return width_ > 0 && height_ > 0 &&
data_.size() == size_t(height_ * BytesPerLine());
}
///预设图像属性并分配图像缓冲区。
Image &Prepare(int width,
int height,
int num_of_channels,
int bytes_per_channel) {
width_ = width;
height_ = height;
num_of_channels_ = num_of_channels;
bytes_per_channel_ = bytes_per_channel;
AllocateDataBuffer();
return *this;
}
///返回每行(行或宽度)的数据大小(以字节为单位)。
int BytesPerLine() const {
return width_ * num_of_channels_ * bytes_per_channel_;
}
///函数,用于访问一个(单通道)浮点图像的双线性插值浮点值。
///返回一个元组,其中第一个bool值表示u,v坐标是否在图像尺寸内,第二个double值是插值的像素值。
std::pair<bool, double> FloatValueAt(double u, double v) const;
///工厂函数,创建一个浮动图像组成的乘数,将深度值转换为相机距离。
///乘数函数M(u,v)定义为:M(u, v) = sqrt(1 + ((u - cx) / fx) ^ 2 + ((v - cy) / fy) ^ 2)。
///这个函数被用作容量集成中性能优化的优化函数(详见Core/ integration /TSDFVolume.h)。
static std::shared_ptr<Image>
CreateDepthToCameraDistanceMultiplierFloatImage(
const camera::PinholeCameraIntrinsic &intrinsic);
///返回一个灰度缩放浮点类型的图像。
std::shared_ptr<Image> CreateFloatImage(
Image::ColorToIntensityConversionType type =
Image::ColorToIntensityConversionType::Weighted) const;
///函数,访问单通道图像的原始数据。
template <typename T>
T *PointerAt(int u, int v) const;
///函数,访问多通道图像的原始数据。
template <typename T>
T *PointerAt(int u, int v, int ch) const;
///重新解释内部数据缓冲区。结果类型的大小必须与bytes_per_channel_相同。这类似于PointerAt<T>(0, 0)。
template <class T>
T *PointerAs() const {
if (sizeof(T) != bytes_per_channel_) {
utility::LogError("sizeof(T) != byte_per_channel_: {} != {}.",
sizeof(T), bytes_per_channel_);
}
return (T *)(data_.data());
}
std::shared_ptr<Image> ConvertDepthToFloatImage(
double depth_scale = 1000.0, double depth_trunc = 3.0) const;
std::shared_ptr<Image> Transpose() const;
///函数,水平翻转图像(从左到右)。
std::shared_ptr<Image> FlipHorizontal() const;
///函数,垂直翻转图像。
std::shared_ptr<Image> FlipVertical() const;
///函数,使用预定义的滤波类型对图像进行滤波操作。
std::shared_ptr<Image> Filter(Image::FilterType type) const;
///函数,滤波图像与任意dx, dy可分离滤波器。
std::shared_ptr<Image> Filter(const std::vector<double> &dx,
const std::vector<double> &dy) const;
std::shared_ptr<Image> FilterHorizontal(
const std::vector<double> &kernel) const;
///函数,对2x图像使用简单的2x2平均进行downsample(下采样)。
std::shared_ptr<Image> Downsample() const;
///函数,扩展8位掩码映射。
std::shared_ptr<Image> Dilate(int half_kernel_size = 1) const;
///函数,线性变换像素强度。
///算法是:image_new = scale * image + offset.
Image &LinearTransform(double scale = 1.0, double offset = 0.0);
///剪切像素强度的函数。
///
///参数min是下限。
///参数max是上限。
Image &ClipIntensity(double min = 0.0, double max = 1.0);
///更改为特定用途制作的图像的数据类型的函数,如单通道浮动图像->8位RGB或16位深度图像。
template <typename T>
std::shared_ptr<Image> CreateImageFromFloatImage() const;
///函数,过滤图像金字塔(image pyramid)。
static ImagePyramid FilterPyramid(const ImagePyramid &input,
Image::FilterType type);
///函数,创建图像金字塔。
ImagePyramid CreatePyramid(size_t num_of_levels,
bool with_gaussian_filter = true) const;
///函数,从深度图像创建深度地图边界掩码。
std::shared_ptr<Image> CreateDepthBoundaryMask(
double depth_threshold_for_discontinuity_check = 0.1,
int half_dilation_kernel_size_for_discontinuity_map = 3) const;
protected:
void AllocateDataBuffer() {
data_.resize(width_ * height_ * num_of_channels_ * bytes_per_channel_);
}
public:
///图像的宽度。
int width_ = 0;
///图像的高度。
int height_ = 0;
///图像中的通道数量。
int num_of_channels_ = 0;
///每个通道字节数。
int bytes_per_channel_ = 0;
///图像存储缓冲区。
std::vector<uint8_t> data_;
};
} // namespace geometry
} // namespace open3d
RGBDImage
RGBDImage用于从同一视角观察具有相同分辨率的一对配准颜色和深度图像。如果有其他格式,需要先转换它。
#pragma once
#include "open3d/geometry/Geometry2D.h"
#include "open3d/geometry/Image.h"
namespace open3d {
namespace geometry {
class RGBDImage;
/// Typedef and functions for RGBDImagePyramid
typedef std::vector<std::shared_ptr<RGBDImage>> RGBDImagePyramid;
/// \class RGBDImage
///RGBDImage类用于一对配准的颜色和深度图像。
///
///从相同的视角看,具有相同的分辨率。
///如果有其他格式,需要先转换它。
class RGBDImage : public Geometry2D {
public:
///默认构造函数。
RGBDImage() : Geometry2D(Geometry::GeometryType::RGBDImage) {}
///参数化构造函数。
///参数color 彩色图像。
///参数depth 深度图像。
RGBDImage(const Image &color, const Image &depth)
: Geometry2D(Geometry::GeometryType::RGBDImage),
color_(color),
depth_(depth) {}
~RGBDImage() override {
color_.Clear();
depth_.Clear();
};
RGBDImage &Clear() override;
bool IsEmpty() const override;
Eigen::Vector2d GetMinBound() const override;
Eigen::Vector2d GetMaxBound() const override;
///工厂函数,由颜色和深度图像创建一个RGBD图像。
///
///参数color 颜色图像。
///参数depth 深度图像。
///参数depth_scale 缩放深度值的比例。深度将首先被缩放,然后被截断。
///参数depth_trunc 大于depth_trunc的深度值被截断为0。深度值将首先被缩放,然后被截断。
///参数convert_rgb_to_intensity 判断是否将RGB图像转换为强度图像。
static std::shared_ptr<RGBDImage> CreateFromColorAndDepth(
const Image &color,
const Image &depth,
double depth_scale = 1000.0,
double depth_trunc = 3.0,
bool convert_rgb_to_intensity = true);
///从Redwood数据集创建一个RGBD图像的工厂函数。
///参数color 颜色图像。
///参数depth 深度图像。
///参数convert_rgb_to_intensity 判断是否将RGB图像转换为强度图像。
static std::shared_ptr<RGBDImage> CreateFromRedwoodFormat(
const Image &color,
const Image &depth,
bool convert_rgb_to_intensity = true);
///工厂函数,根据TUM数据集创建一个RGBD图像。
///参数color 颜色图像。
///参数depth 深度图像。
///参数convert_rgb_to_intensity 判断是否将RGB图像转换为强度图像。
static std::shared_ptr<RGBDImage> CreateFromTUMFormat(
const Image &color,
const Image &depth,
bool convert_rgb_to_intensity = true);
///根据SUN3D数据集创建一个RGBD图像的工厂函数。
///参数color 颜色图像。
///参数depth 深度图像。
///参数convert_rgb_to_intensity 判断是否将RGB图像转换为强度图像。
static std::shared_ptr<RGBDImage> CreateFromTUMFormat(
static std::shared_ptr<RGBDImage> CreateFromSUNFormat(
const Image &color,
const Image &depth,
bool convert_rgb_to_intensity = true);
///工厂函数,根据NYU数据集创建一个RGBD图像。
///参数color 颜色图像。
///参数depth 深度图像。
///参数convert_rgb_to_intensity 判断是否将RGB图像转换为强度图像。
static std::shared_ptr<RGBDImage> CreateFromNYUFormat(
const Image &color,
const Image &depth,
bool convert_rgb_to_intensity = true);
static RGBDImagePyramid FilterPyramid(
const RGBDImagePyramid &rgbd_image_pyramid, Image::FilterType type);
RGBDImagePyramid CreatePyramid(
size_t num_of_levels,
bool with_gaussian_filter_for_color = true,
bool with_gaussian_filter_for_depth = false) const;
public:
///color 颜色图像。
Image color_;
///depth 深度图像。
Image depth_;
};
} // namespace geometry
} // namespace open3d
ImageIO
#pragma once
#include <string>
#include "open3d/geometry/Image.h"
namespace open3d {
namespace io {
///从一个文件创建一个映像的工厂函数。
///如果读取失败,返回一个空图像。
std::shared_ptr<geometry::Image> CreateImageFromFile(
const std::string &filename);
///从文件中读取Image的一般入口。
///该函数基于扩展名filename调用read函数。
///如果读取成功,返回true,否则返回false。
bool ReadImage(const std::string &filename, geometry::Image &image);
constexpr int kOpen3DImageIODefaultQuality = -1;
///将Image写入文件的一般入口。
///函数调用基于扩展名filename的write函数。
///如果write函数支持质量,则使用参数。否则将被忽略。
///参数quality: PNG: [0-9] <=2 快速写入,用于存储中间数据
/// >=3 (默认)正常写入平衡速度和文件大小
/// JPEG: [0-100]
/// 通常在[70,95]。
/// 90是默认值(高质量)。
///如果写入成功,返回true,否则返回false。
bool WriteImage(const std::string &filename,
const geometry::Image &image,
int quality = kOpen3DImageIODefaultQuality);
bool ReadImageFromPNG(const std::string &filename, geometry::Image &image);
bool WriteImageToPNG(const std::string &filename,
const geometry::Image &image,
int quality = kOpen3DImageIODefaultQuality);
bool ReadImageFromJPG(const std::string &filename, geometry::Image &image);
bool WriteImageToJPG(const std::string &filename,
const geometry::Image &image,
int quality = kOpen3DImageIODefaultQuality);
} // namespace io
} // namespace open3d
小结
本篇对Image和RGBDImage的两个基类,以及Image输入输出类进行了源码分析,使我了解了open3D对图像的处理方法,接下来是对open3D中关于BoundingVolume(包围盒)和HalfEdge(半边)相关知识的源码分析,以及对Mesh类和RGBD图像类相关知识的应用。