mirror of
https://github.com/opencv/opencv.git
synced 2026-07-29 15:23:05 +04:00
Merge pull request #29224 from asmorkalov:as/ptcloud2
Dedicated pointcloud module #29224 OpenCV contrib: https://github.com/opencv/opencv_contrib/pull/4134 ### 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 - [ ] The PR is proposed to the proper branch - [ ] There is a reference to the original bug report and related work - [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [ ] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
committed by
GitHub
parent
6a1a2754c8
commit
fc3803c67b
@@ -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), (18, 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()
|
||||
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import numpy as np
|
||||
import math
|
||||
import unittest
|
||||
import cv2 as cv
|
||||
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
class octree_test(NewOpenCVTests):
|
||||
def test_octree_basic_test(self):
|
||||
pointCloudSize = 1000
|
||||
resolution = 0.0001
|
||||
scale = 1 << 20
|
||||
|
||||
pointCloud = np.random.randint(-scale, scale, size=(pointCloudSize, 3)) * (10.0 / scale)
|
||||
pointCloud = pointCloud.astype(np.float32)
|
||||
|
||||
octree = cv.Octree_createWithResolution(resolution, pointCloud)
|
||||
|
||||
restPoint = np.random.randint(-scale, scale, size=(1, 3)) * (10.0 / scale)
|
||||
restPoint = [restPoint[0, 0], restPoint[0, 1], restPoint[0, 2]]
|
||||
|
||||
self.assertTrue(octree.isPointInBound(restPoint))
|
||||
self.assertFalse(octree.deletePoint(restPoint))
|
||||
self.assertTrue(octree.insertPoint(restPoint))
|
||||
self.assertTrue(octree.deletePoint(restPoint))
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import numpy as np
|
||||
import cv2 as cv
|
||||
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
class odometry_test(NewOpenCVTests):
|
||||
def commonOdometryTest(self, needRgb, otype, algoType, useFrame):
|
||||
depth = self.get_sample('cv/rgbd/depth.png', cv.IMREAD_ANYDEPTH).astype(np.float32)
|
||||
if needRgb:
|
||||
rgb = self.get_sample('cv/rgbd/rgb.png', cv.IMREAD_ANYCOLOR)
|
||||
radian = np.radians(1)
|
||||
Rt_warp = np.array(
|
||||
[[np.cos(radian), -np.sin(radian), 0],
|
||||
[np.sin(radian), np.cos(radian), 0],
|
||||
[0, 0, 1]], dtype=np.float32
|
||||
)
|
||||
Rt_curr = np.array(
|
||||
[[np.cos(radian), -np.sin(radian), 0, 0],
|
||||
[np.sin(radian), np.cos(radian), 0, 0],
|
||||
[0, 0, 1, 0],
|
||||
[0, 0, 0, 1]], dtype=np.float32
|
||||
)
|
||||
Rt_res = np.zeros((4, 4))
|
||||
|
||||
if otype is not None:
|
||||
settings = cv.OdometrySettings()
|
||||
odometry = cv.Odometry(otype, settings, algoType)
|
||||
else:
|
||||
odometry = cv.Odometry()
|
||||
|
||||
warped_depth = cv.warpPerspective(depth, Rt_warp, (640, 480))
|
||||
if needRgb:
|
||||
warped_rgb = cv.warpPerspective(rgb, Rt_warp, (640, 480))
|
||||
|
||||
if useFrame:
|
||||
if needRgb:
|
||||
srcFrame = cv.OdometryFrame(depth, rgb)
|
||||
dstFrame = cv.OdometryFrame(warped_depth, warped_rgb)
|
||||
else:
|
||||
srcFrame = cv.OdometryFrame(depth)
|
||||
dstFrame = cv.OdometryFrame(warped_depth)
|
||||
odometry.prepareFrames(srcFrame, dstFrame)
|
||||
isCorrect = odometry.compute(srcFrame, dstFrame, Rt_res)
|
||||
else:
|
||||
if needRgb:
|
||||
isCorrect = odometry.compute(depth, rgb, warped_depth, warped_rgb, Rt_res)
|
||||
else:
|
||||
isCorrect = odometry.compute(depth, warped_depth, Rt_res)
|
||||
|
||||
res = np.absolute(Rt_curr - Rt_res).sum()
|
||||
eps = 0.15
|
||||
self.assertLessEqual(res, eps)
|
||||
self.assertTrue(isCorrect)
|
||||
|
||||
def test_OdometryDefault(self):
|
||||
self.commonOdometryTest(False, None, None, False)
|
||||
|
||||
def test_OdometryDefaultFrame(self):
|
||||
self.commonOdometryTest(False, None, None, True)
|
||||
|
||||
def test_OdometryDepth(self):
|
||||
self.commonOdometryTest(False, cv.OdometryType_DEPTH, cv.OdometryAlgoType_COMMON, False)
|
||||
|
||||
def test_OdometryDepthFast(self):
|
||||
self.commonOdometryTest(False, cv.OdometryType_DEPTH, cv.OdometryAlgoType_FAST, False)
|
||||
|
||||
def test_OdometryDepthFrame(self):
|
||||
self.commonOdometryTest(False, cv.OdometryType_DEPTH, cv.OdometryAlgoType_COMMON, True)
|
||||
|
||||
def test_OdometryDepthFastFrame(self):
|
||||
self.commonOdometryTest(False, cv.OdometryType_DEPTH, cv.OdometryAlgoType_FAST, True)
|
||||
|
||||
def test_OdometryRGB(self):
|
||||
self.commonOdometryTest(True, cv.OdometryType_RGB, cv.OdometryAlgoType_COMMON, False)
|
||||
|
||||
def test_OdometryRGBFrame(self):
|
||||
self.commonOdometryTest(True, cv.OdometryType_RGB, cv.OdometryAlgoType_COMMON, True)
|
||||
|
||||
def test_OdometryRGB_Depth(self):
|
||||
self.commonOdometryTest(True, cv.OdometryType_RGB_DEPTH, cv.OdometryAlgoType_COMMON, False)
|
||||
|
||||
def test_OdometryRGB_DepthFrame(self):
|
||||
self.commonOdometryTest(True, cv.OdometryType_RGB_DEPTH, cv.OdometryAlgoType_COMMON, True)
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -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,43 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Python 2/3 compatibility
|
||||
from __future__ import print_function
|
||||
|
||||
import os, numpy
|
||||
import unittest
|
||||
import cv2 as cv
|
||||
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
class rgbd_test(NewOpenCVTests):
|
||||
|
||||
def test_computeRgbdPlane(self):
|
||||
|
||||
depth_image = self.get_sample('cv/rgbd/depth.png', cv.IMREAD_ANYDEPTH)
|
||||
if depth_image is None:
|
||||
raise unittest.SkipTest("Missing files with test data")
|
||||
|
||||
K = numpy.array([[525, 0, 320.5], [0, 525, 240.5], [0, 0, 1]])
|
||||
points3d = cv.depthTo3d(depth_image, K)
|
||||
normals = cv.RgbdNormals_create(480, 640, cv.CV_32F, K).apply(points3d)
|
||||
_, planes_coeff = cv.findPlanes(points3d, normals, numpy.array([]), numpy.array([]), 40, 1600, 0.01, 0, 0, 0, cv.RGBD_PLANE_METHOD_DEFAULT)
|
||||
|
||||
planes_coeff_expected = \
|
||||
numpy.asarray([[[-0.02447728, -0.8678335 , -0.49625182, 4.02800846]],
|
||||
[[-0.05055107, -0.86144137, -0.50533485, 3.95456314]],
|
||||
[[-0.03294908, -0.86964548, -0.49257591, 3.97052431]],
|
||||
[[-0.02886586, -0.87153459, -0.48948362, 7.77550507]],
|
||||
[[-0.04455929, -0.87659335, -0.47916424, 3.93200684]],
|
||||
[[-0.21514639, 0.18835169, -0.95824611, 7.59479475]],
|
||||
[[-0.01006953, -0.86679155, -0.49856904, 4.01355648]],
|
||||
[[-0.00876531, -0.87571168, -0.48275498, 3.96768975]],
|
||||
[[-0.06395926, -0.86951321, -0.48975089, 4.08618736]],
|
||||
[[-0.01403128, -0.87593341, -0.48222789, 7.74559402]],
|
||||
[[-0.01143177, -0.87495202, -0.4840748 , 7.75355816]]],
|
||||
dtype=numpy.float32)
|
||||
|
||||
eps = 0.05
|
||||
self.assertLessEqual(cv.norm(planes_coeff, planes_coeff_expected, cv.NORM_L2), eps)
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
@@ -0,0 +1,123 @@
|
||||
import numpy as np
|
||||
import cv2 as cv
|
||||
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
class volume_test(NewOpenCVTests):
|
||||
def test_VolumeDefault(self):
|
||||
depthPath = 'cv/rgbd/depth.png'
|
||||
depth = self.get_sample(depthPath, cv.IMREAD_ANYDEPTH).astype(np.float32)
|
||||
if depth.size <= 0:
|
||||
raise Exception('Failed to load depth file: %s' % depthPath)
|
||||
|
||||
Rt = np.eye(4)
|
||||
volume = cv.Volume()
|
||||
volume.integrate(depth, Rt)
|
||||
|
||||
size = (480, 640, 4)
|
||||
points = np.zeros(size, np.float32)
|
||||
normals = np.zeros(size, np.float32)
|
||||
|
||||
Kraycast = np.array([[525. , 0. , 319.5],
|
||||
[ 0. , 525. , 239.5],
|
||||
[ 0. , 0. , 1. ]])
|
||||
|
||||
volume.raycastEx(Rt, size[0], size[1], Kraycast, points, normals)
|
||||
|
||||
self.assertEqual(points.shape, size)
|
||||
self.assertEqual(points.shape, normals.shape)
|
||||
|
||||
volume.raycast(Rt, points, normals)
|
||||
|
||||
self.assertEqual(points.shape, size)
|
||||
self.assertEqual(points.shape, normals.shape)
|
||||
|
||||
def test_VolumeTSDF(self):
|
||||
depthPath = 'cv/rgbd/depth.png'
|
||||
depth = self.get_sample(depthPath, cv.IMREAD_ANYDEPTH).astype(np.float32)
|
||||
if depth.size <= 0:
|
||||
raise Exception('Failed to load depth file: %s' % depthPath)
|
||||
|
||||
Rt = np.eye(4)
|
||||
|
||||
settings = cv.VolumeSettings(cv.VolumeType_TSDF)
|
||||
volume = cv.Volume(cv.VolumeType_TSDF, settings)
|
||||
volume.integrate(depth, Rt)
|
||||
|
||||
size = (480, 640, 4)
|
||||
points = np.zeros(size, np.float32)
|
||||
normals = np.zeros(size, np.float32)
|
||||
|
||||
Kraycast = settings.getCameraRaycastIntrinsics()
|
||||
volume.raycastEx(Rt, size[0], size[1], Kraycast, points, normals)
|
||||
|
||||
self.assertEqual(points.shape, size)
|
||||
self.assertEqual(points.shape, normals.shape)
|
||||
|
||||
volume.raycast(Rt, points, normals)
|
||||
|
||||
self.assertEqual(points.shape, size)
|
||||
self.assertEqual(points.shape, normals.shape)
|
||||
|
||||
def test_VolumeHashTSDF(self):
|
||||
depthPath = 'cv/rgbd/depth.png'
|
||||
depth = self.get_sample(depthPath, cv.IMREAD_ANYDEPTH).astype(np.float32)
|
||||
if depth.size <= 0:
|
||||
raise Exception('Failed to load depth file: %s' % depthPath)
|
||||
|
||||
Rt = np.eye(4)
|
||||
settings = cv.VolumeSettings(cv.VolumeType_HashTSDF)
|
||||
volume = cv.Volume(cv.VolumeType_HashTSDF, settings)
|
||||
volume.integrate(depth, Rt)
|
||||
|
||||
size = (480, 640, 4)
|
||||
points = np.zeros(size, np.float32)
|
||||
normals = np.zeros(size, np.float32)
|
||||
|
||||
Kraycast = settings.getCameraRaycastIntrinsics()
|
||||
volume.raycastEx(Rt, size[0], size[1], Kraycast, points, normals)
|
||||
|
||||
self.assertEqual(points.shape, size)
|
||||
self.assertEqual(points.shape, normals.shape)
|
||||
|
||||
volume.raycast(Rt, points, normals)
|
||||
|
||||
self.assertEqual(points.shape, size)
|
||||
self.assertEqual(points.shape, normals.shape)
|
||||
|
||||
def test_VolumeColorTSDF(self):
|
||||
depthPath = 'cv/rgbd/depth.png'
|
||||
rgbPath = 'cv/rgbd/rgb.png'
|
||||
depth = self.get_sample(depthPath, cv.IMREAD_ANYDEPTH).astype(np.float32)
|
||||
rgb = self.get_sample(rgbPath, cv.IMREAD_ANYCOLOR).astype(np.float32)
|
||||
|
||||
if depth.size <= 0:
|
||||
raise Exception('Failed to load depth file: %s' % depthPath)
|
||||
if rgb.size <= 0:
|
||||
raise Exception('Failed to load RGB file: %s' % rgbPath)
|
||||
|
||||
Rt = np.eye(4)
|
||||
settings = cv.VolumeSettings(cv.VolumeType_ColorTSDF)
|
||||
volume = cv.Volume(cv.VolumeType_ColorTSDF, settings)
|
||||
volume.integrateColor(depth, rgb, Rt)
|
||||
|
||||
size = (480, 640, 4)
|
||||
points = np.zeros(size, np.float32)
|
||||
normals = np.zeros(size, np.float32)
|
||||
colors = np.zeros(size, np.float32)
|
||||
|
||||
Kraycast = settings.getCameraRaycastIntrinsics()
|
||||
volume.raycastExColor(Rt, size[0], size[1], Kraycast, points, normals, colors)
|
||||
|
||||
self.assertEqual(points.shape, size)
|
||||
self.assertEqual(points.shape, normals.shape)
|
||||
|
||||
volume.raycastColor(Rt, points, normals, colors)
|
||||
|
||||
self.assertEqual(points.shape, size)
|
||||
self.assertEqual(points.shape, normals.shape)
|
||||
self.assertEqual(points.shape, colors.shape)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
Reference in New Issue
Block a user