mirror of
https://github.com/opencv/opencv.git
synced 2026-07-30 07:43:03 +04:00
Merge pull request #25221 from savuor:rv/bench_3d
OBJ and PLY loaders extention to support texture coordinates and difused colors #25221 ### This PR changes * Texture coordinates support added to `loadMesh()` and `saveMesh()` * `loadMesh()` changes its behavior: all vertex attribute arrays (vertex coordinates, colors, normals, texture coordinates) now have the same size and same-index corresponce - This makes sense for OBJ files where vertex attribute arrays are independent from each other and are randomly accessed when defining faces - Looks like this behavior may also happen in some PLY files; however, it is not implemented until we encounter such files in a wild nature - At the same time `loadPointCloud()` keeps its behavior and loads vertex attributes as they are given in the file * PLY loader supports synonyms for the properties: `diffuse_red`, `diffuse_green` and `diffuse_blue` along with `red`, `green` and `blue` * `std::vector<cv::Vec3i>` supported as an index array type * Colors are loaded as [0, 1] floats instead of uchars - Since colors are usually saved as floats, internal conversion to uchar at loading significantly drops accuracy - Performing uchar conversion does not always makes sense and can be performed by a user if they needs it * PLY loading fixed: wrong offset ruined x coordinate * Python tests added for `loadPointCloud` and `loadMesh` ### Pull Request Readiness Checklist 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
This commit is contained in:
committed by
GitHub
parent
65074651a4
commit
82038be4cd
@@ -2836,16 +2836,19 @@ protected:
|
||||
/** @brief Loads a point cloud from a file.
|
||||
*
|
||||
* The function loads point cloud from the specified file and returns it.
|
||||
* If the cloud cannot be read, throws an error
|
||||
* If the cloud cannot be read, throws an error.
|
||||
* Vertex coordinates, normals and colors are returned as they are saved in the file
|
||||
* even if these arrays have different sizes and their elements do not correspond to each other
|
||||
* (which is typical for OBJ files for example)
|
||||
*
|
||||
* Currently, the following file formats are supported:
|
||||
* - [Wavefront obj file *.obj](https://en.wikipedia.org/wiki/Wavefront_.obj_file)
|
||||
* - [Polygon File Format *.ply](https://en.wikipedia.org/wiki/PLY_(file_format))
|
||||
*
|
||||
* @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
|
||||
* @param filename Name of the file
|
||||
* @param vertices vertex coordinates, each value contains 3 floats
|
||||
* @param normals per-vertex normals, each value contains 3 floats
|
||||
* @param rgb per-vertex colors, each value contains 3 floats
|
||||
*/
|
||||
CV_EXPORTS_W void loadPointCloud(const String &filename, OutputArray vertices, OutputArray normals = noArray(), OutputArray rgb = noArray());
|
||||
|
||||
@@ -2854,10 +2857,10 @@ CV_EXPORTS_W void loadPointCloud(const String &filename, OutputArray vertices, O
|
||||
* The function saves point cloud to the specified file.
|
||||
* File format is chosen based on the filename extension.
|
||||
*
|
||||
* @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
|
||||
* @param filename Name of the file
|
||||
* @param vertices vertex coordinates, each value contains 3 floats
|
||||
* @param normals per-vertex normals, each value contains 3 floats
|
||||
* @param rgb per-vertex colors, each value contains 3 floats
|
||||
*/
|
||||
CV_EXPORTS_W void savePointCloud(const String &filename, InputArray vertices, InputArray normals = noArray(), InputArray rgb = noArray());
|
||||
|
||||
@@ -2865,18 +2868,24 @@ CV_EXPORTS_W void savePointCloud(const String &filename, InputArray vertices, In
|
||||
*
|
||||
* The function loads mesh from the specified file and returns it.
|
||||
* If the mesh cannot be read, throws an error
|
||||
* Vertex attributes (i.e. space and texture coodinates, normals and colors) are returned in same-sized
|
||||
* arrays with corresponding elements having the same indices.
|
||||
* This means that if a face uses a vertex with a normal or a texture coordinate with different indices
|
||||
* (which is typical for OBJ files for example), this vertex will be duplicated for each face it uses.
|
||||
*
|
||||
* Currently, the following file formats are supported:
|
||||
* - [Wavefront obj file *.obj](https://en.wikipedia.org/wiki/Wavefront_.obj_file) (ONLY TRIANGULATED FACES)
|
||||
* - [Polygon File Format *.ply](https://en.wikipedia.org/wiki/PLY_(file_format))
|
||||
* @param filename Name of the file.
|
||||
* @param vertices (vector of Point3f) vertex coordinates of a mesh
|
||||
* @param indices (vector of vectors of int) vertex normals of a mesh
|
||||
* @param normals (vector of Point3f) vertex normals of a mesh
|
||||
* @param colors (vector of Point3f) vertex colors of a mesh
|
||||
* @param filename Name of the file
|
||||
* @param vertices vertex coordinates, each value contains 3 floats
|
||||
* @param indices per-face list of vertices, each value is a vector of ints
|
||||
* @param normals per-vertex normals, each value contains 3 floats
|
||||
* @param colors per-vertex colors, each value contains 3 floats
|
||||
* @param texCoords per-vertex texture coordinates, each value contains 2 or 3 floats
|
||||
*/
|
||||
CV_EXPORTS_W void loadMesh(const String &filename, OutputArray vertices, OutputArrayOfArrays indices,
|
||||
OutputArray normals = noArray(), OutputArray colors = noArray());
|
||||
OutputArray normals = noArray(), OutputArray colors = noArray(),
|
||||
OutputArray texCoords = noArray());
|
||||
|
||||
/** @brief Saves a mesh to a specified file.
|
||||
*
|
||||
@@ -2884,13 +2893,14 @@ CV_EXPORTS_W void loadMesh(const String &filename, OutputArray vertices, OutputA
|
||||
* File format is chosen based on the filename extension.
|
||||
*
|
||||
* @param filename Name of the file.
|
||||
* @param vertices (vector of Point3f) vertex coordinates of a mesh
|
||||
* @param indices (vector of vectors of int) vertex normals of a mesh
|
||||
* @param normals (vector of Point3f) vertex normals of a mesh
|
||||
* @param colors (vector of Point3f) vertex colors of a mesh
|
||||
* @param vertices vertex coordinates, each value contains 3 floats
|
||||
* @param indices per-face list of vertices, each value is a vector of ints
|
||||
* @param normals per-vertex normals, each value contains 3 floats
|
||||
* @param colors per-vertex colors, each value contains 3 floats
|
||||
* @param texCoords per-vertex texture coordinates, each value contains 2 or 3 floats
|
||||
*/
|
||||
CV_EXPORTS_W void saveMesh(const String &filename, InputArray vertices, InputArrayOfArrays indices,
|
||||
InputArray normals = noArray(), InputArray colors = noArray());
|
||||
InputArray normals = noArray(), InputArray colors = noArray(), InputArray texCoords = noArray());
|
||||
|
||||
|
||||
//! Triangle fill settings
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Python 2/3 compatibility
|
||||
from __future__ import print_function
|
||||
|
||||
import os, numpy
|
||||
import math
|
||||
import unittest
|
||||
import cv2 as cv
|
||||
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
class raster_test(NewOpenCVTests):
|
||||
|
||||
def test_loadCloud(self):
|
||||
fin = self.find_file("pointcloudio/orig.obj")
|
||||
points, normals, colors = cv.loadPointCloud(fin)
|
||||
|
||||
if points.shape != (8, 1, 3):
|
||||
self.fail('point array should be 8x1x3')
|
||||
if normals.shape != (6, 1, 3):
|
||||
self.fail('normals array should be 6x1x3')
|
||||
if colors.shape != (8, 1, 3):
|
||||
self.fail('colors array should be 8x1x3')
|
||||
|
||||
def test_loadMesh(self):
|
||||
fin = self.find_file("pointcloudio/orig.obj")
|
||||
points, indices, normals, colors, texCoords = cv.loadMesh(fin)
|
||||
goodShapes = [(1, 18, 3), (18, 1, 3)]
|
||||
errorMsg = "%s array should be 18x1x3 or 1x18x3"
|
||||
for a, s in [(points, 'points'), (normals, 'normals'), (colors, 'colors')]:
|
||||
if a.shape not in goodShapes:
|
||||
self.fail(errorMsg % s)
|
||||
|
||||
if texCoords.shape not in [(1, 18, 2), (18, 1, 2)]:
|
||||
self.fail('texture coordinates array should be 1x18x2 or 18x1x2')
|
||||
if isinstance(indices, numpy.ndarray):
|
||||
if indices.shape not in [(1, 6, 3), (6, 1, 3)]:
|
||||
self.fail('indices array should be 1x6x3 or 6x1x3')
|
||||
elif isinstance(indices, list) or isinstance(indices, tuple):
|
||||
for i in indices:
|
||||
if len(indices) != 6 or i.shape != (1, 3):
|
||||
self.fail('indices array should be 1x6x3 or 6x1x3')
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -7,26 +7,29 @@
|
||||
|
||||
namespace cv {
|
||||
|
||||
void BasePointCloudDecoder::setSource(const std::string &filename) noexcept
|
||||
void BasePointCloudDecoder::setSource(const std::string& filename) noexcept
|
||||
{
|
||||
m_filename = filename;
|
||||
}
|
||||
|
||||
void BasePointCloudDecoder::readData(std::vector<Point3f> &points, std::vector<Point3f> &normals, std::vector<Point3_<uchar>> &rgb)
|
||||
void BasePointCloudDecoder::readData(std::vector<Point3f> &points, std::vector<Point3f> &normals, std::vector<Point3f> &rgb)
|
||||
{
|
||||
std::vector<std::vector<int32_t>> indices;
|
||||
readData(points, normals, rgb, indices);
|
||||
std::vector<Point3f> texCoords;
|
||||
int nTexCoords;
|
||||
readData(points, normals, rgb, texCoords, nTexCoords, indices, READ_AS_IS_FLAG);
|
||||
}
|
||||
|
||||
void BasePointCloudEncoder::setDestination(const std::string &filename) noexcept
|
||||
void BasePointCloudEncoder::setDestination(const std::string& filename) noexcept
|
||||
{
|
||||
m_filename = filename;
|
||||
}
|
||||
|
||||
void BasePointCloudEncoder::writeData(const std::vector<Point3f> &points, const std::vector<Point3f> &normals, const std::vector<Point3_<uchar>> &rgb)
|
||||
void BasePointCloudEncoder::writeData(const std::vector<Point3f> &points, const std::vector<Point3f> &normals, const std::vector<Point3f> &rgb)
|
||||
{
|
||||
std::vector<std::vector<int32_t>> indices;
|
||||
writeData(points, normals, rgb, indices);
|
||||
std::vector<Point3f> texCoords;
|
||||
writeData(points, normals, rgb, texCoords, 0, indices);
|
||||
}
|
||||
|
||||
} /* namespace cv */
|
||||
|
||||
@@ -18,18 +18,24 @@ class BasePointCloudEncoder;
|
||||
using PointCloudDecoder = std::unique_ptr<BasePointCloudDecoder>;
|
||||
using PointCloudEncoder = std::unique_ptr<BasePointCloudEncoder>;
|
||||
|
||||
// for OBJ files: to read vertices, normals and texture coords as they are given in the file
|
||||
// or duplicate them according to faces
|
||||
const int READ_AS_IS_FLAG = 1;
|
||||
|
||||
///////////////////////////////// base class for decoders ////////////////////////
|
||||
class BasePointCloudDecoder
|
||||
{
|
||||
public:
|
||||
virtual ~BasePointCloudDecoder() = default;
|
||||
|
||||
virtual void setSource(const String &filename) noexcept;
|
||||
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;
|
||||
virtual void setSource(const std::string& filename) noexcept;
|
||||
virtual void readData(std::vector<Point3f>& points, std::vector<Point3f>& normals, std::vector<Point3f>& rgb);
|
||||
virtual void readData(std::vector<Point3f>& points, std::vector<Point3f>& normals, std::vector<Point3f>& rgb,
|
||||
std::vector<Point3f>& texCoords, int& nTexCoords,
|
||||
std::vector<std::vector<int32_t>>& indices, int flags) = 0;
|
||||
|
||||
protected:
|
||||
String m_filename;
|
||||
std::string m_filename;
|
||||
};
|
||||
|
||||
///////////////////////////////// base class for encoders ////////////////////////
|
||||
@@ -38,12 +44,14 @@ class BasePointCloudEncoder
|
||||
public:
|
||||
virtual ~BasePointCloudEncoder() = default;
|
||||
|
||||
virtual void setDestination(const String &filename) noexcept;
|
||||
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;
|
||||
virtual void setDestination(const std::string& filename) noexcept;
|
||||
virtual void writeData(const std::vector<Point3f>& points, const std::vector<Point3f>& normals, const std::vector<Point3f>& rgb);
|
||||
virtual void writeData(const std::vector<Point3f>& points, const std::vector<Point3f>& normals, const std::vector<Point3f>& rgb,
|
||||
const std::vector<Point3f>& texCoords, int nTexCoords,
|
||||
const std::vector<std::vector<int32_t>>& indices) = 0;
|
||||
|
||||
protected:
|
||||
String m_filename;
|
||||
std::string m_filename;
|
||||
};
|
||||
|
||||
} /* namespace cv */
|
||||
|
||||
@@ -12,12 +12,24 @@ namespace cv {
|
||||
|
||||
std::unordered_set<std::string> ObjDecoder::m_unsupportedKeys;
|
||||
|
||||
void ObjDecoder::readData(std::vector<Point3f> &points, std::vector<Point3f> &normals, std::vector<Point3_<uchar>> &rgb, std::vector<std::vector<int32_t>> &indices)
|
||||
void ObjDecoder::readData(std::vector<Point3f>& points, std::vector<Point3f>& normals, std::vector<Point3f>& rgb)
|
||||
{
|
||||
points.clear();
|
||||
normals.clear();
|
||||
rgb.clear();
|
||||
indices.clear();
|
||||
std::vector<Point3f> texCoords;
|
||||
int nTexCoords;
|
||||
std::vector<std::vector<int32_t>> indices;
|
||||
this->readData(points, normals, rgb, texCoords, nTexCoords, indices, READ_AS_IS_FLAG);
|
||||
}
|
||||
|
||||
void ObjDecoder::readData(std::vector<Point3f>& points, std::vector<Point3f>& normals, std::vector<Point3f>& rgb,
|
||||
std::vector<Point3f>& texCoords, int& nTexCoords,
|
||||
std::vector<std::vector<int32_t>>& indices, int flags)
|
||||
{
|
||||
std::vector<Point3f> ptsList, nrmList, texCoordList, rgbList;
|
||||
std::vector<std::vector<int32_t>> idxList, texIdxList, normalIdxList;
|
||||
|
||||
nTexCoords = 0;
|
||||
|
||||
bool duplicateVertices = false;
|
||||
|
||||
std::ifstream file(m_filename, std::ios::binary);
|
||||
if (!file)
|
||||
@@ -30,6 +42,8 @@ void ObjDecoder::readData(std::vector<Point3f> &points, std::vector<Point3f> &no
|
||||
while (!file.eof())
|
||||
{
|
||||
std::getline(file, s);
|
||||
// "\r" symbols are not trimmed by default
|
||||
s = trimSpaces(s);
|
||||
if (s.empty())
|
||||
continue;
|
||||
std::stringstream ss(s);
|
||||
@@ -49,7 +63,7 @@ void ObjDecoder::readData(std::vector<Point3f> &points, std::vector<Point3f> &no
|
||||
}
|
||||
Point3f vertex;
|
||||
ss >> vertex.x >> vertex.y >> vertex.z;
|
||||
points.push_back(vertex);
|
||||
ptsList.push_back(vertex);
|
||||
if (splitArr.size() == 5 || splitArr.size() == 8)
|
||||
{
|
||||
float w;
|
||||
@@ -59,13 +73,10 @@ void ObjDecoder::readData(std::vector<Point3f> &points, std::vector<Point3f> &no
|
||||
if (splitArr.size() >= 7)
|
||||
{
|
||||
Point3f color;
|
||||
if (ss.rdbuf()->in_avail() != 0) {
|
||||
Point3_<uchar> uc_color;
|
||||
if (ss.rdbuf()->in_avail() != 0)
|
||||
{
|
||||
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);
|
||||
rgbList.push_back(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,21 +84,92 @@ void ObjDecoder::readData(std::vector<Point3f> &points, std::vector<Point3f> &no
|
||||
{
|
||||
Point3f normal;
|
||||
ss >> normal.x >> normal.y >> normal.z;
|
||||
normals.push_back(normal);
|
||||
nrmList.push_back(normal);
|
||||
}
|
||||
else if (key == "f")
|
||||
{
|
||||
std::vector<int> vertexInd;
|
||||
// format: "f v0 / t0 / n0 v1 / t1 / n1 v2/t2/n2 ..."
|
||||
std::vector<int> vertexInd, normInd, texInd;
|
||||
vertexInd.reserve(3); normInd.reserve(3); texInd.reserve(3);
|
||||
auto tokens = split(s, ' ');
|
||||
for (size_t i = 1; i < tokens.size(); i++)
|
||||
{
|
||||
auto vertexinfo = split(tokens[i], '/');
|
||||
int index;
|
||||
std::stringstream vs(vertexinfo[0]);
|
||||
vs >> index;
|
||||
vertexInd.push_back(index - 1);
|
||||
std::array<int, 3> idx = { -1, -1, -1 };
|
||||
for (int j = 0; j < (int)vertexinfo.size(); j++)
|
||||
{
|
||||
std::string sj = vertexinfo[j];
|
||||
// trimming spaces; as a result s can become empty - this is not an error
|
||||
auto si = std::find_if(sj.begin(), sj.end(), [](char c) { return (c >= '0' && c <= '9'); });
|
||||
auto ei = std::find_if(sj.rbegin(), sj.rend(), [](char c) { return (c >= '0' && c <= '9'); });
|
||||
if (si != sj.end() && ei != sj.rend())
|
||||
{
|
||||
auto first = std::distance(si, sj.begin());
|
||||
auto last = std::distance(ei, sj.rend());
|
||||
sj = sj.substr(first, last - first + 1);
|
||||
try
|
||||
{
|
||||
idx[j] = std::stoi(sj);
|
||||
}
|
||||
// std::invalid_exception, std::out_of_range
|
||||
catch(const std::exception&)
|
||||
{
|
||||
CV_LOG_ERROR(NULL, "Failed to parse face index: " + sj);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
int vertexIndex = idx[0];
|
||||
int texCoordIndex = idx[1];
|
||||
int normalIndex = idx[2];
|
||||
|
||||
if (vertexIndex <= 0)
|
||||
{
|
||||
CV_LOG_ERROR(NULL, "Vertex index is not present or incorrect");
|
||||
return;
|
||||
}
|
||||
|
||||
if ((vertexIndex != texCoordIndex && texCoordIndex >= 0) ||
|
||||
(vertexIndex != normalIndex && normalIndex >= 0))
|
||||
{
|
||||
duplicateVertices = !(flags & READ_AS_IS_FLAG);
|
||||
}
|
||||
|
||||
vertexInd.push_back(vertexIndex - 1);
|
||||
normInd.push_back(normalIndex - 1);
|
||||
texInd.push_back(texCoordIndex - 1);
|
||||
}
|
||||
indices.push_back(vertexInd);
|
||||
idxList.push_back(vertexInd);
|
||||
texIdxList.push_back(texInd);
|
||||
normalIdxList.push_back(normInd);
|
||||
}
|
||||
else if (key == "vt")
|
||||
{
|
||||
// (u, [v, [w]])
|
||||
auto splitArr = split(s, ' ');
|
||||
int ncoords = (int)splitArr.size() - 1;
|
||||
if (!nTexCoords)
|
||||
{
|
||||
nTexCoords = ncoords;
|
||||
if (nTexCoords < 1 || nTexCoords > 3)
|
||||
{
|
||||
CV_LOG_ERROR(NULL, "The amount of texture coordinates should be between 1 and 3");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (ncoords != nTexCoords)
|
||||
{
|
||||
CV_LOG_ERROR(NULL, "All points should have the same number of texture coordinates");
|
||||
return;
|
||||
}
|
||||
|
||||
Vec3f tc;
|
||||
for (int i = 0; i < nTexCoords; i++)
|
||||
{
|
||||
ss >> tc[i];
|
||||
}
|
||||
texCoordList.push_back(tc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -98,10 +180,53 @@ void ObjDecoder::readData(std::vector<Point3f> &points, std::vector<Point3f> &no
|
||||
}
|
||||
}
|
||||
|
||||
if (duplicateVertices)
|
||||
{
|
||||
points.clear();
|
||||
normals.clear();
|
||||
rgb.clear();
|
||||
texCoords.clear();
|
||||
indices.clear();
|
||||
|
||||
for (int tri = 0; tri < (int)idxList.size(); tri++)
|
||||
{
|
||||
auto vi = idxList[tri];
|
||||
auto ti = texIdxList[tri];
|
||||
auto ni = normalIdxList[tri];
|
||||
|
||||
std::vector<int32_t> newvi;
|
||||
newvi.reserve(3);
|
||||
for (int i = 0; i < (int)vi.size(); i++)
|
||||
{
|
||||
newvi.push_back((int)points.size());
|
||||
points.push_back(ptsList.at(vi[i]));
|
||||
if (!rgbList.empty())
|
||||
{
|
||||
rgb.push_back(rgbList.at(vi[i]));
|
||||
}
|
||||
|
||||
texCoords.push_back(texCoordList.at(ti[i]));
|
||||
normals.push_back(nrmList.at(ni[i]));
|
||||
}
|
||||
|
||||
indices.push_back(newvi);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
points = std::move(ptsList);
|
||||
normals = std::move(nrmList);
|
||||
rgb = std::move(rgbList);
|
||||
texCoords = std::move(texCoordList);
|
||||
indices = std::move(idxList);
|
||||
}
|
||||
|
||||
file.close();
|
||||
}
|
||||
|
||||
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)
|
||||
void ObjEncoder::writeData(const std::vector<Point3f>& points, const std::vector<Point3f>& normals, const std::vector<Point3f>& rgb,
|
||||
const std::vector<Point3f>& texCoords, int nTexCoords,
|
||||
const std::vector<std::vector<int32_t>>& indices)
|
||||
{
|
||||
std::ofstream file(m_filename, std::ios::binary);
|
||||
if (!file) {
|
||||
@@ -114,15 +239,21 @@ void ObjEncoder::writeData(const std::vector<Point3f> &points, const std::vector
|
||||
return;
|
||||
}
|
||||
|
||||
if (texCoords.empty() && nTexCoords > 0)
|
||||
{
|
||||
CV_LOG_ERROR(NULL, "No texture coordinates provided while having nTexCoord > 0");
|
||||
return;
|
||||
}
|
||||
|
||||
file << "# OBJ file writer" << std::endl;
|
||||
file << "o Point_Cloud" << std::endl;
|
||||
|
||||
for (size_t i = 0; i < points.size(); ++i)
|
||||
{
|
||||
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;
|
||||
if (!rgb.empty())
|
||||
{
|
||||
file << " " << rgb[i].x << " " << rgb[i].y << " " << rgb[i].z;
|
||||
}
|
||||
file << std::endl;
|
||||
}
|
||||
@@ -132,6 +263,20 @@ void ObjEncoder::writeData(const std::vector<Point3f> &points, const std::vector
|
||||
file << "vn " << normal.x << " " << normal.y << " " << normal.z << std::endl;
|
||||
}
|
||||
|
||||
for (const auto& tc : texCoords)
|
||||
{
|
||||
file << "vt " << tc.x;
|
||||
if (nTexCoords > 1)
|
||||
{
|
||||
file << " " << tc.y;
|
||||
}
|
||||
if (nTexCoords > 2)
|
||||
{
|
||||
file << " " << tc.z;
|
||||
}
|
||||
file << std::endl;
|
||||
}
|
||||
|
||||
for (const auto& faceIndices : indices)
|
||||
{
|
||||
file << "f ";
|
||||
|
||||
@@ -13,7 +13,10 @@ namespace cv {
|
||||
class ObjDecoder CV_FINAL : public BasePointCloudDecoder
|
||||
{
|
||||
public:
|
||||
void readData(std::vector<Point3f> &points, std::vector<Point3f> &normals, std::vector<Point3_<uchar>> &rgb, std::vector<std::vector<int32_t>> &indices) CV_OVERRIDE;
|
||||
void readData(std::vector<Point3f>& points, std::vector<Point3f>& normals, std::vector<Point3f>& rgb) CV_OVERRIDE;
|
||||
void readData(std::vector<Point3f>& points, std::vector<Point3f>& normals, std::vector<Point3f>& rgb,
|
||||
std::vector<Point3f>& texCoords, int& nTexCoords,
|
||||
std::vector<std::vector<int32_t>>& indices, int flags) CV_OVERRIDE;
|
||||
|
||||
protected:
|
||||
static std::unordered_set<std::string> m_unsupportedKeys;
|
||||
@@ -22,7 +25,9 @@ protected:
|
||||
class ObjEncoder CV_FINAL : public BasePointCloudEncoder
|
||||
{
|
||||
public:
|
||||
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;
|
||||
void writeData(const std::vector<Point3f>& points, const std::vector<Point3f>& normals, const std::vector<Point3f>& rgb,
|
||||
const std::vector<Point3f>& texCoords, int nTexCoords,
|
||||
const std::vector<std::vector<int32_t>>& indices) CV_OVERRIDE;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -13,22 +13,28 @@
|
||||
|
||||
namespace cv {
|
||||
|
||||
void PlyDecoder::readData(std::vector<Point3f> &points, std::vector<Point3f> &normals, std::vector<Point3_<uchar>> &rgb, std::vector<std::vector<int32_t>> &indices)
|
||||
static const std::set<std::string> colorKeys = { "red", "diffuse_red", "green", "diffuse_green", "blue", "diffuse_blue" };
|
||||
|
||||
void PlyDecoder::readData(std::vector<Point3f>& points, std::vector<Point3f>& normals, std::vector<Point3f>& rgb,
|
||||
std::vector<Point3f>& texCoords, int& nTexCoords,
|
||||
std::vector<std::vector<int32_t>>& indices, int /*flags*/)
|
||||
{
|
||||
points.clear();
|
||||
normals.clear();
|
||||
rgb.clear();
|
||||
texCoords.clear();
|
||||
indices.clear();
|
||||
nTexCoords = 0;
|
||||
|
||||
std::ifstream file(m_filename, std::ios::binary);
|
||||
if (parseHeader(file))
|
||||
if (parseHeader(file, nTexCoords))
|
||||
{
|
||||
parseBody(file, points, normals, rgb, indices);
|
||||
parseBody(file, points, normals, rgb, texCoords, indices);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool PlyDecoder::parseHeader(std::ifstream &file)
|
||||
bool PlyDecoder::parseHeader(std::ifstream &file, int& nTexCoords)
|
||||
{
|
||||
std::string s;
|
||||
std::getline(file, s);
|
||||
@@ -211,6 +217,8 @@ bool PlyDecoder::parseHeader(std::ifstream &file)
|
||||
break;
|
||||
}
|
||||
|
||||
static const std::set<std::string> texCoordKeys = { "texture_u", "s", "texture_v", "t", "texture_w" };
|
||||
|
||||
bool good = true;
|
||||
m_vertexCount = m_vertexDescription.amount;
|
||||
std::map<std::string, int> amtProps;
|
||||
@@ -238,7 +246,7 @@ bool PlyDecoder::parseHeader(std::ifstream &file)
|
||||
}
|
||||
m_hasNormal = true;
|
||||
}
|
||||
if (p.name == "red" || p.name == "green" || p.name == "blue")
|
||||
if (colorKeys.count(p.name) > 0)
|
||||
{
|
||||
known = true;
|
||||
if (p.valType != CV_8U)
|
||||
@@ -249,6 +257,17 @@ bool PlyDecoder::parseHeader(std::ifstream &file)
|
||||
}
|
||||
m_hasColour = true;
|
||||
}
|
||||
if (texCoordKeys.count(p.name) > 0)
|
||||
{
|
||||
known = true;
|
||||
if (p.valType != CV_32F)
|
||||
{
|
||||
CV_LOG_ERROR(NULL, "Vertex property " << p.name
|
||||
<< " should be float");
|
||||
good = false;
|
||||
}
|
||||
m_hasTexCoord = true;
|
||||
}
|
||||
if (p.isList)
|
||||
{
|
||||
CV_LOG_ERROR(NULL, "List properties for vertices are not supported");
|
||||
@@ -279,6 +298,50 @@ bool PlyDecoder::parseHeader(std::ifstream &file)
|
||||
}
|
||||
}
|
||||
|
||||
// check for synonyms
|
||||
std::vector<std::pair<size_t, size_t>> propCounts;
|
||||
std::vector<std::pair<std::string, std::string>> synonyms = {
|
||||
{"red", "diffuse_red"},
|
||||
{"green", "diffuse_green"},
|
||||
{"blue", "diffuse_blue"},
|
||||
{"texture_u", "s"},
|
||||
{"texture_v", "t"},
|
||||
};
|
||||
for (const auto& p : synonyms)
|
||||
{
|
||||
std::string a, b;
|
||||
a = p.first; b = p.second;
|
||||
size_t ca = amtProps.count(a), cb = amtProps.count(b);
|
||||
propCounts.push_back({ca, cb});
|
||||
if (ca + cb > 1)
|
||||
{
|
||||
CV_LOG_ERROR(NULL, "Vertex property " << a << " should not go with its synonym " << b);
|
||||
good = false;
|
||||
}
|
||||
}
|
||||
// check for color conventions
|
||||
bool shortColorConv = propCounts[0].first || propCounts[1].first || propCounts[2].first;
|
||||
bool diffuseColorConv = propCounts[0].second || propCounts[1].second || propCounts[2].second;
|
||||
if (shortColorConv && diffuseColorConv)
|
||||
{
|
||||
CV_LOG_ERROR(NULL, "Vertex color properties should not be diffuse and not diffuse at the same time");
|
||||
good = false;
|
||||
}
|
||||
// check for texture conventions
|
||||
bool shortTexConv = propCounts[3].second || propCounts[4].second;
|
||||
bool longTexConv = propCounts[3].first || propCounts[4].first;
|
||||
if (shortTexConv && longTexConv)
|
||||
{
|
||||
CV_LOG_ERROR(NULL, "Vertex texture coordinates properties should not be in a short and in a long form at the same time");
|
||||
good = false;
|
||||
}
|
||||
|
||||
nTexCoords = 0;
|
||||
for (const auto& k : texCoordKeys)
|
||||
{
|
||||
nTexCoords += (int)(amtProps.count(k));
|
||||
}
|
||||
|
||||
m_faceCount = m_faceDescription.amount;
|
||||
int amtLists = 0;
|
||||
for (const auto& p : m_faceDescription.properties)
|
||||
@@ -292,6 +355,12 @@ bool PlyDecoder::parseHeader(std::ifstream &file)
|
||||
<< " should have type uint8 for counter and uint32 for values");
|
||||
good = false;
|
||||
}
|
||||
if (!(p.name == "vertex_index" || p.name == "vertex_indices"))
|
||||
{
|
||||
CV_LOG_ERROR(NULL, "List property should be vertex_index or vertex_indices, "
|
||||
<< p.name << " is not supported");
|
||||
good = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (amtLists > 1)
|
||||
@@ -342,7 +411,9 @@ uchar readNext<uchar>(std::ifstream &file, DataFormat format)
|
||||
return val;
|
||||
}
|
||||
|
||||
void PlyDecoder::parseBody(std::ifstream &file, std::vector<Point3f> &points, std::vector<Point3f> &normals, std::vector<Point3_<uchar>> &rgb,
|
||||
void PlyDecoder::parseBody(std::ifstream &file,
|
||||
std::vector<Point3f>& points, std::vector<Point3f>& normals,
|
||||
std::vector<Point3f>& rgb, std::vector<Point3f>& texCoords,
|
||||
std::vector<std::vector<int32_t>> &indices)
|
||||
{
|
||||
points.reserve(m_vertexCount);
|
||||
@@ -359,12 +430,13 @@ void PlyDecoder::parseBody(std::ifstream &file, std::vector<Point3f> &points, st
|
||||
{
|
||||
float vx, vy, vz;
|
||||
float nx, ny, nz;
|
||||
uchar r, g, b;
|
||||
float u, v, w;
|
||||
float r, g, b;
|
||||
};
|
||||
|
||||
union VertexData
|
||||
{
|
||||
std::array<uchar, 27> bytes;
|
||||
std::array<uchar, sizeof(VertexFields)> bytes;
|
||||
VertexFields vf;
|
||||
};
|
||||
|
||||
@@ -373,7 +445,7 @@ void PlyDecoder::parseBody(std::ifstream &file, std::vector<Point3f> &points, st
|
||||
for (size_t j = 0; j < m_vertexDescription.properties.size(); j++)
|
||||
{
|
||||
const auto& p = m_vertexDescription.properties[j];
|
||||
size_t offset = 0;
|
||||
size_t offset = (size_t)(-1);
|
||||
if (p.name == "x")
|
||||
offset = offsetof(VertexFields, vx);
|
||||
if (p.name == "y")
|
||||
@@ -386,11 +458,17 @@ void PlyDecoder::parseBody(std::ifstream &file, std::vector<Point3f> &points, st
|
||||
offset = offsetof(VertexFields, ny);
|
||||
if (p.name == "nz")
|
||||
offset = offsetof(VertexFields, nz);
|
||||
if (p.name == "red")
|
||||
if (p.name == "texture_u" || p.name == "s")
|
||||
offset = offsetof(VertexFields, u);
|
||||
if (p.name == "texture_v" || p.name == "t")
|
||||
offset = offsetof(VertexFields, v);
|
||||
if (p.name == "texture_w")
|
||||
offset = offsetof(VertexFields, w);
|
||||
if (p.name == "red" || p.name == "diffuse_red")
|
||||
offset = offsetof(VertexFields, r);
|
||||
if (p.name == "green")
|
||||
if (p.name == "green" || p.name == "diffuse_green")
|
||||
offset = offsetof(VertexFields, g);
|
||||
if (p.name == "blue")
|
||||
if (p.name == "blue" || p.name == "diffuse_blue")
|
||||
offset = offsetof(VertexFields, b);
|
||||
vertexOffsets[j] = offset;
|
||||
}
|
||||
@@ -426,18 +504,12 @@ void PlyDecoder::parseBody(std::ifstream &file, std::vector<Point3f> &points, st
|
||||
size_t offset = vertexOffsets[j];
|
||||
if (offset != (size_t)(-1))
|
||||
{
|
||||
switch (p.valType)
|
||||
if (colorKeys.count(p.name) > 0 && p.valType == CV_8U)
|
||||
{
|
||||
case CV_8U: case CV_8S:
|
||||
*(vertexData.bytes.data() + offset) = (uchar)ival;
|
||||
break;
|
||||
case CV_32F:
|
||||
*(float*)(vertexData.bytes.data() + offset) = fval;
|
||||
break;
|
||||
default:
|
||||
// the rest are unused
|
||||
break;
|
||||
fval = ival / 255.f;
|
||||
}
|
||||
|
||||
*(float*)(vertexData.bytes.data() + offset) = fval;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -450,6 +522,10 @@ void PlyDecoder::parseBody(std::ifstream &file, std::vector<Point3f> &points, st
|
||||
{
|
||||
normals.push_back({ vertexData.vf.nx, vertexData.vf.ny, vertexData.vf.nz });
|
||||
}
|
||||
if (m_hasTexCoord)
|
||||
{
|
||||
texCoords.push_back({ vertexData.vf.u, vertexData.vf.v, vertexData.vf.w });
|
||||
}
|
||||
}
|
||||
|
||||
indices.reserve(m_faceCount);
|
||||
@@ -504,14 +580,22 @@ 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<Point3_<uchar>> &rgb, const std::vector<std::vector<int32_t>> &indices)
|
||||
void PlyEncoder::writeData(const std::vector<Point3f>& points, const std::vector<Point3f>& normals, const std::vector<Point3f>& rgb,
|
||||
const std::vector<Point3f>& texCoords, int nTexCoords,
|
||||
const std::vector<std::vector<int32_t>>& indices)
|
||||
{
|
||||
std::ofstream file(m_filename, std::ios::binary);
|
||||
if (!file) {
|
||||
if (!file)
|
||||
{
|
||||
CV_LOG_ERROR(NULL, "Impossible to open the file: " << m_filename);
|
||||
return;
|
||||
}
|
||||
bool hasNormals = !normals.empty(), hasColor = !rgb.empty();
|
||||
if (texCoords.empty() && nTexCoords > 0)
|
||||
{
|
||||
CV_LOG_ERROR(NULL, "No texture coordinates provided while having nTexCoord > 0");
|
||||
return;
|
||||
}
|
||||
|
||||
file << "ply" << std::endl;
|
||||
file << "format ascii 1.0" << std::endl;
|
||||
@@ -536,6 +620,19 @@ void PlyEncoder::writeData(const std::vector<Point3f> &points, const std::vector
|
||||
file << "property float nz" << std::endl;
|
||||
}
|
||||
|
||||
if (nTexCoords > 0)
|
||||
{
|
||||
file << "property float texture_u" << std::endl;
|
||||
}
|
||||
if (nTexCoords > 1)
|
||||
{
|
||||
file << "property float texture_v" << std::endl;
|
||||
}
|
||||
if (nTexCoords > 2)
|
||||
{
|
||||
file << "property float texture_w" << std::endl;
|
||||
}
|
||||
|
||||
if (!indices.empty())
|
||||
{
|
||||
file << "element face " << indices.size() << std::endl;
|
||||
@@ -549,12 +646,24 @@ void PlyEncoder::writeData(const std::vector<Point3f> &points, const std::vector
|
||||
file << std::setprecision(9) << points[i].x << " " << points[i].y << " " << points[i].z;
|
||||
if (hasColor)
|
||||
{
|
||||
file << " " << static_cast<int>(rgb[i].x) << " " << static_cast<int>(rgb[i].y) << " " << static_cast<int>(rgb[i].z);
|
||||
file << " " << static_cast<int>(rgb[i].x * 255.f) << " " << static_cast<int>(rgb[i].y * 255.f) << " " << static_cast<int>(rgb[i].z * 255.f);
|
||||
}
|
||||
if (hasNormals)
|
||||
{
|
||||
file << " " << std::setprecision(9) << normals[i].x << " " << normals[i].y << " " << normals[i].z;
|
||||
}
|
||||
if (nTexCoords > 0)
|
||||
{
|
||||
file << " " << std::setprecision(9) << texCoords[i].x;
|
||||
}
|
||||
if (nTexCoords > 1)
|
||||
{
|
||||
file << " " << std::setprecision(9) << texCoords[i].y;
|
||||
}
|
||||
if (nTexCoords > 2)
|
||||
{
|
||||
file << " " << std::setprecision(9) << texCoords[i].z;
|
||||
}
|
||||
file << std::endl;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,18 +35,23 @@ struct ElementDescription
|
||||
class PlyDecoder CV_FINAL : public BasePointCloudDecoder
|
||||
{
|
||||
public:
|
||||
void readData(std::vector<Point3f> &points, std::vector<Point3f> &normals, std::vector<Point3_<uchar>> &rgb, std::vector<std::vector<int32_t>> &indices) CV_OVERRIDE;
|
||||
void readData(std::vector<Point3f>& points, std::vector<Point3f>& normals, std::vector<Point3f>& rgb,
|
||||
std::vector<Point3f>& texCoords, int& nTexCoords,
|
||||
std::vector<std::vector<int32_t>>& indices, int flags) CV_OVERRIDE;
|
||||
|
||||
protected:
|
||||
bool parseHeader(std::ifstream &file);
|
||||
void parseBody(std::ifstream &file, std::vector<Point3f> &points, std::vector<Point3f> &normals, std::vector<Point3_<uchar>> &rgb,
|
||||
std::vector<std::vector<int32_t>> &indices);
|
||||
bool parseHeader(std::ifstream &file, int& nTexCoords);
|
||||
void parseBody(std::ifstream &file,
|
||||
std::vector<Point3f>& points, std::vector<Point3f>& normals, std::vector<Point3f>& rgb,
|
||||
std::vector<Point3f>& texCoords,
|
||||
std::vector<std::vector<int32_t>>& indices);
|
||||
|
||||
DataFormat m_inputDataFormat;
|
||||
size_t m_vertexCount{0};
|
||||
size_t m_faceCount{0};
|
||||
bool m_hasColour{false};
|
||||
bool m_hasNormal{false};
|
||||
bool m_hasTexCoord{false};
|
||||
ElementDescription m_vertexDescription;
|
||||
ElementDescription m_faceDescription;
|
||||
};
|
||||
@@ -54,7 +59,9 @@ protected:
|
||||
class PlyEncoder CV_FINAL : public BasePointCloudEncoder
|
||||
{
|
||||
public:
|
||||
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;
|
||||
void writeData(const std::vector<Point3f>& points, const std::vector<Point3f>& normals, const std::vector<Point3f>& rgb,
|
||||
const std::vector<Point3f>& texCoords, int nTexCoords,
|
||||
const std::vector<std::vector<int32_t>>& indices) CV_OVERRIDE;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -61,20 +61,18 @@ void loadPointCloud(const String &filename, OutputArray vertices, OutputArray no
|
||||
|
||||
decoder->setSource(filename);
|
||||
|
||||
std::vector<Point3f> vec_vertices;
|
||||
std::vector<Point3f> vec_normals;
|
||||
std::vector<Point3_<uchar>> vec_rgb;
|
||||
std::vector<Point3f> vec_vertices, vec_normals, vec_rgb;
|
||||
|
||||
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);
|
||||
Mat(static_cast<int>(vec_vertices.size()), 1, CV_32FC3, vec_vertices.data()).copyTo(vertices);
|
||||
|
||||
if (!vec_normals.empty() && normals.needed())
|
||||
Mat(static_cast<int>(vec_normals.size()), 1, CV_32FC3, &vec_normals[0]).copyTo(normals);
|
||||
Mat(static_cast<int>(vec_normals.size()), 1, CV_32FC3, vec_normals.data()).copyTo(normals);
|
||||
|
||||
if (!vec_rgb.empty() && rgb.needed())
|
||||
Mat(static_cast<int>(vec_rgb.size()), 1, CV_8UC3, &vec_rgb[0]).copyTo(rgb);
|
||||
Mat(static_cast<int>(vec_rgb.size()), 1, CV_32FC3, vec_rgb.data()).copyTo(rgb);
|
||||
|
||||
#else // OPENCV_HAVE_FILESYSTEM_SUPPORT
|
||||
CV_UNUSED(filename);
|
||||
@@ -101,15 +99,15 @@ void savePointCloud(const String &filename, InputArray vertices, InputArray norm
|
||||
|
||||
encoder->setDestination(filename);
|
||||
|
||||
std::vector<Point3f> vec_vertices(vertices.getMat());
|
||||
std::vector<Point3f> vec_normals;
|
||||
std::vector<Point3_<uchar>> vec_rgb;
|
||||
std::vector<Point3f> vec_vertices(vertices.getMat()), vec_normals, vec_rgb;
|
||||
|
||||
if (!normals.empty()){
|
||||
if (!normals.empty())
|
||||
{
|
||||
vec_normals = normals.getMat();
|
||||
}
|
||||
|
||||
if (!rgb.empty()){
|
||||
if (!rgb.empty())
|
||||
{
|
||||
vec_rgb = rgb.getMat();
|
||||
}
|
||||
encoder->writeData(vec_vertices, vec_normals, vec_rgb);
|
||||
@@ -122,7 +120,8 @@ void savePointCloud(const String &filename, InputArray vertices, InputArray norm
|
||||
#endif
|
||||
}
|
||||
|
||||
void loadMesh(const String &filename, OutputArray vertices, OutputArrayOfArrays indices, OutputArray normals, OutputArray colors)
|
||||
void loadMesh(const String &filename, OutputArray vertices, OutputArrayOfArrays indices,
|
||||
OutputArray normals, OutputArray colors, OutputArray texCoords)
|
||||
{
|
||||
#if OPENCV_HAVE_FILESYSTEM_SUPPORT
|
||||
CV_Assert(vertices.needed());
|
||||
@@ -137,12 +136,13 @@ void loadMesh(const String &filename, OutputArray vertices, OutputArrayOfArrays
|
||||
|
||||
decoder->setSource(filename);
|
||||
|
||||
std::vector<Point3f> vec_vertices;
|
||||
std::vector<Point3f> vec_normals;
|
||||
std::vector<Point3_<uchar>> vec_rgb;
|
||||
std::vector<Point3f> vec_vertices, vec_normals, vec_rgb;
|
||||
std::vector<std::vector<int32_t>> vec_indices;
|
||||
|
||||
decoder->readData(vec_vertices, vec_normals, vec_rgb, vec_indices);
|
||||
std::vector<Point3f> vec_texCoords;
|
||||
int nTexCoords;
|
||||
|
||||
decoder->readData(vec_vertices, vec_normals, vec_rgb, vec_texCoords, nTexCoords, vec_indices, 0);
|
||||
|
||||
if (!vec_vertices.empty())
|
||||
{
|
||||
@@ -156,15 +156,86 @@ void loadMesh(const String &filename, OutputArray vertices, OutputArrayOfArrays
|
||||
|
||||
if (colors.needed() && !vec_rgb.empty())
|
||||
{
|
||||
Mat(1, static_cast<int>(vec_rgb.size()), CV_8UC3, vec_rgb.data()).convertTo(colors, CV_32F, (1.0/255.0));
|
||||
Mat(1, static_cast<int>(vec_rgb.size()), CV_32FC3, vec_rgb.data()).copyTo(colors);
|
||||
}
|
||||
|
||||
if (!vec_indices.empty())
|
||||
{
|
||||
std::vector<std::vector<int32_t>>& vec = *(std::vector<std::vector<int32_t>>*)indices.getObj();
|
||||
vec.resize(vec_indices.size());
|
||||
for (size_t i = 0; i < vec_indices.size(); ++i) {
|
||||
Mat(1, static_cast<int>(vec_indices[i].size()), CV_32SC1, vec_indices[i].data()).copyTo(vec[i]);
|
||||
_InputArray::KindFlag kind = indices.kind();
|
||||
int vecsz = (int)vec_indices.size();
|
||||
if (kind == _InputArray::KindFlag::STD_VECTOR_VECTOR)
|
||||
{
|
||||
CV_Assert(indices.depth() == CV_32S);
|
||||
std::vector<std::vector<int32_t>>& vec = *(std::vector<std::vector<int32_t>>*)indices.getObj();
|
||||
vec.resize(vecsz);
|
||||
for (int i = 0; i < vecsz; ++i)
|
||||
{
|
||||
Mat(1, static_cast<int>(vec_indices[i].size()), CV_32SC1, vec_indices[i].data()).copyTo(vec[i]);
|
||||
}
|
||||
}
|
||||
// std::array<Mat> has fixed size, unsupported
|
||||
else if (kind == _InputArray::KindFlag::STD_VECTOR_MAT)
|
||||
{
|
||||
indices.create(vecsz, 1, CV_32S);
|
||||
for (int i = 0; i < vecsz; i++)
|
||||
{
|
||||
std::vector<int> vi = vec_indices[i];
|
||||
indices.create(1, (int)vi.size(), CV_32S, i);
|
||||
Mat(vi).copyTo(indices.getMat(i));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
indices.create(1, (int)vec_indices.size(), CV_32SC3);
|
||||
std::vector<Vec3i>& vec = *(std::vector<Vec3i>*)indices.getObj();
|
||||
for (int i = 0; i < vecsz; ++i)
|
||||
{
|
||||
Vec3i tri;
|
||||
size_t sz = vec_indices[i].size();
|
||||
if (sz != 3)
|
||||
{
|
||||
CV_Error(Error::StsBadArg, "Face contains " + std::to_string(sz) + " vertices, can not put it into 3-channel indices array");
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int j = 0; j < 3; j++)
|
||||
{
|
||||
tri[j] = vec_indices[i][j];
|
||||
}
|
||||
}
|
||||
vec[i] = tri;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (texCoords.needed())
|
||||
{
|
||||
int ch = texCoords.empty() ? 0 : texCoords.channels();
|
||||
Mat texMat = Mat(1, static_cast<int>(vec_texCoords.size()), CV_MAKETYPE(CV_32F, nTexCoords), vec_texCoords.data());
|
||||
if (ch == nTexCoords)
|
||||
{
|
||||
texMat.copyTo(texCoords);
|
||||
}
|
||||
else
|
||||
{
|
||||
Mat newTexMat;
|
||||
std::vector<Mat> varr;
|
||||
cv::split(texMat, varr);
|
||||
if (ch == 2 && nTexCoords == 3)
|
||||
{
|
||||
std::vector<Mat> marr = { varr[0], varr[1] };
|
||||
cv::merge(marr, newTexMat);
|
||||
}
|
||||
else if (ch == 3 && nTexCoords == 2)
|
||||
{
|
||||
std::vector<Mat> marr = { varr[0], varr[1], Mat::zeros(varr[0].size(), CV_32F) };
|
||||
cv::merge(marr, newTexMat);
|
||||
}
|
||||
else
|
||||
{
|
||||
newTexMat = texMat;
|
||||
}
|
||||
newTexMat.copyTo(texCoords);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +249,8 @@ void loadMesh(const String &filename, OutputArray vertices, OutputArrayOfArrays
|
||||
#endif
|
||||
}
|
||||
|
||||
void saveMesh(const String &filename, InputArray vertices, InputArrayOfArrays indices, InputArray normals, InputArray colors)
|
||||
void saveMesh(const String &filename, InputArray vertices, InputArrayOfArrays indices,
|
||||
InputArray normals, InputArray colors, InputArray texCoords)
|
||||
{
|
||||
#if OPENCV_HAVE_FILESYSTEM_SUPPORT
|
||||
if (vertices.empty()) {
|
||||
@@ -195,9 +267,7 @@ void saveMesh(const String &filename, InputArray vertices, InputArrayOfArrays in
|
||||
|
||||
encoder->setDestination(filename);
|
||||
|
||||
std::vector<Point3f> vec_vertices(vertices.getMat());
|
||||
std::vector<Point3f> vec_normals;
|
||||
std::vector<Point3_<uchar>> vec_rgb;
|
||||
std::vector<Point3f> vec_vertices(vertices.getMat()), vec_normals, vec_rgb;
|
||||
if (!normals.empty())
|
||||
{
|
||||
vec_normals = normals.getMat();
|
||||
@@ -205,19 +275,57 @@ void saveMesh(const String &filename, InputArray vertices, InputArrayOfArrays in
|
||||
|
||||
if (!colors.empty())
|
||||
{
|
||||
colors.getMat().convertTo(vec_rgb, CV_8U, 255.0);
|
||||
vec_rgb = colors.getMat();
|
||||
}
|
||||
|
||||
std::vector<Mat> mat_indices;
|
||||
indices.getMatVector(mat_indices);
|
||||
std::vector<std::vector<int32_t>> vec_indices(mat_indices.size());
|
||||
|
||||
for (size_t i = 0; i < mat_indices.size(); ++i)
|
||||
std::vector<std::vector<int32_t>> vec_indices;
|
||||
CV_Assert(indices.depth() == CV_32S);
|
||||
if (indices.kind() == _InputArray::KindFlag::STD_VECTOR_VECTOR)
|
||||
{
|
||||
mat_indices[i].copyTo(vec_indices[i]);
|
||||
std::vector<Mat> mat_indices;
|
||||
indices.getMatVector(mat_indices);
|
||||
vec_indices.resize(mat_indices.size());
|
||||
for (size_t i = 0; i < mat_indices.size(); ++i)
|
||||
{
|
||||
mat_indices[i].copyTo(vec_indices[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CV_Assert(indices.channels() == 3);
|
||||
std::vector<Vec3i>& vec = *(std::vector<Vec3i>*)indices.getObj();
|
||||
vec_indices.resize(vec.size());
|
||||
for (size_t i = 0; i < vec.size(); ++i)
|
||||
{
|
||||
for (int j = 0; j < 3; j++)
|
||||
{
|
||||
vec_indices[i].push_back(vec[i][j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
encoder->writeData(vec_vertices, vec_normals, vec_rgb, vec_indices);
|
||||
std::vector<Point3f> vec_texCoords;
|
||||
int nTexCoords = 0;
|
||||
if (!texCoords.empty())
|
||||
{
|
||||
nTexCoords = texCoords.channels();
|
||||
}
|
||||
if (nTexCoords == 2)
|
||||
{
|
||||
std::vector<Point2f> vec2_texCoords;
|
||||
texCoords.copyTo(vec2_texCoords);
|
||||
for (size_t i = 0; i < vec2_texCoords.size(); i++)
|
||||
{
|
||||
Point2f p = vec2_texCoords[i];
|
||||
vec_texCoords.push_back({p.x, p.y, 0});
|
||||
}
|
||||
}
|
||||
if (nTexCoords == 3)
|
||||
{
|
||||
texCoords.copyTo(vec_texCoords);
|
||||
}
|
||||
|
||||
encoder->writeData(vec_vertices, vec_normals, vec_rgb, vec_texCoords, nTexCoords, vec_indices);
|
||||
|
||||
#else // OPENCV_HAVE_FILESYSTEM_SUPPORT
|
||||
CV_UNUSED(filename);
|
||||
|
||||
@@ -11,39 +11,40 @@
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
TEST(PointCloud, LoadObj)
|
||||
TEST(PointCloud, LoadPointCloudObj)
|
||||
{
|
||||
std::vector<cv::Point3f> points_gold = {
|
||||
{-5.93915f, -0.13257f, 2.55837f},
|
||||
{-5.93915f, 1.86743f, 2.55837f},
|
||||
{-5.93915f, -0.13257f, 2.55837f},
|
||||
{-5.93915f, 1.86743f, 2.55837f},
|
||||
{-5.93915f, -0.13257f, -1.16339f},
|
||||
{-5.93915f, 1.86743f, -1.16339f},
|
||||
{0.399941f, -0.13257f, 2.55837f},
|
||||
{0.399941f, 1.86743f, 2.55837f},
|
||||
{-5.93915f, 1.86743f, -1.16339f},
|
||||
{0.399941f, -0.13257f, 2.55837f},
|
||||
{0.399941f, 1.86743f, 2.55837f},
|
||||
{0.399941f, -0.13257f, -1.16339f},
|
||||
{0.399941f, 1.86743f, -1.16339f}};
|
||||
{0.399941f, 1.86743f, -1.16339f}
|
||||
};
|
||||
|
||||
std::vector<cv::Point3f> normals_gold = {
|
||||
{-1.0000f, 0.0000f, 0.0000f},
|
||||
{0.0000f, 0.0000f, -1.0000f},
|
||||
{1.0000f, 0.0000f, 0.0000f},
|
||||
{0.0000f, 0.0000f, 1.0000f},
|
||||
{0.0000f, -1.0000f, 0.0000f},
|
||||
{0.0000f, 1.0000f, 0.0000f}};
|
||||
{-1.0000f, 0.0000f, 0.0000f},
|
||||
{ 0.0000f, 0.0000f, -1.0000f},
|
||||
{ 1.0000f, 0.0000f, 0.0000f},
|
||||
{ 0.0000f, 0.0000f, 1.0000f},
|
||||
{ 0.0000f, -1.0000f, 0.0000f},
|
||||
{ 0.0000f, 1.0000f, 0.0000f}
|
||||
};
|
||||
|
||||
std::vector<cv::Point3_<uchar>> rgb_gold = {
|
||||
{19, 144, 149},
|
||||
{219, 28, 216},
|
||||
{218, 157, 101},
|
||||
{11, 161, 78},
|
||||
{248, 183, 214},
|
||||
{63, 196, 6},
|
||||
{165, 190, 153},
|
||||
{89, 203, 11}};
|
||||
std::vector<cv::Point3f> rgb_gold = {
|
||||
{0.0756f, 0.5651f, 0.5829f},
|
||||
{0.8596f, 0.1105f, 0.8455f},
|
||||
{0.8534f, 0.6143f, 0.3950f},
|
||||
{0.0438f, 0.6308f, 0.3065f},
|
||||
{0.9716f, 0.7170f, 0.8378f},
|
||||
{0.2472f, 0.7701f, 0.0234f},
|
||||
{0.6472f, 0.7467f, 0.5981f},
|
||||
{0.3502f, 0.7954f, 0.0443f}
|
||||
};
|
||||
|
||||
std::vector<cv::Point3f> points;
|
||||
std::vector<cv::Point3f> normals;
|
||||
std::vector<cv::Point3_<uchar>> rgb;
|
||||
std::vector<cv::Point3f> points, normals, rgb;
|
||||
|
||||
auto folder = cvtest::TS::ptr()->get_data_path();
|
||||
cv::loadPointCloud(folder + "pointcloudio/orig.obj", points, normals, rgb);
|
||||
@@ -65,8 +66,7 @@ TEST(PointCloud, LoadObjNoNormals)
|
||||
{0.399941f, -0.13257f, -1.16339f},
|
||||
{0.399941f, 1.86743f, -1.16339f}};
|
||||
|
||||
std::vector<cv::Point3f> points;
|
||||
std::vector<cv::Point3f> normals;
|
||||
std::vector<cv::Point3f> points, normals;
|
||||
|
||||
auto folder = cvtest::TS::ptr()->get_data_path();
|
||||
cv::loadPointCloud(folder + "pointcloudio/orig_no_norms.obj", points, normals);
|
||||
@@ -77,9 +77,7 @@ TEST(PointCloud, LoadObjNoNormals)
|
||||
|
||||
TEST(PointCloud, SaveObj)
|
||||
{
|
||||
std::vector<cv::Point3f> points_gold;
|
||||
std::vector<cv::Point3f> normals_gold;
|
||||
std::vector<cv::Point3_<uchar>> rgb_gold;
|
||||
std::vector<cv::Point3f> points_gold, normals_gold, rgb_gold;
|
||||
|
||||
auto folder = cvtest::TS::ptr()->get_data_path();
|
||||
auto new_path = tempfile("new.obj");
|
||||
@@ -87,9 +85,7 @@ TEST(PointCloud, SaveObj)
|
||||
cv::loadPointCloud(folder + "pointcloudio/orig.obj", points_gold, normals_gold, rgb_gold);
|
||||
cv::savePointCloud(new_path, points_gold, normals_gold, rgb_gold);
|
||||
|
||||
std::vector<cv::Point3f> points;
|
||||
std::vector<cv::Point3f> normals;
|
||||
std::vector<cv::Point3_<uchar>> rgb;
|
||||
std::vector<cv::Point3f> points, normals, rgb;
|
||||
|
||||
cv::loadPointCloud(new_path, points, normals, rgb);
|
||||
|
||||
@@ -101,9 +97,7 @@ TEST(PointCloud, SaveObj)
|
||||
|
||||
TEST(PointCloud, LoadSavePly)
|
||||
{
|
||||
std::vector<cv::Point3f> points;
|
||||
std::vector<cv::Point3f> normals;
|
||||
std::vector<cv::Point3_<uchar>> rgb;
|
||||
std::vector<cv::Point3f> points, normals, rgb;
|
||||
|
||||
auto folder = cvtest::TS::ptr()->get_data_path();
|
||||
std::string new_path = tempfile("new.ply");
|
||||
@@ -111,9 +105,7 @@ TEST(PointCloud, LoadSavePly)
|
||||
cv::loadPointCloud(folder + "pointcloudio/orig.ply", points, normals, rgb);
|
||||
cv::savePointCloud(new_path, points, normals, rgb);
|
||||
|
||||
std::vector<cv::Point3f> points_gold;
|
||||
std::vector<cv::Point3f> normals_gold;
|
||||
std::vector<cv::Point3_<uchar>> rgb_gold;
|
||||
std::vector<cv::Point3f> points_gold, normals_gold, rgb_gold;
|
||||
|
||||
cv::loadPointCloud(new_path, points_gold, normals_gold, rgb_gold);
|
||||
|
||||
@@ -125,21 +117,31 @@ TEST(PointCloud, LoadSavePly)
|
||||
|
||||
TEST(PointCloud, LoadSaveMeshObj)
|
||||
{
|
||||
std::vector<cv::Point3f> points;
|
||||
std::vector<cv::Point3f> normals;
|
||||
std::vector<cv::Point3f> points, normals, colors;
|
||||
std::vector<cv::Point2f> texCoords;
|
||||
std::vector<std::vector<int32_t>> indices;
|
||||
|
||||
auto folder = cvtest::TS::ptr()->get_data_path();
|
||||
std::string new_path = tempfile("new_mesh.obj");
|
||||
|
||||
cv::loadMesh(folder + "pointcloudio/orig.obj", points, indices, normals);
|
||||
cv::saveMesh(new_path, points, indices, normals);
|
||||
cv::loadMesh(folder + "pointcloudio/orig.obj", points, indices, normals, colors, texCoords);
|
||||
EXPECT_FALSE(points.empty());
|
||||
EXPECT_FALSE(indices.empty());
|
||||
EXPECT_FALSE(normals.empty());
|
||||
EXPECT_FALSE(colors.empty());
|
||||
EXPECT_FALSE(texCoords.empty());
|
||||
cv::saveMesh(new_path, points, indices, normals, colors, texCoords);
|
||||
|
||||
std::vector<cv::Point3f> points_gold;
|
||||
std::vector<cv::Point3f> normals_gold;
|
||||
std::vector<cv::Point3f> points_gold, normals_gold, colors_gold;
|
||||
std::vector<cv::Point2f> texCoords_gold;
|
||||
std::vector<std::vector<int32_t>> indices_gold;
|
||||
|
||||
cv::loadMesh(new_path, points_gold, indices_gold, normals_gold);
|
||||
cv::loadMesh(new_path, points_gold, indices_gold, normals_gold, colors_gold, texCoords_gold);
|
||||
EXPECT_FALSE(points_gold.empty());
|
||||
EXPECT_FALSE(indices_gold.empty());
|
||||
EXPECT_FALSE(normals_gold.empty());
|
||||
EXPECT_FALSE(colors_gold.empty());
|
||||
EXPECT_FALSE(texCoords_gold.empty());
|
||||
|
||||
EXPECT_EQ(normals_gold, normals);
|
||||
EXPECT_EQ(points_gold, points);
|
||||
@@ -156,7 +158,7 @@ TEST_P(PlyTest, LoadSaveMesh)
|
||||
std::string fname = GetParam();
|
||||
|
||||
std::vector<cv::Point3f> points_gold, normals_gold, colors_gold;
|
||||
std::vector<std::vector<int32_t>> indices_gold;
|
||||
std::vector<cv::Vec3i> indices_gold;
|
||||
|
||||
auto folder = cvtest::TS::ptr()->get_data_path();
|
||||
std::string new_path = tempfile("new_mesh.ply");
|
||||
@@ -168,7 +170,7 @@ TEST_P(PlyTest, LoadSaveMesh)
|
||||
cv::saveMesh(new_path, points_gold, indices_gold, normals_gold, colors_gold);
|
||||
|
||||
std::vector<cv::Point3f> points, normals, colors;
|
||||
std::vector<std::vector<int32_t>> indices;
|
||||
std::vector<cv::Vec3i> indices;
|
||||
cv::loadMesh(new_path, points, indices, normals, colors);
|
||||
|
||||
if (!normals.empty())
|
||||
|
||||
@@ -94,8 +94,9 @@ void generateSphere(OutputArray sphere_pts, const vector<float> &model, float th
|
||||
// Let r change then generate thickness
|
||||
float r = i < sphere_num ? model[3] : rng.uniform(model[3] - thr, model[3] + thr);
|
||||
// Generate a random vector and normalize it.
|
||||
// Note: these vectors are not spread uniformly across the sphere
|
||||
Vec3f vec(rng.uniform(limit[0], limit[1]), rng.uniform(limit[2], limit[3]),
|
||||
rng.uniform(limit[4], limit[5]));
|
||||
rng.uniform(limit[4], limit[5]));
|
||||
float l = sqrt(vec.dot(vec));
|
||||
// Normalizes it to have a magnitude of r
|
||||
vec /= l / r;
|
||||
|
||||
@@ -649,19 +649,19 @@ TEST_P(RenderingTest, floatParams)
|
||||
thr.depthInfThreshold = 1;
|
||||
if (width == 320 && height == 240 && shadingType == RASTERIZE_SHADING_SHADED && cullingMode == RASTERIZE_CULLING_CCW)
|
||||
{
|
||||
thr.rgbInfThreshold = 0.000157;
|
||||
thr.rgbL2Threshold = 6e-09;
|
||||
thr.depthL2Threshold = 0.000413;
|
||||
thr.rgbInfThreshold = 0.000229;
|
||||
thr.rgbL2Threshold = 6.37e-09;
|
||||
thr.depthL2Threshold = 0.000427;
|
||||
}
|
||||
else if (width == 700 && height == 700 && shadingType == RASTERIZE_SHADING_SHADED && cullingMode == RASTERIZE_CULLING_CW)
|
||||
{
|
||||
thr.rgbInfThreshold = 0.000303;
|
||||
thr.rgbL2Threshold = 1.9e-09;
|
||||
thr.depthL2Threshold = 0.00012;
|
||||
thr.rgbInfThreshold = 0.000277;
|
||||
thr.rgbL2Threshold = 1.8e-09;
|
||||
thr.depthL2Threshold = 0.000124;
|
||||
}
|
||||
else if (width == 700 && height == 700 && shadingType == RASTERIZE_SHADING_WHITE && cullingMode == RASTERIZE_CULLING_NONE)
|
||||
{
|
||||
thr.depthL2Threshold = 0.00012;
|
||||
thr.depthL2Threshold = 0.000124;
|
||||
}
|
||||
break;
|
||||
case ModelType::Centered:
|
||||
@@ -803,27 +803,27 @@ TEST_P(RenderingTest, accuracy)
|
||||
case ModelType::File:
|
||||
if (width == 320 && height == 240 && shadingType == RASTERIZE_SHADING_SHADED && cullingMode == RASTERIZE_CULLING_CCW)
|
||||
{
|
||||
thr.rgbInfThreshold = 0.93;
|
||||
thr.rgbL2Threshold = 2.45E-05;
|
||||
thr.depthMaskThreshold = 2;
|
||||
thr.rgbInfThreshold = 0.836;
|
||||
thr.rgbL2Threshold = 2.08e-05;
|
||||
thr.depthMaskThreshold = 1;
|
||||
thr.depthInfThreshold = 99;
|
||||
thr.depthL2Threshold = 0.00544;
|
||||
}
|
||||
else if (width == 700 && height == 700 && shadingType == RASTERIZE_SHADING_SHADED && cullingMode == RASTERIZE_CULLING_CW)
|
||||
{
|
||||
thr.rgbInfThreshold = 0.973;
|
||||
thr.rgbL2Threshold = 4.46E-06;
|
||||
thr.depthMaskThreshold = 3;
|
||||
thr.rgbL2Threshold = 5.2e-06;
|
||||
thr.depthMaskThreshold = 4;
|
||||
thr.depthInfThreshold = 258;
|
||||
thr.depthL2Threshold = 0.00142;
|
||||
thr.depthL2Threshold = 0.00228;
|
||||
}
|
||||
else if (width == 700 && height == 700 && shadingType == RASTERIZE_SHADING_WHITE && cullingMode == RASTERIZE_CULLING_NONE)
|
||||
{
|
||||
thr.rgbInfThreshold = 1;
|
||||
thr.rgbL2Threshold = 6.13E-06;
|
||||
thr.depthMaskThreshold = 3;
|
||||
thr.rgbL2Threshold = 7.07e-06;
|
||||
thr.depthMaskThreshold = 4;
|
||||
thr.depthInfThreshold = 258;
|
||||
thr.depthL2Threshold = 0.00142;
|
||||
thr.depthL2Threshold = 0.00228;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -914,7 +914,7 @@ TEST_P(RenderingTest, glCompatibleDepth)
|
||||
}
|
||||
|
||||
double normL2Diff = cv::norm(depth_buf, convertedDepth, cv::NORM_L2) / (height * width);
|
||||
const double normL2Threshold = 1.e-9;
|
||||
const double normL2Threshold = 5.53e-10;
|
||||
EXPECT_LE(normL2Diff, normL2Threshold);
|
||||
// add --test_debug to output differences
|
||||
if (debugLevel > 0)
|
||||
|
||||
Reference in New Issue
Block a user