问题分析
数据采集设备为GoCator 2300 Series线激光传感器,数据获取途径为基于GoCator SDK与团队开发的软件实现。定义单次扫描激光线方向为X方向;激光线移动方向为Y方向,采用右手系。采集过程参数如下:
- 线激光移动速度:1000mm/min
- 刷新率:50Hz
- X向范围:±200mm
可以看到,对于以上扫描参数,Y向分辨率约为$1000mm/60/50 = 0.33mm$,精度优于电弧增材的成形精度,参数可行。
一次扫描得到的点云数据如下图所示:
通过简单的查看若干点的坐标信息,我们可以看到,直壁顶部的高度约为0,而基板的高度约为负值。显然,这与我们的认知不符。造成这一现象的原因主要为测量时未重置机床的外部坐标偏移,线激光工作位置所连接的整体机头上升,基板高度下降。
最终需要的结果为:
- 只需要直壁部分点云及其周围部分的基板
- 将基板的高度调整为0,直壁顶面的高度按原关系上升
在明确了背景条件与需求后,开始对点云进行处理。
处理过程
基于VS2017+PCL1.9.1环境,编写MFC程序进行数据处理。主要分为以下几个步骤:
- 通过高度滤波初步找到焊道点云;
- 第一次区域生长方法,找到最大区域,确定为直壁顶面;
- 获取顶面的XY边界;
- 回到原始点云,通过XY边界+一定的松弛量获得直壁+基板点云;
- 第二次区域生长方法,找到平均高度最低的区域,确定为基板,并记录其平均高度;
- 对整体点云进行偏移;
- 完成。
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);
}
最终效果: