diff --git a/modules/3d/include/opencv2/3d.hpp b/modules/3d/include/opencv2/3d.hpp index f8b284e0b2..1355774ae9 100644 --- a/modules/3d/include/opencv2/3d.hpp +++ b/modules/3d/include/opencv2/3d.hpp @@ -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_) 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_) 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 diff --git a/modules/3d/misc/python/test/test_iomesh.py b/modules/3d/misc/python/test/test_iomesh.py new file mode 100644 index 0000000000..20f301e8d2 --- /dev/null +++ b/modules/3d/misc/python/test/test_iomesh.py @@ -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() diff --git a/modules/3d/src/pointcloud/io_base.cpp b/modules/3d/src/pointcloud/io_base.cpp index 99b94b496c..15b723543f 100644 --- a/modules/3d/src/pointcloud/io_base.cpp +++ b/modules/3d/src/pointcloud/io_base.cpp @@ -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 &points, std::vector &normals, std::vector> &rgb) +void BasePointCloudDecoder::readData(std::vector &points, std::vector &normals, std::vector &rgb) { std::vector> indices; - readData(points, normals, rgb, indices); + std::vector 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 &points, const std::vector &normals, const std::vector> &rgb) +void BasePointCloudEncoder::writeData(const std::vector &points, const std::vector &normals, const std::vector &rgb) { std::vector> indices; - writeData(points, normals, rgb, indices); + std::vector texCoords; + writeData(points, normals, rgb, texCoords, 0, indices); } } /* namespace cv */ diff --git a/modules/3d/src/pointcloud/io_base.hpp b/modules/3d/src/pointcloud/io_base.hpp index 446e80a34a..30afd7dfbf 100644 --- a/modules/3d/src/pointcloud/io_base.hpp +++ b/modules/3d/src/pointcloud/io_base.hpp @@ -18,18 +18,24 @@ class BasePointCloudEncoder; using PointCloudDecoder = std::unique_ptr; using PointCloudEncoder = std::unique_ptr; +// 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 &points, std::vector &normals, std::vector> &rgb); - virtual void readData(std::vector &points, std::vector &normals, std::vector> &rgb, std::vector> &indices) = 0; + virtual void setSource(const std::string& filename) noexcept; + virtual void readData(std::vector& points, std::vector& normals, std::vector& rgb); + virtual void readData(std::vector& points, std::vector& normals, std::vector& rgb, + std::vector& texCoords, int& nTexCoords, + std::vector>& 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 &points, const std::vector &normals, const std::vector> &rgb); - virtual void writeData(const std::vector &points, const std::vector &normals, const std::vector> &rgb, const std::vector> &indices) = 0; + virtual void setDestination(const std::string& filename) noexcept; + virtual void writeData(const std::vector& points, const std::vector& normals, const std::vector& rgb); + virtual void writeData(const std::vector& points, const std::vector& normals, const std::vector& rgb, + const std::vector& texCoords, int nTexCoords, + const std::vector>& indices) = 0; protected: - String m_filename; + std::string m_filename; }; } /* namespace cv */ diff --git a/modules/3d/src/pointcloud/io_obj.cpp b/modules/3d/src/pointcloud/io_obj.cpp index 51b1c47a4f..737396443c 100644 --- a/modules/3d/src/pointcloud/io_obj.cpp +++ b/modules/3d/src/pointcloud/io_obj.cpp @@ -12,12 +12,24 @@ namespace cv { std::unordered_set ObjDecoder::m_unsupportedKeys; -void ObjDecoder::readData(std::vector &points, std::vector &normals, std::vector> &rgb, std::vector> &indices) +void ObjDecoder::readData(std::vector& points, std::vector& normals, std::vector& rgb) { - points.clear(); - normals.clear(); - rgb.clear(); - indices.clear(); + std::vector texCoords; + int nTexCoords; + std::vector> indices; + this->readData(points, normals, rgb, texCoords, nTexCoords, indices, READ_AS_IS_FLAG); +} + +void ObjDecoder::readData(std::vector& points, std::vector& normals, std::vector& rgb, + std::vector& texCoords, int& nTexCoords, + std::vector>& indices, int flags) +{ + std::vector ptsList, nrmList, texCoordList, rgbList; + std::vector> 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 &points, std::vector &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 &points, std::vector &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 &points, std::vector &no if (splitArr.size() >= 7) { Point3f color; - if (ss.rdbuf()->in_avail() != 0) { - Point3_ uc_color; + if (ss.rdbuf()->in_avail() != 0) + { ss >> color.x >> color.y >> color.z; - uc_color.x = static_cast(std::round(255.f * color.x)); - uc_color.y = static_cast(std::round(255.f * color.y)); - uc_color.z = static_cast(std::round(255.f * color.z)); - rgb.push_back(uc_color); + rgbList.push_back(color); } } } @@ -73,21 +84,92 @@ void ObjDecoder::readData(std::vector &points, std::vector &no { Point3f normal; ss >> normal.x >> normal.y >> normal.z; - normals.push_back(normal); + nrmList.push_back(normal); } else if (key == "f") { - std::vector vertexInd; + // format: "f v0 / t0 / n0 v1 / t1 / n1 v2/t2/n2 ..." + std::vector 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 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 &points, std::vector &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 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 &points, const std::vector &normals, const std::vector> &rgb, const std::vector> &indices) +void ObjEncoder::writeData(const std::vector& points, const std::vector& normals, const std::vector& rgb, + const std::vector& texCoords, int nTexCoords, + const std::vector>& indices) { std::ofstream file(m_filename, std::ios::binary); if (!file) { @@ -114,15 +239,21 @@ void ObjEncoder::writeData(const std::vector &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(rgb[i].x) / 255.f << " " << - static_cast(rgb[i].y) / 255.f << " " << static_cast(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 &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 "; diff --git a/modules/3d/src/pointcloud/io_obj.hpp b/modules/3d/src/pointcloud/io_obj.hpp index e85782eee3..4f0572c24a 100644 --- a/modules/3d/src/pointcloud/io_obj.hpp +++ b/modules/3d/src/pointcloud/io_obj.hpp @@ -13,7 +13,10 @@ namespace cv { class ObjDecoder CV_FINAL : public BasePointCloudDecoder { public: - void readData(std::vector &points, std::vector &normals, std::vector> &rgb, std::vector> &indices) CV_OVERRIDE; + void readData(std::vector& points, std::vector& normals, std::vector& rgb) CV_OVERRIDE; + void readData(std::vector& points, std::vector& normals, std::vector& rgb, + std::vector& texCoords, int& nTexCoords, + std::vector>& indices, int flags) CV_OVERRIDE; protected: static std::unordered_set m_unsupportedKeys; @@ -22,7 +25,9 @@ protected: class ObjEncoder CV_FINAL : public BasePointCloudEncoder { public: - void writeData(const std::vector &points, const std::vector &normals, const std::vector> &rgb, const std::vector> &indices) CV_OVERRIDE; + void writeData(const std::vector& points, const std::vector& normals, const std::vector& rgb, + const std::vector& texCoords, int nTexCoords, + const std::vector>& indices) CV_OVERRIDE; }; diff --git a/modules/3d/src/pointcloud/io_ply.cpp b/modules/3d/src/pointcloud/io_ply.cpp index 8d29691f56..7c419557a8 100644 --- a/modules/3d/src/pointcloud/io_ply.cpp +++ b/modules/3d/src/pointcloud/io_ply.cpp @@ -13,22 +13,28 @@ namespace cv { -void PlyDecoder::readData(std::vector &points, std::vector &normals, std::vector> &rgb, std::vector> &indices) +static const std::set colorKeys = { "red", "diffuse_red", "green", "diffuse_green", "blue", "diffuse_blue" }; + +void PlyDecoder::readData(std::vector& points, std::vector& normals, std::vector& rgb, + std::vector& texCoords, int& nTexCoords, + std::vector>& 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 texCoordKeys = { "texture_u", "s", "texture_v", "t", "texture_w" }; + bool good = true; m_vertexCount = m_vertexDescription.amount; std::map 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> propCounts; + std::vector> 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(std::ifstream &file, DataFormat format) return val; } -void PlyDecoder::parseBody(std::ifstream &file, std::vector &points, std::vector &normals, std::vector> &rgb, +void PlyDecoder::parseBody(std::ifstream &file, + std::vector& points, std::vector& normals, + std::vector& rgb, std::vector& texCoords, std::vector> &indices) { points.reserve(m_vertexCount); @@ -359,12 +430,13 @@ void PlyDecoder::parseBody(std::ifstream &file, std::vector &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 bytes; + std::array bytes; VertexFields vf; }; @@ -373,7 +445,7 @@ void PlyDecoder::parseBody(std::ifstream &file, std::vector &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 &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 &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 &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 &points, st } } -void PlyEncoder::writeData(const std::vector &points, const std::vector &normals, const std::vector> &rgb, const std::vector> &indices) +void PlyEncoder::writeData(const std::vector& points, const std::vector& normals, const std::vector& rgb, + const std::vector& texCoords, int nTexCoords, + const std::vector>& 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 &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 &points, const std::vector file << std::setprecision(9) << points[i].x << " " << points[i].y << " " << points[i].z; if (hasColor) { - file << " " << static_cast(rgb[i].x) << " " << static_cast(rgb[i].y) << " " << static_cast(rgb[i].z); + file << " " << static_cast(rgb[i].x * 255.f) << " " << static_cast(rgb[i].y * 255.f) << " " << static_cast(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; } diff --git a/modules/3d/src/pointcloud/io_ply.hpp b/modules/3d/src/pointcloud/io_ply.hpp index 1553f109aa..9aa1d8542f 100644 --- a/modules/3d/src/pointcloud/io_ply.hpp +++ b/modules/3d/src/pointcloud/io_ply.hpp @@ -35,18 +35,23 @@ struct ElementDescription class PlyDecoder CV_FINAL : public BasePointCloudDecoder { public: - void readData(std::vector &points, std::vector &normals, std::vector> &rgb, std::vector> &indices) CV_OVERRIDE; + void readData(std::vector& points, std::vector& normals, std::vector& rgb, + std::vector& texCoords, int& nTexCoords, + std::vector>& indices, int flags) CV_OVERRIDE; protected: - bool parseHeader(std::ifstream &file); - void parseBody(std::ifstream &file, std::vector &points, std::vector &normals, std::vector> &rgb, - std::vector> &indices); + bool parseHeader(std::ifstream &file, int& nTexCoords); + void parseBody(std::ifstream &file, + std::vector& points, std::vector& normals, std::vector& rgb, + std::vector& texCoords, + std::vector>& 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 &points, const std::vector &normals, const std::vector> &rgb, const std::vector> &indices) CV_OVERRIDE; + void writeData(const std::vector& points, const std::vector& normals, const std::vector& rgb, + const std::vector& texCoords, int nTexCoords, + const std::vector>& indices) CV_OVERRIDE; }; diff --git a/modules/3d/src/pointcloud/load_point_cloud.cpp b/modules/3d/src/pointcloud/load_point_cloud.cpp index a7c9e0ac70..c99f4d2c24 100644 --- a/modules/3d/src/pointcloud/load_point_cloud.cpp +++ b/modules/3d/src/pointcloud/load_point_cloud.cpp @@ -61,20 +61,18 @@ void loadPointCloud(const String &filename, OutputArray vertices, OutputArray no decoder->setSource(filename); - std::vector vec_vertices; - std::vector vec_normals; - std::vector> vec_rgb; + std::vector vec_vertices, vec_normals, vec_rgb; decoder->readData(vec_vertices, vec_normals, vec_rgb); if (!vec_vertices.empty()) - Mat(static_cast(vec_vertices.size()), 1, CV_32FC3, &vec_vertices[0]).copyTo(vertices); + Mat(static_cast(vec_vertices.size()), 1, CV_32FC3, vec_vertices.data()).copyTo(vertices); if (!vec_normals.empty() && normals.needed()) - Mat(static_cast(vec_normals.size()), 1, CV_32FC3, &vec_normals[0]).copyTo(normals); + Mat(static_cast(vec_normals.size()), 1, CV_32FC3, vec_normals.data()).copyTo(normals); if (!vec_rgb.empty() && rgb.needed()) - Mat(static_cast(vec_rgb.size()), 1, CV_8UC3, &vec_rgb[0]).copyTo(rgb); + Mat(static_cast(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 vec_vertices(vertices.getMat()); - std::vector vec_normals; - std::vector> vec_rgb; + std::vector 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 vec_vertices; - std::vector vec_normals; - std::vector> vec_rgb; + std::vector vec_vertices, vec_normals, vec_rgb; std::vector> vec_indices; - decoder->readData(vec_vertices, vec_normals, vec_rgb, vec_indices); + std::vector 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(vec_rgb.size()), CV_8UC3, vec_rgb.data()).convertTo(colors, CV_32F, (1.0/255.0)); + Mat(1, static_cast(vec_rgb.size()), CV_32FC3, vec_rgb.data()).copyTo(colors); } if (!vec_indices.empty()) { - std::vector>& vec = *(std::vector>*)indices.getObj(); - vec.resize(vec_indices.size()); - for (size_t i = 0; i < vec_indices.size(); ++i) { - Mat(1, static_cast(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>& vec = *(std::vector>*)indices.getObj(); + vec.resize(vecsz); + for (int i = 0; i < vecsz; ++i) + { + Mat(1, static_cast(vec_indices[i].size()), CV_32SC1, vec_indices[i].data()).copyTo(vec[i]); + } + } + // std::array 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 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& vec = *(std::vector*)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(vec_texCoords.size()), CV_MAKETYPE(CV_32F, nTexCoords), vec_texCoords.data()); + if (ch == nTexCoords) + { + texMat.copyTo(texCoords); + } + else + { + Mat newTexMat; + std::vector varr; + cv::split(texMat, varr); + if (ch == 2 && nTexCoords == 3) + { + std::vector marr = { varr[0], varr[1] }; + cv::merge(marr, newTexMat); + } + else if (ch == 3 && nTexCoords == 2) + { + std::vector 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 vec_vertices(vertices.getMat()); - std::vector vec_normals; - std::vector> vec_rgb; + std::vector 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_indices; - indices.getMatVector(mat_indices); - std::vector> vec_indices(mat_indices.size()); - - for (size_t i = 0; i < mat_indices.size(); ++i) + std::vector> 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_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& vec = *(std::vector*)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 vec_texCoords; + int nTexCoords = 0; + if (!texCoords.empty()) + { + nTexCoords = texCoords.channels(); + } + if (nTexCoords == 2) + { + std::vector 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); diff --git a/modules/3d/test/test_pointcloud.cpp b/modules/3d/test/test_pointcloud.cpp index 466d3f7270..ba5a34e385 100644 --- a/modules/3d/test/test_pointcloud.cpp +++ b/modules/3d/test/test_pointcloud.cpp @@ -11,39 +11,40 @@ namespace opencv_test { namespace { -TEST(PointCloud, LoadObj) +TEST(PointCloud, LoadPointCloudObj) { std::vector 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 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> 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 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 points; - std::vector normals; - std::vector> rgb; + std::vector 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 points; - std::vector normals; + std::vector 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 points_gold; - std::vector normals_gold; - std::vector> rgb_gold; + std::vector 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 points; - std::vector normals; - std::vector> rgb; + std::vector points, normals, rgb; cv::loadPointCloud(new_path, points, normals, rgb); @@ -101,9 +97,7 @@ TEST(PointCloud, SaveObj) TEST(PointCloud, LoadSavePly) { - std::vector points; - std::vector normals; - std::vector> rgb; + std::vector 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 points_gold; - std::vector normals_gold; - std::vector> rgb_gold; + std::vector 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 points; - std::vector normals; + std::vector points, normals, colors; + std::vector texCoords; std::vector> 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 points_gold; - std::vector normals_gold; + std::vector points_gold, normals_gold, colors_gold; + std::vector texCoords_gold; std::vector> 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 points_gold, normals_gold, colors_gold; - std::vector> indices_gold; + std::vector 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 points, normals, colors; - std::vector> indices; + std::vector indices; cv::loadMesh(new_path, points, indices, normals, colors); if (!normals.empty()) diff --git a/modules/3d/test/test_ptcloud_utils.cpp b/modules/3d/test/test_ptcloud_utils.cpp index c3c7451e57..9618a66f7d 100644 --- a/modules/3d/test/test_ptcloud_utils.cpp +++ b/modules/3d/test/test_ptcloud_utils.cpp @@ -94,8 +94,9 @@ void generateSphere(OutputArray sphere_pts, const vector &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; diff --git a/modules/3d/test/test_rendering.cpp b/modules/3d/test/test_rendering.cpp index 99b66c0e8e..b5de26602b 100644 --- a/modules/3d/test/test_rendering.cpp +++ b/modules/3d/test/test_rendering.cpp @@ -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)