This commit is contained in:
杰仔 2026-03-25 18:45:54 +08:00
commit 7cd7508c61
6 changed files with 794 additions and 104 deletions

View File

@ -48,13 +48,13 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>

View File

@ -139,6 +139,12 @@ SG_APISHARED_EXPORT void wd_getLineCornerFeature_PSM(
const SSG_cornerParam cornerPara,
SSG_lineFeature* line_features);
SG_APISHARED_EXPORT void wd_computeDirAngle_wholeLine(
std::vector< SVzNL3DPosition>& line_data,
const SSG_cornerParam cornerPara,
std::vector< SSG_pntDirAngle>& ptDirAngles
);
/// 提取激光线上的圆环的上半段弧。宽度由圆环的宽度确定
/// seg端点z距离大于门限
/// nPointIdx被重新定义成Feature类型

View File

@ -3969,7 +3969,7 @@ void _computeDirAngle_perSeg(
}
//计算方向角(不分段)
void _computeDirAngle_wholeLine(
void wd_computeDirAngle_wholeLine(
std::vector< SVzNL3DPosition>& line_data,
const SSG_cornerParam cornerPara,
std::vector< SSG_pntDirAngle>& ptDirAngles
@ -4309,7 +4309,7 @@ void wd_getRodArcFeature_peakCornerMethod(
double arcTotalCornerMinValue = 45; //整个Arc的转角最小值
//计算前向角和后向角
std::vector< SSG_pntDirAngle> ptDirAngles;
_computeDirAngle_wholeLine( lineData, cornerPara, ptDirAngles);
wd_computeDirAngle_wholeLine( lineData, cornerPara, ptDirAngles);
int dataSize = (int)lineData.size();
//搜索z极值。

View File

@ -10,7 +10,8 @@
//version 1.1.0 : c对工件姿态规范化为中心点操作点加三个方向矢量
//version 1.2.0 : 算法完成了6轴验证
//version 1.3.0 : (1)算法进行了迭代 (2)对结果进行了分层和排序,输出最上层目标
std::string m_strVersion = "1.3.0";
//version 1.4.0 : 添加了华航孔定位功能
std::string m_strVersion = "1.4.0";
const char* wd_workpieceHolePositioningVersion(void)
{
return m_strVersion.c_str();
@ -299,89 +300,19 @@ void _getYTopLine(
std::sort(firstLine.begin(), firstLine.end(), _compareByXValue);
return;
}
//工件孔定位
void wd_workpieceHolePositioning(
std::vector< std::vector<SVzNL3DPosition>>& scanLinesInput,
const WD_workpieceHoleParam workpiecePara,
void wd_getHoleInfo(
std::vector< std::vector<SVzNL3DPosition>>& scanLines,
const SSG_lineSegParam lineSegPara,
const SSG_outlierFilterParam filterParam,
const SSG_treeGrowParam growParam,
const SSG_planeCalibPara groundCalibPara,
std::vector< WD_workpieceInfo>& workpiecePositioning,
int* errCode)
std::vector<SWD_segFeatureTree>& segTrees_v,
std::vector<SWD_segFeatureTree>& segTrees_h,
std::vector<SSG_intPair>& validObjects
)
{
*errCode = 0;
int lineNum = (int)scanLinesInput.size();
std::vector< std::vector<SVzNL3DPosition>> scanLines;
scanLines.resize(lineNum);
int linePtNum = (int)scanLinesInput[0].size();
bool isGridData = true;
for (int i = 0; i < lineNum; i++)
{
if (linePtNum != (int)scanLinesInput[i].size())
isGridData = false;
scanLines[i].resize(scanLinesInput[i].size());
std::copy(scanLinesInput[i].begin(), scanLinesInput[i].end(), scanLines[i].begin()); // 使用std::copy算法
for (int j = 0; j < (int)scanLinesInput[i].size(); j++)
scanLinesInput[i][j].nPointIdx = 0; //清零用于debug时记录信息
}
if (false == isGridData)//数据不是网格格式
{
*errCode = SG_ERR_NOT_GRID_FORMAT;
return;
}
for (int i = 0; i < lineNum; i++)
{ //行处理
//调平,去除地面
wd_lineDataR(scanLines[i], groundCalibPara.planeCalib, -1);
}
//生成量化数据以1mm为量化尺度用于确定工件表面高度
SVzNL3DRangeD roi3D = sg_getScanDataROI_vector( scanLines);
SVzNLRect roi2D;
roi2D.left = (int)roi3D.xRange.min;
roi2D.right = (int)roi3D.xRange.max;
roi2D.top = (int)roi3D.yRange.min;
roi2D.bottom = (int)roi3D.yRange.max;
int quanti_X = roi2D.right - roi2D.left + 1;
int quanti_Y = roi2D.bottom - roi2D.top + 1;
std::vector<std::vector<double>> quantiValue;
std::vector<std::vector<int>> quantiHist;
quantiValue.resize(quanti_X);
quantiHist.resize(quanti_X);
for (int i = 0; i < quanti_X; i++)
{
quantiValue[i].resize(quanti_Y);
std::fill(quantiValue[i].begin(), quantiValue[i].end(), 0);//初始化为0
quantiHist[i].resize(quanti_Y);
std::fill(quantiHist[i].begin(), quantiHist[i].end(), 0);//初始化为0
}
//以1mm尺度量化
for (int line = 0; line < lineNum; line++)
{
for (int j = 0; j < linePtNum; j++)
{
SVzNL3DPoint& a_pt = scanLines[line][j].pt3D;
if (a_pt.z > 1e-4)
{
int qx = (int)a_pt.x - roi2D.left;
int qy = (int)a_pt.y - roi2D.top;
quantiValue[qx][qy] += a_pt.z;
quantiHist[qx][qy] += 1;
}
}
}
for (int ix = 0; ix < quanti_X; ix++)
{
for (int iy = 0; iy < quanti_Y; iy++)
{
if (quantiHist[ix][iy] > 0)
quantiValue[ix][iy] = quantiValue[ix][iy] / quantiHist[ix][iy];
}
}
int lineNum = (int)scanLines.size();
int linePtNum = (int)scanLines[0].size();
std::vector<std::vector<int>> pointMask;
pointMask.resize(lineNum);
@ -421,7 +352,6 @@ void wd_workpieceHolePositioning(
holeGaps.push_back(line_gaps);
}
//特征生长
std::vector<SWD_segFeatureTree> segTrees_v;
wd_getSegFeatureGrowingTrees_2(holeGaps, segTrees_v, growParam);
//生成水平扫描
@ -471,7 +401,6 @@ void wd_workpieceHolePositioning(
holeGaps_h.push_back(line_gaps);
}
//特征生长
std::vector<SWD_segFeatureTree> segTrees_h;
wd_getSegFeatureGrowingTrees_2(holeGaps_h, segTrees_h, growParam);
//创建Tree所在孔洞的Mask
@ -497,9 +426,9 @@ void wd_workpieceHolePositioning(
treeInfo_v[i].eLineIdx = a_tree.eLineIdx;
treeInfo_v[i].vTreeFlag = 0;
treeInfo_v[i].treeType = 0;
treeInfo_v[i].roi = {-1, -1, -1, -1};
treeInfo_v[i].roi = { -1, -1, -1, -1 };
int nullPtSize = 0;
SSG_ROIRectD roi = {-1, -1, -1, -1};
SSG_ROIRectD roi = { -1, -1, -1, -1 };
SVzNLRange ptIdxRange = { -1, -1 };
if (a_tree.treeNodes.size() > 2)
{
@ -648,7 +577,7 @@ void wd_workpieceHolePositioning(
}
}
}
std::vector<SSG_intPair> validObjects;
for (int i = 0; i < (int)objects.size(); i++)
{
int vTreeIdx = objects[i].data_0;
@ -657,6 +586,96 @@ void wd_workpieceHolePositioning(
validObjects.push_back(objects[i]);
}
}
//工件孔定位-拓普发工件孔定位
void wd_workpieceHolePositioning(
std::vector< std::vector<SVzNL3DPosition>>& scanLinesInput,
const WD_workpieceHoleParam workpiecePara,
const SSG_lineSegParam lineSegPara,
const SSG_outlierFilterParam filterParam,
const SSG_treeGrowParam growParam,
const SSG_planeCalibPara groundCalibPara,
std::vector< WD_workpieceInfo>& workpiecePositioning,
int* errCode)
{
*errCode = 0;
int lineNum = (int)scanLinesInput.size();
std::vector< std::vector<SVzNL3DPosition>> scanLines;
scanLines.resize(lineNum);
int linePtNum = (int)scanLinesInput[0].size();
bool isGridData = true;
for (int i = 0; i < lineNum; i++)
{
if (linePtNum != (int)scanLinesInput[i].size())
isGridData = false;
scanLines[i].resize(scanLinesInput[i].size());
std::copy(scanLinesInput[i].begin(), scanLinesInput[i].end(), scanLines[i].begin()); // 使用std::copy算法
for (int j = 0; j < (int)scanLinesInput[i].size(); j++)
scanLinesInput[i][j].nPointIdx = 0; //清零用于debug时记录信息
}
if (false == isGridData)//数据不是网格格式
{
*errCode = SG_ERR_NOT_GRID_FORMAT;
return;
}
for (int i = 0; i < lineNum; i++)
{ //行处理
//调平,去除地面
wd_lineDataR(scanLines[i], groundCalibPara.planeCalib, -1);
}
//生成量化数据以1mm为量化尺度用于确定工件表面高度
SVzNL3DRangeD roi3D = sg_getScanDataROI_vector( scanLines);
SVzNLRect roi2D;
roi2D.left = (int)roi3D.xRange.min;
roi2D.right = (int)roi3D.xRange.max;
roi2D.top = (int)roi3D.yRange.min;
roi2D.bottom = (int)roi3D.yRange.max;
int quanti_X = roi2D.right - roi2D.left + 1;
int quanti_Y = roi2D.bottom - roi2D.top + 1;
std::vector<std::vector<double>> quantiValue;
std::vector<std::vector<int>> quantiHist;
quantiValue.resize(quanti_X);
quantiHist.resize(quanti_X);
for (int i = 0; i < quanti_X; i++)
{
quantiValue[i].resize(quanti_Y);
std::fill(quantiValue[i].begin(), quantiValue[i].end(), 0);//初始化为0
quantiHist[i].resize(quanti_Y);
std::fill(quantiHist[i].begin(), quantiHist[i].end(), 0);//初始化为0
}
//以1mm尺度量化
for (int line = 0; line < lineNum; line++)
{
for (int j = 0; j < linePtNum; j++)
{
SVzNL3DPoint& a_pt = scanLines[line][j].pt3D;
if (a_pt.z > 1e-4)
{
int qx = (int)a_pt.x - roi2D.left;
int qy = (int)a_pt.y - roi2D.top;
quantiValue[qx][qy] += a_pt.z;
quantiHist[qx][qy] += 1;
}
}
}
for (int ix = 0; ix < quanti_X; ix++)
{
for (int iy = 0; iy < quanti_Y; iy++)
{
if (quantiHist[ix][iy] > 0)
quantiValue[ix][iy] = quantiValue[ix][iy] / quantiHist[ix][iy];
}
}
std::vector<SWD_segFeatureTree> segTrees_v;
std::vector<SWD_segFeatureTree> segTrees_h;
std::vector<SSG_intPair> validObjects;
wd_getHoleInfo(scanLines, lineSegPara, filterParam, growParam, segTrees_v, segTrees_h, validObjects);
//生成聚类信息,
std::vector<std::vector< SVzNL2DPoint>> clusters; //只记录位置
@ -921,4 +940,445 @@ void wd_workpieceHolePositioning(
}
return;
}
typedef struct
{
int flag;
SWDIndexing3DPoint zMaxPos;
SVzNLRect peakROI;
double pkValue;
}_zMaxInfo;
bool _getZMaxPeakROI(SVzNL2DPoint seedPos,
std::vector<std::vector<SSG_pntDirAngle>>& pntDirAngles_v,
std::vector<std::vector<SSG_pntDirAngle>>& pntDirAngles_h,
SVzNLRect& roi
)
{
int lineNum = (int)pntDirAngles_v.size();
int ptNum = (int)pntDirAngles_h.size();
//向左搜索
int left = -1;
for (int line = seedPos.x; line >= 0; line--)
{
if ((pntDirAngles_v[line][seedPos.y].type >= 0) && (abs(pntDirAngles_v[line][seedPos.y].corner) < 5.0))
{
left = line;
break;
}
}
//向右搜索
int right = -1;
for (int line = seedPos.x; line < lineNum; line++)
{
if ((pntDirAngles_v[line][seedPos.y].type >= 0) && (abs(pntDirAngles_v[line][seedPos.y].corner) < 5.0))
{
right = line;
break;
}
}
//向上搜索
int top = -1;
for (int ptIdx = seedPos.y; ptIdx >= 0; ptIdx--)
{
if ((pntDirAngles_h[ptIdx][seedPos.x].type >= 0) && (abs(pntDirAngles_h[ptIdx][seedPos.x].corner) < 5.0))
{
top = ptIdx;
break;
}
}
//向下搜索
int bottom = -1;
for (int ptIdx = seedPos.y; ptIdx < ptNum; ptIdx++)
{
if ((pntDirAngles_h[ptIdx][seedPos.x].type >= 0) && (abs(pntDirAngles_h[ptIdx][seedPos.x].corner) < 5.0))
{
bottom = ptIdx;
break;
}
}
if ((left < 0) || (right < 0) || (top < 0) || (bottom < 0))
return false;
roi.left = left;
roi.right = right;
roi.top = top;
roi.bottom = bottom;
return true;
}
SVzNLRect _mergeROI(SVzNLRect roi_1, SVzNLRect roi_2)
{
SVzNLRect roi;
roi.left = roi_1.left < roi_2.left ? roi_1.left : roi_2.left;
roi.right = roi_1.right > roi_2.right ? roi_1.right : roi_2.right;
roi.top = roi_1.top < roi_2.top ? roi_1.top : roi_2.top;
roi.bottom = roi_1.bottom > roi_2.bottom ? roi_1.bottom : roi_2.bottom;
return roi;
}
//单个孔或凹坑定位-华航孔定位
void wd_HolePositioning(
std::vector< std::vector<SVzNL3DPosition>>& scanLinesInput,
const SSG_lineSegParam lineSegPara,
const SSG_cornerParam cornerParam,
const SSG_outlierFilterParam filterParam,
const SSG_treeGrowParam growParam,
std::vector< WD_HolePositionInfo>& holePositioning,
int* errCode)
{
*errCode = 0;
int lineNum = (int)scanLinesInput.size();
std::vector< std::vector<SVzNL3DPosition>> scanLines;
scanLines.resize(lineNum);
int linePtNum = (int)scanLinesInput[0].size();
bool isGridData = true;
for (int i = 0; i < lineNum; i++)
{
if (linePtNum != (int)scanLinesInput[i].size())
isGridData = false;
scanLines[i].resize(scanLinesInput[i].size());
std::copy(scanLinesInput[i].begin(), scanLinesInput[i].end(), scanLines[i].begin()); // 使用std::copy算法
for (int j = 0; j < (int)scanLinesInput[i].size(); j++)
scanLinesInput[i][j].nPointIdx = 0; //清零用于debug时记录信息
}
if (false == isGridData)//数据不是网格格式
{
*errCode = SG_ERR_NOT_GRID_FORMAT;
return;
}
//内部参数
double zPeakScale = 25.0; //计算ZPeak时的尺度
double minZPeakHeight = 2.5; //最小的凹坑深度
double planeInlierDistTh = 1.0; //平面点距离平面的距离。超出此距离被判别为离群点
//计算dirAngle
std::vector<std::vector<SSG_pntDirAngle>> pntDirAngles_v;
std::vector<std::vector<SSG_basicFeature1D>> zMaxPeaks_v;
for (int line = 0; line < lineNum; line++)
{
if (line == 1047)
int kkk = 1;
std::vector<SVzNL3DPosition>& lineData = scanLines[line];
//滤波,滤除异常点
sg_lineDataRemoveOutlier_changeOriginData(&lineData[0], linePtNum, filterParam);
std::vector< SSG_pntDirAngle> line_ptDirAngles;
wd_computeDirAngle_wholeLine(lineData, cornerParam, line_ptDirAngles);
pntDirAngles_v.push_back(line_ptDirAngles);
std::vector< SSG_basicFeature1D> localZMax;
std::vector< SSG_basicFeature1D> localZMin;
sg_getLineLocalPeaks_2( lineData, line, zPeakScale, localZMax, localZMin);
zMaxPeaks_v.push_back(localZMax);
}
//生成水平扫描
std::vector<std::vector<SVzNL3DPosition>> hLines_raw;
hLines_raw.resize(linePtNum);
for (int i = 0; i < linePtNum; i++)
hLines_raw[i].resize(lineNum);
for (int line = 0; line < lineNum; line++)
{
for (int j = 0; j < linePtNum; j++)
{
scanLines[line][j].nPointIdx = 0; //将原始数据的序列清0会转义使用
hLines_raw[j][line] = scanLines[line][j];
hLines_raw[j][line].pt3D.x = scanLines[line][j].pt3D.y;
hLines_raw[j][line].pt3D.y = scanLines[line][j].pt3D.x;
}
}
//水平arc特征提取
//创建水平ZMax的Mask
std::vector<std::vector<int>> zMaxMask_h;
zMaxMask_h.resize(lineNum);
for (int i = 0; i < lineNum; i++)
{
zMaxMask_h[i].resize(linePtNum);
std::fill(zMaxMask_h[i].begin(), zMaxMask_h[i].end(), 0);
}
std::vector<std::vector<SSG_pntDirAngle>> pntDirAngles_h;
for (int line = 0; line < linePtNum; line++)
{
if (line == 974)
int kkk = 1;
std::vector<SVzNL3DPosition>& lineData = hLines_raw[line];
//滤波,滤除异常点
int ptNum = (int)lineData.size();
sg_lineDataRemoveOutlier_changeOriginData(&lineData[0], ptNum, filterParam);
std::vector< SSG_pntDirAngle> line_ptDirAngles;
wd_computeDirAngle_wholeLine(lineData, cornerParam, line_ptDirAngles);
pntDirAngles_h.push_back(line_ptDirAngles);
std::vector< SSG_basicFeature1D> localZMax;
std::vector< SSG_basicFeature1D> localZMin;
sg_getLineLocalPeaks_2(lineData, line, zPeakScale, localZMax, localZMin);
for (int j = 0; j < (int)localZMax.size(); j++)
{
int idx_line = localZMax[j].jumpPos2D.y;
int idx_pt = localZMax[j].jumpPos2D.x;
zMaxMask_h[idx_line][idx_pt] = 1;
}
}
//判断真正的ZMax并记录
//1真正的ZMax一定是同时为X方向的ZMax和Y方向的ZMax.(2)满足一定的Z差值
std::vector<_zMaxInfo> objPeaks;
for (int line = 0; line < lineNum; line++)
{
std::vector<SSG_basicFeature1D>& a_lineZMax = zMaxPeaks_v[line];
for (int j = 0; j < (int)a_lineZMax.size(); j++)
{
int idx_line = a_lineZMax[j].jumpPos2D.x;
int idx_pt = a_lineZMax[j].jumpPos2D.y;
if (zMaxMask_h[idx_line][idx_pt] > 0) //在X和Y方向均为极大值
{
//判断ROI
SVzNLRect a_roi;
bool validPk = _getZMaxPeakROI(a_lineZMax[j].jumpPos2D, pntDirAngles_v, pntDirAngles_h, a_roi);
if (true == validPk)
{
double meanZ = scanLines[a_roi.left][idx_pt].pt3D.z;
meanZ += scanLines[a_roi.right][idx_pt].pt3D.z;
meanZ += scanLines[idx_line][a_roi.top].pt3D.z;
meanZ += scanLines[idx_line][a_roi.bottom].pt3D.z;
meanZ = meanZ / 4.0;
double z_diff = a_lineZMax[j].jumpPos.z - meanZ;
if (z_diff > minZPeakHeight)
{
_zMaxInfo a_info;
a_info.flag = 0;
a_info.peakROI = a_roi;
a_info.zMaxPos.lineIdx = a_lineZMax[j].jumpPos2D.x;
a_info.zMaxPos.ptIdx = a_lineZMax[j].jumpPos2D.y;
a_info.zMaxPos.point = { a_lineZMax[j].jumpPos.x, a_lineZMax[j].jumpPos.y, a_lineZMax[j].jumpPos.z };
a_info.pkValue = a_lineZMax[j].jumpPos.z;
objPeaks.push_back(a_info);
}
}
}
}
}
int pkSize = (int)objPeaks.size();
for (int i = 0; i < pkSize; i++)
{
_zMaxInfo a_info = objPeaks[i];
if (a_info.flag < 0)
continue;
for (int j = i + 1; j < pkSize; j++)
{
if (objPeaks[j].flag < 0)
continue;
if ((a_info.peakROI.right >= objPeaks[j].peakROI.left) && (objPeaks[j].peakROI.right >= a_info.peakROI.left) &&
(a_info.peakROI.bottom >= objPeaks[j].peakROI.top) && (objPeaks[j].peakROI.bottom >= a_info.peakROI.top)) //重叠
{
a_info.peakROI = _mergeROI(a_info.peakROI, objPeaks[j].peakROI);
if (a_info.pkValue < objPeaks[j].pkValue)
{
a_info.pkValue = objPeaks[j].pkValue;
a_info.zMaxPos = objPeaks[j].zMaxPos;
objPeaks[j].flag = -1;
}
}
}
objPeaks[i] = a_info;
}
//逐个目标提取
int contourWin = 4; //取周围3行和3列的点
for (int peakIdx = 0; peakIdx < pkSize; peakIdx++)
{
if (objPeaks[peakIdx].flag < 0)
continue;
SVzNLRect& a_roi = objPeaks[peakIdx].peakROI;
//提取孔周围点
SVzNLRect extend_roi = { a_roi.left - contourWin, a_roi.right + contourWin, a_roi.top - contourWin , a_roi.bottom + contourWin };
if (extend_roi.left < 0) extend_roi.left = 0;
if (extend_roi.right >= lineNum) extend_roi.right = lineNum - 1;
if (extend_roi.top < 0) extend_roi.top = 0;
if (extend_roi.bottom >= linePtNum) extend_roi.bottom = linePtNum - 1;
std::vector<cv::Point3f> Points3ds;
//左
for (int line = extend_roi.left; line <= a_roi.left-1; line++)
{
for (int j = a_roi.top; j <= a_roi.bottom; j++)
{
if (scanLines[line][j].pt3D.z > 1e-4)
{
cv::Point3f a_pt = cv::Point3f(scanLines[line][j].pt3D.x , scanLines[line][j].pt3D.y, scanLines[line][j].pt3D.z);
Points3ds.push_back(a_pt);
}
}
}
//右
for (int line = a_roi.right+1; line <=extend_roi.right; line++)
{
for (int j = a_roi.top; j <= a_roi.bottom; j++)
{
if (scanLines[line][j].pt3D.z > 1e-4)
{
cv::Point3f a_pt = cv::Point3f(scanLines[line][j].pt3D.x, scanLines[line][j].pt3D.y, scanLines[line][j].pt3D.z);
Points3ds.push_back(a_pt);
}
}
}
//上
for (int ptIdx = extend_roi.top; ptIdx <= a_roi.top-1; ptIdx++)
{
for (int j = a_roi.left; j <= a_roi.right; j++)
{
if (scanLines[j][ptIdx].pt3D.z > 1e-4)
{
cv::Point3f a_pt = cv::Point3f(scanLines[j][ptIdx].pt3D.x, scanLines[j][ptIdx].pt3D.y, scanLines[j][ptIdx].pt3D.z);
Points3ds.push_back(a_pt);
}
}
}
//下
for (int ptIdx = a_roi.bottom+1; ptIdx < extend_roi.bottom; ptIdx++)
{
for (int j = a_roi.left; j <= a_roi.right; j++)
{
if (scanLines[j][ptIdx].pt3D.z > 1e-4)
{
cv::Point3f a_pt = cv::Point3f(scanLines[j][ptIdx].pt3D.x, scanLines[j][ptIdx].pt3D.y, scanLines[j][ptIdx].pt3D.z);
Points3ds.push_back(a_pt);
}
}
}
//计算面参数: z = Ax + By + C , res: [0]=A, [1]= B, [2]=-1.0, [3]=C,
std::vector<double> res;
vzCaculateLaserPlane(Points3ds, res);
double normValue = sqrt(pow(res[0], 2) + pow(res[1], 2) + pow(res[2],2));
double norm_A = res[0] / normValue;
double norm_B = res[1] / normValue;
double norm_C = res[2] / normValue;
double norm_D = res[3] / normValue;
//生成ROI scanLines
int roi_lineNum = extend_roi.right - extend_roi.left + 1;
int roi_ptNum = extend_roi.bottom - extend_roi.top + 1;
std::vector< std::vector<SVzNL3DPosition>> roi_scanLines;
roi_scanLines.resize(roi_lineNum);
for (int j = 0; j < roi_lineNum; j++)
roi_scanLines[j].resize(roi_ptNum);
//生成面上点
for (int line = extend_roi.left; line <= extend_roi.right; line++)
{
int roi_line = line - extend_roi.left;
for (int ptIdx = extend_roi.top; ptIdx <= extend_roi.bottom; ptIdx++)
{
int roi_ptIdx = ptIdx - extend_roi.top;
roi_scanLines[roi_line][roi_ptIdx] = scanLines[line][ptIdx];
//最外圈不进行处理
if ((line != extend_roi.left) && (line != extend_roi.right) && (ptIdx != extend_roi.top) && (ptIdx != extend_roi.bottom))
{
if (scanLines[line][ptIdx].pt3D.z > 1e-4)
{
SVzNL3DPoint& a_pt3D = scanLines[line][ptIdx].pt3D;
double dist = abs(norm_A * a_pt3D.x + norm_B * a_pt3D.y + norm_C * a_pt3D.z + norm_D);
if (dist > planeInlierDistTh)
roi_scanLines[roi_line][roi_ptIdx].pt3D = { 0, 0, 0 };
}
}
}
}
std::vector<SWD_segFeatureTree> segTrees_v;
std::vector<SWD_segFeatureTree> segTrees_h;
std::vector<SSG_intPair> validObjects;
wd_getHoleInfo(roi_scanLines, lineSegPara, filterParam, growParam, segTrees_v, segTrees_h, validObjects);
if (validObjects.size() > 0)
{
SSG_intPair a_hvPair = validObjects[0];
if (validObjects.size() > 1)
{
for (int j = 1; j < (int)validObjects.size(); j++)
{
if (validObjects[j].idx < validObjects[j].idx)
a_hvPair = validObjects[j];
}
}
//生成Contour
std::vector<SVzNL3DPoint> cluster_pointArray;
int vTreeIdx = a_hvPair.data_0;
int hTreeIdx = a_hvPair.data_1;
for (int m = 0; m < (int)segTrees_v[vTreeIdx].treeNodes.size(); m++)
{
SWD_segFeature& a_seg = segTrees_v[vTreeIdx].treeNodes[m];
if (roi_scanLines[a_seg.lineIdx][a_seg.endPtIdx].nPointIdx == 0)
{
roi_scanLines[a_seg.lineIdx][a_seg.endPtIdx].nPointIdx = vTreeIdx + 1; // 0x01;
SVzNL3DPoint a_pos = roi_scanLines[a_seg.lineIdx][a_seg.endPtIdx].pt3D;
cluster_pointArray.push_back(a_pos);
}
if (roi_scanLines[a_seg.lineIdx][a_seg.startPtIdx].nPointIdx == 0)
{
roi_scanLines[a_seg.lineIdx][a_seg.startPtIdx].nPointIdx = vTreeIdx + 1; // 0x01;
SVzNL3DPoint a_pos = roi_scanLines[a_seg.lineIdx][a_seg.startPtIdx].pt3D;
cluster_pointArray.push_back(a_pos);
}
}
for (int m = 0; m < (int)segTrees_h[hTreeIdx].treeNodes.size(); m++)
{
SWD_segFeature& a_seg = segTrees_h[hTreeIdx].treeNodes[m];
if (roi_scanLines[a_seg.startPtIdx][a_seg.lineIdx].nPointIdx == 0)
{
roi_scanLines[a_seg.startPtIdx][a_seg.lineIdx].nPointIdx = vTreeIdx + 1; // 0x02;
SVzNL3DPoint a_pos = roi_scanLines[a_seg.startPtIdx][a_seg.lineIdx].pt3D;
cluster_pointArray.push_back(a_pos);
}
if (roi_scanLines[a_seg.endPtIdx][a_seg.lineIdx].nPointIdx == 0)
{
roi_scanLines[a_seg.endPtIdx][a_seg.lineIdx].nPointIdx = vTreeIdx + 1; // 0x02;
SVzNL3DPoint a_pos = roi_scanLines[a_seg.endPtIdx][a_seg.lineIdx].pt3D;
cluster_pointArray.push_back(a_pos);
}
}
//计算重心
#if 0
SVzNL3DPoint center;
double radius;
double err = fitCircleByLeastSquare(cluster_pointArray, center, radius);
#endif
if (cluster_pointArray.size() > 0)
{
SVzNL3DPoint center = { 0, 0, 0 };
int ptSize = (int)cluster_pointArray.size();
for (int m = 0; m < ptSize; m++)
{
center.x += cluster_pointArray[m].x;
center.y += cluster_pointArray[m].y;
center.z += cluster_pointArray[m].z;
}
center.x = center.x / ptSize;
center.y = center.y / ptSize;
center.z = center.z / ptSize;
//求在平面上的垂足
double t = -(center.x * norm_A + center.y * norm_B + center.z * norm_C + norm_D);
SVzNL3DPoint realCenter;
realCenter.x = center.x + t * norm_A;
realCenter.y = center.y + t * norm_B;
realCenter.z = center.z + t * norm_C;
WD_HolePositionInfo a_hole;
a_hole.center = realCenter;
a_hole.normDir = { norm_A, norm_B, norm_C };
holePositioning.push_back(a_hole);
}
}
}
return;
}

View File

@ -27,6 +27,12 @@ typedef struct
SVzNL3DPoint x_dir; //x方向向量归一化
}WD_workpieceInfo;
typedef struct
{
SVzNL3DPoint center;
SVzNL3DPoint normDir; //方向向量(归一化)
}WD_HolePositionInfo;
//读版本号
SG_APISHARED_EXPORT const char* wd_workpieceHolePositioningVersion(void);
@ -41,7 +47,7 @@ SG_APISHARED_EXPORT void wd_lineDataR(
const double* camPoseR,
double groundH);
//工件孔定位
//工件孔定位-拓普发工件孔定位
SG_APISHARED_EXPORT void wd_workpieceHolePositioning(
std::vector< std::vector<SVzNL3DPosition>>& scanLinesInput,
const WD_workpieceHoleParam workpiecePara,
@ -51,3 +57,13 @@ SG_APISHARED_EXPORT void wd_workpieceHolePositioning(
const SSG_planeCalibPara groundCalibPara,
std::vector< WD_workpieceInfo>& workpiecePositioning,
int* errCode);
//单个孔或凹坑定位-华航孔定位
SG_APISHARED_EXPORT void wd_HolePositioning(
std::vector< std::vector<SVzNL3DPosition>>& scanLinesInput,
const SSG_lineSegParam lineSegPara,
const SSG_cornerParam cornerParam,
const SSG_outlierFilterParam filterParam,
const SSG_treeGrowParam growParam,
std::vector< WD_HolePositionInfo>& holePositioning,
int* errCode);

View File

@ -164,6 +164,24 @@ void _outputWorkpieceInfo(char* fileName, std::vector< WD_workpieceInfo>& workpi
sw.close();
}
void _outputHoleInfo(char* fileName, std::vector< WD_HolePositionInfo>& holePositioning)
{
std::ofstream sw(fileName);
char dataStr[250];
int number = (int)holePositioning.size();
for (int i = 0; i < number; i++)
{
sprintf_s(dataStr, 250, "孔_%d", i + 1);
sw << dataStr << std::endl;
sprintf_s(dataStr, 50, " center: (%g, %g, %g)", holePositioning[i].center.x, holePositioning[i].center.y, holePositioning[i].center.z);
sw << dataStr << std::endl;
sprintf_s(dataStr, 50, " norm_dir: (%g, %g, %g)", holePositioning[i].normDir.x, holePositioning[i].normDir.y, holePositioning[i].normDir.z);
sw << dataStr << std::endl;
}
sw.close();
}
void _outputScanDataFile_vector(char* fileName, std::vector<std::vector<SVzNL3DPosition>>& scanLines, bool removeZeros, int* headNullLines)
{
std::ofstream sw(fileName);
@ -410,6 +428,117 @@ void _outputRGBDResult_RGBD(
sw.close();
}
void _outputRGBDResult_HoleInfo(
char* fileName,
std::vector<std::vector<SVzNL3DPosition>>& scanLines,
std::vector< WD_HolePositionInfo>& holePositioning)
{
std::vector<SVzNL3DPosition> objects;
int objNumber = (int)holePositioning.size();
for (int i = 0; i < objNumber; i++)
{
SVzNL3DPosition a_objPt;
a_objPt.pt3D = holePositioning[i].center;
objects.push_back(a_objPt);
}
int lineNum = (int)scanLines.size();
std::ofstream sw(fileName);
int realLines = (objNumber == 0) ? lineNum : (lineNum + 1);
sw << "LineNum:" << realLines << std::endl;
sw << "DataType: 0" << std::endl;
sw << "ScanSpeed: 0" << std::endl;
sw << "PointAdjust: 1" << std::endl;
sw << "MaxTimeStamp: 0_0" << std::endl;
int maxLineIndex = 0;
int max_stamp = 0;
SG_color rgb = { 0, 0, 0 };
SG_color objColor[8] = {
{245,222,179},//淡黄色
{210,105, 30},//巧克力色
{240,230,140},//黄褐色
{135,206,235},//天蓝色
{250,235,215},//古董白
{189,252,201},//薄荷色
{221,160,221},//梅红色
{188,143,143},//玫瑰红色
};
int size = 1;
int lineIdx = 0;
for (int line = 0; line < lineNum; line++)
{
int linePtNum = (int)scanLines[line].size();
if (linePtNum == 0)
continue;
sw << "Line_" << lineIdx << "_0_" << linePtNum << std::endl;
lineIdx++;
for (int i = 0; i < linePtNum; i++)
{
SVzNL3DPosition* pt3D = &scanLines[line][i];
if (pt3D->nPointIdx > 0)
int kkk = 1;
int flag = pt3D->nPointIdx & 0xffff;
if (flag > 0)
{
rgb = objColor[flag % 8]; // { 255, 97, 0 };
size = 5;
}
else
{
rgb = { 200, 200, 200 };
size = 1;
}
float x = (float)pt3D->pt3D.x;
float y = (float)pt3D->pt3D.y;
float z = (float)pt3D->pt3D.z;
sw << "{" << x << "," << y << "," << z << "}-";
sw << "{0,0}-{0,0}-";
sw << "{" << rgb.r << "," << rgb.g << "," << rgb.b << "," << size << " }" << std::endl;
}
}
int linePtNum = (int)objects.size();
sw << "Line_" << lineNum << "_0_" << linePtNum + 1 << std::endl;
lineNum++;
size = 10;
for (int i = 0; i < linePtNum; i++)
{
rgb = {255, 0, 0};
float x = (float)objects[i].pt3D.x;
float y = (float)objects[i].pt3D.y;
float z = (float)objects[i].pt3D.z;
sw << "{" << x << "," << y << "," << z << "}-";
sw << "{0,0}-{0,0}-";
sw << "{" << rgb.r << "," << rgb.g << "," << rgb.b << "," << size << " }" << std::endl;
}
//输出方向线条
rgb = { 255, 0, 0 };
size = 2;
for (int i = 0; i < objNumber; i++)
{
SVzNL3DPoint dirPt_1, dirPt_2;
dirPt_1 = { holePositioning[i].center.x - holePositioning[i].normDir.x * 20,
holePositioning[i].center.y - holePositioning[i].normDir.y * 20,
holePositioning[i].center.z - holePositioning[i].normDir.z * 20 };
dirPt_2 = { holePositioning[i].center.x + holePositioning[i].normDir.x * 10,
holePositioning[i].center.y + holePositioning[i].normDir.y * 10,
holePositioning[i].center.z + holePositioning[i].normDir.z * 10 };
sw << "Poly_" << lineIdx << "_2" << std::endl;
sw << "{" << dirPt_1.x << "," << dirPt_1.y << "," << dirPt_1.z << "}-";
sw << "{0,0}-{0,0}-";
sw << "{" << (int)rgb.r << "," << (int)rgb.g << "," << (int)rgb.b << "," << size << "}" << std::endl;
sw << "{" << dirPt_2.x << "," << dirPt_2.y << "," << dirPt_2.z << "}-";
sw << "{0,0}-{0,0}-";
sw << "{" << (int)rgb.r << "," << (int)rgb.g << "," << (int)rgb.b << "," << size << "}" << std::endl;
lineIdx++;
}
sw.close();
}
SVzNL3DPoint _pointRT(SVzNL3DPoint& origin, const double* R, const double* T)
{
SVzNL3DPoint result;
@ -425,17 +554,17 @@ SVzNL3DPoint _pointRT(SVzNL3DPoint& origin, const double* R, const double* T)
#define TEST_COMPUTE_CALIB_PARA 0
#define TEST_COMPUTE_HOLE 1
#define TEST_COMPUTE_RT 0
#define TEST_GROUP 2
int main()
#define TPF_TEST_GROUP 2
//拓普发工件孔定位(工件定位)
void TuoPuFa_holePosition_test(void)
{
const char* dataPath[TEST_GROUP] = {
const char* dataPath[TPF_TEST_GROUP] = {
"F:/ShangGu/项目/冠钦项目/拓普发工件孔定位/拓普发点云/", //0
"F:/ShangGu/项目/冠钦项目/拓普发工件孔定位/拓普发点云2/", //1
};
SVzNLRange fileIdx[TEST_GROUP] = {
SVzNLRange fileIdx[TPF_TEST_GROUP] = {
{6,6}, {1, 15}
};
@ -681,13 +810,92 @@ int main()
#endif
}
// 运行程序: Ctrl + F5 或调试 >“开始执行(不调试)”菜单
// 调试程序: F5 或调试 >“开始调试”菜单
//华航孔定位
#define HH_TEST_GROUP 4
void HuaHang_holePosition_test(void)
{
const char* dataPath[HH_TEST_GROUP] = {
// 入门使用技巧:
// 1. 使用解决方案资源管理器窗口添加/管理文件
// 2. 使用团队资源管理器窗口连接到源代码管理
// 3. 使用输出窗口查看生成输出和其他消息
// 4. 使用错误列表窗口查看错误
// 5. 转到“项目”>“添加新项”以创建新的代码文件,或转到“项目”>“添加现有项”以将现有代码文件添加到项目
// 6. 将来,若要再次打开此项目,请转到“文件”>“打开”>“项目”并选择 .sln 文件
"F:/ShangGu/项目/冠钦项目/华航孔定位/圆孔识别_数据1/", //0
"F:/ShangGu/项目/冠钦项目/华航孔定位/圆孔识别_数据2/1/", //1
"F:/ShangGu/项目/冠钦项目/华航孔定位/圆孔识别_数据2/2/", //1
"F:/ShangGu/项目/冠钦项目/华航孔定位/圆孔识别_数据2/3/", //1
};
SVzNLRange fileIdx[HH_TEST_GROUP] = {
{1,15}, {1, 21},{1,19}, {1,9}
};
const char* ver = wd_workpieceHolePositioningVersion();
printf("ver:%s\n", ver);
#if TEST_COMPUTE_HOLE
for (int grp = 1; grp <= 3; grp++)
{
for (int fidx = fileIdx[grp].nMin; fidx <= fileIdx[grp].nMax; fidx++)
{
//fidx =4;
char _scan_file[256];
sprintf_s(_scan_file, "%sLaserData_%d.txt", dataPath[grp], fidx);
std::vector<std::vector< SVzNL3DPosition>> scanLines;
vzReadLaserScanPointFromFile_XYZ_vector(_scan_file, scanLines);
if (scanLines.size() == 0)
continue;
long t1 = (long)GetTickCount64();//统计时间
SSG_lineSegParam lineSegPara;
lineSegPara.distScale = 25.0;
lineSegPara.segGapTh_y = 15.0; //
lineSegPara.segGapTh_z = 0.0; //z方向间隔大于10mm认为是分段
SSG_cornerParam cornerParam;
cornerParam.cornerTh = 60; //45度角
cornerParam.scale = 4; // algoParam.bagParam.bagH / 8; // 15; // algoParam.bagParam.bagH / 8;
cornerParam.minEndingGap = 20; // algoParam.bagParam.bagW / 4;
cornerParam.minEndingGap_z = 5.0;
cornerParam.jumpCornerTh_1 = 15; //水平角度,小于此角度视为水平
cornerParam.jumpCornerTh_2 = 60;
SSG_outlierFilterParam filterParam;
filterParam.continuityTh = 20.0; //噪声滤除。当相邻点的z跳变大于此门限时检查是否为噪声。若长度小于outlierLen 视为噪声
filterParam.outlierTh = 5;
SSG_treeGrowParam growParam;
growParam.maxLineSkipNum = 2;
growParam.yDeviation_max = 4.0;
growParam.maxSkipDistance = 0.0;
growParam.zDeviation_max = 10.0;//
growParam.minLTypeTreeLen = 2.0; //mm
growParam.minVTypeTreeLen = 2.0; //mm
WD_workpieceHoleParam workpiecePara;
workpiecePara.workpieceType = 0;
workpiecePara.holeDiameter = 6.0; //
workpiecePara.holeDist_W = 32.0;
workpiecePara.holeDist_L = 40.0;
workpiecePara.xLen = 44;
workpiecePara.yLen = 70;
workpiecePara.H = 47;
int errCode = 0;
std::vector< WD_HolePositionInfo> holePositioning;
wd_HolePositioning(scanLines, lineSegPara, cornerParam, filterParam, growParam, holePositioning, &errCode);
long t2 = (long)GetTickCount64();
printf("%s: %d(ms)!\n", _scan_file, (int)(t2 - t1));
//输出测试结果
sprintf_s(_scan_file, "%sresult\\LaserLine%d_result.txt", dataPath[grp], fidx);
_outputRGBDResult_HoleInfo(_scan_file, scanLines, holePositioning);
sprintf_s(_scan_file, "%sresult\\LaserLine%d_corner_info.txt", dataPath[grp], fidx);
_outputHoleInfo(_scan_file, holePositioning);
}
}
#endif
}
int main()
{
#if 0
TuoPuFa_holePosition_test();
#else
HuaHang_holePosition_test();
#endif
}