from enum import Enum
from typing import Optional
from scipy.spatial.transform import Rotation
import numpy as np
from typing import Dict
import math
import open3d as o3d
[docs]
class CameraType(Enum):
PinHole = "PinHole"
Fisheye = "Fisheye"
OmniDirectional = "OmniDirectional"
[docs]
@classmethod
def camera_type_list(cls) -> list:
return [_.value for _ in CameraType]
[docs]
class Camera:
def __init__(self,
camera_type: str,
heading: Optional[tuple] = None,
position: Optional[tuple] = None,
intrinsic: Optional[tuple] = None,
radial: Optional[tuple] = None,
tangential: Optional[tuple] = None,
fov: Optional[int] = None,
skew: Optional[int] = 0,
projection: Optional[tuple] = None,
name: str = None
):
"""
camera object
Args:
camera_type:
Pinholes, fish eyes, panoramic views
heading:
quaternion (x, y, z, w)
position:
offset (x, y, z)
intrinsic:
camera parameter (fx, fy, cx, cy) or (fx, fy, c, d, e, cx, cy)
radial:
radial distortion (k1...)
tangential:
tangential distortion (p1...)
fov:
camera range (angle system)
projection:
panoramic camera parameters (p0...)
Returns:
Camera inatance
Examples:
.. code-block:: python
cam = Camera(
'PinHole',
heading=(0.4351436321314605, -0.4677553765588058, 0.5492505771794342, -0.5386824023335351),
position=(1.5601294, -0.37986112, 1.3333596),
intrinsic=(1846.008599353862, 1857.930995475229, 1948.09353722228, 1078.768188621484),
radial=(0, 0, 0, 0),
tangential=(0, 0)
)
"""
camera_type_list = CameraType.camera_type_list()
assert camera_type in camera_type_list, f'camera_type is one of "{" ".join(camera_type_list)}"'
self.camera_type = camera_type
self.heading = heading
self.position = position
self.intrinsic = intrinsic
self.name = name
if camera_type == CameraType.OmniDirectional.value:
assert len(self.intrinsic) == 7, 'intrinsic is fx, fy, c, d, e, cx, cy'
else:
assert len(self.intrinsic) == 4, 'intrinsic is fx, fy, cx, cy'
self.radial = radial
self.tangential = tangential
self.fov = fov
self.projection = projection
self.skew = skew
def __repr__(self):
return "the camera param"
@property
def rotated_vector(self):
"""
Extract the x, y, and z components of the rotated vector
"""
additional_rotation = R.from_quat(self.heading)
# 创建一个向量,初始方向为 x 轴方向
vector = np.array([1.0, 0.0, 0.0])
rotated_vector = additional_rotation.apply(vector)
return rotated_vector
@property
def cam2lidar_rt(self) -> np.matrix:
"""
Camera rt matrix relative to radar
"""
r = Rotation.from_quat(self.heading).as_matrix()
rt = np.r_[np.c_[r, np.array(self.position)], np.array([[0, 0, 0, 1]])]
return np.matrix(rt)
@property
def intrinsic_matrix(self) -> Optional[np.ndarray]:
"""
Internal parameter matrix of pinhole and fisheye cameras
"""
if self.camera_type != CameraType.OmniDirectional.value:
fx, fy, cx, cy = self.intrinsic
return np.array([
[fx, 0, cx],
[0, fy, cy],
[0, 0, 1]])
else:
return None
@property
def _radial(self) -> tuple:
"""
Transverse distortion
"""
assert len(self.radial) <= 6, 'The radial distortion of this model only supports k1-k6 at present'
_ = {f'k{num}': item for num, item in enumerate(self.radial, start=1)}
k1 = _.get('k1', 0)
k2 = _.get('k2', 0)
k3 = _.get('k3', 0)
k4 = _.get('k4', 0)
k5 = _.get('k5', 0)
k6 = _.get('k6', 0)
return k1, k2, k3, k4, k5, k6
def _pinhole_3dto2d(self, **kwargs) -> tuple:
"""
opencv pinhole camera pixel coordinates
"""
x, y, z = kwargs['point_in_cam']
fx, fy, cx, cy = self.intrinsic
k1, k2, k3, k4, k5, k6 = self._radial
p1, p2 = self.tangential
u, v = x / z, y / z
r2 = u ** 2 + v ** 2
r4 = r2 ** 2
r6 = r2 * r4
radial = (1 + k1 * r2 + k2 * r4 + k3 * r6) / (1 + k4 * r2 + k5 * r4 + k6 * r6)
u = u * radial + 2 * p1 * v * u + p2 * (r2 + 2 * u ** 2)
v = v * radial + 2 * p2 * v * u + p1 * (r2 + 2 * v ** 2)
return u * fx + cx, v * fy + cy
# opencv
def _fisheye_3dto2d(self, **kwargs) -> tuple:
"""
opencv fisheye camera pixel coordinates
"""
x, y, z = kwargs['point_in_cam']
fx, fy, cx, cy = self.intrinsic
k1, k2, k3, k4, k5, k6 = self._radial
alpha = self.skew
a = x / abs(z)
b = y / abs(z)
r2 = a ** 2 + b ** 2
r = math.sqrt(r2)
theta = math.atan(r) if z > 0 else math.pi - math.atan(r)
theta2 = theta ** 2
theta4 = theta2 ** 2
theta6 = theta2 * theta4
theta8 = theta4 ** 2
theta_d = theta * (1.0 + k1 * theta2 + k2 * theta4 + k3 * theta6 + k4 * theta8)
scale = 1.0 / r if r > 1e-8 else 1.0
u, v = theta_d * scale * a, theta_d * scale * b
return fx * (u + alpha * v) + cx, fy * v + cy
# kb8
def _fisheye2_3dto2d(self, **kwargs) -> tuple:
"""
kb8 fisheye camera pixel coordinates
"""
x, y, z = kwargs['point_in_cam']
fx, fy, cx, cy = self.intrinsic
k1, k2, k3, k4, k5, k6 = self._radial
alpha = self.skew
a = x / abs(z)
b = y / abs(z)
r2 = a ** 2 + b ** 2
r = math.sqrt(r2)
theta = math.atan(r) if z > 0 else math.pi - math.atan(r)
theta2 = theta ** 2
theta4 = theta2 ** 2
theta6 = theta2 * theta4
theta8 = theta4 ** 2
theta_d = theta * (1.0 + k1 * theta2 + k2 * theta4 + k3 * theta6 + k4 * theta8)
scale = 1.0 / r if r > 1e-8 else 1.0
u, v = theta_d * scale * a, theta_d * scale * b
return fx * (u + alpha * v) + cx, fy * v + cy
def _omnidirectional_3dto2d(self, **kwargs) -> tuple:
"""
opencv omnidirectional camera pixel coordinates
"""
x, y, z = kwargs['point_in_cam']
fx, fy, c, d, e, cx, cy = self.intrinsic
norm = math.sqrt(x * x + y * y)
theta = math.atan2(-z, norm)
theta_i, rho = 1, 0
for pi in self.projection:
rho += theta_i * pi
theta_i *= theta
inv_norm = 1 / norm
xn1, xn2 = x * inv_norm * rho, y * inv_norm * rho
return xn1 * c + xn2 * d + cx, xn1 * e + xn2 + cy
[docs]
def get_pixel_coordinate(self, point_in_cam: tuple) -> tuple:
"""
Convert point cloud points to pixel coordinates
Args:
point_in_cam: (x, y, z)
The coordinates of points in the camera coordinate system
"""
if self.camera_type == CameraType.PinHole.value:
return self._pinhole_3dto2d(point_in_cam=point_in_cam)
elif self.camera_type == CameraType.Fisheye.value:
return self._fisheye_3dto2d(point_in_cam=point_in_cam)
else:
return self._omnidirectional_3dto2d(point_in_cam=point_in_cam)
[docs]
@staticmethod
def gen_camera(camera: Dict):
"""
generate the Camera obj
Args:
camera: Dict
camera data
Returns:
obj: Camera
"""
def _temp_cam(data: Dict):
if not data:
return
if isinstance(data, Dict):
return tuple(data.values())
return data
camera_type = _temp_cam(camera.get("type"))
assert isinstance(camera_type, str)
return Camera(camera_type=_temp_cam(camera.get("type")),
heading=_temp_cam(camera.get("heading", None)),
position=_temp_cam(camera.get("position", None)),
intrinsic=_temp_cam(camera.get("intrinsic", None)),
radial=_temp_cam(camera.get("radial", None)),
tangential=_temp_cam(camera.get("tangential", None)),
fov=_temp_cam(camera.get("fov", None)),
projection=_temp_cam(camera.get("projection", None))
)
if __name__ == '__main__':
info = {
'position': {
'x': 19.617147983589785,
'y': -0.3571670183003431,
'z': 0.2083393465328241},
'scale': {
'height': 4.793909959547556,
'width': 2.0032044225562577,
'depth': 1.5418855556278792},
'rotation': {
'x': 0.0,
'y': 0.0,
'z': -1.5731558280731583}
}
path = '/Users/stardust/Desktop/1681532060081818.pcd'
points = o3d.io.read_point_cloud(path)
center = [info['position']['x'], info['position']['y'], info['position']['z']] # 中心点坐标
extent = [info['scale']['width'], info['scale']['height'], info['scale']['depth']] # 每个轴上的长度
theta = info['rotation']['z']
rot_mat = np.array([[np.cos(theta), -np.sin(theta), 0],
[np.sin(theta), np.cos(theta), 0],
[0, 0, 1]])
bbox = o3d.geometry.OrientedBoundingBox(center=center, R=rot_mat, extent=extent)
_ = bbox.get_point_indices_within_bounding_box(points.points)
_len = len(_)
_ = np.array([np.asarray(points.points)[p] for p in _])
matrix = np.c_[_, np.array([1] * _len)]
cam = Camera(
'PinHole',
heading=(0.4351436321314605, -0.4677553765588058, 0.5492505771794342, -0.5386824023335351),
position=(1.5601294, -0.37986112, 1.3333596),
intrinsic=(1846.008599353862, 1857.930995475229, 1948.09353722228, 1078.768188621484),
radial=(0, 0, 0, 0),
tangential=(0, 0)
)
import cv2 as cv
image_path = '/Users/stardust/Desktop/1681532060061245.jpg'
img = cv.imread(image_path)
point_size = 1
point_color = (0, 0, 255) # BGR
thickness = 4 # 可以为 0 、4、8
for points in np.dot(matrix, cam.cam2lidar_rt.I.T).tolist():
point = np.array(cam.get_pixel_coordinate(points[:3]), int)
cv.circle(img, point, point_size, point_color, thickness)
cv.namedWindow("image")
cv.imshow('image', img)
cv.waitKey(10000) # 显示 10000 ms 即 10s 后消失
cv.destroyAllWindows()
from pathlib import Path
for file in Path("你的目录").glob("*.json"):
print(file.name)