深度相机进行障碍物检测

1. 检测原理

1.1 深度信息的获取

Depth 相机(深度相机)可以获取场景中每个像素点到相机的距离信息。它的工作原理基于不同的技术,如结构光(Structured - Light)、飞行时间(Time - of - Flight,ToF)等。

以结构光为例,相机通过发射特定图案(如条纹或点阵)的光到场景中,然后接收反射光。根据反射光图案的变形来计算物体表面的深度信息。飞行时间原理则是通过测量光脉冲从发射到返回相机的时间来确定距离。

1.2 障碍物的定义与识别依据

对于障碍物检测,首先要确定一个距离阈值。如果某个像素点或者一组像素点(代表一个物体)的距离小于这个阈值,就可以被判定为在关注范围内的物体,可能是障碍物。

例如,在一个自动导航的机器人应用中,如果设定机器人周围 1 米内的物体为障碍物,那么深度相机获取到的距离数据小于 1 米的像素区域对应的物体就会被标记为障碍物。

1.3 空间位置关系的考虑

除了距离,还需要考虑物体在三维空间中的位置。通过深度相机的坐标系(通常以相机镜头中心为原点,建立 x、y、z 轴坐标系,z 轴表示深度方向),可以确定物体相对于相机的位置。

例如,在汽车的防撞系统中,不仅要知道前方物体的距离,还要知道物体在车辆行驶方向(通常是 x - z 平面)的位置,以便准确判断是否会发生碰撞。

2. 方法实现

2.1 数据读取与预处理

**获取深度数据:**首先需要通过相机的软件开发工具包(SDK)或相关接口来获取深度图像数据。这些数据通常以数组的形式存储,数组中的每个元素代表一个像素的深度值(单位可能是毫米或厘米等)。

**数据格式转换与校准:**深度数据可能需要进行格式转换,以适应后续的处理。同时,为了提高数据的准确性,可能需要进行校准。校准包括内部参数校准(如焦距等光学参数)和外部参数校准(如相机在机器人或车辆等载体上的安装位置和姿态)。

2.2 阈值处理

**设定距离阈值:**根据具体应用场景设定一个合适的距离阈值。例如,在室内机器人避障应用中,这个阈值可能是 50 厘米左右。

**比较距离数据与阈值:**遍历深度图像数据中的每个像素的深度值,将其与设定的阈值进行比较。如果深度值小于阈值,就将该像素标记为可能属于障碍物的像素。

2.3 区域分割与聚类

**连通区域分析:**经过阈值处理后,标记为可能是障碍物的像素通常是离散的或者部分连通的。可以使用连通区域分析算法(如基于广度优先搜索或深度优先搜索的算法)来将这些像素分组,每个组代表一个潜在的障碍物。

**聚类算法:**除了连通区域分析,还可以使用聚类算法(如 K - Means 聚类)来对深度数据进行聚类。聚类算法可以根据像素点的空间位置和深度值将它们划分为不同的类别,每个类别可以被看作是一个障碍物或者物体的一部分。

2.4 障碍物特征提取与判断

**尺寸与形状特征提取:**对于每个聚类后的障碍物区域,可以提取其尺寸(如面积、长度、宽度等)和形状特征(如圆形度、矩形度等)。这些特征可以帮助进一步判断障碍物的类型。例如,一个面积较大且形状规则的障碍物可能是墙壁,而一个面积较小且形状不规则的障碍物可能是一个小摆件。

**位置与姿态判断:**根据深度相机坐标系下障碍物的像素位置和深度值,计算障碍物相对于相机的三维位置和姿态。这对于路径规划和碰撞避免非常重要。例如,在无人机的障碍物检测中,需要知道障碍物相对于无人机的位置和姿态,以便无人机能够安全地绕过它。

3. 基于 OpenCV 的障碍物检测原理及操作

// 在水平和垂直方向上翻转图像,使其符合显示或处理的坐标习惯等需求
            cv::flip(depthImg, depthImg, -1);

            // 将深度图像转换为二进制图像,根据设定的深度阈值区分障碍物区域(白色)和其他区域(黑色)
            cv::Mat obstacleMask;
            cv::inRange(depthImg, MIN_OBSTACLE_DISTANCE, MAX_OBSTACLE_DISTANCE, obstacleMask);

            // 查找轮廓(即障碍物的边界),使用合适的轮廓查找模式和近似方法
            std::vector<std::vector<cv::Point>> contours;
            std::vector<cv::Vec4i> hierarchy;
            cv::findContours(obstacleMask, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);

            // 根据是否找到轮廓来确定是否检测到障碍物
            bool obstacleDetected =!contours.empty();
            std::ostringstream output;  // 用于构建输出的字符串流,记录障碍物详细信息
            // 构造要发布的消息
            your_package::msg::ObstacleInfo message;
            message.obstacle_detected = obstacleDetected;

            if (obstacleDetected) {
                // 遍历找到的轮廓,对每个符合条件的轮廓进行处理
                for (size_t j = 0; j < contours.size(); j++)
                {
                    double area = cv::contourArea(contours[j]);
                    if (area > 500) // 过滤掉太小的区域,可根据实际需求调整此阈值
                    {
                        // 计算每个障碍物的边界框,便于后续定位、绘制等操作
                        cv::Rect boundingBox = cv::boundingRect(contours[j]);

                        // 计算障碍物的中心点坐标
                        cv::Point2f center = (boundingBox.tl() + boundingBox.br()) * 0.5;

                        // 获取中心点的深度值(距离),用于判断和后续信息记录
                        ushort distance = depthImg.at<ushort>(center.y, center.x);

                        // 如果距离值在有效范围内,进行相关信息处理和发布等操作
                        if (distance > MIN_OBSTACLE_DISTANCE && distance < MAX_OBSTACLE_DISTANCE)
                        {
                            // 将毫米转换为米,符合实际距离单位使用习惯(比如ROS中通常用米作为长度单位)
                            float distanceInMeters = distance / 1000.0; 

                            // 填充消息中的各个字段
                            message.obstacle_detected = obstacleDetected;
                            message.id = static_cast<int32_t>(j + 1);
                            message.area = static_cast<float64_t>(area);
                            message.position_x = boundingBox.x;
                            message.position_y = boundingBox.y;
                            message.size_width = boundingBox.width;
                            message.size_height = boundingBox.height;
                            message.distance = static_cast<float64_t>(distanceInMeters);

                            // 将障碍物的详细信息添加到输出流,方便后续统一输出查看
                            output << "Obstacle " << j + 1
                                   << " Area: " << area << " pixels"
                                   << " Position: (" << boundingBox.x << ", " << boundingBox.y << ")"
                                   << " Size: (" << boundingBox.width << "x" << boundingBox.height << ")"
                                   << " Distance: " << distanceInMeters << " m" << std::endl;
                        }
                    }
                }
            }
            // 发布包含障碍物信息的消息到指定的ROS 2话题
            obstacle_pub_->publish(message);

            // 根据是否检测到障碍物输出相应的日志信息
            if (obstacleDetected) {
                RCLCPP_INFO(node->get_logger(), "Obstacle detected: true. Detected %zu obstacles in this frame.", contours.size());
                RCLCPP_INFO(node->get_logger(), "Details of obstacles: \n%s", output.str().c_str());
            } else {
                RCLCPP_INFO(node->get_logger(), "Obstacle detected: false. No obstacles found in this frame.");
            }

3.1 图像翻转(根据实际返回图像调整)

cv::flip(depthImg, depthImg, -1); 这一步操作是对深度图像进行翻转,参数 -1 表示在水平和垂直方向同时翻转。这样做的目的通常是为了使图像的坐标系统符合后续处理(比如显示、轮廓查找等操作)的习惯,确保图像的坐标与实际期望的视觉或空间关系相匹配。

3.2 阈值处理(二值化)

cv::inRange(depthImg, MIN_OBSTACLE_DISTANCE, MAX_OBSTACLE_DISTANCE, obstacleMask); 运用了 OpenCV 的 inRange 函数对深度图像进行阈值处理,将其转换为二进制图像(即二值化)。原理是遍历深度图像中的每个像素点对应的深度值,若该深度值处于设定的 MIN_OBSTACLE_DISTANCE(最小障碍物距离阈值)和 MAX_OBSTACLE_DISTANCE(最大障碍物距离阈值)之间,则在输出的 obstacleMask 图像中对应的像素位置置为白色(通常表示为 255),代表可能的障碍物区域;若不在此范围内,则置为黑色(通常表示为 0),以此区分出潜在的障碍物区域和其他区域。

3.3 轮廓查找

cv::findContours(obstacleMask, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE); 使用 findContours 函数查找二值化图像 obstacleMask 中的轮廓,也就是潜在障碍物的边界。RETR_EXTERNAL 参数表示只检测最外层的轮廓,忽略内部嵌套的轮廓,适用于只关注独立的障碍物轮廓的场景;CHAIN_APPROX_SIMPLE 参数则是对轮廓的表示方式进行简化,通过仅保留轮廓的端点信息等方式来压缩轮廓数据,减少内存占用,同时又能足够表示出轮廓的形状特征,便于后续处理。

3.4 障碍物特征提取与判断

**面积过滤:**在遍历找到的轮廓时,通过 cv::contourArea(contours[j]) 计算每个轮廓对应的面积,并且设置了面积阈值(area > 500)来过滤掉太小的区域,因为这些小区域可能是噪声或者不符合实际应用中对障碍物定义的微小物体,以此提高检测的准确性和实用性。

**边界框计算与中心点确定:**利用 cv::boundingRect(contours[j]) 为每个符合条件的轮廓计算其边界框(以矩形框的形式包围轮廓),边界框的坐标信息(boundingBox.x、boundingBox.y、boundingBox.width、boundingBox.height)可用于确定障碍物在图像中的位置和尺寸范围。通过 cv::Point2f center = (boundingBox.tl() + boundingBox.br()) * 0.5; 计算出边界框的中心点坐标,后续可基于中心点坐标从深度图像中获取对应的深度值(距离),进而确定障碍物距离相机的实际远近情况。

**距离判断与消息填充:**获取中心点的深度值 distance 后,再次判断其是否处于设定的有效距离范围(distance > MIN_OBSTACLE_DISTANCE && distance < MAX_OBSTACLE_DISTANCE)内,如果是,则将相关的障碍物特征信息(如是否检测到障碍物、障碍物编号、面积、位置坐标、尺寸大小、距离等)填充到要发布的 your_package::msg::ObstacleInfo 消息结构体中,准备进行发布。

3.5 可视化与交互(与 ROS 2 结合部分)

代码中一方面通过 obstacle_pub_->publish(message); 将包含障碍物详细信息的消息发布到名为 “lidar_info_topic” 的 ROS 2 话题上,使得其他订阅了该话题的 ROS 2 节点(如机器人的决策、控制等相关节点)可以获取到障碍物信息来进行相应的操作(比如路径规划、避障决策等);另一方面,根据是否检测到障碍物,使用 RCLCPP_INFO 等 ROS 2 日志输出函数输出相应的日志信息,方便在程序运行过程中查看检测结果以及进行调试等操作。同时,代码中还涉及了显示彩色图像(虽然目前代码中被注释掉了 cv::imshow(“raw color”, colorImg);)以及通过 cv::waitKey(1) 来等待用户按键操作(按下 q 键可结束程序运行),从一定程度上实现了可视化和简单的交互功能。

4. 基于点云库(PCL)的方法

4.1 原理

深度相机获取的数据可以转换为点云数据(三维空间中的点的集合)。PCL 提供了大量用于处理点云数据的算法。例如,对于障碍物检测,可以通过将点云数据分割成不同的部分来识别障碍物。一种常用的分割方法是欧氏聚类(Euclidean Clustering),它基于点之间的欧氏距离。在点云空间中,距离较近的点被聚类为一个组,这些组可能代表一个物体。如果一个组的点云在一定的距离范围内(例如,在机器人行驶路径的前方一定距离内),就可以被判定为障碍物。

4.2 实现

**点云数据获取与转换:**首先从深度相机获取深度数据,并将其转换为点云数据格式。假设深度相机的数据存储在一个数组中,并且相机的内参已知(包括焦距等参数),可以使用以下伪代码将深度数据转换为点云(这里以简单的针孔相机模型为例)

import numpy as np

# 假设深度数据存储在depth_image数组中,图像宽度为width,高度为height
# 相机内参(示例),需要根据实际相机校准得到
fx = 500.0  # x方向焦距
fy = 500.0  # y方向焦距
cx = width / 2.0
cy = height / 2.0

point_cloud = []
for v in range(height):
    for u in range(width):
        depth = depth_image[v][u]
        if depth > 0:
            x = (u - cx) * depth / fx
            y = (v - cy) * depth / fy
            point_cloud.append([x, y, depth])
point_cloud = np.array(point_cloud)

**点云滤波:**为了去除噪声和离群点,通常会进行滤波操作。例如,可以使用体素网格滤波(Voxel Grid Filter)来下采样点云,减少数据量的同时保留点云的形状特征。在 PCL 中,以下代码可以实现体素网格滤波:

#include <pcl/filters/voxel_grid.h>

pcl::VoxelGrid<pcl::PointXYZ> voxel_grid;
voxel_grid.setInputCloud(input_cloud);
voxel_grid.setLeafSize(0.01f, 0.01f, 0.01f);  // 设置体素大小,根据实际情况调整
pcl::PointCloud<pcl::PointXYZ> filtered_cloud;
voxel_grid.filter(filtered_cloud);

**欧氏聚类分割:**使用欧氏聚类算法来分割点云。在 PCL 中,可以使用以下代码实现

#include <pcl/segmentation/extract_clusters.h>

pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>);
tree->setInputCloud(filtered_cloud);
std::vector<pcl::PointIndices> cluster_indices;
pcl::EuclideanClusterExtraction<pcl::PointXYZ> ec;
ec.setClusterTolerance(0.2);  // 设置聚类容差,即距离阈值,根据实际情况调整
ec.setMinClusterSize(100);   // 设置最小聚类大小,过滤小的聚类
ec.setMaxClusterSize(25000);  // 设置最大聚类大小
ec.setSearchMethod(tree);
ec.setInputCloud(filtered_cloud);
ec.extract(cluster_indices);

障碍物判断与处理:对于每个聚类后的点云组,根据其位置(例如,相对于机器人或车辆的位置)和大小等特征来判断是否为障碍物。如果一个聚类在机器人的运动路径上并且大小符合一定的范围,就可以将其标记为障碍物。可以通过计算聚类的质心位置(中心点位置)来确定其在三维空间中的位置,用于后续的避障策略。

5. 深度学习方法(以卷积神经网络 - CNN 为例)

5.1 原理

可以使用深度神经网络来学习深度图像或点云数据中的障碍物特征。以深度图像为例,将深度图像作为神经网络的输入,网络通过大量的标记数据(已标记出障碍物位置和类型的深度图像)进行训练。在训练过程中,网络学习到深度图像中不同障碍物的形状、纹理(深度纹理)等特征。例如,一个基于 CNN 的障碍物检测网络可能包括卷积层、池化层和全连接层。卷积层用于提取图像中的局部特征,池化层用于减少数据量和防止过拟合,全连接层用于分类或回归任务(判断是否为障碍物以及确定障碍物的位置等)。

5.2 实现(以使用 TensorFlow 和 Keras 为例)

**数据准备:**收集深度图像数据集,并对其进行标注,标记出障碍物的位置和类型。将数据集划分为训练集、验证集和测试集。假设数据集已经准备好,并且存储在相应的文件夹中,每个深度图像都有对应的标注文件。

**模型构建:**构建一个简单的 CNN 模型,例如:

import tensorflow as tf
from tensorflow.keras import layers

model = tf.keras.Sequential([
    layers.Conv2D(32, (3, 3), activation='relu', input_shape=(height, width, 1)),  # 输入深度图像,高度为height,宽度为width,通道数为1(因为是深度图像)
    layers.MaxPooling2D((2, 2)),
    layers.Conv2D(64, (3, 3), activation='relu'),
    layers.MaxPooling2D((2, 2)),
    layers.Flatten(),
    layers.Dense(64, activation='relu'),
    layers.Dense(num_classes, activation='softmax')  # num_classes是障碍物类别数,如果是二分类(是/不是障碍物),则num_classes = 2
])

**模型训练:**使用训练集对模型进行训练,设置合适的损失函数(如交叉熵损失函数用于分类任务)和优化器(如 Adam 优化器)

model.compile(optimizer='adam',
              loss='categorical_crossentropy',
              metrics=['accuracy'])
model.fit(train_images, train_labels, epochs=10, validation_data=(val_images, val_labels))

**模型评估与应用:**使用测试集对训练好的模型进行评估,计算准确率等指标。在实际应用中,将深度相机获取的实时深度图像输入到训练好的模型中,模型输出预测结果,判断是否为障碍物以及障碍物的类别等信息,根据这些信息进行相应的障碍物处理策略。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值