Skip to content

GSoC: Modified PLY reader to support color attribute read/write #23805

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 25, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions modules/3d/include/opencv2/3d.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2636,8 +2636,9 @@ class CV_EXPORTS Octree {
* @param filename Name of the file.
* @param vertices (vector of Point3f) Point coordinates of a point cloud
* @param normals (vector of Point3f) Point normals of a point cloud
* @param rgb (vector of Point3_<uchar>) Point RGB color of a point cloud
*/
CV_EXPORTS_W void loadPointCloud(const String &filename, OutputArray vertices, OutputArray normals = noArray());
CV_EXPORTS_W void loadPointCloud(const String &filename, OutputArray vertices, OutputArray normals = noArray(), OutputArray rgb = noArray());

/** @brief Saves a point cloud to a specified file.
*
Expand All @@ -2647,8 +2648,9 @@ CV_EXPORTS_W void loadPointCloud(const String &filename, OutputArray vertices, O
* @param filename Name of the file.
* @param vertices (vector of Point3f) Point coordinates of a point cloud
* @param normals (vector of Point3f) Point normals of a point cloud
* @param rgb (vector of Point3_<uchar>) Point RGB color of a point cloud
*/
CV_EXPORTS_W void savePointCloud(const String &filename, InputArray vertices, InputArray normals = noArray());
CV_EXPORTS_W void savePointCloud(const String &filename, InputArray vertices, InputArray normals = noArray(), InputArray rgb = noArray());

/** @brief Loads a mesh from a file.
*
Expand Down
8 changes: 4 additions & 4 deletions modules/3d/src/pointcloud/io_base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,21 @@ void BasePointCloudDecoder::setSource(const std::string &filename) noexcept
m_filename = filename;
}

void BasePointCloudDecoder::readData(std::vector<Point3f> &points, std::vector<Point3f> &normals)
void BasePointCloudDecoder::readData(std::vector<Point3f> &points, std::vector<Point3f> &normals, std::vector<Point3_<uchar>> &rgb)
{
std::vector<std::vector<int32_t>> indices;
readData(points, normals, indices);
readData(points, normals, rgb, indices);
}

void BasePointCloudEncoder::setDestination(const std::string &filename) noexcept
{
m_filename = filename;
}

void BasePointCloudEncoder::writeData(const std::vector<Point3f> &points, const std::vector<Point3f> &normals)
void BasePointCloudEncoder::writeData(const std::vector<Point3f> &points, const std::vector<Point3f> &normals, const std::vector<Point3_<uchar>> &rgb)
{
std::vector<std::vector<int32_t>> indices;
writeData(points, normals, indices);
writeData(points, normals, rgb, indices);
}

} /* namespace cv */
8 changes: 4 additions & 4 deletions modules/3d/src/pointcloud/io_base.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ class BasePointCloudDecoder
virtual ~BasePointCloudDecoder() = default;

virtual void setSource(const String &filename) noexcept;
virtual void readData(std::vector<Point3f> &points, std::vector<Point3f> &normals);
virtual void readData(std::vector<Point3f> &points, std::vector<Point3f> &normals, std::vector<std::vector<int32_t>> &indices) = 0;
virtual void readData(std::vector<Point3f> &points, std::vector<Point3f> &normals, std::vector<Point3_<uchar>> &rgb);
virtual void readData(std::vector<Point3f> &points, std::vector<Point3f> &normals, std::vector<Point3_<uchar>> &rgb, std::vector<std::vector<int32_t>> &indices) = 0;

protected:
String m_filename;
Expand All @@ -39,8 +39,8 @@ class BasePointCloudEncoder
virtual ~BasePointCloudEncoder() = default;

virtual void setDestination(const String &filename) noexcept;
virtual void writeData(const std::vector<Point3f> &points, const std::vector<Point3f> &normals);
virtual void writeData(const std::vector<Point3f> &points, const std::vector<Point3f> &normals, const std::vector<std::vector<int32_t>> &indices) = 0;
virtual void writeData(const std::vector<Point3f> &points, const std::vector<Point3f> &normals, const std::vector<Point3_<uchar>> &rgb);
virtual void writeData(const std::vector<Point3f> &points, const std::vector<Point3f> &normals, const std::vector<Point3_<uchar>> &rgb, const std::vector<std::vector<int32_t>> &indices) = 0;

protected:
String m_filename;
Expand Down
6 changes: 4 additions & 2 deletions modules/3d/src/pointcloud/io_obj.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ namespace cv {

std::unordered_set<std::string> ObjDecoder::m_unsupportedKeys;

void ObjDecoder::readData(std::vector<Point3f> &points, std::vector<Point3f> &normals, std::vector<std::vector<int32_t>> &indices)
void ObjDecoder::readData(std::vector<Point3f> &points, std::vector<Point3f> &normals, std::vector<Point3_<uchar>> &rgb, std::vector<std::vector<int32_t>> &indices)
{
points.clear();
normals.clear();
CV_UNUSED(rgb);
indices.clear();

std::ifstream file(m_filename, std::ios::binary);
Expand Down Expand Up @@ -75,9 +76,10 @@ void ObjDecoder::readData(std::vector<Point3f> &points, std::vector<Point3f> &no
file.close();
}

void ObjEncoder::writeData(const std::vector<Point3f> &points, const std::vector<Point3f> &normals, const std::vector<std::vector<int32_t>> &indices)
void ObjEncoder::writeData(const std::vector<Point3f> &points, const std::vector<Point3f> &normals, const std::vector<Point3_<uchar>> &rgb, const std::vector<std::vector<int32_t>> &indices)
{
std::ofstream file(m_filename, std::ios::binary);
CV_UNUSED(rgb);
if (!file) {
CV_LOG_ERROR(NULL, "Impossible to open the file: " << m_filename);
return;
Expand Down
4 changes: 2 additions & 2 deletions modules/3d/src/pointcloud/io_obj.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace cv {
class ObjDecoder CV_FINAL : public BasePointCloudDecoder
{
public:
void readData(std::vector<Point3f> &points, std::vector<Point3f> &normals, std::vector<std::vector<int32_t>> &indices) CV_OVERRIDE;
void readData(std::vector<Point3f> &points, std::vector<Point3f> &normals, std::vector<Point3_<uchar>> &rgb, std::vector<std::vector<int32_t>> &indices) CV_OVERRIDE;

protected:
static std::unordered_set<std::string> m_unsupportedKeys;
Expand All @@ -22,7 +22,7 @@ class ObjDecoder CV_FINAL : public BasePointCloudDecoder
class ObjEncoder CV_FINAL : public BasePointCloudEncoder
{
public:
void writeData(const std::vector<Point3f> &points, const std::vector<Point3f> &normals, const std::vector<std::vector<int32_t>> &indices) CV_OVERRIDE;
void writeData(const std::vector<Point3f> &points, const std::vector<Point3f> &normals, const std::vector<Point3_<uchar>> &rgb, const std::vector<std::vector<int32_t>> &indices) CV_OVERRIDE;

};

Expand Down
39 changes: 25 additions & 14 deletions modules/3d/src/pointcloud/io_ply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,17 @@

namespace cv {

void PlyDecoder::readData(std::vector<Point3f> &points, std::vector<Point3f> &normals, std::vector<std::vector<int32_t>> &indices)
void PlyDecoder::readData(std::vector<Point3f> &points, std::vector<Point3f> &normals, std::vector<Point3_<uchar>> &rgb, std::vector<std::vector<int32_t>> &indices)
{
points.clear();
normals.clear();
rgb.clear();
CV_UNUSED(indices);

std::ifstream file(m_filename, std::ios::binary);
if (parseHeader(file))
{
parseBody(file, points, normals);
parseBody(file, points, normals, rgb);
}
}

Expand Down Expand Up @@ -83,7 +84,7 @@ bool PlyDecoder::parseHeader(std::ifstream &file)
auto splitArrElem = split(s, ' ');
if (splitArrElem[2] == "x" || splitArrElem[2] == "red" || splitArrElem[2] == "nx")
{
if (splitArrElem[1] != "float") {
if (splitArrElem[1] != "float" && splitArrElem[1] != "uchar") {
CV_LOG_ERROR(NULL, "Provided PLY file format '" << splitArrElem[1] << "' is not supported");
return false;
}
Expand Down Expand Up @@ -130,7 +131,7 @@ T readNext(std::ifstream &file, DataFormat format)
return val;
}

void PlyDecoder::parseBody(std::ifstream &file, std::vector<Point3f> &points, std::vector<Point3f> &normals)
void PlyDecoder::parseBody(std::ifstream &file, std::vector<Point3f> &points, std::vector<Point3f> &normals, std::vector<Point3_<uchar>> &rgb)
{
points.reserve(m_vertexCount);
if (m_hasNormal)
Expand All @@ -146,9 +147,11 @@ void PlyDecoder::parseBody(std::ifstream &file, std::vector<Point3f> &points, st
points.push_back(vertex);
if (m_hasColour)
{
readNext<float>(file, m_inputDataFormat);
readNext<float>(file, m_inputDataFormat);
readNext<float>(file, m_inputDataFormat);
Point3_<uchar> colour;
colour.x = readNext<int>(file, m_inputDataFormat) & 0xff;
colour.y = readNext<int>(file, m_inputDataFormat) & 0xff;
colour.z = readNext<int>(file, m_inputDataFormat) & 0xff;
rgb.push_back(colour);
}
if (m_hasNormal)
{
Expand All @@ -161,14 +164,15 @@ void PlyDecoder::parseBody(std::ifstream &file, std::vector<Point3f> &points, st
}
}

void PlyEncoder::writeData(const std::vector<Point3f> &points, const std::vector<Point3f> &normals, const std::vector<std::vector<int32_t>> &indices)
void PlyEncoder::writeData(const std::vector<Point3f> &points, const std::vector<Point3f> &normals, const std::vector<Point3_<uchar>> &rgb, const std::vector<std::vector<int32_t>> &indices)
{
CV_UNUSED(indices);
std::ofstream file(m_filename, std::ios::binary);
if (!file) {
CV_LOG_ERROR(NULL, "Impossible to open the file: " << m_filename);
return;
}
bool hasNormals = !normals.empty(), hasColor = !rgb.empty();

file << "ply" << std::endl;
file << "format ascii 1.0" << std::endl;
Expand All @@ -179,23 +183,30 @@ void PlyEncoder::writeData(const std::vector<Point3f> &points, const std::vector
file << "property float y" << std::endl;
file << "property float z" << std::endl;

file << "end_header" << std::endl;
if(hasColor) {
file << "property uchar red" << std::endl;
file << "property uchar green" << std::endl;
file << "property uchar blue" << std::endl;
}

if (normals.size() != 0)
if (hasNormals)
{
file << "property float nx" << std::endl;
file << "property float ny" << std::endl;
file << "property float nz" << std::endl;
}

bool isNormals = (normals.size() != 0);
file << "end_header" << std::endl;


for (size_t i = 0; i < points.size(); i++)
{
file << points[i].x << " " << points[i].y << " " << points[i].z;
if (isNormals)
{
file << normals[i].x << " " << normals[i].y << " " << normals[i].z;
if (hasColor) {
file << " " << static_cast<int>(rgb[i].x) << " " << static_cast<int>(rgb[i].y) << " " << static_cast<int>(rgb[i].z);
}
if (hasNormals) {
file << " " << normals[i].x << " " << normals[i].y << " " << normals[i].z;
}
file << std::endl;
}
Expand Down
6 changes: 3 additions & 3 deletions modules/3d/src/pointcloud/io_ply.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ enum class DataFormat
class PlyDecoder CV_FINAL : public BasePointCloudDecoder
{
public:
void readData(std::vector<Point3f> &points, std::vector<Point3f> &normals, std::vector<std::vector<int32_t>> &indices) CV_OVERRIDE;
void readData(std::vector<Point3f> &points, std::vector<Point3f> &normals, std::vector<Point3_<uchar>> &rgb, std::vector<std::vector<int32_t>> &indices) CV_OVERRIDE;

protected:
bool parseHeader(std::ifstream &file);
void parseBody(std::ifstream &file, std::vector<Point3f> &points, std::vector<Point3f> &normals);
void parseBody(std::ifstream &file, std::vector<Point3f> &points, std::vector<Point3f> &normals, std::vector<Point3_<uchar>> &rgb);

DataFormat m_inputDataFormat;
size_t m_vertexCount{0};
Expand All @@ -36,7 +36,7 @@ class PlyDecoder CV_FINAL : public BasePointCloudDecoder
class PlyEncoder CV_FINAL : public BasePointCloudEncoder
{
public:
void writeData(const std::vector<Point3f> &points, const std::vector<Point3f> &normals, const std::vector<std::vector<int32_t>> &indices) CV_OVERRIDE;
void writeData(const std::vector<Point3f> &points, const std::vector<Point3f> &normals, const std::vector<Point3_<uchar>> &rgb, const std::vector<std::vector<int32_t>> &indices) CV_OVERRIDE;

};

Expand Down
24 changes: 18 additions & 6 deletions modules/3d/src/pointcloud/load_point_cloud.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ static PointCloudEncoder findEncoder(const String &filename)

#endif

void loadPointCloud(const String &filename, OutputArray vertices, OutputArray normals)
void loadPointCloud(const String &filename, OutputArray vertices, OutputArray normals, OutputArray rgb)
{
#if OPENCV_HAVE_FILESYSTEM_SUPPORT
auto decoder = findDecoder(filename);
Expand All @@ -63,14 +63,19 @@ void loadPointCloud(const String &filename, OutputArray vertices, OutputArray no

std::vector<Point3f> vec_vertices;
std::vector<Point3f> vec_normals;
std::vector<Point3_<uchar>> vec_rgb;

decoder->readData(vec_vertices, vec_normals);
decoder->readData(vec_vertices, vec_normals, vec_rgb);

if (!vec_vertices.empty())
Mat(static_cast<int>(vec_vertices.size()), 1, CV_32FC3, &vec_vertices[0]).copyTo(vertices);

if (!vec_normals.empty() && normals.needed())
Mat(static_cast<int>(vec_normals.size()), 1, CV_32FC3, &vec_normals[0]).copyTo(normals);

if (!vec_rgb.empty() && rgb.needed())
Mat(static_cast<int>(vec_rgb.size()), 1, CV_8UC3, &vec_rgb[0]).copyTo(rgb);

#else // OPENCV_HAVE_FILESYSTEM_SUPPORT
CV_UNUSED(filename);
CV_UNUSED(vertices);
Expand All @@ -79,7 +84,7 @@ void loadPointCloud(const String &filename, OutputArray vertices, OutputArray no
#endif
}

void savePointCloud(const String &filename, InputArray vertices, InputArray normals)
void savePointCloud(const String &filename, InputArray vertices, InputArray normals, InputArray rgb)
{
#if OPENCV_HAVE_FILESYSTEM_SUPPORT
if (vertices.empty()) {
Expand All @@ -98,11 +103,16 @@ void savePointCloud(const String &filename, InputArray vertices, InputArray norm

std::vector<Point3f> vec_vertices(vertices.getMat());
std::vector<Point3f> vec_normals;
std::vector<Point3_<uchar>> vec_rgb;

if (!normals.empty()){
vec_normals = normals.getMat();
}

encoder->writeData(vec_vertices, vec_normals);
if (!rgb.empty()){
vec_rgb = rgb.getMat();
}
encoder->writeData(vec_vertices, vec_normals, vec_rgb);

#else // OPENCV_HAVE_FILESYSTEM_SUPPORT
CV_UNUSED(filename);
Expand All @@ -126,9 +136,10 @@ void loadMesh(const String &filename, OutputArray vertices, OutputArray normals,

std::vector<Point3f> vec_vertices;
std::vector<Point3f> vec_normals;
std::vector<Point3_<uchar>> vec_rgb;
std::vector<std::vector<int32_t>> vec_indices;

decoder->readData(vec_vertices, vec_normals, vec_indices);
decoder->readData(vec_vertices, vec_normals, vec_rgb, vec_indices);

if (!vec_vertices.empty()) {
Mat(1, static_cast<int>(vec_vertices.size()), CV_32FC3, vec_vertices.data()).copyTo(vertices);
Expand Down Expand Up @@ -173,6 +184,7 @@ void saveMesh(const String &filename, InputArray vertices, InputArray normals, I

std::vector<Point3f> vec_vertices(vertices.getMat());
std::vector<Point3f> vec_normals;
std::vector<Point3_<uchar>> vec_rgb;
if (!normals.empty()){
vec_normals = normals.getMat();
}
Expand All @@ -185,7 +197,7 @@ void saveMesh(const String &filename, InputArray vertices, InputArray normals, I
mat_indices[i].copyTo(vec_indices[i]);
}

encoder->writeData(vec_vertices, vec_normals, vec_indices);
encoder->writeData(vec_vertices, vec_normals, vec_rgb, vec_indices);

#else // OPENCV_HAVE_FILESYSTEM_SUPPORT
CV_UNUSED(filename);
Expand Down
5 changes: 4 additions & 1 deletion modules/3d/test/test_pointcloud.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ TEST(PointCloud, LoadSavePly)
{
std::vector<cv::Point3f> points;
std::vector<cv::Point3f> normals;
std::vector<cv::Point3_<uchar>> rgb;

auto folder = cvtest::TS::ptr()->get_data_path();
std::string new_path = tempfile("new.ply");
Expand All @@ -96,11 +97,13 @@ TEST(PointCloud, LoadSavePly)

std::vector<cv::Point3f> points_gold;
std::vector<cv::Point3f> normals_gold;
std::vector<cv::Point3_<uchar>> rgb_gold;

cv::loadPointCloud(new_path, points_gold, normals_gold);
cv::loadPointCloud(new_path, points_gold, normals_gold, rgb_gold);

EXPECT_EQ(normals_gold, normals);
EXPECT_EQ(points_gold, points);
EXPECT_EQ(rgb_gold, rgb);
std::remove(new_path.c_str());
}

Expand Down