????????本节主要介绍将三维点云检测到的目标框投影到图像中进行可视化,含算法步骤、数据、代码以及可视化效果。
????????为了更好地展示可视化效果,需要把点云中图像范围的点云滤除掉,详细过程请参考:三维点云目标检测 — VoxelNet详解crop.py (一)_Coding的叶子的博客-CSDN博客_三维点云目标检测。
????????下方代码与展示效果基于mini kitti数据集,详细介绍和下载地址请参考:KITTI数据集简介 — Mini KITTI_Coding的叶子的博客-CSDN博客。
?????????下文中代码参考来源于:GitHub - skyhehe123/VoxelNet-pytorch。
1 点云剔除(此步骤也可以省略)
????????运行crop.py文件,将图像视角范围外的点云剔除掉。运行程序参考:三维点云目标检测 — VoxelNet详解crop.py (一)_Coding的叶子的博客-CSDN博客_三维点云目标检测。
????????也可以直接下载处理好的crop点云,无需运行crop.py文件,下载地址为:kittiminidataobjectcrop数据-深度学习文档类资源-CSDN下载。
2 获取目标三维检测框
????????KITTI目标的三维检测框可以从标签中获取,得到目标框的8个顶点坐标,gt_box3d。详细过程介绍请参考:kitti三维目标标注可视化_Coding的叶子的博客-CSDN博客。
3 将三维检测框投影到图像
????????将点云第4个维度反射强度暂时设置为1,以便与外参矩阵和R0矩阵相乘时进行平移操作。内参矩阵左乘R0矩阵左乘外参矩阵左乘点云坐标后得到点云在相机坐标系中的坐标,参考:KITTI数据集简介(四) — 标定校准数据calib_Coding的叶子的博客-CSDN博客。筛选出z大于0的点,即在相机前方的点,这样点云维度进一步下降至4xL。将得到的点云坐标除以z方向上坐标后,可得到点云在像平面上的ix、iy坐标。相应的函数段为:
def project_velo2rgb(velo,calib):
T=np.zeros([4,4],dtype=np.float32)
T[:3,:]=calib['Tr_velo2cam']
T[3,3]=1
R=np.zeros([4,4],dtype=np.float32)
R[:3,:3]=calib['R0']
R[3,3]=1
num=len(velo)
projections = np.zeros((num,8,2), dtype=np.int32)
for i in range(len(velo)):
box3d=np.ones([8,4],dtype=np.float32)
box3d[:,:3]=velo[i]
M=np.dot(calib['P2'],R)
M=np.dot(M,T)
box2d=np.dot(M,box3d.T)
box2d=box2d[:2,:].T/box2d[2,:].reshape(8,1)
projections[i] = box2d
return projections
4 绘制目标框
? ? ? ? 目标框的各个点的关系如下图所示。? Kiti标注中三维目标的中心点定义成目标框底部平面的中心。中心坐标利用Tr_velo2cam的逆矩阵从相机坐标系(x,y,z)变换到雷达坐标系(tx, ty, tz)。根据标注信息,物体的高度、宽度、长度分别维d、w、l。那么,8个顶点坐标分别为(-l/2, w/2, 0)、(-l/2, -w/2, 0)、(l/2, -w/2, 0)、(l/2, w/2, 0)、(-l/2, w/2, h)、(-l/2, -w/2, h)、(l/2, -w/2, h)、(l/2, w/2, h)。这8个顶点分别对应下面示意图的0-7。如下图所示,在雷达坐标系中,x表示汽车前进方向,对应方向的尺寸为目标长度l;y表示车身方向,对应方向的尺寸为目标宽度w;z表示高度方向,对应方向的尺寸为目标高度h。以坐标系为视点,2、3、6、7构成的平面为目标自身的前方,用白色进行标注。
? ? ? ? ?相应函数段为:
def draw_rgb_projections(image, projections, color=(255,255,255), thickness=2, darker=1):
img = image.copy()*darker
num=len(projections)
forward_color=(255,255,0)
for n in range(num):
qs = projections[n]
for k in range(0,4):
i,j=k,(k+1)%4
cv2.line(img, (qs[i,0],qs[i,1]), (qs[j,0],qs[j,1]), color, thickness, cv2.LINE_AA)
i,j=k+4,(k+1)%4 + 4
cv2.line(img, (qs[i,0],qs[i,1]), (qs[j,0],qs[j,1]), color, thickness, cv2.LINE_AA)
i,j=k,k+4
cv2.line(img, (qs[i,0],qs[i,1]), (qs[j,0],qs[j,1]), color, thickness, cv2.LINE_AA)
cv2.line(img, (qs[3,0],qs[3,1]), (qs[7,0],qs[7,1]), forward_color, thickness, cv2.LINE_AA)
cv2.line(img, (qs[7,0],qs[7,1]), (qs[6,0],qs[6,1]), forward_color, thickness, cv2.LINE_AA)
cv2.line(img, (qs[6,0],qs[6,1]), (qs[2,0],qs[2,1]), forward_color, thickness, cv2.LINE_AA)
cv2.line(img, (qs[2,0],qs[2,1]), (qs[3,0],qs[3,1]), forward_color, thickness, cv2.LINE_AA)
cv2.line(img, (qs[3,0],qs[3,1]), (qs[6,0],qs[6,1]), forward_color, thickness, cv2.LINE_AA)
cv2.line(img, (qs[2,0],qs[2,1]), (qs[7,0],qs[7,1]), forward_color, thickness, cv2.LINE_AA)
return img
5 完整程序
# -*- coding: utf-8 -*-
"""
乐乐感知学堂公众号
@author: https://blog.csdn.net/suiyingy
"""
from __future__ import division
import os
import numpy as np
import cv2
def project_velo2rgb(velo,calib):
T=np.zeros([4,4],dtype=np.float32)
T[:3,:]=calib['Tr_velo2cam']
T[3,3]=1
R=np.zeros([4,4],dtype=np.float32)
R[:3,:3]=calib['R0']
R[3,3]=1
num=len(velo)
projections = np.zeros((num,8,2), dtype=np.int32)
for i in range(len(velo)):
box3d=np.ones([8,4],dtype=np.float32)
box3d[:,:3]=velo[i]
M=np.dot(calib['P2'],R)
M=np.dot(M,T)
box2d=np.dot(M,box3d.T)
box2d=box2d[:2,:].T/box2d[2,:].reshape(8,1)
projections[i] = box2d
return projections
def draw_rgb_projections(image, projections, color=(255,255,255), thickness=2, darker=1):
img = image.copy()*darker
num=len(projections)
forward_color=(255,255,0)
for n in range(num):
qs = projections[n]
for k in range(0,4):
i,j=k,(k+1)%4
cv2.line(img, (qs[i,0],qs[i,1]), (qs[j,0],qs[j,1]), color, thickness, cv2.LINE_AA)
i,j=k+4,(k+1)%4 + 4
cv2.line(img, (qs[i,0],qs[i,1]), (qs[j,0],qs[j,1]), color, thickness, cv2.LINE_AA)
i,j=k,k+4
cv2.line(img, (qs[i,0],qs[i,1]), (qs[j,0],qs[j,1]), color, thickness, cv2.LINE_AA)
cv2.line(img, (qs[3,0],qs[3,1]), (qs[7,0],qs[7,1]), forward_color, thickness, cv2.LINE_AA)
cv2.line(img, (qs[7,0],qs[7,1]), (qs[6,0],qs[6,1]), forward_color, thickness, cv2.LINE_AA)
cv2.line(img, (qs[6,0],qs[6,1]), (qs[2,0],qs[2,1]), forward_color, thickness, cv2.LINE_AA)
cv2.line(img, (qs[2,0],qs[2,1]), (qs[3,0],qs[3,1]), forward_color, thickness, cv2.LINE_AA)
cv2.line(img, (qs[3,0],qs[3,1]), (qs[6,0],qs[6,1]), forward_color, thickness, cv2.LINE_AA)
cv2.line(img, (qs[2,0],qs[2,1]), (qs[7,0],qs[7,1]), forward_color, thickness, cv2.LINE_AA)
return img
#过滤指定范围之外的点和目标框
def get_filtered_lidar(lidar, boxes3d=None):
xrange = (0, 70.4)
yrange = (-40, 40)
zrange = (-3, 1)
pxs = lidar[:, 0]
pys = lidar[:, 1]
pzs = lidar[:, 2]
filter_x = np.where((pxs >= xrange[0]) & (pxs < xrange[1]))[0]
filter_y = np.where((pys >= yrange[0]) & (pys < yrange[1]))[0]
filter_z = np.where((pzs >= zrange[0]) & (pzs < zrange[1]))[0]
filter_xy = np.intersect1d(filter_x, filter_y)
filter_xyz = np.intersect1d(filter_xy, filter_z)
if boxes3d is not None:
box_x = (boxes3d[:, :, 0] >= xrange[0]) & (boxes3d[:, :, 0] < xrange[1])
box_y = (boxes3d[:, :, 1] >= yrange[0]) & (boxes3d[:, :, 1] < yrange[1])
box_z = (boxes3d[:, :, 2] >= zrange[0]) & (boxes3d[:, :, 2] < zrange[1])
box_xyz = np.sum(box_x & box_y & box_z,axis=1)
return lidar[filter_xyz], boxes3d[box_xyz>0]
return lidar[filter_xyz]
def load_kitti_calib(calib_file):
"""
load projection matrix
"""
with open(calib_file) as fi:
lines = fi.readlines()
assert (len(lines) == 8)
obj = lines[0].strip().split(' ')[1:]
P0 = np.array(obj, dtype=np.float32)
obj = lines[1].strip().split(' ')[1:]
P1 = np.array(obj, dtype=np.float32)
obj = lines[2].strip().split(' ')[1:]
P2 = np.array(obj, dtype=np.float32)
obj = lines[3].strip().split(' ')[1:]
P3 = np.array(obj, dtype=np.float32)
obj = lines[4].strip().split(' ')[1:]
R0 = np.array(obj, dtype=np.float32)
obj = lines[5].strip().split(' ')[1:]
Tr_velo_to_cam = np.array(obj, dtype=np.float32)
obj = lines[6].strip().split(' ')[1:]
Tr_imu_to_velo = np.array(obj, dtype=np.float32)
return {'P2': P2.reshape(3, 4),
'R0': R0.reshape(3, 3),
'Tr_velo2cam': Tr_velo_to_cam.reshape(3, 4)}
def box3d_cam_to_velo(box3d, Tr):
def project_cam2velo(cam, Tr):
T = np.zeros([4, 4], dtype=np.float32)
T[:3, :] = Tr
T[3, 3] = 1
T_inv = np.linalg.inv(T)
lidar_loc_ = np.dot(T_inv, cam)
lidar_loc = lidar_loc_[:3]
return lidar_loc.reshape(1, 3)
def ry_to_rz(ry):
angle = -ry - np.pi / 2
if angle >= np.pi:
angle -= np.pi
if angle < -np.pi:
angle = 2*np.pi + angle
return angle
h,w,l,tx,ty,tz,ry = [float(i) for i in box3d]
cam = np.ones([4, 1])
cam[0] = tx
cam[1] = ty
cam[2] = tz
t_lidar = project_cam2velo(cam, Tr)
Box = np.array([[-l / 2, -l / 2, l / 2, l / 2, -l / 2, -l / 2, l / 2, l / 2],
[w / 2, -w / 2, -w / 2, w / 2, w / 2, -w / 2, -w / 2, w / 2],
[0, 0, 0, 0, h, h, h, h]])
rz = ry_to_rz(ry)
rotMat = np.array([
[np.cos(rz), -np.sin(rz), 0.0],
[np.sin(rz), np.cos(rz), 0.0],
[0.0, 0.0, 1.0]])
velo_box = np.dot(rotMat, Box)
cornerPosInVelo = velo_box + np.tile(t_lidar, (8, 1)).T
box3d_corner = cornerPosInVelo.transpose()
return box3d_corner.astype(np.float32)
def load_kitti_label(label_file, Tr):
with open(label_file,'r') as f:
lines = f.readlines()
gt_boxes3d_corner = []
num_obj = len(lines)
for j in range(num_obj):
obj = lines[j].strip().split(' ')
obj_class = obj[0].strip()
if obj_class not in ['Car']:
continue
box3d_corner = box3d_cam_to_velo(obj[8:], Tr)
gt_boxes3d_corner.append(box3d_corner)
gt_boxes3d_corner = np.array(gt_boxes3d_corner).reshape(-1,8,3)
return gt_boxes3d_corner
def test():
lidar_path = os.path.join('./data/KITTI/training', "velodyne/")
calib_path = os.path.join('./data/KITTI/training', "calib/")
label_path = os.path.join('./data/KITTI/training', "label_2/")
image_path = os.path.join('./data/KITTI/training', "image_2/")
lidar_file = lidar_path + '/' + '000016' + '.bin'
calib_file = calib_path + '/' + '000016' + '.txt'
label_file = label_path + '/' + '000016' + '.txt'
image_file = image_path + '/' + '000016' + '.png'
image = cv2.imread(image_file)
#加载雷达数据
print("Processing: ", lidar_file)
lidar = np.fromfile(lidar_file, dtype=np.float32)
lidar = lidar.reshape((-1, 4))
#加载标注文件
calib = load_kitti_calib(calib_file)
#标注转三维目标检测框
gt_box3d = load_kitti_label(label_file, calib['Tr_velo2cam'])
#过滤指定范围之外的点和目标框
lidar, gt_box3d = get_filtered_lidar(lidar, gt_box3d)
# view in point cloud,可视化
gt_3dTo2D = project_velo2rgb(gt_box3d, calib)
img_with_box = draw_rgb_projections(image,gt_3dTo2D, color=(0,0,255),thickness=1)
cv2.imwrite('img_with_box.png', img_with_box)
cv2.imshow('img_with_box.png', img_with_box)
cv2.waitKey(0)
if __name__ == '__main__':
test()
6 效果图
?
更多三维、二维感知算法和金融量化分析算法请关注“乐乐感知学堂”微信公众号,并将持续进行更新。
|