增材成形直壁的点云预处理

问题分析

数据采集设备为GoCator 2300 Series线激光传感器,数据获取途径为基于GoCator SDK与团队开发的软件实现。定义单次扫描激光线方向为X方向;激光线移动方向为Y方向,采用右手系。采集过程参数如下:

  • 线激光移动速度:1000mm/min
  • 刷新率:50Hz
  • X向范围:±200mm

可以看到,对于以上扫描参数,Y向分辨率约为$1000mm/60/50 = 0.33mm$,精度优于电弧增材的成形精度,参数可行。

一次扫描得到的点云数据如下图所示:

origincloud

通过简单的查看若干点的坐标信息,我们可以看到,直壁顶部的高度约为0,而基板的高度约为负值。显然,这与我们的认知不符。造成这一现象的原因主要为测量时未重置机床的外部坐标偏移,线激光工作位置所连接的整体机头上升,基板高度下降。

origincloud_loc

最终需要的结果为:

  • 只需要直壁部分点云及其周围部分的基板
  • 将基板的高度调整为0,直壁顶面的高度按原关系上升

在明确了背景条件与需求后,开始对点云进行处理。

处理过程

基于VS2017+PCL1.9.1环境,编写MFC程序进行数据处理。主要分为以下几个步骤:

  1. 通过高度滤波初步找到焊道点云;
  2. 第一次区域生长方法,找到最大区域,确定为直壁顶面;
  3. 获取顶面的XY边界;
  4. 回到原始点云,通过XY边界+一定的松弛量获得直壁+基板点云;
  5. 第二次区域生长方法,找到平均高度最低的区域,确定为基板,并记录其平均高度;
  6. 对整体点云进行偏移;
  7. 完成。
void CDataPreProcessSingleDlg::SingleBeadProcess(double xOversize, double yOversize)
{
// 焊道处理代码
m_pointCloud.PassThrough(-5.5f, 5.0f, 2);//初步找到焊道
m_pointCloud.StatisticalOutlierRemoval(30, 1.0);//移除离群点
#pragma region 第一次区域生长-找到焊道
std::vector <pcl::PointIndices> clusters;
pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients);
pcl::PointIndices::Ptr inliers(new pcl::PointIndices);
m_pointCloud.Segmentation_RegionGrow(clusters, 50, 100, 5.0, 1.0);
int Idx_Max_Cluster = 0, Max_Cluster = 0;
for (size_t index_i = 0; index_i < clusters.size(); index_i++) {
    //寻找最大区域 
    if (clusters[index_i].indices.size() > Max_Cluster) {
        Max_Cluster = clusters[index_i].indices.size();
        Idx_Max_Cluster = index_i;
    }
}
m_pointCloud.ExtractCloutByIndice(clusters[Idx_Max_Cluster], false);
#pragma endregion

std::vector<double> boundary = m_pointCloud.FindBoundary();//找到XY边界
m_pointCloud.ReloadRawData();//重载原始数据
m_pointCloud.PassThrough(boundary[0] - xOversize, boundary[1] + xOversize, 0);//X边界滤波
m_pointCloud.PassThrough(boundary[2] - yOversize, boundary[3] + yOversize, 1);//Y边界滤波
m_pointCloud.Downsample(0.3f, 0.3f, 0.3f);//稀疏化
m_pointCloud.StatisticalOutlierRemoval(30, 1.0);//移除离群点

#pragma region 第二次区域生长-坐标校正
m_pointCloud.Segmentation_RegionGrow(clusters, 50, 100, 5.0, 1.0);
double Min_Cluster_Height = 0;
double tempHeight = 0;
Idx_Max_Cluster = 0;
for (size_t index_i = 0; index_i < clusters.size(); index_i++) {
    //寻找均值高度最低的区域
    tempHeight = m_pointCloud.GetAverageHeightByIndice(clusters[index_i]);
    if (tempHeight < Min_Cluster_Height) {
        Min_Cluster_Height = tempHeight;
        Idx_Max_Cluster = index_i;
    }
}
m_pointCloud.SimpleTranslation(0, 0, -Min_Cluster_Height);
#pragma endregion
}

增加打开文件与保存的功能

void CDataPreProcessSingleDlg::OnBnClickedBtnBeadsprocessandsave()
{
// TODO: 在此添加控件通知处理程序代码
CFileDialogdlgFile(TRUE, NULL, NULL, OFN_ALLOWMULTISELECT, _T("TXT Files (*.txt)|*.txt|PCD Files (*.pcd)|*.pcd|All Files (*.*)|*.*||"), NULL);
std::vector<CString> vecFileNames;
const int MIN_FILE_NUMBER = 300;   //至少允许选择10个文件
dlgFile.m_ofn.lpstrFile = new TCHAR[_MAX_PATH * MIN_FILE_NUMBER]; //重新定义缓冲区大小  
memset(dlgFile.m_ofn.lpstrFile, 0, _MAX_PATH * MIN_FILE_NUMBER);  //初始化定义的缓冲区
dlgFile.m_ofn.nMaxFile = _MAX_PATH * MIN_FILE_NUMBER;
if (dlgFile.DoModal())
{
    CString tempFileName = _T("");
    POSITION pos = dlgFile.GetStartPosition();
    while (pos != NULL)
    {
        vecFileNames.push_back(dlgFile.GetNextPathName(pos));
    }
}
m_vdOutputArray.clear();
for (size_t i = 0; i < vecFileNames.size(); i++)
{
    CString strFile = vecFileNames[i];
    CString tempSaveName;
    tempSaveName.Format(_T("%d"), i);
    tempSaveName = "a" + tempSaveName + ".pcd";
    m_pointCloud.ReadTXTFile(strFile.GetBuffer(0));
    SingleBeadProcess(20, 20);
    m_pointCloud.SaveCurrentCloudASCII(tempSaveName.GetBuffer(0));
}

UpdateData(false);
}

最终效果:

processedcloud