mirror of
https://github.com/opencv/opencv.git
synced 2024-12-13 16:09:23 +08:00
6699ca1a40
Fix mesh loading for texture coordinates and face indices #25382 ### This PR changes * Texture coordinates were stored incorrectly (3-channel array is read as if there were 2 channels), fixed * Faces were pushed back to the output array instead of indexed writing which produced a lot of empty faces, fixed * A set of ground truth tests were added to cover these issues * `std::vector<cv::Mat>` support added for `saveMesh()` which is required for Python bindings * More command line args were added to rasterization test data generator ### 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
343 lines
9.3 KiB
C++
343 lines
9.3 KiB
C++
// This file is part of OpenCV project.
|
|
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
|
// of this distribution and at http://opencv.org/license.html
|
|
|
|
#include "perf_precomp.hpp"
|
|
|
|
namespace opencv_test
|
|
{
|
|
|
|
// that was easier than using CV_ENUM() macro
|
|
namespace
|
|
{
|
|
using namespace cv;
|
|
struct ShadingTypeEnum
|
|
{
|
|
static const std::array<TriangleShadingType, 3> vals;
|
|
static const std::array<std::string, 3> svals;
|
|
|
|
ShadingTypeEnum(TriangleShadingType v = RASTERIZE_SHADING_WHITE) : val(v) {}
|
|
operator TriangleShadingType() const { return val; }
|
|
void PrintTo(std::ostream *os) const
|
|
{
|
|
int v = int(val);
|
|
if (v >= 0 && v < (int)vals.size())
|
|
{
|
|
*os << svals[v];
|
|
}
|
|
else
|
|
{
|
|
*os << "UNKNOWN";
|
|
}
|
|
}
|
|
static ::testing::internal::ParamGenerator<ShadingTypeEnum> all()
|
|
{
|
|
return ::testing::Values(ShadingTypeEnum(vals[0]),
|
|
ShadingTypeEnum(vals[1]),
|
|
ShadingTypeEnum(vals[2]));
|
|
}
|
|
|
|
private:
|
|
TriangleShadingType val;
|
|
};
|
|
|
|
const std::array<TriangleShadingType, 3> ShadingTypeEnum::vals
|
|
{
|
|
RASTERIZE_SHADING_WHITE,
|
|
RASTERIZE_SHADING_FLAT,
|
|
RASTERIZE_SHADING_SHADED
|
|
};
|
|
const std::array<std::string, 3> ShadingTypeEnum::svals
|
|
{
|
|
std::string("White"),
|
|
std::string("Flat"),
|
|
std::string("Shaded")
|
|
};
|
|
|
|
static inline void PrintTo(const ShadingTypeEnum &t, std::ostream *os) { t.PrintTo(os); }
|
|
|
|
|
|
using namespace cv;
|
|
struct GlCompatibleModeEnum
|
|
{
|
|
static const std::array<TriangleGlCompatibleMode, 2> vals;
|
|
static const std::array<std::string, 2> svals;
|
|
|
|
GlCompatibleModeEnum(TriangleGlCompatibleMode v = RASTERIZE_COMPAT_DISABLED) : val(v) {}
|
|
operator TriangleGlCompatibleMode() const { return val; }
|
|
void PrintTo(std::ostream *os) const
|
|
{
|
|
int v = int(val);
|
|
if (v >= 0 && v < (int)vals.size())
|
|
{
|
|
*os << svals[v];
|
|
}
|
|
else
|
|
{
|
|
*os << "UNKNOWN";
|
|
}
|
|
}
|
|
static ::testing::internal::ParamGenerator<GlCompatibleModeEnum> all()
|
|
{
|
|
return ::testing::Values(GlCompatibleModeEnum(vals[0]),
|
|
GlCompatibleModeEnum(vals[1]));
|
|
}
|
|
|
|
private:
|
|
TriangleGlCompatibleMode val;
|
|
};
|
|
|
|
const std::array<TriangleGlCompatibleMode, 2> GlCompatibleModeEnum::vals
|
|
{
|
|
RASTERIZE_COMPAT_DISABLED,
|
|
RASTERIZE_COMPAT_INVDEPTH,
|
|
};
|
|
const std::array<std::string, 2> GlCompatibleModeEnum::svals
|
|
{
|
|
std::string("Disabled"),
|
|
std::string("InvertedDepth"),
|
|
};
|
|
|
|
static inline void PrintTo(const GlCompatibleModeEnum &t, std::ostream *os) { t.PrintTo(os); }
|
|
}
|
|
|
|
enum class Outputs
|
|
{
|
|
DepthOnly = 0,
|
|
ColorOnly = 1,
|
|
DepthColor = 2,
|
|
};
|
|
|
|
// that was easier than using CV_ENUM() macro
|
|
namespace
|
|
{
|
|
using namespace cv;
|
|
struct OutputsEnum
|
|
{
|
|
static const std::array<Outputs, 3> vals;
|
|
static const std::array<std::string, 3> svals;
|
|
|
|
OutputsEnum(Outputs v = Outputs::DepthColor) : val(v) {}
|
|
operator Outputs() const { return val; }
|
|
void PrintTo(std::ostream *os) const
|
|
{
|
|
int v = int(val);
|
|
if (v >= 0 && v < (int)vals.size())
|
|
{
|
|
*os << svals[v];
|
|
}
|
|
else
|
|
{
|
|
*os << "UNKNOWN";
|
|
}
|
|
}
|
|
static ::testing::internal::ParamGenerator<OutputsEnum> all()
|
|
{
|
|
return ::testing::Values(OutputsEnum(vals[0]),
|
|
OutputsEnum(vals[1]),
|
|
OutputsEnum(vals[2]));
|
|
}
|
|
|
|
private:
|
|
Outputs val;
|
|
};
|
|
|
|
const std::array<Outputs, 3> OutputsEnum::vals
|
|
{
|
|
Outputs::DepthOnly,
|
|
Outputs::ColorOnly,
|
|
Outputs::DepthColor
|
|
};
|
|
const std::array<std::string, 3> OutputsEnum::svals
|
|
{
|
|
std::string("DepthOnly"),
|
|
std::string("ColorOnly"),
|
|
std::string("DepthColor")
|
|
};
|
|
|
|
static inline void PrintTo(const OutputsEnum &t, std::ostream *os) { t.PrintTo(os); }
|
|
}
|
|
|
|
static Matx44d lookAtMatrixCal(const Vec3d& position, const Vec3d& lookat, const Vec3d& upVector)
|
|
{
|
|
Vec3d w = cv::normalize(position - lookat);
|
|
Vec3d u = cv::normalize(upVector.cross(w));
|
|
|
|
Vec3d v = w.cross(u);
|
|
|
|
Matx44d res(u[0], u[1], u[2], 0,
|
|
v[0], v[1], v[2], 0,
|
|
w[0], w[1], w[2], 0,
|
|
0, 0, 0, 1.0);
|
|
|
|
Matx44d translate(1.0, 0, 0, -position[0],
|
|
0, 1.0, 0, -position[1],
|
|
0, 0, 1.0, -position[2],
|
|
0, 0, 0, 1.0);
|
|
res = res * translate;
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
static void generateNormals(const std::vector<Vec3f>& points, const std::vector<std::vector<int>>& indices,
|
|
std::vector<Vec3f>& normals)
|
|
{
|
|
std::vector<std::vector<Vec3f>> preNormals(points.size(), std::vector<Vec3f>());
|
|
|
|
for (const auto& tri : indices)
|
|
{
|
|
Vec3f p0 = points[tri[0]];
|
|
Vec3f p1 = points[tri[1]];
|
|
Vec3f p2 = points[tri[2]];
|
|
|
|
Vec3f cross = cv::normalize((p1 - p0).cross(p2 - p0));
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
preNormals[tri[i]].push_back(cross);
|
|
}
|
|
}
|
|
|
|
normals.reserve(points.size());
|
|
for (const auto& pn : preNormals)
|
|
{
|
|
Vec3f sum { };
|
|
for (const auto& n : pn)
|
|
{
|
|
sum += n;
|
|
}
|
|
normals.push_back(cv::normalize(sum));
|
|
}
|
|
}
|
|
|
|
// load model once and keep it in static memory
|
|
static void getModelOnce(const std::string& objectPath, std::vector<Vec3f>& vertices,
|
|
std::vector<Vec3i>& indices, std::vector<Vec3f>& colors)
|
|
{
|
|
static bool load = false;
|
|
static std::vector<Vec3f> vert, col;
|
|
static std::vector<Vec3i> ind;
|
|
|
|
if (!load)
|
|
{
|
|
std::vector<vector<int>> indvec;
|
|
// using per-vertex normals as colors
|
|
loadMesh(objectPath, vert, indvec);
|
|
generateNormals(vert, indvec, col);
|
|
|
|
for (const auto &vec : indvec)
|
|
{
|
|
ind.push_back({vec[0], vec[1], vec[2]});
|
|
}
|
|
|
|
for (auto &color : col)
|
|
{
|
|
color = Vec3f(abs(color[0]), abs(color[1]), abs(color[2]));
|
|
}
|
|
|
|
load = true;
|
|
}
|
|
|
|
vertices = vert;
|
|
colors = col;
|
|
indices = ind;
|
|
}
|
|
|
|
template<typename T>
|
|
std::string printEnum(T v)
|
|
{
|
|
std::ostringstream ss;
|
|
v.PrintTo(&ss);
|
|
return ss.str();
|
|
}
|
|
|
|
// resolution, shading type, outputs needed
|
|
typedef perf::TestBaseWithParam<std::tuple<std::tuple<int, int>, ShadingTypeEnum, OutputsEnum, GlCompatibleModeEnum>> RenderingTest;
|
|
|
|
PERF_TEST_P(RenderingTest, rasterizeTriangles, ::testing::Combine(
|
|
::testing::Values(std::make_tuple(1920, 1080), std::make_tuple(1024, 768), std::make_tuple(640, 480)),
|
|
ShadingTypeEnum::all(),
|
|
OutputsEnum::all(),
|
|
GlCompatibleModeEnum::all()
|
|
))
|
|
{
|
|
auto t = GetParam();
|
|
auto wh = std::get<0>(t);
|
|
int width = std::get<0>(wh);
|
|
int height = std::get<1>(wh);
|
|
auto shadingType = std::get<1>(t);
|
|
auto outputs = std::get<2>(t);
|
|
auto glCompatibleMode = std::get<3>(t);
|
|
|
|
string objectPath = findDataFile("viz/dragon.ply");
|
|
|
|
Vec3f position = Vec3d( 1.9, 0.4, 1.3);
|
|
Vec3f lookat = Vec3d( 0.0, 0.0, 0.0);
|
|
Vec3f upVector = Vec3d( 0.0, 1.0, 0.0);
|
|
|
|
double fovy = 45.0;
|
|
|
|
std::vector<Vec3f> vertices;
|
|
std::vector<Vec3i> indices;
|
|
std::vector<Vec3f> colors;
|
|
|
|
getModelOnce(objectPath, vertices, indices, colors);
|
|
if (shadingType != RASTERIZE_SHADING_WHITE)
|
|
{
|
|
// let vertices be in BGR format to avoid later color conversions
|
|
// mixChannels does not support in-place operation
|
|
cv::mixChannels(Mat(colors).clone(), colors, {0, 2, 1, 1, 2, 0});
|
|
}
|
|
|
|
double zNear = 0.1, zFar = 50.0;
|
|
|
|
Matx44d cameraPose = lookAtMatrixCal(position, lookat, upVector);
|
|
double fovYradians = fovy * (CV_PI / 180.0);
|
|
TriangleRasterizeSettings settings;
|
|
settings.setCullingMode(RASTERIZE_CULLING_CW)
|
|
.setShadingType(shadingType)
|
|
.setGlCompatibleMode(glCompatibleMode);
|
|
|
|
Mat depth_buf, color_buf;
|
|
while (next())
|
|
{
|
|
// Prefilled to measure pure rendering time w/o allocation and clear
|
|
float zMax = (glCompatibleMode == RASTERIZE_COMPAT_INVDEPTH) ? 1.f : (float)zFar;
|
|
depth_buf = Mat(height, width, CV_32F, zMax);
|
|
color_buf = Mat(height, width, CV_32FC3, Scalar::all(0));
|
|
|
|
startTimer();
|
|
if (outputs == Outputs::ColorOnly)
|
|
{
|
|
cv::triangleRasterizeColor(vertices, indices, colors, color_buf, cameraPose,
|
|
fovYradians, zNear, zFar, settings);
|
|
}
|
|
else if (outputs == Outputs::DepthOnly)
|
|
{
|
|
cv::triangleRasterizeDepth(vertices, indices, depth_buf, cameraPose,
|
|
fovYradians, zNear, zFar, settings);
|
|
}
|
|
else // Outputs::DepthColor
|
|
{
|
|
cv::triangleRasterize(vertices, indices, colors, color_buf, depth_buf,
|
|
cameraPose, fovYradians, zNear, zFar, settings);
|
|
}
|
|
stopTimer();
|
|
}
|
|
|
|
if (debugLevel > 0)
|
|
{
|
|
depth_buf.convertTo(depth_buf, CV_16U, 1000.0);
|
|
|
|
std::string shadingName = printEnum(shadingType);
|
|
std::string suffix = cv::format("%dx%d_%s", width, height, shadingName.c_str());
|
|
|
|
imwrite("perf_color_image_" + suffix + ".png", color_buf * 255.f);
|
|
imwrite("perf_depth_image_" + suffix + ".png", depth_buf);
|
|
}
|
|
|
|
SANITY_CHECK_NOTHING();
|
|
}
|
|
|
|
} // namespace
|