一种简化的3D点云车道线自动识别标注一些思考

news2024/11/29 14:55:28

0. 简介

作为3D车道线训练来说,数据集是至关重要的,而使用点云的精确性来完成准确的车道线地图构建是一个非常重要且有趣的事情。下面我们将会从一个例子开始,分阶段来告诉大家该怎么样去完成一个简单的3D点云车道线自动识别标注工具。

1. 前言

按照《Automatic Road Markings Extraction Classification And Vectorization Mobile From Laser Scanning Data》文章的工作,提出了一种高效的从移动激光扫描(MLS)点云中进行道路标线的自动化提取、分类和矢量化实现的方法。**首先,将移动激光扫描点云划分为地面点和非地面点。其次,在图像处理方法下,生成多个地理参考图像,并进一步用于道路标线像素的检测。第三,从图像中检索(匹配)道路标线的点云,并进一步分割为连接的对象。采用OSTU最大类间方差—阈值分割法和统计离群点去除法对道路标线目标进行细化。接下来,根据边界框信息将每个道路标线对象分为几类,如边界线、矩形道路标线等。其它不规则的道路标线则通过模型匹配方法进行分类。最后,在重新连接断裂边界线后,将所有分类的道路标线矢量化为封闭或非封闭多段线。**在城市和高速公路场景的各种移动激光扫描点云上进行了综合实验,实验结果表明,该方法的道路标线提取准确率和召回率均超过95%,公路场景的准确率和召回率高达93%。其中城市场景的比例分别为92%和85%。相关代码也已经在Github上开源
在这里插入图片描述

我们根据上文的提炼,基本上就是将里面的内容分为四个部分

  1. 地面过滤 Ground Filter
  2. 二值化,即根据地理参考图像处理 Geo-referenced Image Processing
  3. DBSCAN聚类,即道路标线点云分割和提取
  4. 曲线拟合

下面我们来对这四个部分进行扩展,这里我们选用的是Lane_Extractor这一个项目作为模板,并向大家展示如何完成每一个部分的搭建

2. 地面过滤

这一部分就是根据z轴的坐标来粗略的完成拟合的操作。这部分的操作相对而言比较简单,而如果想要获取更准确的地面信息,则可以采用FEC地面滤除算法将点云分割为地面点和非地面点。这一步对于自动提取道路标线非常必要,点云数据中来自汽车、树木和交通标志上的非地面点会干扰地面元素的检测。

    void CloudProcessing::passthrough() // 滤波
    {
        pcl::PassThrough<pcl::PointXYZI> pass; // 创建直通滤波器对象
        pass.setInputCloud(cloud);             // 设置输入点云
        // pass.setFilterFieldName ("x"); // X..
        // pass.setFilterLimits (-150.0, 150.0);//设置在过滤字段的范围
        // pass.filter(*cloud);

        // pass.setFilterFieldName ("y"); // Y..
        // pass.setFilterLimits (-100.0, 300.0);
        // pass.filter(*cloud);

        pass.setFilterFieldName("z");    // 选择
        pass.setFilterLimits(-1.0, 1.0); // 设置在过滤字段的范围
        pass.filter(*cloud);

        ROS_INFO("Z axis Passthrough Complete!");
    }

    void CloudProcessing::VoxelGridFilter()
    {
        pcl::VoxelGrid<pcl::PointXYZI> voxel; // 创建体素格滤波对象
        voxel.setInputCloud(cloud);
        voxel.setLeafSize(leaf_size, leaf_size, leaf_size);
        voxel.filter(*cloud);

        ROS_INFO("%fM VoxelGridFilter Complete!", leaf_size);
    }

    void CloudProcessing::MapDownsampling(bool voxel) // 地图降采样
    {
        passthrough();
        if (voxel)
        {
            VoxelGridFilter();
        }
        ROS_INFO("downsampling Complete!");
    }

在这里插入图片描述

2. 二值化

在这一步,主要的就是每帧点云在分割出地面后,然后根据反射率的强度来完成车道线地图的查询,这部分的内容相对而言比较简单。当然这样会带来噪声等问题,我们可以通过DBSCAN聚类等方法可以有效地避免误检,但是同样的可以通过上述论文中的方法。

 if ((cp.cloud->points[pointIdxRadiusSearch[i]].intensity > cp.searchinfo.min_intensity)) // 指定强度  && (cp.cloud->points[pointIdxRadiusSearch[i]].intensity < cp.searchinfo.max_intensity)
                {
                    cp.cloud_filtered->points.push_back(cp.cloud->points[pointIdxRadiusSearch[i]]); // 插入所有找到的调试点
                    centroidpoint.add(cp.cloud->points[pointIdxRadiusSearch[i]]);                   // 插入所有找到的点以获得重心
                    s_point.push_back(cp.cloud->points[pointIdxRadiusSearch[i]]);                   // 插入所有找到的点进行条件判定
                }

文中认为虽然道路标线的反射强度随范围大小、入射角和道路材质的变化很大,但是单独一块点云的强度梯度与道路标线边界大致相同,因此一个阈值足以检测标线边界。最大熵阈值分割方法无监督地选择了实现图像直方图后验已知熵的先验最大化的阈值。结果发现,在Otsu大津法和GMM高斯混合模型等常用的阈值分割算法中,最大熵阈值分割对该任务的效果最好。在这一步,移动激光扫描点云沿着激光扫描仪的运动轨迹被划分成若干块。非地面点和地面点都被投影到水平面上,然后根据平均点间距计算出的分辨率进行光栅化。对于每个点云块,生成三个灰度地理参考图像(高程图、强度图以及点密度图像)。对于每个网格,这三幅图像的灰度值分别计算为非地面点的平均Z值、地面点的平均反射强度值和地面点的数目。中值滤波用于去噪,利用Sobel算子从高程图得到斜率(高程的梯度)图像,从反射强度图像得到强度梯度图像。然后,应用最大熵阈值分割(Pun,1980)生成斜率二值图像和点密度二值图像。

在这里插入图片描述

3. DBSCAN聚类

这部分其实就是将没条车道线提取出来的方法,当然也有用kd-tree做的,比如说下面的就是通过半径搜索的方法,并根据kd-tree来完成检索的。

 int LaneExtractor::RadiusSearch(int SearchLine, float rad) // 按照半径搜索
    {
        std::vector<pcl::PointXYZI> s_point;
        pcl::PointXYZI cetroidpoint;
        int chance = 5; // 检查出线范围的机会数
        float z = 0.3;  // 利用点的高度差(没用)
        cp.searchinfo.radius = rad;

        if (rotation_direction == LEFT || rotation_direction == RIGHT)
        {
            int chance = 6;                  // 重置次数
            L_lane_break = LANE_BREAK_LIMIT; //
            R_lane_break = LANE_BREAK_LIMIT;
            cp.searchinfo.radius = cp.searchinfo.radius + 0.4f; // 范围增大
            // cp.searchinfo.min_intensity = cp.searchinfo.min_intensity+change; intensity change
        }

        int size = lineRadiusSearch(cetroidpoint, s_point, SearchLine); // 使用kd-tree获取个数,并返回重心点以及对应的点集

        if (size > 0) // 分割出来一些特征
        {
            // Apply to Multi-Rain Only
            if (SearchLine == MULTILEFT || SearchLine == MULTIRIGHT) // 判断是否
            {
                if (size <= 3)
                {
                    s_point.clear(), chance = 0; // 聚类过小,则认为没问题
                }                                // Under 3 point remove;
                while (!s_point.empty())
                {
                    Eigen::Vector2f poseP(cetroidpoint.x, cetroidpoint.y); // 获取重心位置
                    pcl::PointXYZI target_p = s_point.back();              // 获取调试点
                    s_point.pop_back();                                    // 压出
                    Eigen::Vector2f targetP(target_p.x, target_p.y);
                    double distance = getPointToDistance(poseP, tan_yaw, targetP); // 判断距离
                    if (distance > 0.26f)
                        chance--; // 重校验次数减一
                    if (chance == 0)
                        break;
                }
                s_point.clear();

                if (chance != 0 && (fabs(cetroidpoint.z - save_point.z) > z)) // 没用这段
                {                                                             // pass -> extract
                    if (SearchLine == MULTILEFT)
                    {
                        Multi_left_point.push_back(cetroidpoint); // just save Multi lnae
                        cp.cloud_filtered->points.push_back(cetroidpoint);
                        L_continuos_line = 1; // continuous multiline
                    }
                    else if (SearchLine == MULTIRIGHT)
                    {
                        Multi_right_point.push_back(cetroidpoint); // just save Multi lnae
                        cp.cloud_filtered->points.push_back(cetroidpoint);
                        R_continuos_line = 1; // continuous multiline
                    }
                }
                else if (L_continuos_line == 1 || R_continuos_line == 1)
                { // extract type change
                    if (SearchLine == MULTILEFT)
                    {
                        L_lane_break--;
                        if (Multi_left_point.size() >= 8)
                        {
                            while (!Multi_left_point.empty())
                            {
                                cp.Intesity_Cloud->points.push_back(Multi_left_point.back());
                                Multi_left_point.pop_back();
                            }
                            L_lane_break = LANE_BREAK_LIMIT;
                        }
                        else if (L_lane_break == 0)
                        {
                            Multi_left_point.clear();
                            L_lane_break = LANE_BREAK_LIMIT;
                        }
                        L_continuos_line = 0; // non-continuous multiline
                    }
                    else if (SearchLine == MULTIRIGHT)
                    {
                        R_lane_break--;
                        if (Multi_right_point.size() >= 8)
                        {
                            while (!Multi_right_point.empty())
                            {
                                cp.Intesity_Cloud->points.push_back(Multi_right_point.back());
                                Multi_right_point.pop_back();
                            }
                            R_lane_break = LANE_BREAK_LIMIT;
                        }
                        else if (R_lane_break == 0)
                        {
                            Multi_right_point.clear();
                            R_lane_break = LANE_BREAK_LIMIT;
                        }
                        R_continuos_line = 0; // non-continuous multiline
                    }
                }
            } // Apply to Multi-Rain Only
            else
            {
                cp.Intesity_Cloud->points.push_back(cetroidpoint); // Not Multi-lane
            }

            return SearchLine; // Return the lane number if lane found
        }
        else
            return 0; // If you don't find the lane, return 0.
    }

	// just kdtree radius search
    int LaneExtractor::lineRadiusSearch(pcl::PointXYZI &centerpoint, std::vector<pcl::PointXYZI> &s_point, int SearchLine)
    {
        pcl::KdTreeFLANN<pcl::PointXYZI> kdtree; // Set up your search tree
        std::vector<int> pointIdxRadiusSearch;
        std::vector<float> pointRadiusSquaredDistance;
        kdtree.setInputCloud(cp.cloud);
        pcl::CentroidPoint<pcl::PointXYZI> centroidpoint;
        //                            (   setting search_point  ), (  setting radius  ),
        int size = kdtree.radiusSearch(cp.searchPoint[SearchLine], cp.searchinfo.radius, pointIdxRadiusSearch, pointRadiusSquaredDistance); // 使用kd-tree来拿到对应的索引
        if (size > 0)
        {
            for (size_t i = 0; i < pointIdxRadiusSearch.size(); ++i)
            {
                if ((cp.cloud->points[pointIdxRadiusSearch[i]].intensity > cp.searchinfo.min_intensity)) // 指定强度  && (cp.cloud->points[pointIdxRadiusSearch[i]].intensity < cp.searchinfo.max_intensity)
                {
                    cp.cloud_filtered->points.push_back(cp.cloud->points[pointIdxRadiusSearch[i]]); // 插入所有找到的调试点
                    centroidpoint.add(cp.cloud->points[pointIdxRadiusSearch[i]]);                   // 插入所有找到的点以获得重心
                    s_point.push_back(cp.cloud->points[pointIdxRadiusSearch[i]]);                   // 插入所有找到的点进行条件判定
                }
            }
            centroidpoint.get(centerpoint); // 得到重心点;
            return size;
        }
        else
        {
            // ROS_INFO("No point could be found.."); //TOO NOISY INFO
            return 0;
        }
    }

当然也可以通过DBSCAN完成聚类,pcl里面已经有非常好的嵌套,如果说我们没有一个标准的相对车辆距离的车道线信息,则可以使用DBSCAN来完成拟合,DBSCAN就不单单会处理车道线的点云信息了


	DBSCANKdtreeCluster<pcl::PointXYZ> ec;
	ec.setCorePointMinPts(10);

	// test 4. uncomment the following line to test the EuclideanClusterExtraction
	// pcl::EuclideanClusterExtraction<pcl::PointXYZ> ec;
	pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>);
	tree->setInputCloud(cloud);
	std::vector<pcl::PointIndices> cluster_indices;

	ec.setClusterTolerance(0.1);//搜索近邻点半径
	ec.setMinClusterSize(100);//最小簇点数要求
	ec.setMaxClusterSize(5000000);//最大簇点数限制
	ec.setSearchMethod(tree);
	ec.setInputCloud(cloud);
	ec.extract(cluster_indices);

	clock_t end_ms = clock();
	std::cout << "cluster time cost:" << double(end_ms - start_ms) / CLOCKS_PER_SEC << " s" << std::endl;
	pcl::PointCloud<pcl::PointXYZI>::Ptr cloud_clustered(new pcl::PointCloud<pcl::PointXYZI>);
	int j = 0;
	// visualization, use indensity to show different color for each cluster.
	for (std::vector<pcl::PointIndices>::const_iterator it = cluster_indices.begin(); it != cluster_indices.end(); it++, j++) {
		for (std::vector<int>::const_iterator pit = it->indices.begin(); pit != it->indices.end(); ++pit) {
			pcl::PointXYZI tmp;
			tmp.x = cloud->points[*pit].x;
			tmp.y = cloud->points[*pit].y;
			tmp.z = cloud->points[*pit].z;
			tmp.intensity = j % 8;
			cloud_clustered->points.push_back(tmp);
		}
	}

在这里插入图片描述

4. 曲线拟合

…详情请参照古月居

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/763400.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

WordPress作为可扩展的企业级解决方案

网络商业世界就像一片汪洋大海&#xff0c;大型企业是大海中最大的鱼。然而&#xff0c;只因为你比其他人都大&#xff0c;并不意味着你不能逆流而上。相反&#xff0c;企业业务面临的挑战更大&#xff0c;对网站的技术要求更高。 多年来&#xff0c;大型公司通常依赖最昂贵的…

爆肝整理,接口自动化测试面试题+答案,25k*15薪如何达成的...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、请问你是如何做…

MB10S-ASEMI迷你贴片整流桥50MIL芯片MB10S

编辑&#xff1a;ll MB10S-ASEMI迷你贴片整流桥50MIL芯片MB10S 型号&#xff1a;MB10S 品牌&#xff1a;ASEMI 芯片个数&#xff1a;4 封装&#xff1a;MBS-4 恢复时间&#xff1a;50ns 工作温度&#xff1a;-50C~150C 浪涌电流&#xff1a;30A 正向电流&#xff1a;1…

HttpRunner自动化测试之辅助函数debugtalk.py

辅助函数debugtalk.py Httprunner框架中&#xff0c;使用yaml或json文件进行用例描述&#xff0c;无法做一些复杂操作&#xff0c;如保存一些数据跨文件调用&#xff0c;或者实现一些复杂逻辑判断等&#xff0c;为了解决这个问题&#xff0c;引入了debugtalk.py辅助函数来进行一…

Linux操作系统升级低版本的OpenSSH到9.3的高版本

OpenSSH 9.3之前的版本存在各种各样的安全漏洞&#xff0c;为此&#xff0c;我们需要将OpenSSH升级到最新的9.3的版本。 执行&#xff1a;ssh -V&#xff0c;我们可以查看当前的openssh版本 为了避免升级过程中出现意外而导致服务器无法正常使用&#xff0c;建议操作前先对服务…

插槽的使用!!

什么是插槽 插槽&#xff08;Slot&#xff09;是 vue 为组件的封装者提供的能力。允许开发者在封装组件时&#xff0c;把不确定的、希望由用户指定的部分定义为插槽。可以把插槽认为是组件封装期间&#xff0c;为用户预留的内容的占位符 即&#xff1a;使用者来决定某一块区域…

开利网络拜访番禺前后仓国际珠宝基地,以数字化技术赋能产业升级

近日&#xff0c;开利网络拜访位于番禺的前后仓国际珠宝基地&#xff0c;对基地目前的数字化需求和产业升级方向进行了解和探讨。目前&#xff0c;基地拥有以数字贸易综合服务中心&#xff0c;以人才、流量、运营、金融为抓手&#xff0c;以供应链选品、直播电商、跨境电商为媒…

【C++医学影像PACS】CT检查中的三维重建是什么检查?

一、【PACS影像科普】CT检查中的三维重建是什么检查&#xff1f; 三维重建是多层螺旋CT的一个最大的优点&#xff0c;也是影像工作多年来&#xff0c;从横断解剖到多平面&#xff0c;乃至立体的一次飞跃&#xff0c;让抽象变的形象&#xff0c;大大地提高了准确性&#xff0c;为…

大数据测试之数据仓测试怎么做(下)

前面的文章我们为大家介绍了一个常见的互联网大厂的数据仓的技术框架&#xff0c;也就是下面这张图所展示的内容。 为大家介绍了从操作数据层&#xff0c;到DW层&#xff0c;再到汇总数据层&#xff0c;最后到维度层和数据应用层的整个流程。本文我们将整个架构打平来展示制作…

我们正在开发一套组件库,欢迎你的加入~

项目地址 github地址 可以先点进来康康~ 技术栈 目前我们整体采用的是vue3typescriptless作为整体的开发的选择 需要说的是&#xff0c;我们并没有采用很多组件库采用的TSX的写法&#xff0c;而是选择了SFC的写法&#xff0c;这是因为我们觉得对于大部分的vue开发者来说&am…

用得最多的企业文件加密软件【企业文件加密软件前十】

企业文件加密软件是一种专门设计用于保护企业敏感信息的软件工具。它通过使用加密算法将企业的文件、文件夹和移动设备上的数据转化为不可读的格式&#xff0c;以防止未经授权的访问和数据泄露。这些软件通常提供了多种加密算法和安全控制选项&#xff0c;以满足不同企业的安全…

国外访问学者博士后常用的网络视频面试软件

面试是获得邀请函的重要环节&#xff0c;随着网络的广泛应用&#xff0c;现在视频面试逐渐取代了电话面试&#xff0c;本篇知识人网小编介绍几种国外访问学者博士后常用的网络视频软件。 在申请国外博士后或者访问学者职位时&#xff0c;当接收方导师收到CV&#xff08;简历&am…

postgis上传 上千个资源文件

需求背景所需工具解决流程1. 获取文件名信息2.复制到 excel 表格中3.转成 csv 文件 需求背景 需要把上千个文件资源上传到远端&#xff0c;并建立数据表 所需工具 Notepad 7wps office 11.1.0 解决流程 1. 获取文件名信息 复制文件路径&#xff0c;在 cmd 中进到文件夹&…

Python程序设计基础:列表与元组(二)

文章目录 一、数值列表的生成1、通过input()函数输入创建列表2、通过list()函数转换3、列表生成式4、数值列表的几种统计计算 二、元组1、元组的定义2、元组的操作3、元组作为列表元素 三、转换函数1、元组和列表之间的转换2、字符串和列表之间的转换3、split()方法 一、数值列…

深度学习模型:Pytorch搭建ResNet、DenseNet网络,完成一维数据分类任务

2023.7.17 DenseNet和ResNet都是深度学习中常用的网络结构&#xff0c;它们各有优缺点。 DenseNet的优点是可以充分利用网络中的信息&#xff0c;因为每个层都可以接收来自前面所有层的信息。这种密集连接的结构可以提高网络的准确性&#xff0c;减少过拟合的风险。此外&…

教你一招,动态规划思想

动态规划 什么是动态规划&#xff1f; 动态规划也是算法设计的一种方法/思想。它将一个问题分解为相互重叠的子问题&#xff0c;通过反复求解子问题&#xff0c;来解决原来的问题。 基础案例 场景一 斐波那契数列 当前数等于前面两个数的和。 定义子问题&#xff1a;f(n)…

Python异步网络编程框架Twisted使用方法

Twisted概念 Twisted是一个Python异步网络编程框架&#xff0c;它可以帮助我们开发高性能的网络应用程序。它提供了一些基本概念&#xff0c;如reactor、protocol、transport和factory等&#xff0c;用于构建高效的网络应用程序。 优点&#xff1a; 异步并发处理&#xff1a…

Ceph集群

目录 一、存储概述 1.单机存储设备 1.1 DAS 1.2 NAS 1.3 SAN 2. 单机存储的问题 3. 商业存储解决方案 4.分布式存储&#xff08;软件定义的存储 SDS&#xff09; 4.1 分布式存储的类型 二、Ceph简介 1.Ceph 优势 2. Ceph 架构 2.1 RADOS 基础存储系统 2.2 LIBRADOS…

Arch - 多线程设计架构模式

文章目录 概述细节 概述 多线程设计架构模式是一种通过合理地使用线程来提高系统性能和响应能力的设计模式。以下是一些常见的多线程设计架构模式&#xff1a; 线程池模式&#xff1a;通过预先创建一组线程&#xff0c;将任务提交到线程池中执行&#xff0c;避免了线程的频繁创…

Perforce Helix Core新版本推出资源压力感知功能,提升服务器可用性,助力大规模开发

您的版本控制系统帮助团队进行主动监控吗&#xff1f; Perforce Helix Core的客户经常在不同维度上测试规模和性能的极限。其中一些维度包括文件数量、文件大小、用户数和并发事务数量。随着这些维度的压力增加&#xff0c;服务器资源&#xff08;如内存和CPU&#xff09;通常…