Skip to content

Commit

Permalink
Merge pull request #23805 from starga2er777:5.x
Browse files Browse the repository at this point in the history
GSoC: Modified PLY reader to support color attribute read/write #23805

* Modified PLY reader to support color attribute read/write
* Fix bugs & Modified OBJ reader for color IO
* Replace with correct test file
* Fix I/O of property [w] in OBJ files

### Pull Request Readiness Checklist

**Merged with opencv/opencv_extra#1075

[Issue for GSoC 2023](#23624)
The ply loader in 3D module doesn't support color attribute reading. I modified that to support color attribute reading & writing for the color attribute compression as described in proposal.

See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request

- [x] I agree to contribute to the project under Apache 2 License.
- [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV
- [x] The PR is proposed to the proper branch
- [x] There is a reference to the original bug report and related work
- [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
      Patch to opencv_extra has the same branch name.
- [x] The feature is well documented and sample code can be built with the project CMake
  • Loading branch information
starga2er777 committed Jul 25, 2023
1 parent 18f280b commit 4d695cd
Show file tree
Hide file tree
Showing 9 changed files with 139 additions and 48 deletions.
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
44 changes: 40 additions & 4 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();
rgb.clear();
indices.clear();

std::ifstream file(m_filename, std::ios::binary);
Expand All @@ -39,9 +40,34 @@ void ObjDecoder::readData(std::vector<Point3f> &points, std::vector<Point3f> &no
continue;
else if (key == "v")
{
// (x, y, z, [w], [r, g, b])
auto splitArr = split(s, ' ');
if (splitArr.size() <= 3)
{
CV_LOG_ERROR(NULL, "Vertex should have at least 3 coordinate values.");
return;
}
Point3f vertex;
ss >> vertex.x >> vertex.y >> vertex.z;
points.push_back(vertex);
if (splitArr.size() == 5 || splitArr.size() == 8)
{
float w;
ss >> w;
CV_UNUSED(w);
}
if (splitArr.size() >= 7)
{
Point3f color;
if (ss.rdbuf()->in_avail() != 0) {
Point3_<uchar> uc_color;
ss >> color.x >> color.y >> color.z;
uc_color.x = static_cast<uchar>(std::round(255.f * color.x));
uc_color.y = static_cast<uchar>(std::round(255.f * color.y));
uc_color.z = static_cast<uchar>(std::round(255.f * color.z));
rgb.push_back(uc_color);
}
}
}
else if (key == "vn")
{
Expand Down Expand Up @@ -75,20 +101,30 @@ 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);
if (!file) {
CV_LOG_ERROR(NULL, "Impossible to open the file: " << m_filename);
return;
}

if (!rgb.empty() && rgb.size() != points.size()) {
CV_LOG_ERROR(NULL, "Vertices and Colors have different size.");
return;
}

file << "# OBJ file writer" << std::endl;
file << "o Point_Cloud" << std::endl;

for (const auto& point : points)
for (size_t i = 0; i < points.size(); ++i)
{
file << "v " << point.x << " " << point.y << " " << point.z << std::endl;
file << "v " << points[i].x << " " << points[i].y << " " << points[i].z;
if (!rgb.empty()) {
file << " " << static_cast<float>(rgb[i].x) / 255.f << " " <<
static_cast<float>(rgb[i].y) / 255.f << " " << static_cast<float>(rgb[i].z) / 255.f;
}
file << std::endl;
}

for (const auto& normal : normals)
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
54 changes: 38 additions & 16 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 @@ -81,19 +82,30 @@ bool PlyDecoder::parseHeader(std::ifstream &file)
if (onVertexRead)
{
auto splitArrElem = split(s, ' ');
if (splitArrElem[2] == "x" || splitArrElem[2] == "red" || splitArrElem[2] == "nx")
if (splitArrElem[2] == "x" || splitArrElem[2] == "y" || splitArrElem[2] == "z")
{
if (splitArrElem[1] != "float") {
CV_LOG_ERROR(NULL, "Provided PLY file format '" << splitArrElem[1] << "' is not supported");
CV_LOG_ERROR(NULL, "Provided property '" << splitArrElem[2] << "' with format '" << splitArrElem[1]
<< "' is not supported");
return false;
}
}
if (splitArrElem[2] == "red")
if (splitArrElem[2] == "red" || splitArrElem[2] == "green" || splitArrElem[2] == "blue")
{
if (splitArrElem[1] != "uchar") {
CV_LOG_ERROR(NULL, "Provided property '" << splitArrElem[2] << "' with format '" << splitArrElem[1]
<< "' is not supported");
return false;
}
m_hasColour = true;
}
if (splitArrElem[2] == "nx")
{
if (splitArrElem[1] != "float") {
CV_LOG_ERROR(NULL, "Provided property '" << splitArrElem[2] << "' with format '" << splitArrElem[1]
<< "' is not supported");
return false;
}
m_hasNormal = true;
}
}
Expand Down Expand Up @@ -130,7 +142,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 +158,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 +175,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 +194,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
Loading

0 comments on commit 4d695cd

Please sign in to comment.