mirror of https://github.com/opencv/opencv.git
Merge pull request #24459 from savuor:tri_rasterize
Triangle rasterization function #24459 #24065 reopened since the previous one was automatically closed after rebase Connected PR with ground truth data: [#1113@extra](https://github.com/opencv/opencv_extra/pull/1113) ### 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 CMakepull/25080/head
parent
f084a229b4
commit
fa745553bf
7 changed files with 2398 additions and 0 deletions
@ -0,0 +1,113 @@ |
||||
#!/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 |
||||
|
||||
def lookAtMatrixCal(position, lookat, upVector): |
||||
tmp = position - lookat |
||||
norm = numpy.linalg.norm(tmp) |
||||
w = tmp / norm |
||||
tmp = numpy.cross(upVector, w) |
||||
norm = numpy.linalg.norm(tmp) |
||||
u = tmp / norm |
||||
v = numpy.cross(w, u) |
||||
res = numpy.array([ |
||||
[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] |
||||
], dtype=numpy.float32) |
||||
translate = numpy.array([ |
||||
[1.0, 0, 0, -position[0]], |
||||
[0, 1.0, 0, -position[1]], |
||||
[0, 0, 1.0, -position[2]], |
||||
[0, 0, 0, 1.0] |
||||
], dtype=numpy.float32) |
||||
res = numpy.matmul(res, translate) |
||||
return res |
||||
|
||||
class raster_test(NewOpenCVTests): |
||||
|
||||
def prepareData(self): |
||||
self.vertices = numpy.array([ |
||||
[ 2.0, 0.0, -2.0], |
||||
[ 0.0, -6.0, -2.0], |
||||
[-2.0, 0.0, -2.0], |
||||
[ 3.5, -1.0, -5.0], |
||||
[ 2.5, -2.5, -5.0], |
||||
[-1.0, 1.0, -5.0], |
||||
[-6.5, -1.0, -3.0], |
||||
[-2.5, -2.0, -3.0], |
||||
[ 1.0, 1.0, -5.0], |
||||
], dtype=numpy.float32) |
||||
|
||||
self.indices = numpy.array([ [0, 1, 2], [3, 4, 5], [6, 7, 8]], dtype=int) |
||||
|
||||
col1 = [ 185.0, 238.0, 217.0 ] |
||||
col2 = [ 238.0, 217.0, 185.0 ] |
||||
col3 = [ 238.0, 10.0, 150.0 ] |
||||
|
||||
self.colors = numpy.array([ |
||||
col1, col2, col3, |
||||
col2, col3, col1, |
||||
col3, col1, col2, |
||||
], dtype=numpy.float32) |
||||
|
||||
self.colors = self.colors / 255.0 |
||||
|
||||
self.zNear = 0.1 |
||||
self.zFar = 50.0 |
||||
self.fovy = 45.0 * math.pi / 180.0 |
||||
|
||||
position = numpy.array([0.0, 0.0, 5.0], dtype=numpy.float32) |
||||
lookat = numpy.array([0.0, 0.0, 0.0], dtype=numpy.float32) |
||||
upVector = numpy.array([0.0, 1.0, 0.0], dtype=numpy.float32) |
||||
self.cameraPose = lookAtMatrixCal(position, lookat, upVector) |
||||
|
||||
self.depth_buf = numpy.ones((240, 320), dtype=numpy.float32) * self.zFar |
||||
self.color_buf = numpy.zeros((240, 320, 3), dtype=numpy.float32) |
||||
|
||||
self.settings = cv.TriangleRasterizeSettings().setShadingType(cv.RASTERIZE_SHADING_SHADED) |
||||
self.settings = self.settings.setCullingMode(cv.RASTERIZE_CULLING_NONE) |
||||
|
||||
def compareResults(self, needRgb, needDepth): |
||||
if needDepth: |
||||
depth = self.get_sample('rendering/depth_image_Clipping_320x240_CullNone.png', cv.IMREAD_ANYDEPTH).astype(numpy.float32) |
||||
depthFactor = 1000.0 |
||||
diff = depth/depthFactor - self.depth_buf |
||||
norm = numpy.linalg.norm(diff) |
||||
self.assertLessEqual(norm, 356.0) |
||||
|
||||
if needRgb: |
||||
rgb = self.get_sample('rendering/example_image_Clipping_320x240_CullNone_Shaded.png', cv.IMREAD_ANYCOLOR) |
||||
diff = rgb/255.0 - self.color_buf |
||||
norm = numpy.linalg.norm(diff) |
||||
self.assertLessEqual(norm, 11.62) |
||||
|
||||
def test_rasterizeBoth(self): |
||||
self.prepareData() |
||||
self.color_buf, self.depth_buf = cv.triangleRasterize(self.vertices, self.indices, self.colors, self.color_buf, self.depth_buf, |
||||
self.cameraPose, self.fovy, self.zNear, self.zFar, self.settings) |
||||
self.compareResults(needRgb=True, needDepth=True) |
||||
|
||||
def test_rasterizeDepth(self): |
||||
self.prepareData() |
||||
self.depth_buf = cv.triangleRasterizeDepth(self.vertices, self.indices, self.depth_buf, |
||||
self.cameraPose, self.fovy, self.zNear, self.zFar, self.settings) |
||||
self.compareResults(needRgb=False, needDepth=True) |
||||
|
||||
def test_rasterizeColor(self): |
||||
self.prepareData() |
||||
self.color_buf = cv.triangleRasterizeColor(self.vertices, self.indices, self.colors, self.color_buf, |
||||
self.cameraPose, self.fovy, self.zNear, self.zFar, self.settings) |
||||
self.compareResults(needRgb=True, needDepth=False) |
||||
|
||||
if __name__ == '__main__': |
||||
NewOpenCVTests.bootstrap() |
@ -0,0 +1,344 @@ |
||||
// 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
|
||||
// cvtColor does not work with 1d Mats
|
||||
std::vector<Mat> xyz; |
||||
cv::split(colors, xyz); |
||||
cv::merge(std::vector<Mat>{xyz[2], xyz[1], xyz[0]}, colors); |
||||
} |
||||
|
||||
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
|
@ -0,0 +1,402 @@ |
||||
// 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 "precomp.hpp" |
||||
|
||||
namespace cv { |
||||
|
||||
TriangleRasterizeSettings::TriangleRasterizeSettings() |
||||
{ |
||||
shadingType = RASTERIZE_SHADING_SHADED; |
||||
cullingMode = RASTERIZE_CULLING_CW; |
||||
glCompatibleMode = RASTERIZE_COMPAT_DISABLED; |
||||
} |
||||
|
||||
static void drawTriangle(Vec4f verts[3], Vec3f colors[3], Mat& depthBuf, Mat& colorBuf, |
||||
TriangleRasterizeSettings settings) |
||||
{ |
||||
// this will be useful during refactoring
|
||||
// if there's gonna be more supported data types
|
||||
CV_DbgAssert(depthBuf.empty() || depthBuf.type() == CV_32FC1); |
||||
CV_DbgAssert(colorBuf.empty() || colorBuf.type() == CV_32FC3); |
||||
|
||||
// any of buffers can be empty
|
||||
int width = std::max(colorBuf.cols, depthBuf.cols); |
||||
int height = std::max(colorBuf.rows, depthBuf.rows); |
||||
|
||||
Point minPt(width, height), maxPt(0, 0); |
||||
for (int i = 0; i < 3; i++) |
||||
{ |
||||
// round down to cover the whole pixel
|
||||
int x = (int)(verts[i][0]), y = (int)(verts[i][1]); |
||||
minPt.x = std::min( x, minPt.x); |
||||
minPt.y = std::min( y, minPt.y); |
||||
maxPt.x = std::max(x + 1, maxPt.x); |
||||
maxPt.y = std::max(y + 1, maxPt.y); |
||||
} |
||||
|
||||
minPt.x = std::max(minPt.x, 0); maxPt.x = std::min(maxPt.x, width); |
||||
minPt.y = std::max(minPt.y, 0); maxPt.y = std::min(maxPt.y, height); |
||||
|
||||
Point2f a(verts[0][0], verts[0][1]), b(verts[1][0], verts[1][1]), c(verts[2][0], verts[2][1]); |
||||
Point2f bc = b - c, ac = a - c; |
||||
float d = ac.x*bc.y - ac.y*bc.x; |
||||
|
||||
// culling and degenerated triangle removal
|
||||
if ((settings.cullingMode == RASTERIZE_CULLING_CW && d <= 0) || |
||||
(settings.cullingMode == RASTERIZE_CULLING_CCW && d >= 0) || |
||||
(abs(d) < 1e-6)) |
||||
{ |
||||
return; |
||||
} |
||||
|
||||
float invd = 1.f / d; |
||||
Vec3f zinv { verts[0][2], verts[1][2], verts[2][2] }; |
||||
Vec3f w { verts[0][3], verts[1][3], verts[2][3] }; |
||||
|
||||
for (int y = minPt.y; y < maxPt.y; y++) |
||||
{ |
||||
for (int x = minPt.x; x < maxPt.x; x++) |
||||
{ |
||||
Point2f p(x + 0.5f, y + 0.5f), pc = p - c; |
||||
// barycentric coordinates
|
||||
Vec3f f; |
||||
f[0] = ( pc.x * bc.y - pc.y * bc.x) * invd; |
||||
f[1] = ( pc.y * ac.x - pc.x * ac.y) * invd; |
||||
f[2] = 1.f - f[0] - f[1]; |
||||
// if inside the triangle
|
||||
if ((f[0] >= 0) && (f[1] >= 0) && (f[2] >= 0)) |
||||
{ |
||||
bool update = false; |
||||
if (!depthBuf.empty()) |
||||
{ |
||||
float zCurrent = depthBuf.at<float>(height - 1 - y, x); |
||||
float zNew = f[0] * zinv[0] + f[1] * zinv[1] + f[2] * zinv[2]; |
||||
if (zNew < zCurrent) |
||||
{ |
||||
update = true; |
||||
depthBuf.at<float>(height - 1 - y, x) = zNew; |
||||
} |
||||
} |
||||
else // RASTERIZE_SHADING_WHITE
|
||||
{ |
||||
update = true; |
||||
} |
||||
|
||||
if (!colorBuf.empty() && update) |
||||
{ |
||||
Vec3f color; |
||||
if (settings.shadingType == RASTERIZE_SHADING_WHITE) |
||||
{ |
||||
color = { 1.f, 1.f, 1.f }; |
||||
} |
||||
else if (settings.shadingType == RASTERIZE_SHADING_FLAT) |
||||
{ |
||||
color = colors[0]; |
||||
} |
||||
else // TriangleShadingType::Shaded
|
||||
{ |
||||
float zInter = 1.0f / (f[0] * w[0] + f[1] * w[1] + f[2] * w[2]); |
||||
color = { 0, 0, 0 }; |
||||
for (int j = 0; j < 3; j++) |
||||
{ |
||||
color += (f[j] * w[j]) * colors[j]; |
||||
} |
||||
color *= zInter; |
||||
} |
||||
colorBuf.at<Vec3f>(height - 1 - y, x) = color; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
// values outside of [zNear, zFar] have to be restored
|
||||
// [0, 1] -> [zNear, zFar]
|
||||
static void linearizeDepth(const Mat& inbuf, const Mat& validMask, Mat outbuf, double zFar, double zNear) |
||||
{ |
||||
CV_Assert(inbuf.type() == CV_32FC1); |
||||
CV_Assert(validMask.type() == CV_8UC1); |
||||
CV_Assert(outbuf.type() == CV_32FC1); |
||||
CV_Assert(outbuf.size() == inbuf.size()); |
||||
|
||||
float scaleNear = (float)(1.0 / zNear); |
||||
float scaleFar = (float)(1.0 / zFar); |
||||
for (int y = 0; y < inbuf.rows; y++) |
||||
{ |
||||
const float* inp = inbuf.ptr<float>(y); |
||||
const uchar * validPtr = validMask.ptr<uchar>(y); |
||||
float * outp = outbuf.ptr<float>(y); |
||||
for (int x = 0; x < inbuf.cols; x++) |
||||
{ |
||||
if (validPtr[x]) |
||||
{ |
||||
float d = inp[x]; |
||||
// precision-optimized version of this:
|
||||
//float z = - zFar * zNear / (d * (zFar - zNear) - zFar);
|
||||
float z = 1.f / ((1.f - d) * scaleNear + d * scaleFar ); |
||||
outp[x] = z; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// [zNear, zFar] -> [0, 1]
|
||||
static void invertDepth(const Mat& inbuf, Mat& outbuf, Mat& validMask, double zNear, double zFar) |
||||
{ |
||||
CV_Assert(inbuf.type() == CV_32FC1); |
||||
outbuf.create(inbuf.size(), CV_32FC1); |
||||
validMask.create(inbuf.size(), CV_8UC1); |
||||
|
||||
float fNear = (float)zNear, fFar = (float)zFar; |
||||
float zadd = (float)(zFar / (zFar - zNear)); |
||||
float zmul = (float)(-zNear * zFar / (zFar - zNear)); |
||||
for (int y = 0; y < inbuf.rows; y++) |
||||
{ |
||||
const float * inp = inbuf.ptr<float>(y); |
||||
float * outp = outbuf.ptr<float>(y); |
||||
uchar * validPtr = validMask.ptr<uchar>(y); |
||||
for (int x = 0; x < inbuf.cols; x++) |
||||
{ |
||||
float z = inp[x]; |
||||
uchar m = (z >= fNear) && (z <= fFar); |
||||
z = std::max(std::min(z, fFar), fNear); |
||||
// precision-optimized version of this:
|
||||
// outp[x] = (z - zNear) / z * zFar / (zFar - zNear);
|
||||
outp[x] = zadd + zmul / z; |
||||
validPtr[x] = m; |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
static void triangleRasterizeInternal(InputArray _vertices, InputArray _indices, InputArray _colors, |
||||
Mat& colorBuf, Mat& depthBuf, |
||||
InputArray world2cam, double fovyRadians, double zNear, double zFar, |
||||
const TriangleRasterizeSettings& settings) |
||||
{ |
||||
CV_Assert(world2cam.type() == CV_32FC1 || world2cam.type() == CV_64FC1); |
||||
CV_Assert((world2cam.size() == Size {4, 3}) || (world2cam.size() == Size {4, 4})); |
||||
|
||||
CV_Assert((fovyRadians > 0) && (fovyRadians < CV_PI)); |
||||
CV_Assert(zNear > 0); |
||||
CV_Assert(zFar > zNear); |
||||
|
||||
Mat cpMat; |
||||
world2cam.getMat().convertTo(cpMat, CV_64FC1); |
||||
Matx44d camPoseMat = Matx44d::eye(); |
||||
for (int i = 0; i < 3; i++) |
||||
{ |
||||
for (int j = 0; j < 4; j++) |
||||
{ |
||||
camPoseMat(i, j) = cpMat.at<double>(i, j); |
||||
} |
||||
} |
||||
|
||||
if(_indices.empty()) |
||||
{ |
||||
return; |
||||
} |
||||
|
||||
CV_CheckFalse(_vertices.empty(), "No vertices provided along with indices array"); |
||||
|
||||
Mat vertices, colors, triangles; |
||||
int nVerts = 0, nColors = 0, nTriangles = 0; |
||||
|
||||
int vertexType = _vertices.type(); |
||||
CV_Assert(vertexType == CV_32FC1 || vertexType == CV_32FC3); |
||||
vertices = _vertices.getMat(); |
||||
// transform 3xN matrix to Nx3, except 3x3
|
||||
if ((_vertices.channels() == 1) && (_vertices.rows() == 3) && (_vertices.cols() != 3)) |
||||
{ |
||||
vertices = vertices.t(); |
||||
} |
||||
// This transposition is performed on 1xN matrix so it's almost free in terms of performance
|
||||
vertices = vertices.reshape(3, 1).t(); |
||||
nVerts = (int)vertices.total(); |
||||
|
||||
int indexType = _indices.type(); |
||||
CV_Assert(indexType == CV_32SC1 || indexType == CV_32SC3); |
||||
triangles = _indices.getMat(); |
||||
// transform 3xN matrix to Nx3, except 3x3
|
||||
if ((_indices.channels() == 1) && (_indices.rows() == 3) && (_indices.cols() != 3)) |
||||
{ |
||||
triangles = triangles.t(); |
||||
} |
||||
// This transposition is performed on 1xN matrix so it's almost free in terms of performance
|
||||
triangles = triangles.reshape(3, 1).t(); |
||||
nTriangles = (int)triangles.total(); |
||||
|
||||
if (!_colors.empty()) |
||||
{ |
||||
int colorType = _colors.type(); |
||||
CV_Assert(colorType == CV_32FC1 || colorType == CV_32FC3); |
||||
colors = _colors.getMat(); |
||||
// transform 3xN matrix to Nx3, except 3x3
|
||||
if ((_colors.channels() == 1) && (_colors.rows() == 3) && (_colors.cols() != 3)) |
||||
{ |
||||
colors = colors.t(); |
||||
} |
||||
colors = colors.reshape(3, 1).t(); |
||||
nColors = (int)colors.total(); |
||||
|
||||
CV_Assert(nColors == nVerts); |
||||
} |
||||
|
||||
// any of buffers can be empty
|
||||
Size imgSize {std::max(colorBuf.cols, depthBuf.cols), std::max(colorBuf.rows, depthBuf.rows)}; |
||||
|
||||
// world-to-camera coord system
|
||||
Matx44d lookAtMatrix = camPoseMat; |
||||
|
||||
double ys = 1.0 / std::tan(fovyRadians / 2); |
||||
double xs = ys / (double)imgSize.width * (double)imgSize.height; |
||||
double zz = (zNear + zFar) / (zNear - zFar); |
||||
double zw = 2.0 * zFar * zNear / (zNear - zFar); |
||||
|
||||
// camera to NDC: [-1, 1]^3
|
||||
Matx44d perspectMatrix (xs, 0, 0, 0, |
||||
0, ys, 0, 0, |
||||
0, 0, zz, zw, |
||||
0, 0, -1, 0); |
||||
|
||||
Matx44f mvpMatrix = perspectMatrix * lookAtMatrix; |
||||
|
||||
// vertex transform stage
|
||||
|
||||
Mat screenVertices(vertices.size(), CV_32FC4); |
||||
for (int i = 0; i < nVerts; i++) |
||||
{ |
||||
Vec3f vglobal = vertices.at<Vec3f>(i); |
||||
|
||||
Vec4f vndc = mvpMatrix * Vec4f(vglobal[0], vglobal[1], vglobal[2], 1.f); |
||||
|
||||
float invw = 1.f / vndc[3]; |
||||
Vec4f vdiv = {vndc[0] * invw, vndc[1] * invw, vndc[2] * invw, invw}; |
||||
|
||||
// [-1, 1]^3 => [0, width] x [0, height] x [0, 1]
|
||||
Vec4f vscreen = { |
||||
(vdiv[0] + 1.f) * 0.5f * (float)imgSize.width, |
||||
(vdiv[1] + 1.f) * 0.5f * (float)imgSize.height, |
||||
(vdiv[2] + 1.f) * 0.5f, |
||||
vdiv[3] |
||||
}; |
||||
|
||||
screenVertices.at<Vec4f>(i) = vscreen; |
||||
} |
||||
|
||||
// draw stage
|
||||
|
||||
for (int t = 0; t < nTriangles; t++) |
||||
{ |
||||
Vec3i tri = triangles.at<Vec3i>(t); |
||||
|
||||
Vec3f col[3]; |
||||
Vec4f ver[3]; |
||||
for (int i = 0; i < 3; i++) |
||||
{ |
||||
int idx = tri[i]; |
||||
CV_DbgAssert(idx >= 0 && idx < nVerts); |
||||
|
||||
col[i] = colors.empty() ? Vec3f::all(0) : colors.at<Vec3f>(idx); |
||||
ver[i] = screenVertices.at<Vec4f>(idx); |
||||
} |
||||
|
||||
drawTriangle(ver, col, depthBuf, colorBuf, settings); |
||||
} |
||||
} |
||||
|
||||
|
||||
void triangleRasterizeDepth(InputArray _vertices, InputArray _indices, InputOutputArray _depthBuf, |
||||
InputArray world2cam, double fovY, double zNear, double zFar, |
||||
const TriangleRasterizeSettings& settings) |
||||
{ |
||||
CV_Assert(!_depthBuf.empty()); |
||||
CV_Assert(_depthBuf.type() == CV_32FC1); |
||||
|
||||
Mat emptyColorBuf; |
||||
// out-of-range values from user-provided depthBuf should not be altered, let's mark them
|
||||
Mat_<uchar> validMask; |
||||
Mat depthBuf; |
||||
if (settings.glCompatibleMode == RASTERIZE_COMPAT_INVDEPTH) |
||||
{ |
||||
depthBuf = _depthBuf.getMat(); |
||||
} |
||||
else // RASTERIZE_COMPAT_DISABLED
|
||||
{ |
||||
invertDepth(_depthBuf.getMat(), depthBuf, validMask, zNear, zFar); |
||||
} |
||||
|
||||
triangleRasterizeInternal(_vertices, _indices, noArray(), emptyColorBuf, depthBuf, world2cam, fovY, zNear, zFar, settings); |
||||
|
||||
if (settings.glCompatibleMode == RASTERIZE_COMPAT_DISABLED) |
||||
{ |
||||
linearizeDepth(depthBuf, validMask, _depthBuf.getMat(), zFar, zNear); |
||||
} |
||||
} |
||||
|
||||
void triangleRasterizeColor(InputArray _vertices, InputArray _indices, InputArray _colors, InputOutputArray _colorBuf, |
||||
InputArray world2cam, double fovY, double zNear, double zFar, |
||||
const TriangleRasterizeSettings& settings) |
||||
{ |
||||
CV_Assert(!_colorBuf.empty()); |
||||
CV_Assert(_colorBuf.type() == CV_32FC3); |
||||
Mat colorBuf = _colorBuf.getMat(); |
||||
|
||||
Mat depthBuf; |
||||
if (_colors.empty()) |
||||
{ |
||||
// full white shading does not require depth test
|
||||
CV_Assert(settings.shadingType == RASTERIZE_SHADING_WHITE); |
||||
} |
||||
else |
||||
{ |
||||
// internal depth buffer is not exposed outside
|
||||
depthBuf.create(_colorBuf.size(), CV_32FC1); |
||||
depthBuf.setTo(1.0); |
||||
} |
||||
|
||||
triangleRasterizeInternal(_vertices, _indices, _colors, colorBuf, depthBuf, world2cam, fovY, zNear, zFar, settings); |
||||
} |
||||
|
||||
void triangleRasterize(InputArray _vertices, InputArray _indices, InputArray _colors, |
||||
InputOutputArray _colorBuffer, InputOutputArray _depthBuffer, |
||||
InputArray world2cam, double fovyRadians, double zNear, double zFar, |
||||
const TriangleRasterizeSettings& settings) |
||||
{ |
||||
if (_colors.empty()) |
||||
{ |
||||
CV_Assert(settings.shadingType == RASTERIZE_SHADING_WHITE); |
||||
} |
||||
|
||||
CV_Assert(!_colorBuffer.empty()); |
||||
CV_Assert(_colorBuffer.type() == CV_32FC3); |
||||
CV_Assert(!_depthBuffer.empty()); |
||||
CV_Assert(_depthBuffer.type() == CV_32FC1); |
||||
|
||||
CV_Assert(_depthBuffer.size() == _colorBuffer.size()); |
||||
|
||||
Mat colorBuf = _colorBuffer.getMat(); |
||||
|
||||
// out-of-range values from user-provided depthBuf should not be altered, let's mark them
|
||||
Mat_<uchar> validMask; |
||||
Mat depthBuf; |
||||
if (settings.glCompatibleMode == RASTERIZE_COMPAT_INVDEPTH) |
||||
{ |
||||
depthBuf = _depthBuffer.getMat(); |
||||
} |
||||
else // RASTERIZE_COMPAT_DISABLED
|
||||
{ |
||||
invertDepth(_depthBuffer.getMat(), depthBuf, validMask, zNear, zFar); |
||||
} |
||||
|
||||
triangleRasterizeInternal(_vertices, _indices, _colors, colorBuf, depthBuf, world2cam, fovyRadians, zNear, zFar, settings); |
||||
|
||||
if (settings.glCompatibleMode == RASTERIZE_COMPAT_DISABLED) |
||||
{ |
||||
linearizeDepth(depthBuf, validMask, _depthBuffer.getMat(), zFar, zNear); |
||||
} |
||||
} |
||||
} // namespace cv
|
@ -0,0 +1,948 @@ |
||||
// 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 "test_precomp.hpp" |
||||
|
||||
namespace opencv_test { namespace { |
||||
using namespace cv; |
||||
|
||||
// that was easier than using CV_ENUM() macro
|
||||
namespace |
||||
{ |
||||
using namespace cv; |
||||
struct CullingModeEnum |
||||
{ |
||||
static const std::array<TriangleCullingMode, 3> vals; |
||||
static const std::array<std::string, 3> svals; |
||||
|
||||
CullingModeEnum(TriangleCullingMode v = RASTERIZE_CULLING_NONE) : val(v) {} |
||||
operator TriangleCullingMode() 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<CullingModeEnum> all() |
||||
{ |
||||
return ::testing::Values(CullingModeEnum(vals[0]), |
||||
CullingModeEnum(vals[1]), |
||||
CullingModeEnum(vals[2])); |
||||
} |
||||
|
||||
private: |
||||
TriangleCullingMode val; |
||||
}; |
||||
|
||||
const std::array<TriangleCullingMode, 3> CullingModeEnum::vals |
||||
{ |
||||
RASTERIZE_CULLING_NONE, |
||||
RASTERIZE_CULLING_CW, |
||||
RASTERIZE_CULLING_CCW |
||||
}; |
||||
const std::array<std::string, 3> CullingModeEnum::svals |
||||
{ |
||||
std::string("None"), |
||||
std::string("CW"), |
||||
std::string("CCW") |
||||
}; |
||||
|
||||
static inline void PrintTo(const CullingModeEnum &t, std::ostream *os) { t.PrintTo(os); } |
||||
} |
||||
|
||||
// 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); } |
||||
} |
||||
|
||||
|
||||
enum class ModelType |
||||
{ |
||||
Empty = 0, |
||||
File = 1, |
||||
Clipping = 2, |
||||
Color = 3, |
||||
Centered = 4 |
||||
}; |
||||
|
||||
// that was easier than using CV_ENUM() macro
|
||||
namespace |
||||
{ |
||||
using namespace cv; |
||||
struct ModelTypeEnum |
||||
{ |
||||
static const std::array<ModelType, 5> vals; |
||||
static const std::array<std::string, 5> svals; |
||||
|
||||
ModelTypeEnum(ModelType v = ModelType::Empty) : val(v) {} |
||||
operator ModelType() 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<ModelTypeEnum> all() |
||||
{ |
||||
return ::testing::Values(ModelTypeEnum(vals[0]), |
||||
ModelTypeEnum(vals[1]), |
||||
ModelTypeEnum(vals[2]), |
||||
ModelTypeEnum(vals[3]), |
||||
ModelTypeEnum(vals[4])); |
||||
} |
||||
|
||||
private: |
||||
ModelType val; |
||||
}; |
||||
|
||||
const std::array<ModelType, 5> ModelTypeEnum::vals |
||||
{ |
||||
ModelType::Empty, |
||||
ModelType::File, |
||||
ModelType::Clipping, |
||||
ModelType::Color, |
||||
ModelType::Centered |
||||
}; |
||||
const std::array<std::string, 5> ModelTypeEnum::svals |
||||
{ |
||||
std::string("Empty"), |
||||
std::string("File"), |
||||
std::string("Clipping"), |
||||
std::string("Color"), |
||||
std::string("Centered") |
||||
}; |
||||
|
||||
static inline void PrintTo(const ModelTypeEnum &t, std::ostream *os) { t.PrintTo(os); } |
||||
} |
||||
|
||||
template<typename T> |
||||
std::string printEnum(T v) |
||||
{ |
||||
std::ostringstream ss; |
||||
v.PrintTo(&ss); |
||||
return ss.str(); |
||||
} |
||||
|
||||
|
||||
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; |
||||
} |
||||
|
||||
class ModelData |
||||
{ |
||||
public: |
||||
ModelData(ModelType type = ModelType::Empty) |
||||
{ |
||||
switch (type) |
||||
{ |
||||
case ModelType::Empty: |
||||
{ |
||||
position = Vec3d(0.0, 0.0, 0.0); |
||||
lookat = Vec3d(0.0, 0.0, 0.0); |
||||
upVector = Vec3d(0.0, 1.0, 0.0); |
||||
|
||||
fovy = 45.0; |
||||
|
||||
vertices = std::vector<Vec3f>(4, {2.0f, 0, -2.0f}); |
||||
colors = std::vector<Vec3f>(4, {0, 0, 1.0f}); |
||||
indices = { }; |
||||
} |
||||
break; |
||||
case ModelType::File: |
||||
{ |
||||
string objectPath = findDataFile("viz/dragon.ply"); |
||||
|
||||
position = Vec3d( 1.9, 0.4, 1.3); |
||||
lookat = Vec3d( 0.0, 0.0, 0.0); |
||||
upVector = Vec3d( 0.0, 1.0, 0.0); |
||||
|
||||
fovy = 45.0; |
||||
|
||||
getModelOnce(objectPath, vertices, indices, colors); |
||||
} |
||||
break; |
||||
case ModelType::Clipping: |
||||
{ |
||||
position = Vec3d(0.0, 0.0, 5.0); |
||||
lookat = Vec3d(0.0, 0.0, 0.0); |
||||
upVector = Vec3d(0.0, 1.0, 0.0); |
||||
|
||||
fovy = 45.0; |
||||
|
||||
vertices = |
||||
{ |
||||
{ 2.0, 0.0, -2.0}, { 0.0, -6.0, -2.0}, {-2.0, 0.0, -2.0}, |
||||
{ 3.5, -1.0, -5.0}, { 2.5, -2.5, -5.0}, {-1.0, 1.0, -5.0}, |
||||
{-6.5, -1.0, -3.0}, {-2.5, -2.0, -3.0}, { 1.0, 1.0, -5.0}, |
||||
}; |
||||
|
||||
indices = { {0, 1, 2}, {3, 4, 5}, {6, 7, 8} }; |
||||
|
||||
Vec3f col1(217.0, 238.0, 185.0); |
||||
Vec3f col2(185.0, 217.0, 238.0); |
||||
Vec3f col3(150.0, 10.0, 238.0); |
||||
|
||||
col1 *= (1.f / 255.f); |
||||
col2 *= (1.f / 255.f); |
||||
col3 *= (1.f / 255.f); |
||||
|
||||
colors = |
||||
{ |
||||
col1, col2, col3, |
||||
col2, col3, col1, |
||||
col3, col1, col2, |
||||
}; |
||||
} |
||||
break; |
||||
case ModelType::Centered: |
||||
{ |
||||
position = Vec3d(0.0, 0.0, 5.0); |
||||
lookat = Vec3d(0.0, 0.0, 0.0); |
||||
upVector = Vec3d(0.0, 1.0, 0.0); |
||||
|
||||
fovy = 45.0; |
||||
|
||||
vertices = |
||||
{ |
||||
{ 2.0, 0.0, -2.0}, { 0.0, -2.0, -2.0}, {-2.0, 0.0, -2.0}, |
||||
{ 3.5, -1.0, -5.0}, { 2.5, -1.5, -5.0}, {-1.0, 0.5, -5.0}, |
||||
}; |
||||
|
||||
indices = { {0, 1, 2}, {3, 4, 5} }; |
||||
|
||||
Vec3f col1(217.0, 238.0, 185.0); |
||||
Vec3f col2(185.0, 217.0, 238.0); |
||||
|
||||
col1 *= (1.f / 255.f); |
||||
col2 *= (1.f / 255.f); |
||||
|
||||
colors = |
||||
{ |
||||
col1, col2, col1, |
||||
col2, col1, col2, |
||||
}; |
||||
} |
||||
break; |
||||
case ModelType::Color: |
||||
{ |
||||
position = Vec3d(0.0, 0.0, 5.0); |
||||
lookat = Vec3d(0.0, 0.0, 0.0); |
||||
upVector = Vec3d(0.0, 1.0, 0.0); |
||||
|
||||
fovy = 60.0; |
||||
|
||||
vertices = |
||||
{ |
||||
{ 2.0, 0.0, -2.0}, |
||||
{ 0.0, 2.0, -3.0}, |
||||
{-2.0, 0.0, -2.0}, |
||||
{ 0.0, -2.0, 1.0}, |
||||
}; |
||||
|
||||
indices = { {0, 1, 2}, {0, 2, 3} }; |
||||
|
||||
colors = |
||||
{ |
||||
{ 0.0f, 0.0f, 1.0f}, |
||||
{ 0.0f, 1.0f, 0.0f}, |
||||
{ 1.0f, 0.0f, 0.0f}, |
||||
{ 0.0f, 1.0f, 0.0f}, |
||||
}; |
||||
} |
||||
break; |
||||
|
||||
default: |
||||
CV_Error(Error::StsBadArg, "Unknown model type"); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
Vec3d position; |
||||
Vec3d lookat; |
||||
Vec3d upVector; |
||||
|
||||
double fovy; |
||||
|
||||
std::vector<Vec3f> vertices; |
||||
std::vector<Vec3i> indices; |
||||
std::vector<Vec3f> colors; |
||||
}; |
||||
|
||||
|
||||
void compareDepth(const cv::Mat& gt, const cv::Mat& mat, cv::Mat& diff, double zFar, double scale, |
||||
double maskThreshold, double normInfThreshold, double normL2Threshold) |
||||
{ |
||||
ASSERT_EQ(CV_16UC1, gt.type()); |
||||
ASSERT_EQ(CV_16UC1, mat.type()); |
||||
ASSERT_EQ(gt.size(), mat.size()); |
||||
|
||||
Mat gtMask = gt < zFar*scale; |
||||
Mat matMask = mat < zFar*scale; |
||||
|
||||
Mat diffMask = gtMask != matMask; |
||||
int nzDepthDiff = cv::countNonZero(diffMask); |
||||
EXPECT_LE(nzDepthDiff, maskThreshold); |
||||
|
||||
Mat jointMask = gtMask & matMask; |
||||
int nzJointMask = cv::countNonZero(jointMask); |
||||
double normInfDepth = cv::norm(gt, mat, cv::NORM_INF, jointMask); |
||||
EXPECT_LE(normInfDepth, normInfThreshold); |
||||
double normL2Depth = nzJointMask ? (cv::norm(gt, mat, cv::NORM_L2, jointMask) / nzJointMask) : 0; |
||||
EXPECT_LE(normL2Depth, normL2Threshold); |
||||
|
||||
// add --test_debug to output differences
|
||||
if (debugLevel > 0) |
||||
{ |
||||
std::cout << "nzDepthDiff: " << nzDepthDiff << " vs " << maskThreshold << std::endl; |
||||
std::cout << "normInfDepth: " << normInfDepth << " vs " << normInfThreshold << std::endl; |
||||
std::cout << "normL2Depth: " << normL2Depth << " vs " << normL2Threshold << std::endl; |
||||
} |
||||
|
||||
diff = (gt - mat) + (1 << 15); |
||||
} |
||||
|
||||
|
||||
void compareRGB(const cv::Mat& gt, const cv::Mat& mat, cv::Mat& diff, double normInfThreshold, double normL2Threshold) |
||||
{ |
||||
ASSERT_EQ(CV_32FC3, gt.type()); |
||||
ASSERT_EQ(CV_32FC3, mat.type()); |
||||
ASSERT_EQ(gt.size(), mat.size()); |
||||
|
||||
double normInfRgb = cv::norm(gt, mat, cv::NORM_INF); |
||||
EXPECT_LE(normInfRgb, normInfThreshold); |
||||
double normL2Rgb = cv::norm(gt, mat, cv::NORM_L2) / gt.total(); |
||||
EXPECT_LE(normL2Rgb, normL2Threshold); |
||||
// add --test_debug to output differences
|
||||
if (debugLevel > 0) |
||||
{ |
||||
std::cout << "normInfRgb: " << normInfRgb << " vs " << normInfThreshold << std::endl; |
||||
std::cout << "normL2Rgb: " << normL2Rgb << " vs " << normL2Threshold << std::endl; |
||||
} |
||||
diff = (gt - mat) * 0.5 + 0.5; |
||||
} |
||||
|
||||
|
||||
struct RenderTestThresholds |
||||
{ |
||||
RenderTestThresholds( |
||||
double _rgbInfThreshold, |
||||
double _rgbL2Threshold, |
||||
double _depthMaskThreshold, |
||||
double _depthInfThreshold, |
||||
double _depthL2Threshold) : |
||||
rgbInfThreshold(_rgbInfThreshold), |
||||
rgbL2Threshold(_rgbL2Threshold), |
||||
depthMaskThreshold(_depthMaskThreshold), |
||||
depthInfThreshold(_depthInfThreshold), |
||||
depthL2Threshold(_depthL2Threshold) |
||||
{ } |
||||
|
||||
double rgbInfThreshold; |
||||
double rgbL2Threshold; |
||||
double depthMaskThreshold; |
||||
double depthInfThreshold; |
||||
double depthL2Threshold; |
||||
}; |
||||
|
||||
// resolution, shading type, culling mode, model type, float type, index type
|
||||
typedef std::tuple<std::tuple<int, int>, ShadingTypeEnum, CullingModeEnum, ModelTypeEnum, MatDepth, MatDepth> RenderTestParamType; |
||||
|
||||
class RenderingTest : public ::testing::TestWithParam<RenderTestParamType> |
||||
{ |
||||
protected: |
||||
void SetUp() override |
||||
{ |
||||
params = GetParam(); |
||||
auto wh = std::get<0>(params); |
||||
width = std::get<0>(wh); |
||||
height = std::get<1>(wh); |
||||
shadingType = std::get<1>(params); |
||||
cullingMode = std::get<2>(params); |
||||
modelType = std::get<3>(params); |
||||
modelData = ModelData(modelType); |
||||
ftype = std::get<4>(params); |
||||
itype = std::get<5>(params); |
||||
|
||||
zNear = 0.1, zFar = 50.0; |
||||
depthScale = 1000.0; |
||||
|
||||
depth_buf = Mat(height, width, ftype, zFar); |
||||
color_buf = Mat(height, width, CV_MAKETYPE(ftype, 3), Scalar::all(0)); |
||||
|
||||
cameraPose = lookAtMatrixCal(modelData.position, modelData.lookat, modelData.upVector); |
||||
fovYradians = modelData.fovy * (CV_PI / 180.0); |
||||
|
||||
verts = Mat(modelData.vertices); |
||||
verts.convertTo(verts, ftype); |
||||
|
||||
if (shadingType != RASTERIZE_SHADING_WHITE) |
||||
{ |
||||
colors = Mat(modelData.colors); |
||||
colors.convertTo(colors, ftype); |
||||
// let vertices be in BGR format to avoid later color conversions
|
||||
// cvtColor does not work with 1d Mats
|
||||
std::vector<Mat> xyz; |
||||
cv::split(colors, xyz); |
||||
cv::merge(std::vector<Mat>{xyz[2], xyz[1], xyz[0]}, colors); |
||||
} |
||||
|
||||
indices = Mat(modelData.indices); |
||||
if (itype != CV_32S) |
||||
{ |
||||
indices.convertTo(indices, itype); |
||||
} |
||||
|
||||
settings = TriangleRasterizeSettings().setCullingMode(cullingMode).setShadingType(shadingType); |
||||
|
||||
triangleRasterize(verts, indices, colors, color_buf, depth_buf, |
||||
cameraPose, fovYradians, zNear, zFar, settings); |
||||
} |
||||
|
||||
public: |
||||
RenderTestParamType params; |
||||
int width, height; |
||||
double zNear, zFar, depthScale; |
||||
|
||||
Mat depth_buf, color_buf; |
||||
|
||||
Mat verts, colors, indices; |
||||
Matx44d cameraPose; |
||||
double fovYradians; |
||||
TriangleRasterizeSettings settings; |
||||
|
||||
ModelData modelData; |
||||
ModelTypeEnum modelType; |
||||
ShadingTypeEnum shadingType; |
||||
CullingModeEnum cullingMode; |
||||
int ftype, itype; |
||||
}; |
||||
|
||||
|
||||
// depth-only or RGB-only rendering should produce the same result as usual rendering
|
||||
TEST_P(RenderingTest, noArrays) |
||||
{ |
||||
Mat depthOnly(height, width, ftype, zFar); |
||||
Mat colorOnly(height, width, CV_MAKETYPE(ftype, 3), Scalar::all(0)); |
||||
|
||||
triangleRasterizeDepth(verts, indices, depthOnly, cameraPose, fovYradians, zNear, zFar, settings); |
||||
triangleRasterizeColor(verts, indices, colors, colorOnly, cameraPose, fovYradians, zNear, zFar, settings); |
||||
|
||||
Mat rgbDiff, depthDiff; |
||||
compareRGB(color_buf, colorOnly, rgbDiff, 0, 0); |
||||
depth_buf.convertTo(depth_buf, CV_16U, depthScale); |
||||
depthOnly.convertTo(depthOnly, CV_16U, depthScale); |
||||
compareDepth(depth_buf, depthOnly, depthDiff, zFar, depthScale, 0, 0, 0); |
||||
|
||||
// add --test_debug to output resulting images
|
||||
if (debugLevel > 0) |
||||
{ |
||||
std::string modelName = printEnum(modelType); |
||||
std::string shadingName = printEnum(shadingType); |
||||
std::string cullingName = printEnum(cullingMode); |
||||
std::string suffix = cv::format("%s_%dx%d_Cull%s", modelName.c_str(), width, height, cullingName.c_str()); |
||||
|
||||
std::string outColorPath = "noarray_color_image_" + suffix + "_" + shadingName + ".png"; |
||||
std::string outDepthPath = "noarray_depth_image_" + suffix + "_" + shadingName + ".png"; |
||||
|
||||
imwrite(outColorPath, color_buf * 255.f); |
||||
imwrite(outDepthPath, depth_buf); |
||||
imwrite("diff_" + outColorPath, rgbDiff * 255.f); |
||||
imwrite("diff_" + outDepthPath, depthDiff); |
||||
} |
||||
} |
||||
|
||||
|
||||
// passing the same parameters in float should give the same result
|
||||
TEST_P(RenderingTest, floatParams) |
||||
{ |
||||
Mat depth_buf2(height, width, ftype, zFar); |
||||
Mat color_buf2(height, width, CV_MAKETYPE(ftype, 3), Scalar::all(0)); |
||||
|
||||
// cameraPose can also be float, checking it
|
||||
triangleRasterize(verts, indices, colors, color_buf2, depth_buf2, |
||||
Matx44f(cameraPose), (float)fovYradians, (float)zNear, (float)zFar, settings); |
||||
|
||||
RenderTestThresholds thr(0, 0, 0, 0, 0); |
||||
switch (modelType) |
||||
{ |
||||
case ModelType::Empty: break; |
||||
case ModelType::Color: break; |
||||
case ModelType::Clipping: |
||||
if (width == 320 && height == 240 && shadingType == RASTERIZE_SHADING_FLAT && cullingMode == RASTERIZE_CULLING_CW) |
||||
{ |
||||
thr.depthInfThreshold = 1; |
||||
thr.depthL2Threshold = 0.00127; |
||||
} |
||||
else if (width == 320 && height == 240 && shadingType == RASTERIZE_SHADING_SHADED && cullingMode == RASTERIZE_CULLING_NONE) |
||||
{ |
||||
thr.rgbInfThreshold = 3e-7; |
||||
thr.rgbL2Threshold = 1.86e-10; |
||||
thr.depthInfThreshold = 1; |
||||
thr.depthL2Threshold = 0.000406; |
||||
} |
||||
else if (width == 256 && height == 256 && shadingType == RASTERIZE_SHADING_SHADED && cullingMode == RASTERIZE_CULLING_CW) |
||||
{ |
||||
thr.rgbInfThreshold = 2.39e-07; |
||||
thr.rgbL2Threshold = 1.86e-10; |
||||
thr.depthInfThreshold = 1; |
||||
thr.depthL2Threshold = 0.0016; |
||||
} |
||||
else if (width == 256 && height == 256 && shadingType == RASTERIZE_SHADING_FLAT && cullingMode == RASTERIZE_CULLING_CCW) |
||||
{ |
||||
thr.rgbInfThreshold = 0.934; |
||||
thr.rgbL2Threshold = 0.000102; |
||||
thr.depthMaskThreshold = 21; |
||||
} |
||||
else if (width == 640 && height == 480 && shadingType == RASTERIZE_SHADING_WHITE && cullingMode == RASTERIZE_CULLING_NONE) |
||||
{ |
||||
thr.rgbL2Threshold = 1; |
||||
thr.depthInfThreshold = 1; |
||||
thr.depthL2Threshold = 0.000248; |
||||
} |
||||
else if (width == 700 && height == 700 && shadingType == RASTERIZE_SHADING_FLAT && cullingMode == RASTERIZE_CULLING_CCW) |
||||
{ |
||||
thr.rgbInfThreshold = 0.934; |
||||
thr.rgbL2Threshold = 3.18e-5; |
||||
thr.depthMaskThreshold = 114; |
||||
} |
||||
break; |
||||
case ModelType::File: |
||||
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; |
||||
} |
||||
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; |
||||
} |
||||
else if (width == 700 && height == 700 && shadingType == RASTERIZE_SHADING_WHITE && cullingMode == RASTERIZE_CULLING_NONE) |
||||
{ |
||||
thr.depthL2Threshold = 0.00012; |
||||
} |
||||
break; |
||||
case ModelType::Centered: |
||||
if (shadingType == RASTERIZE_SHADING_SHADED && cullingMode != RASTERIZE_CULLING_CW) |
||||
{ |
||||
thr.rgbInfThreshold = 3.58e-07; |
||||
thr.rgbL2Threshold = 1.51e-10; |
||||
} |
||||
break; |
||||
} |
||||
|
||||
Mat rgbDiff, depthDiff; |
||||
compareRGB(color_buf, color_buf2, rgbDiff, thr.rgbInfThreshold, thr.rgbL2Threshold); |
||||
depth_buf.convertTo(depth_buf, CV_16U, depthScale); |
||||
depth_buf2.convertTo(depth_buf2, CV_16U, depthScale); |
||||
compareDepth(depth_buf, depth_buf2, depthDiff, zFar, depthScale, thr.depthMaskThreshold, thr.depthInfThreshold, thr.depthL2Threshold); |
||||
|
||||
// add --test_debug to output resulting images
|
||||
if (debugLevel > 0) |
||||
{ |
||||
std::string modelName = printEnum(modelType); |
||||
std::string shadingName = printEnum(shadingType); |
||||
std::string cullingName = printEnum(cullingMode); |
||||
std::string suffix = cv::format("%s_%dx%d_Cull%s", modelName.c_str(), width, height, cullingName.c_str()); |
||||
|
||||
std::string outColorPath = "float_color_image_" + suffix + "_" + shadingName + ".png"; |
||||
std::string outDepthPath = "float_depth_image_" + suffix + "_" + shadingName + ".png"; |
||||
|
||||
imwrite(outColorPath, color_buf * 255.f); |
||||
imwrite(outDepthPath, depth_buf); |
||||
imwrite("diff_" + outColorPath, rgbDiff * 255.f); |
||||
imwrite("diff_" + outDepthPath, depthDiff); |
||||
} |
||||
} |
||||
|
||||
|
||||
// some culling options produce the same pictures, let's join them
|
||||
TriangleCullingMode findSameCulling(ModelType modelType, TriangleShadingType shadingType, TriangleCullingMode cullingMode, bool forRgb) |
||||
{ |
||||
TriangleCullingMode sameCullingMode = cullingMode; |
||||
|
||||
if ((modelType == ModelType::Centered && cullingMode == RASTERIZE_CULLING_CCW) || |
||||
(modelType == ModelType::Color && cullingMode == RASTERIZE_CULLING_CW) || |
||||
(modelType == ModelType::File && shadingType == RASTERIZE_SHADING_WHITE && forRgb) || |
||||
(modelType == ModelType::File && cullingMode == RASTERIZE_CULLING_CW)) |
||||
{ |
||||
sameCullingMode = RASTERIZE_CULLING_NONE; |
||||
} |
||||
|
||||
return sameCullingMode; |
||||
} |
||||
|
||||
// compare rendering results to the ones produced by samples/opengl/opengl_testdata_generator app
|
||||
TEST_P(RenderingTest, accuracy) |
||||
{ |
||||
depth_buf.convertTo(depth_buf, CV_16U, depthScale); |
||||
|
||||
if (modelType == ModelType::Empty || |
||||
(modelType == ModelType::Centered && cullingMode == RASTERIZE_CULLING_CW) || |
||||
(modelType == ModelType::Color && cullingMode == RASTERIZE_CULLING_CCW)) |
||||
{ |
||||
// empty image case
|
||||
EXPECT_EQ(0, cv::norm(color_buf, NORM_INF)); |
||||
|
||||
Mat depthDiff; |
||||
absdiff(depth_buf, Scalar(zFar * depthScale), depthDiff); |
||||
EXPECT_EQ(0, cv::norm(depthDiff, cv::NORM_INF)); |
||||
} |
||||
else |
||||
{ |
||||
RenderTestThresholds thr(0, 0, 0, 0, 0); |
||||
switch (modelType) |
||||
{ |
||||
case ModelType::Centered: |
||||
if (shadingType == RASTERIZE_SHADING_SHADED) |
||||
{ |
||||
thr.rgbInfThreshold = 0.00218; |
||||
thr.rgbL2Threshold = 2.85e-06; |
||||
} |
||||
break; |
||||
case ModelType::Clipping: |
||||
if (width == 320 && height == 240 && shadingType == RASTERIZE_SHADING_FLAT && cullingMode == RASTERIZE_CULLING_CW) |
||||
{ |
||||
thr.depthInfThreshold = 1; |
||||
thr.depthL2Threshold = 0.0016; |
||||
} |
||||
else if (width == 320 && height == 240 && shadingType == RASTERIZE_SHADING_SHADED && cullingMode == RASTERIZE_CULLING_NONE) |
||||
{ |
||||
thr.rgbInfThreshold = 0.934; |
||||
thr.rgbL2Threshold = 8.03E-05; |
||||
thr.depthMaskThreshold = 23; |
||||
thr.depthInfThreshold = 1; |
||||
thr.depthL2Threshold = 0.000544; |
||||
} |
||||
else if (width == 256 && height == 256 && shadingType == RASTERIZE_SHADING_SHADED && cullingMode == RASTERIZE_CULLING_CW) |
||||
{ |
||||
thr.rgbInfThreshold = 0.0022; |
||||
thr.rgbL2Threshold = 2.54E-06; |
||||
thr.depthInfThreshold = 1; |
||||
thr.depthL2Threshold = 0.00175; |
||||
} |
||||
else if (width == 256 && height == 256 && shadingType == RASTERIZE_SHADING_FLAT && cullingMode == RASTERIZE_CULLING_CCW) |
||||
{ |
||||
thr.rgbInfThreshold = 0.934; |
||||
thr.rgbL2Threshold = 0.000102; |
||||
thr.depthMaskThreshold = 21; |
||||
} |
||||
else if (width == 640 && height == 480 && shadingType == RASTERIZE_SHADING_WHITE && cullingMode == RASTERIZE_CULLING_NONE) |
||||
{ |
||||
thr.rgbInfThreshold = 1; |
||||
thr.rgbL2Threshold = 3.95E-05; |
||||
thr.depthMaskThreshold = 49; |
||||
thr.depthInfThreshold = 1; |
||||
thr.depthL2Threshold = 0.000269; |
||||
} |
||||
else if (width == 700 && height == 700 && shadingType == RASTERIZE_SHADING_FLAT && cullingMode == RASTERIZE_CULLING_CCW) |
||||
{ |
||||
thr.rgbInfThreshold = 0.934; |
||||
thr.rgbL2Threshold = 3.27e-5; |
||||
thr.depthMaskThreshold = 121; |
||||
} |
||||
break; |
||||
case ModelType::Color: |
||||
thr.depthInfThreshold = 1; |
||||
if (width == 320 && height == 240) |
||||
{ |
||||
thr.depthL2Threshold = 0.000989; |
||||
} |
||||
else if (width == 256 && height == 256) |
||||
{ |
||||
thr.depthL2Threshold = 0.000785; |
||||
} |
||||
if (shadingType == RASTERIZE_SHADING_SHADED) |
||||
{ |
||||
thr.rgbInfThreshold = 0.0022; |
||||
thr.rgbL2Threshold = 3.13e-06; |
||||
} |
||||
break; |
||||
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.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.depthInfThreshold = 258; |
||||
thr.depthL2Threshold = 0.00142; |
||||
} |
||||
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.depthInfThreshold = 258; |
||||
thr.depthL2Threshold = 0.00142; |
||||
} |
||||
break; |
||||
default: |
||||
break; |
||||
} |
||||
|
||||
CullingModeEnum cullingModeRgb = findSameCulling(modelType, shadingType, cullingMode, true); |
||||
CullingModeEnum cullingModeDepth = findSameCulling(modelType, shadingType, cullingMode, false); |
||||
|
||||
std::string modelName = printEnum(modelType); |
||||
std::string shadingName = printEnum(shadingType); |
||||
std::string cullingName = printEnum(cullingMode); |
||||
std::string cullingRgbName = printEnum(cullingModeRgb); |
||||
std::string cullingDepthName = printEnum(cullingModeDepth); |
||||
|
||||
std::string path = findDataDirectory("rendering"); |
||||
std::string suffix = cv::format("%s_%dx%d_Cull%s", modelName.c_str(), width, height, cullingName.c_str()); |
||||
std::string suffixRgb = cv::format("%s_%dx%d_Cull%s", modelName.c_str(), width, height, cullingRgbName.c_str()); |
||||
std::string suffixDepth = cv::format("%s_%dx%d_Cull%s", modelName.c_str(), width, height, cullingDepthName.c_str()); |
||||
std::string gtPathColor = path + "/example_image_" + suffixRgb + "_" + shadingName + ".png"; |
||||
std::string gtPathDepth = path + "/depth_image_" + suffixDepth + ".png"; |
||||
|
||||
Mat rgbDiff, depthDiff; |
||||
Mat groundTruthColor = imread(gtPathColor); |
||||
groundTruthColor.convertTo(groundTruthColor, CV_32F, (1.f / 255.f)); |
||||
compareRGB(groundTruthColor, color_buf, rgbDiff, thr.rgbInfThreshold, thr.rgbL2Threshold); |
||||
|
||||
Mat groundTruthDepth = imread(gtPathDepth, cv::IMREAD_GRAYSCALE | cv::IMREAD_ANYDEPTH); |
||||
compareDepth(groundTruthDepth, depth_buf, depthDiff, zFar, depthScale, thr.depthMaskThreshold, thr.depthInfThreshold, thr.depthL2Threshold); |
||||
|
||||
// add --test_debug to output resulting images
|
||||
if (debugLevel > 0) |
||||
{ |
||||
std::string outColorPath = "color_image_" + suffix + "_" + shadingName + ".png"; |
||||
std::string outDepthPath = "depth_image_" + suffix + "_" + shadingName + ".png"; |
||||
imwrite(outColorPath, color_buf * 255.f); |
||||
imwrite(outDepthPath, depth_buf); |
||||
imwrite("diff_" + outColorPath, rgbDiff * 255.f); |
||||
imwrite("diff_" + outDepthPath, depthDiff); |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
// drawing model as a whole or as two halves should give the same result
|
||||
TEST_P(RenderingTest, keepDrawnData) |
||||
{ |
||||
if (modelType != ModelType::Empty) |
||||
{ |
||||
Mat depth_buf2(height, width, ftype, zFar); |
||||
Mat color_buf2(height, width, CV_MAKETYPE(ftype, 3), Scalar::all(0)); |
||||
|
||||
Mat idx1, idx2; |
||||
int nTriangles = (int)indices.total(); |
||||
idx1 = indices.reshape(3, 1)(Range::all(), Range(0, nTriangles / 2)); |
||||
idx2 = indices.reshape(3, 1)(Range::all(), Range(nTriangles / 2, nTriangles)); |
||||
|
||||
triangleRasterize(verts, idx1, colors, color_buf2, depth_buf2, cameraPose, fovYradians, zNear, zFar, settings); |
||||
triangleRasterize(verts, idx2, colors, color_buf2, depth_buf2, cameraPose, fovYradians, zNear, zFar, settings); |
||||
|
||||
Mat rgbDiff, depthDiff; |
||||
compareRGB(color_buf, color_buf2, rgbDiff, 0, 0); |
||||
depth_buf.convertTo(depth_buf, CV_16U, depthScale); |
||||
depth_buf2.convertTo(depth_buf2, CV_16U, depthScale); |
||||
compareDepth(depth_buf, depth_buf2, depthDiff, zFar, depthScale, 0, 0, 0); |
||||
} |
||||
} |
||||
|
||||
|
||||
TEST_P(RenderingTest, glCompatibleDepth) |
||||
{ |
||||
Mat depth_buf2(height, width, ftype, 1.0); |
||||
|
||||
triangleRasterizeDepth(verts, indices, depth_buf2, cameraPose, fovYradians, zNear, zFar, |
||||
settings.setGlCompatibleMode(RASTERIZE_COMPAT_INVDEPTH)); |
||||
|
||||
Mat convertedDepth(height, width, ftype); |
||||
// map from [0, 1] to [zNear, zFar]
|
||||
double scaleNear = (1.0 / zNear); |
||||
double scaleFar = (1.0 / zFar); |
||||
for (int y = 0; y < height; y++) |
||||
{ |
||||
for (int x = 0; x < width; x++) |
||||
{ |
||||
double z = (double)depth_buf2.at<float>(y, x); |
||||
convertedDepth.at<float>(y, x) = (float)(1.0 / ((1.0 - z) * scaleNear + z * scaleFar )); |
||||
} |
||||
} |
||||
|
||||
double normL2Diff = cv::norm(depth_buf, convertedDepth, cv::NORM_L2) / (height * width); |
||||
const double normL2Threshold = 1.e-9; |
||||
EXPECT_LE(normL2Diff, normL2Threshold); |
||||
// add --test_debug to output differences
|
||||
if (debugLevel > 0) |
||||
{ |
||||
std::cout << "normL2Diff: " << normL2Diff << " vs " << normL2Threshold << std::endl; |
||||
} |
||||
} |
||||
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(Rendering, RenderingTest, ::testing::Values( |
||||
RenderTestParamType { std::make_tuple(320, 240), RASTERIZE_SHADING_SHADED, RASTERIZE_CULLING_NONE, ModelType::Centered, CV_32F, CV_32S }, |
||||
RenderTestParamType { std::make_tuple(256, 256), RASTERIZE_SHADING_SHADED, RASTERIZE_CULLING_NONE, ModelType::Centered, CV_32F, CV_32S }, |
||||
RenderTestParamType { std::make_tuple(256, 256), RASTERIZE_SHADING_WHITE, RASTERIZE_CULLING_NONE, ModelType::Centered, CV_32F, CV_32S }, |
||||
RenderTestParamType { std::make_tuple(640, 480), RASTERIZE_SHADING_FLAT, RASTERIZE_CULLING_NONE, ModelType::Centered, CV_32F, CV_32S }, |
||||
RenderTestParamType { std::make_tuple(320, 240), RASTERIZE_SHADING_FLAT, RASTERIZE_CULLING_CW, ModelType::Color, CV_32F, CV_32S }, |
||||
RenderTestParamType { std::make_tuple(320, 240), RASTERIZE_SHADING_SHADED, RASTERIZE_CULLING_NONE, ModelType::Color, CV_32F, CV_32S }, |
||||
RenderTestParamType { std::make_tuple(256, 256), RASTERIZE_SHADING_SHADED, RASTERIZE_CULLING_NONE, ModelType::Color, CV_32F, CV_32S }, |
||||
RenderTestParamType { std::make_tuple(256, 256), RASTERIZE_SHADING_WHITE, RASTERIZE_CULLING_NONE, ModelType::Color, CV_32F, CV_32S }, |
||||
RenderTestParamType { std::make_tuple(320, 240), RASTERIZE_SHADING_FLAT, RASTERIZE_CULLING_CW, ModelType::Clipping, CV_32F, CV_32S }, |
||||
RenderTestParamType { std::make_tuple(320, 240), RASTERIZE_SHADING_SHADED, RASTERIZE_CULLING_NONE, ModelType::Clipping, CV_32F, CV_32S }, |
||||
RenderTestParamType { std::make_tuple(256, 256), RASTERIZE_SHADING_FLAT, RASTERIZE_CULLING_CCW, ModelType::Clipping, CV_32F, CV_32S }, |
||||
RenderTestParamType { std::make_tuple(256, 256), RASTERIZE_SHADING_SHADED, RASTERIZE_CULLING_CW, ModelType::Clipping, CV_32F, CV_32S }, |
||||
RenderTestParamType { std::make_tuple(640, 480), RASTERIZE_SHADING_WHITE, RASTERIZE_CULLING_NONE, ModelType::Clipping, CV_32F, CV_32S }, |
||||
RenderTestParamType { std::make_tuple(700, 700), RASTERIZE_SHADING_FLAT, RASTERIZE_CULLING_CCW, ModelType::Clipping, CV_32F, CV_32S }, |
||||
RenderTestParamType { std::make_tuple(320, 240), RASTERIZE_SHADING_SHADED, RASTERIZE_CULLING_CCW, ModelType::File, CV_32F, CV_32S }, |
||||
RenderTestParamType { std::make_tuple(700, 700), RASTERIZE_SHADING_SHADED, RASTERIZE_CULLING_CW, ModelType::File, CV_32F, CV_32S }, |
||||
RenderTestParamType { std::make_tuple(700, 700), RASTERIZE_SHADING_WHITE, RASTERIZE_CULLING_NONE, ModelType::File, CV_32F, CV_32S } |
||||
)); |
||||
|
||||
} // namespace ::
|
||||
} // namespace opencv_test
|
@ -0,0 +1,460 @@ |
||||
#include <iostream> |
||||
|
||||
#ifdef _WIN32 |
||||
#define WIN32_LEAN_AND_MEAN 1 |
||||
#define NOMINMAX 1 |
||||
#include <windows.h> |
||||
#endif |
||||
|
||||
#if defined(__APPLE__) |
||||
#include <OpenGL/gl.h> |
||||
#include <OpenGL/glu.h> |
||||
#else |
||||
#include <GL/gl.h> |
||||
#include <GL/glu.h> |
||||
#endif |
||||
|
||||
#include "opencv2/core.hpp" |
||||
#include "opencv2/core/opengl.hpp" |
||||
#include "opencv2/3d.hpp" |
||||
#include "opencv2/imgproc.hpp" |
||||
#include "opencv2/highgui.hpp" |
||||
|
||||
using namespace std; |
||||
using namespace cv; |
||||
using namespace cv::cuda; |
||||
|
||||
// model data should be identical to the code from tests
|
||||
enum class ModelType |
||||
{ |
||||
Empty = 0, |
||||
File = 1, |
||||
Clipping = 2, |
||||
Color = 3, |
||||
Centered = 4 |
||||
}; |
||||
|
||||
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)); |
||||
} |
||||
} |
||||
|
||||
class ModelData |
||||
{ |
||||
public: |
||||
ModelData(ModelType type = ModelType::Empty, std::string objPath = { }) |
||||
{ |
||||
switch (type) |
||||
{ |
||||
case ModelType::Empty: |
||||
{ |
||||
position = Vec3d(0.0, 0.0, 0.0); |
||||
lookat = Vec3d(0.0, 0.0, 0.0); |
||||
upVector = Vec3d(0.0, 1.0, 0.0); |
||||
|
||||
fovy = 45.0; |
||||
|
||||
vertices = std::vector<Vec3f>(4, {2.0f, 0, -2.0f}); |
||||
colors = std::vector<Vec3f>(4, {0, 0, 1.0f}); |
||||
indices = { }; |
||||
} |
||||
break; |
||||
case ModelType::File: |
||||
{ |
||||
position = Vec3d( 1.9, 0.4, 1.3); |
||||
lookat = Vec3d( 0.0, 0.0, 0.0); |
||||
upVector = Vec3d( 0.0, 1.0, 0.0); |
||||
|
||||
fovy = 45.0; |
||||
|
||||
objectPath = objPath; |
||||
std::vector<vector<int>> indvec; |
||||
loadMesh(objectPath, vertices, indvec); |
||||
// using per-vertex normals as colors
|
||||
generateNormals(vertices, indvec, colors); |
||||
if (vertices.size() != colors.size()) |
||||
{ |
||||
std::runtime_error("Model should contain normals for each vertex"); |
||||
} |
||||
for (const auto &vec : indvec) |
||||
{ |
||||
indices.push_back({vec[0], vec[1], vec[2]}); |
||||
} |
||||
|
||||
for (auto &color : colors) |
||||
{ |
||||
color = Vec3f(abs(color[0]), abs(color[1]), abs(color[2])); |
||||
} |
||||
} |
||||
break; |
||||
case ModelType::Clipping: |
||||
{ |
||||
position = Vec3d(0.0, 0.0, 5.0); |
||||
lookat = Vec3d(0.0, 0.0, 0.0); |
||||
upVector = Vec3d(0.0, 1.0, 0.0); |
||||
|
||||
fovy = 45.0; |
||||
|
||||
vertices = |
||||
{ |
||||
{ 2.0, 0.0, -2.0}, { 0.0, -6.0, -2.0}, {-2.0, 0.0, -2.0}, |
||||
{ 3.5, -1.0, -5.0}, { 2.5, -2.5, -5.0}, {-1.0, 1.0, -5.0}, |
||||
{-6.5, -1.0, -3.0}, {-2.5, -2.0, -3.0}, { 1.0, 1.0, -5.0}, |
||||
}; |
||||
|
||||
indices = { {0, 1, 2}, {3, 4, 5}, {6, 7, 8} }; |
||||
|
||||
Vec3f col1(217.0, 238.0, 185.0); |
||||
Vec3f col2(185.0, 217.0, 238.0); |
||||
Vec3f col3(150.0, 10.0, 238.0); |
||||
|
||||
col1 *= (1.f / 255.f); |
||||
col2 *= (1.f / 255.f); |
||||
col3 *= (1.f / 255.f); |
||||
|
||||
colors = |
||||
{ |
||||
col1, col2, col3, |
||||
col2, col3, col1, |
||||
col3, col1, col2, |
||||
}; |
||||
} |
||||
break; |
||||
case ModelType::Centered: |
||||
{ |
||||
position = Vec3d(0.0, 0.0, 5.0); |
||||
lookat = Vec3d(0.0, 0.0, 0.0); |
||||
upVector = Vec3d(0.0, 1.0, 0.0); |
||||
|
||||
fovy = 45.0; |
||||
|
||||
vertices = |
||||
{ |
||||
{ 2.0, 0.0, -2.0}, { 0.0, -2.0, -2.0}, {-2.0, 0.0, -2.0}, |
||||
{ 3.5, -1.0, -5.0}, { 2.5, -1.5, -5.0}, {-1.0, 0.5, -5.0}, |
||||
}; |
||||
|
||||
indices = { {0, 1, 2}, {3, 4, 5} }; |
||||
|
||||
Vec3f col1(217.0, 238.0, 185.0); |
||||
Vec3f col2(185.0, 217.0, 238.0); |
||||
|
||||
col1 *= (1.f / 255.f); |
||||
col2 *= (1.f / 255.f); |
||||
|
||||
colors = |
||||
{ |
||||
col1, col2, col1, |
||||
col2, col1, col2, |
||||
}; |
||||
} |
||||
break; |
||||
case ModelType::Color: |
||||
{ |
||||
position = Vec3d(0.0, 0.0, 5.0); |
||||
lookat = Vec3d(0.0, 0.0, 0.0); |
||||
upVector = Vec3d(0.0, 1.0, 0.0); |
||||
|
||||
fovy = 60.0; |
||||
|
||||
vertices = |
||||
{ |
||||
{ 2.0, 0.0, -2.0}, |
||||
{ 0.0, 2.0, -3.0}, |
||||
{-2.0, 0.0, -2.0}, |
||||
{ 0.0, -2.0, 1.0}, |
||||
}; |
||||
|
||||
indices = { {0, 1, 2}, {0, 2, 3} }; |
||||
|
||||
colors = |
||||
{ |
||||
{ 0.0f, 0.0f, 1.0f}, |
||||
{ 0.0f, 1.0f, 0.0f}, |
||||
{ 1.0f, 0.0f, 0.0f}, |
||||
{ 0.0f, 1.0f, 0.0f}, |
||||
}; |
||||
} |
||||
break; |
||||
|
||||
default: |
||||
CV_Error(Error::StsBadArg, "Unknown model type"); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
Vec3d position; |
||||
Vec3d lookat; |
||||
Vec3d upVector; |
||||
|
||||
double fovy; |
||||
|
||||
std::vector<Vec3f> vertices; |
||||
std::vector<Vec3i> indices; |
||||
std::vector<Vec3f> colors; |
||||
|
||||
string objectPath; |
||||
}; |
||||
|
||||
|
||||
struct DrawData |
||||
{ |
||||
ogl::Arrays arr; |
||||
ogl::Buffer indices; |
||||
}; |
||||
|
||||
void draw(void* userdata); |
||||
|
||||
void draw(void* userdata) |
||||
{ |
||||
DrawData* data = static_cast<DrawData*>(userdata); |
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
||||
ogl::render(data->arr, data->indices, ogl::TRIANGLES); |
||||
} |
||||
|
||||
static void generateImage(cv::Size imgSz, TriangleShadingType shadingType, TriangleCullingMode cullingMode, |
||||
ModelType modelType, std::string modelPath, cv::Mat& colorImage, cv::Mat& depthImage) |
||||
{ |
||||
namedWindow("OpenGL", WINDOW_OPENGL); |
||||
resizeWindow("OpenGL", imgSz.width, imgSz.height); |
||||
|
||||
ModelData modelData(modelType, modelPath); |
||||
|
||||
DrawData data; |
||||
|
||||
std::vector<Vec3f> vertices; |
||||
std::vector<Vec4f> colors4f; |
||||
std::vector<int> idxLinear; |
||||
|
||||
if (shadingType == RASTERIZE_SHADING_FLAT) |
||||
{ |
||||
// rearrange vertices and colors for flat shading
|
||||
int ctr = 0; |
||||
for (const auto& idx : modelData.indices) |
||||
{ |
||||
for (int i = 0; i < 3; i++) |
||||
{ |
||||
vertices.push_back(modelData.vertices[idx[i]]); |
||||
idxLinear.push_back(ctr++); |
||||
} |
||||
|
||||
Vec3f ci = modelData.colors[idx[0]]; |
||||
for (int i = 0; i < 3; i++) |
||||
{ |
||||
colors4f.emplace_back(ci[0], ci[1], ci[2], 1.f); |
||||
} |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
vertices = modelData.vertices; |
||||
for (const auto& c : modelData.colors) |
||||
{ |
||||
Vec3f ci = (shadingType == RASTERIZE_SHADING_SHADED) ? c: cv::Vec3f::all(1.f); |
||||
colors4f.emplace_back(ci[0], ci[1], ci[2], 1.0); |
||||
} |
||||
|
||||
for (const auto& idx : modelData.indices) |
||||
{ |
||||
for (int i = 0; i < 3; i++) |
||||
{ |
||||
idxLinear.push_back(idx[i]); |
||||
} |
||||
} |
||||
} |
||||
|
||||
data.arr.setVertexArray(vertices); |
||||
data.arr.setColorArray(colors4f); |
||||
data.indices.copyFrom(idxLinear); |
||||
|
||||
double zNear = 0.1, zFar = 50; |
||||
glMatrixMode(GL_PROJECTION); |
||||
glLoadIdentity(); |
||||
gluPerspective(modelData.fovy, (double)imgSz.width / imgSz.height, zNear, zFar); |
||||
|
||||
glMatrixMode(GL_MODELVIEW); |
||||
glLoadIdentity(); |
||||
|
||||
//gluLookAt(0, 0, 5, 0, 0, 0, 0, 1, 0);
|
||||
gluLookAt(modelData.position[0], modelData.position[1], modelData.position[2], |
||||
modelData.lookat [0], modelData.lookat [1], modelData.lookat [2], |
||||
modelData.upVector[0], modelData.upVector[1], modelData.upVector[2]); |
||||
|
||||
if (cullingMode == RASTERIZE_CULLING_NONE) |
||||
{ |
||||
glDisable(GL_CULL_FACE); |
||||
} |
||||
else |
||||
{ |
||||
glEnable(GL_CULL_FACE); |
||||
glCullFace(GL_FRONT); |
||||
if (cullingMode == RASTERIZE_CULLING_CW) |
||||
{ |
||||
glFrontFace(GL_CW); |
||||
} |
||||
else |
||||
{ |
||||
glFrontFace(GL_CCW); |
||||
} |
||||
} |
||||
|
||||
glEnable(GL_DEPTH_TEST); |
||||
|
||||
cv::setOpenGlDrawCallback("OpenGL", draw, &data); |
||||
|
||||
const int framesToSkip = 10; |
||||
for (int f = 0; f < framesToSkip; f++) |
||||
{ |
||||
updateWindow("OpenGL"); |
||||
|
||||
colorImage = cv::Mat(imgSz.height, imgSz.width, CV_8UC3); |
||||
glReadPixels(0, 0, imgSz.width, imgSz.height, GL_RGB, GL_UNSIGNED_BYTE, colorImage.data); |
||||
cv::cvtColor(colorImage, colorImage, cv::COLOR_RGB2BGR); |
||||
cv::flip(colorImage, colorImage, 0); |
||||
|
||||
depthImage = cv::Mat(imgSz.height, imgSz.width, CV_32F); |
||||
glReadPixels(0, 0, imgSz.width, imgSz.height, GL_DEPTH_COMPONENT, GL_FLOAT, depthImage.data); |
||||
// map from [0, 1] to [zNear, zFar]
|
||||
for (auto it = depthImage.begin<float>(); it != depthImage.end<float>(); ++it) |
||||
{ |
||||
*it = (float)(zNear * zFar / (double(*it) * (zNear - zFar) + zFar)); |
||||
} |
||||
cv::flip(depthImage, depthImage, 0); |
||||
depthImage.convertTo(depthImage, CV_16U, 1000.0); |
||||
|
||||
char key = (char)waitKey(40); |
||||
if (key == 27) |
||||
break; |
||||
} |
||||
|
||||
cv::setOpenGlDrawCallback("OpenGL", 0, 0); |
||||
cv::destroyAllWindows(); |
||||
} |
||||
|
||||
|
||||
int main(int argc, char* argv[]) |
||||
{ |
||||
cv::CommandLineParser parser(argc, argv, |
||||
"{ help h usage ? | | show this message }" |
||||
"{ outPath | | output path for generated images }" |
||||
"{ modelPath | | path to 3d model to render }" |
||||
); |
||||
parser.about("This app is used to generate test data for triangleRasterize() function"); |
||||
|
||||
if (parser.has("help")) |
||||
{ |
||||
parser.printMessage(); |
||||
return 0; |
||||
} |
||||
|
||||
std::string modelPath = parser.get<std::string>("modelPath"); |
||||
if (modelPath.empty()) |
||||
{ |
||||
std::cout << "No model path given" << std::endl; |
||||
return -1; |
||||
} |
||||
|
||||
std::string outPath = parser.get<std::string>("outPath"); |
||||
if (outPath.empty()) |
||||
{ |
||||
std::cout << "No output path given" << std::endl; |
||||
return -1; |
||||
} |
||||
|
||||
std::array<cv::Size, 4> resolutions = { cv::Size {700, 700}, cv::Size {640, 480}, cv::Size(256, 256), cv::Size(320, 240) }; |
||||
for (const auto& res : resolutions) |
||||
{ |
||||
for (const auto shadingType : { |
||||
RASTERIZE_SHADING_WHITE, |
||||
RASTERIZE_SHADING_FLAT, |
||||
RASTERIZE_SHADING_SHADED |
||||
}) |
||||
{ |
||||
std::string shadingName; |
||||
switch (shadingType) |
||||
{ |
||||
case RASTERIZE_SHADING_WHITE: shadingName = "White"; break; |
||||
case RASTERIZE_SHADING_FLAT: shadingName = "Flat"; break; |
||||
case RASTERIZE_SHADING_SHADED: shadingName = "Shaded"; break; |
||||
default: |
||||
break; |
||||
} |
||||
|
||||
for (const auto cullingMode : { |
||||
RASTERIZE_CULLING_NONE, |
||||
RASTERIZE_CULLING_CW, |
||||
RASTERIZE_CULLING_CCW |
||||
}) |
||||
{ |
||||
std::string cullingName; |
||||
switch (cullingMode) |
||||
{ |
||||
case RASTERIZE_CULLING_NONE: cullingName = "None"; break; |
||||
case RASTERIZE_CULLING_CW: cullingName = "CW"; break; |
||||
case RASTERIZE_CULLING_CCW: cullingName = "CCW"; break; |
||||
default: break; |
||||
} |
||||
|
||||
for (const auto modelType : { |
||||
ModelType::File, |
||||
ModelType::Clipping, |
||||
ModelType::Color, |
||||
ModelType::Centered, |
||||
}) |
||||
{ |
||||
std::string modelName; |
||||
switch (modelType) |
||||
{ |
||||
case ModelType::File: modelName = "File"; break; |
||||
case ModelType::Clipping: modelName = "Clipping"; break; |
||||
case ModelType::Color: modelName = "Color"; break; |
||||
case ModelType::Centered: modelName = "Centered"; break; |
||||
default: |
||||
break; |
||||
} |
||||
|
||||
std::string suffix = cv::format("%s_%dx%d_Cull%s", modelName.c_str(), res.width, res.height, cullingName.c_str()); |
||||
|
||||
std::cout << suffix + "_" + shadingName << "..." << std::endl; |
||||
|
||||
cv::Mat colorImage, depthImage; |
||||
generateImage(res, shadingType, cullingMode, modelType, modelPath, colorImage, depthImage); |
||||
|
||||
std::string gtPathColor = outPath + "/example_image_" + suffix + "_" + shadingName + ".png"; |
||||
std::string gtPathDepth = outPath + "/depth_image_" + suffix + ".png"; |
||||
|
||||
cv::imwrite(gtPathColor, colorImage); |
||||
cv::imwrite(gtPathDepth, depthImage); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
return 0; |
||||
} |
Loading…
Reference in new issue