175 lines
5.7 KiB
Rust
175 lines
5.7 KiB
Rust
//! LiDAR point cloud processing
|
|
|
|
use crate::{VisionResult, VisionError};
|
|
use crate::detection::three_d::{PointCloud, Detection3DResult, BoundingBox3D};
|
|
use crate::autonomous::{LidarData, AutonomousDetector, SensorType};
|
|
use crate::tensor_utils::TensorExt;
|
|
use rtx_tensor::{Tensor, Device, DType};
|
|
|
|
/// LiDAR-based object detector
|
|
pub struct LidarDetector {
|
|
point_net: PointNetBackbone,
|
|
detection_head: LidarDetectionHead,
|
|
}
|
|
|
|
impl LidarDetector {
|
|
pub fn new() -> VisionResult<Self> {
|
|
let point_net = PointNetBackbone::new()?;
|
|
let detection_head = LidarDetectionHead::new(1024, 10)?; // 10 classes
|
|
|
|
Ok(Self {
|
|
point_net,
|
|
detection_head,
|
|
})
|
|
}
|
|
|
|
/// Process point cloud for object detection
|
|
pub fn process_point_cloud(&mut self, point_cloud: &PointCloud) -> VisionResult<Vec<BoundingBox3D>> {
|
|
// Extract features
|
|
let features = self.point_net.forward(&point_cloud.points)?;
|
|
|
|
// Detect objects
|
|
let detections = self.detection_head.forward(&features)?;
|
|
|
|
Ok(detections)
|
|
}
|
|
}
|
|
|
|
impl AutonomousDetector for LidarDetector {
|
|
fn detect_lidar(&mut self, lidar_data: &LidarData) -> VisionResult<Detection3DResult> {
|
|
let start_time = std::time::Instant::now();
|
|
|
|
let detections = self.process_point_cloud(&lidar_data.point_cloud)?;
|
|
|
|
let processing_time = start_time.elapsed().as_millis() as f32;
|
|
|
|
Ok(Detection3DResult {
|
|
boxes_3d: detections,
|
|
processing_time_ms: processing_time,
|
|
model_name: "LiDAR Detector".to_string(),
|
|
})
|
|
}
|
|
|
|
fn supported_sensors(&self) -> Vec<SensorType> {
|
|
vec![SensorType::LiDAR]
|
|
}
|
|
}
|
|
|
|
/// PointNet backbone for LiDAR processing
|
|
struct PointNetBackbone {
|
|
conv_layers: Vec<Tensor>,
|
|
}
|
|
|
|
impl PointNetBackbone {
|
|
fn new() -> VisionResult<Self> {
|
|
let conv_layers = vec![
|
|
Tensor::randn(&[64, 3, 1], &Device::default())?,
|
|
Tensor::randn(&[128, 64, 1], &Device::default())?,
|
|
Tensor::randn(&[1024, 128, 1], &Device::default())?,
|
|
];
|
|
|
|
Ok(Self { conv_layers })
|
|
}
|
|
|
|
fn forward(&self, points: &Tensor) -> VisionResult<Tensor> {
|
|
let mut x = points.transpose(-2, -1)?; // (B, 3, N)
|
|
|
|
for conv in &self.conv_layers {
|
|
x = x.conv1d(conv, 0, 1, 1)?.relu()?;
|
|
}
|
|
|
|
// Global max pooling
|
|
let global_features = x.max_dim(2, true)?;
|
|
Ok(global_features.0)
|
|
}
|
|
}
|
|
|
|
/// LiDAR detection head
|
|
struct LidarDetectionHead {
|
|
fc_layers: Vec<Tensor>,
|
|
num_classes: usize,
|
|
}
|
|
|
|
impl LidarDetectionHead {
|
|
fn new(in_features: usize, num_classes: usize) -> VisionResult<Self> {
|
|
let fc_layers = vec![
|
|
Tensor::randn(&[512, in_features], &Device::default())?,
|
|
Tensor::randn(&[256, 512], &Device::default())?,
|
|
Tensor::randn(&[(num_classes + 7) * 100, 256], DType::F32, &Device::default())?, // 7 for box params, 100 proposals
|
|
];
|
|
|
|
Ok(Self { fc_layers, num_classes })
|
|
}
|
|
|
|
fn forward(&self, features: &Tensor) -> VisionResult<Vec<BoundingBox3D>> {
|
|
let mut x = features.clone();
|
|
|
|
for fc in &self.fc_layers[..2] {
|
|
x = x.matmul(&fc.transpose(-2, -1)?)?.relu()?;
|
|
}
|
|
|
|
let predictions = x.matmul(&self.fc_layers[2].transpose(-2, -1)?)?;
|
|
|
|
// Decode predictions
|
|
self.decode_predictions(&predictions)
|
|
}
|
|
|
|
fn decode_predictions(&self, predictions: &Tensor) -> VisionResult<Vec<BoundingBox3D>> {
|
|
let pred_data = predictions.to_vec()?;
|
|
let mut boxes = Vec::new();
|
|
|
|
let stride = self.num_classes + 7;
|
|
for i in (0..pred_data.len()).step_by(stride) {
|
|
if i + 6 < pred_data.len() {
|
|
let center = [pred_data[i], pred_data[i + 1], pred_data[i + 2]];
|
|
let size = [pred_data[i + 3], pred_data[i + 4], pred_data[i + 5]];
|
|
let rotation = [pred_data[i + 6], 0.0, 0.0];
|
|
|
|
// Find best class
|
|
let mut best_class = 0;
|
|
let mut best_confidence = 0.0;
|
|
|
|
for class_id in 0..self.num_classes {
|
|
let conf_idx = i + 7 + class_id;
|
|
if conf_idx < pred_data.len() {
|
|
let confidence = pred_data[conf_idx].sigmoid();
|
|
if confidence > best_confidence {
|
|
best_confidence = confidence;
|
|
best_class = class_id;
|
|
}
|
|
}
|
|
}
|
|
|
|
if best_confidence > 0.3 {
|
|
boxes.push(BoundingBox3D::new(center, size, rotation, best_confidence, best_class));
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(boxes)
|
|
}
|
|
}
|
|
|
|
/// Point cloud preprocessing utilities
|
|
pub struct LidarPreprocessor;
|
|
|
|
impl LidarPreprocessor {
|
|
/// Remove ground points using RANSAC plane fitting
|
|
pub fn remove_ground_plane(point_cloud: &PointCloud) -> VisionResult<PointCloud> {
|
|
// Simplified ground removal - in practice use proper RANSAC
|
|
Ok(point_cloud.clone())
|
|
}
|
|
|
|
/// Voxel downsampling to reduce point density
|
|
pub fn voxel_downsample(point_cloud: &PointCloud, voxel_size: f32) -> VisionResult<PointCloud> {
|
|
point_cloud.voxelize(voxel_size).map(|voxels| {
|
|
PointCloud::new(voxels.voxel_coords)
|
|
})
|
|
}
|
|
|
|
/// Statistical outlier removal
|
|
pub fn remove_outliers(point_cloud: &PointCloud, k_neighbors: usize, std_multiplier: f32) -> VisionResult<PointCloud> {
|
|
// Simplified outlier removal
|
|
Ok(point_cloud.clone())
|
|
}
|
|
} |