2026-02-02 15:10:42 +08:00
|
|
|
|
#include <vector>
|
2026-01-26 15:57:19 +08:00
|
|
|
|
#include "SG_baseDataType.h"
|
|
|
|
|
|
#include "SG_baseAlgo_Export.h"
|
|
|
|
|
|
#include "workpieceHolePositioning_Export.h"
|
|
|
|
|
|
#include <opencv2/opencv.hpp>
|
|
|
|
|
|
#include <limits>
|
|
|
|
|
|
|
|
|
|
|
|
//version 1.0.0 : base version release to customer
|
2026-02-02 15:10:42 +08:00
|
|
|
|
//version 1.0.2 : 添加了工件姿态(欧拉角输出)
|
|
|
|
|
|
//version 1.1.0 : c对工件姿态规范化为中心点(操作点)加三个方向矢量
|
|
|
|
|
|
//version 1.2.0 : 算法完成了6轴验证
|
2026-03-24 00:43:58 +08:00
|
|
|
|
//version 1.3.0 : (1)算法进行了迭代 (2)对结果进行了分层和排序,输出最上层目标
|
2026-03-25 18:24:56 +08:00
|
|
|
|
//version 1.4.0 : 添加了华航孔定位功能
|
2026-03-26 07:17:19 +08:00
|
|
|
|
//version 1.4.1 : 华航孔定位客户发布初始版本,修正了1.4.0版本的一些问题
|
2026-03-27 00:01:16 +08:00
|
|
|
|
//version 1.4.2 : 华航孔定位改进:(1)法向量计算改进(2)添加3x3平滑(3)修正了bug
|
2026-04-10 23:58:27 +08:00
|
|
|
|
//version 1.4.3 : 对1.3.0拓普发孔定位中调平Bug进行了修正
|
2026-04-11 00:51:17 +08:00
|
|
|
|
//version 1.4.4 : 对1.4.3一个小的修正, 不影响结果
|
|
|
|
|
|
std::string m_strVersion = "1.4.4";
|
2026-01-26 15:57:19 +08:00
|
|
|
|
const char* wd_workpieceHolePositioningVersion(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
return m_strVersion.c_str();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-02 15:10:42 +08:00
|
|
|
|
//相机水平安装计算地面调平参数。
|
|
|
|
|
|
//相机Z轴基本平行地面时,需要以地面为参照,将相机调水平
|
|
|
|
|
|
//旋转矩阵为调平参数,即将平面法向调整为垂直向量的参数
|
2026-01-26 15:57:19 +08:00
|
|
|
|
SSG_planeCalibPara wd_getGroundCalibPara(
|
|
|
|
|
|
std::vector< std::vector<SVzNL3DPosition>>& scanLines)
|
|
|
|
|
|
{
|
|
|
|
|
|
return sg_getPlaneCalibPara2(scanLines);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-02 15:10:42 +08:00
|
|
|
|
//相机水平时姿态调平,并去除地面
|
2026-01-26 15:57:19 +08:00
|
|
|
|
void wd_lineDataR(
|
|
|
|
|
|
std::vector< SVzNL3DPosition>& a_line,
|
|
|
|
|
|
const double* camPoseR,
|
|
|
|
|
|
double groundH)
|
|
|
|
|
|
{
|
|
|
|
|
|
lineDataRT_vector(a_line, camPoseR, groundH);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
SVzNL3DPoint _ptRotate(SVzNL3DPoint pt3D, const double matrix3d[9])
|
|
|
|
|
|
{
|
|
|
|
|
|
SVzNL3DPoint _r_pt;
|
|
|
|
|
|
_r_pt.x = pt3D.x * matrix3d[0] + pt3D.y * matrix3d[1] + pt3D.z * matrix3d[2];
|
|
|
|
|
|
_r_pt.y = pt3D.x * matrix3d[3] + pt3D.y * matrix3d[4] + pt3D.z * matrix3d[5];
|
|
|
|
|
|
_r_pt.z = pt3D.x * matrix3d[6] + pt3D.y * matrix3d[7] + pt3D.z * matrix3d[8];
|
|
|
|
|
|
return _r_pt;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-02 15:10:42 +08:00
|
|
|
|
//搜索最接近distance的目标
|
2026-01-26 15:57:19 +08:00
|
|
|
|
int distanceSearchObject(SVzNL3DPoint seed, std::vector<SWD_HoleInfo>& holes, double distance, double distDeviation)
|
|
|
|
|
|
{
|
|
|
|
|
|
int result = -1;
|
|
|
|
|
|
int holeSize = (int)holes.size();
|
|
|
|
|
|
double minDistDiff = DBL_MAX;
|
|
|
|
|
|
int minDistIndex = -1;
|
|
|
|
|
|
for (int i = 0; i < holeSize; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (holes[i].radius < 0)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
double dist = sqrt(pow(seed.x - holes[i].center.x, 2) + pow(seed.y - holes[i].center.y, 2));
|
|
|
|
|
|
double distDiff = abs(dist - distance);
|
|
|
|
|
|
if (minDistDiff > distDiff)
|
|
|
|
|
|
{
|
|
|
|
|
|
minDistDiff = distDiff;
|
|
|
|
|
|
minDistIndex = i;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if ((minDistIndex >= 0) && (minDistDiff < distDeviation))
|
|
|
|
|
|
result = minDistIndex;
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-02 15:10:42 +08:00
|
|
|
|
//搜索最接近distance且角度为angle的目标, 以角度为优先
|
2026-01-26 15:57:19 +08:00
|
|
|
|
int angleConditionDistanceSearch(
|
|
|
|
|
|
SVzNL3DPoint seed, SVzNL3DPoint angleSide,
|
|
|
|
|
|
std::vector<SWD_HoleInfo>& holes,
|
|
|
|
|
|
double distance, double distDeviation,
|
|
|
|
|
|
SVzNLRangeD angleRange)
|
|
|
|
|
|
{
|
|
|
|
|
|
int result = -1;
|
|
|
|
|
|
int holeSize = (int)holes.size();
|
|
|
|
|
|
std::vector< int> distValidHoleIndex;
|
|
|
|
|
|
for (int i = 0; i < holeSize; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (holes[i].radius < 0)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
double dist = sqrt(pow(seed.x - holes[i].center.x, 2) + pow(seed.y - holes[i].center.y, 2));
|
|
|
|
|
|
double distDiff = abs(dist - distance);
|
|
|
|
|
|
if (distDiff < distDeviation)
|
|
|
|
|
|
{
|
|
|
|
|
|
distValidHoleIndex.push_back(i);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (distValidHoleIndex.size() == 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
int idx = distValidHoleIndex[0];
|
|
|
|
|
|
double angle = computeXOYVertexAngle(seed, angleSide, holes[idx].center);
|
|
|
|
|
|
if( (angle >= angleRange.min) &&(angle <= angleRange.max))
|
|
|
|
|
|
result = idx;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (distValidHoleIndex.size() > 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
double bestAngle = (angleRange.min + angleRange.max) / 2;
|
|
|
|
|
|
double minAngleDeviateion = DBL_MAX;
|
|
|
|
|
|
int minAngleIdx = -1;
|
|
|
|
|
|
for (int i = 0, i_max = (int)distValidHoleIndex.size(); i < i_max; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
int idx = distValidHoleIndex[i];
|
|
|
|
|
|
double angle = computeXOYVertexAngle(seed, angleSide, holes[idx].center);
|
|
|
|
|
|
if ((angle >= angleRange.min) && (angle <= angleRange.max))
|
|
|
|
|
|
{
|
|
|
|
|
|
double angleDiff = abs(angle - bestAngle);
|
|
|
|
|
|
if (minAngleDeviateion > angleDiff)
|
|
|
|
|
|
{
|
|
|
|
|
|
minAngleDeviateion = angleDiff;
|
|
|
|
|
|
minAngleIdx = idx;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
result = minAngleIdx;
|
|
|
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-26 22:36:13 +08:00
|
|
|
|
double _getMeanZ(std::vector<std::vector<double>>& quantiValue, SVzNL3DPoint seed, SVzNLRect& roi2D, double rectR)
|
|
|
|
|
|
{
|
|
|
|
|
|
int cols = (int)quantiValue.size();
|
|
|
|
|
|
int rows = (int)quantiValue[0].size();
|
|
|
|
|
|
|
|
|
|
|
|
int px = (int)seed.x - roi2D.left;
|
|
|
|
|
|
int py = (int)seed.y - roi2D.top;
|
|
|
|
|
|
int win = (int)rectR;
|
|
|
|
|
|
int hist = 0;
|
|
|
|
|
|
double zSum = 0;
|
|
|
|
|
|
for (int i = -win; i <= win; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
for (int j = -win; j <= win; j++)
|
|
|
|
|
|
{
|
|
|
|
|
|
int qx = px + i;
|
|
|
|
|
|
int qy = py + j;
|
|
|
|
|
|
if ((qx >= 0) && (qx < cols) && (qy >= 0) && (qy < rows))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (quantiValue[qx][qy] > 1e-4)
|
|
|
|
|
|
{
|
|
|
|
|
|
zSum += quantiValue[qx][qy];
|
|
|
|
|
|
hist++;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (hist == 0)
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
else
|
|
|
|
|
|
return (zSum / hist);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-24 00:43:58 +08:00
|
|
|
|
void _updateROI(SSG_ROIRectD& roi, SVzNL3DPoint& a_pt)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (a_pt.z > 1E-4)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (roi.left < 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
roi.left = a_pt.x;
|
|
|
|
|
|
roi.right = a_pt.x;
|
|
|
|
|
|
roi.left = a_pt.y;
|
|
|
|
|
|
roi.right = a_pt.y;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
if (roi.left > a_pt.x)
|
|
|
|
|
|
roi.left = a_pt.x;
|
|
|
|
|
|
if (roi.right < a_pt.x)
|
|
|
|
|
|
roi.right = a_pt.x;
|
|
|
|
|
|
if (roi.top > a_pt.y)
|
|
|
|
|
|
roi.top = a_pt.y;
|
|
|
|
|
|
if (roi.bottom < a_pt.y)
|
|
|
|
|
|
roi.bottom = a_pt.y;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void _updateRoi3D(SVzNL3DRangeD& roi, SVzNL3DPoint& a_pt)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (a_pt.z > 1E-4)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (roi.zRange.max < 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
roi.xRange.min = a_pt.x;
|
|
|
|
|
|
roi.xRange.max = a_pt.x;
|
|
|
|
|
|
roi.yRange.min = a_pt.y;
|
|
|
|
|
|
roi.yRange.max = a_pt.y;
|
|
|
|
|
|
roi.zRange.min = a_pt.z;
|
|
|
|
|
|
roi.zRange.max = a_pt.z;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
if (roi.xRange.min > a_pt.x)
|
|
|
|
|
|
roi.xRange.min = a_pt.x;
|
|
|
|
|
|
if (roi.xRange.max < a_pt.x)
|
|
|
|
|
|
roi.xRange.max = a_pt.x;
|
|
|
|
|
|
if (roi.yRange.min > a_pt.y)
|
|
|
|
|
|
roi.yRange.min = a_pt.y;
|
|
|
|
|
|
if (roi.yRange.max < a_pt.y)
|
|
|
|
|
|
roi.yRange.max = a_pt.y;
|
|
|
|
|
|
if (roi.zRange.min > a_pt.z)
|
|
|
|
|
|
roi.zRange.min = a_pt.z;
|
|
|
|
|
|
if (roi.zRange.max < a_pt.z)
|
|
|
|
|
|
roi.zRange.max = a_pt.z;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void _updateRenge(SVzNLRange& range, int idx)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (range.nMin < 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
range.nMin = idx;
|
|
|
|
|
|
range.nMax = idx;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
if (range.nMin > idx)
|
|
|
|
|
|
range.nMin = idx;
|
|
|
|
|
|
if (range.nMax < idx)
|
|
|
|
|
|
range.nMax = idx;
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool _checkExist(int id, std::vector<int>& buff)
|
|
|
|
|
|
{
|
|
|
|
|
|
for (int i = 0; i < (int)buff.size(); i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (id == buff[i])
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void _searchNeighbours(
|
|
|
|
|
|
int selfId, int chkExtening,
|
|
|
|
|
|
int sLineIdx, int eLineIdx,
|
|
|
|
|
|
SVzNLRange ptIdxRange,
|
|
|
|
|
|
std::vector<std::vector<int>>& treeMask,
|
|
|
|
|
|
std::vector<int>& neighbours)
|
|
|
|
|
|
{
|
|
|
|
|
|
int lineNum = (int)treeMask.size();
|
|
|
|
|
|
int ptNum = treeMask[0].size();
|
|
|
|
|
|
for (int line = sLineIdx - chkExtening; line <= eLineIdx+ chkExtening; line++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if ((line >= 0) && (line < lineNum))
|
|
|
|
|
|
{
|
|
|
|
|
|
for (int ptIdx = ptIdxRange.nMin - chkExtening; ptIdx <= ptIdxRange.nMax + chkExtening; ptIdx++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if ((ptIdx >= 0) && (ptIdx < ptNum))
|
|
|
|
|
|
{
|
|
|
|
|
|
if ((treeMask[line][ptIdx] >= 0) && (treeMask[line][ptIdx] != selfId))
|
|
|
|
|
|
{
|
|
|
|
|
|
bool isExist = _checkExist(treeMask[line][ptIdx], neighbours);
|
|
|
|
|
|
if (false == isExist)
|
|
|
|
|
|
neighbours.push_back(treeMask[line][ptIdx]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool _compareByXValue(WD_workpieceInfo& a, WD_workpieceInfo& b)
|
|
|
|
|
|
{
|
|
|
|
|
|
return a.center.x < b.center.x;
|
|
|
|
|
|
}
|
|
|
|
|
|
void _getYTopLine(
|
|
|
|
|
|
std::vector< WD_workpieceInfo>& workpieceSrc,
|
|
|
|
|
|
std::vector< WD_workpieceInfo>& firstLine,
|
|
|
|
|
|
std::vector< WD_workpieceInfo>& restWorkpiece,
|
|
|
|
|
|
double yLayerTh)
|
|
|
|
|
|
{
|
|
|
|
|
|
//搜索Y最小值
|
|
|
|
|
|
if (workpieceSrc.size() == 0)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
double minY = workpieceSrc[0].center.y;
|
|
|
|
|
|
for (int i = 1; i < (int)workpieceSrc.size(); i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (minY > workpieceSrc[i].center.y)
|
|
|
|
|
|
minY = workpieceSrc[i].center.y;
|
|
|
|
|
|
}
|
|
|
|
|
|
double topLayerTh = minY + yLayerTh;
|
|
|
|
|
|
for (int i = 0; i < (int)workpieceSrc.size(); i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (workpieceSrc[i].center.y < topLayerTh)
|
|
|
|
|
|
firstLine.push_back(workpieceSrc[i]);
|
|
|
|
|
|
else
|
|
|
|
|
|
restWorkpiece.push_back(workpieceSrc[i]);
|
|
|
|
|
|
}
|
|
|
|
|
|
std::sort(firstLine.begin(), firstLine.end(), _compareByXValue);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-03-25 18:24:56 +08:00
|
|
|
|
|
|
|
|
|
|
void wd_getHoleInfo(
|
|
|
|
|
|
std::vector< std::vector<SVzNL3DPosition>>& scanLines,
|
2026-01-26 15:57:19 +08:00
|
|
|
|
const SSG_lineSegParam lineSegPara,
|
|
|
|
|
|
const SSG_outlierFilterParam filterParam,
|
|
|
|
|
|
const SSG_treeGrowParam growParam,
|
2026-03-27 00:01:16 +08:00
|
|
|
|
const double valieCommonNumRatio,
|
2026-03-25 18:24:56 +08:00
|
|
|
|
std::vector<SWD_segFeatureTree>& segTrees_v,
|
|
|
|
|
|
std::vector<SWD_segFeatureTree>& segTrees_h,
|
|
|
|
|
|
std::vector<SSG_intPair>& validObjects
|
|
|
|
|
|
)
|
2026-01-26 15:57:19 +08:00
|
|
|
|
{
|
2026-03-25 18:24:56 +08:00
|
|
|
|
int lineNum = (int)scanLines.size();
|
|
|
|
|
|
int linePtNum = (int)scanLines[0].size();
|
2026-01-26 15:57:19 +08:00
|
|
|
|
|
|
|
|
|
|
std::vector<std::vector<int>> pointMask;
|
|
|
|
|
|
pointMask.resize(lineNum);
|
|
|
|
|
|
|
2026-03-24 00:43:58 +08:00
|
|
|
|
//提取空白线段特征(孔特征)
|
|
|
|
|
|
std::vector<std::vector<SWD_segFeature>> holeGaps;
|
2026-02-02 15:10:42 +08:00
|
|
|
|
//提取线段端点特征
|
2026-01-26 15:57:19 +08:00
|
|
|
|
for (int line = 0; line < lineNum; line++)
|
|
|
|
|
|
{
|
2026-03-24 00:43:58 +08:00
|
|
|
|
if (line == 1047)
|
2026-01-26 15:57:19 +08:00
|
|
|
|
int kkk = 1;
|
|
|
|
|
|
|
|
|
|
|
|
std::vector<SVzNL3DPosition>& lineData = scanLines[line];
|
|
|
|
|
|
pointMask[line].resize(lineData.size());
|
2026-02-02 15:10:42 +08:00
|
|
|
|
std::fill(pointMask[line].begin(), pointMask[line].end(), 0);//初始化为0
|
|
|
|
|
|
//滤波,滤除异常点
|
2026-01-26 15:57:19 +08:00
|
|
|
|
sg_lineDataRemoveOutlier_changeOriginData(&lineData[0], linePtNum, filterParam);
|
|
|
|
|
|
|
|
|
|
|
|
std::vector<SSG_RUN> segs;
|
2026-03-24 00:43:58 +08:00
|
|
|
|
wd_getLineDataNullIntervals(lineData, lineSegPara, segs);
|
|
|
|
|
|
|
2026-02-02 15:10:42 +08:00
|
|
|
|
//将seg端点作为边缘点。做了地面调平后,垂直孔的内侧在XY平面上均为边缘点。
|
2026-03-24 00:43:58 +08:00
|
|
|
|
std::vector<SWD_segFeature> line_gaps;
|
2026-01-26 15:57:19 +08:00
|
|
|
|
for (int i = 0, i_max = (int)segs.size(); i < i_max; i++)
|
|
|
|
|
|
{
|
2026-03-24 00:43:58 +08:00
|
|
|
|
int ptIdx_1 = segs[i].start;
|
|
|
|
|
|
int ptIdx_2 = segs[i].start + segs[i].len - 1;
|
|
|
|
|
|
SWD_segFeature a_gap;
|
|
|
|
|
|
a_gap.lineIdx = line;
|
|
|
|
|
|
a_gap.startPtIdx = ptIdx_1;
|
|
|
|
|
|
a_gap.endPtIdx = ptIdx_2;
|
|
|
|
|
|
a_gap.startPt = lineData[ptIdx_1].pt3D;
|
|
|
|
|
|
a_gap.endPt = lineData[ptIdx_2].pt3D;
|
|
|
|
|
|
a_gap.featureValue = abs(a_gap.startPt.y - a_gap.endPt.y);
|
|
|
|
|
|
line_gaps.push_back(a_gap);
|
2026-01-26 15:57:19 +08:00
|
|
|
|
}
|
2026-03-24 00:43:58 +08:00
|
|
|
|
holeGaps.push_back(line_gaps);
|
2026-01-26 15:57:19 +08:00
|
|
|
|
}
|
2026-03-24 00:43:58 +08:00
|
|
|
|
//特征生长
|
|
|
|
|
|
wd_getSegFeatureGrowingTrees_2(holeGaps, segTrees_v, growParam);
|
2026-01-26 15:57:19 +08:00
|
|
|
|
|
2026-02-02 15:10:42 +08:00
|
|
|
|
//生成水平扫描
|
2026-01-26 15:57:19 +08:00
|
|
|
|
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++)
|
|
|
|
|
|
{
|
2026-02-02 15:10:42 +08:00
|
|
|
|
scanLines[line][j].nPointIdx = 0; //将原始数据的序列清0(会转义使用)
|
2026-01-26 15:57:19 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-02 15:10:42 +08:00
|
|
|
|
//水平arc特征提取
|
2026-03-24 00:43:58 +08:00
|
|
|
|
std::vector<std::vector<SWD_segFeature>> holeGaps_h;
|
|
|
|
|
|
for (int line = 0; line < linePtNum; line++)
|
2026-01-26 15:57:19 +08:00
|
|
|
|
{
|
|
|
|
|
|
if (line == 974)
|
|
|
|
|
|
int kkk = 1;
|
|
|
|
|
|
std::vector<SVzNL3DPosition>& lineData = hLines_raw[line];
|
2026-02-02 15:10:42 +08:00
|
|
|
|
//滤波,滤除异常点
|
2026-01-26 15:57:19 +08:00
|
|
|
|
int ptNum = (int)lineData.size();
|
|
|
|
|
|
sg_lineDataRemoveOutlier_changeOriginData(&lineData[0], ptNum, filterParam);
|
|
|
|
|
|
|
|
|
|
|
|
std::vector<SSG_RUN> segs;
|
2026-03-24 00:43:58 +08:00
|
|
|
|
wd_getLineDataNullIntervals(lineData, lineSegPara, segs);
|
|
|
|
|
|
|
2026-02-02 15:10:42 +08:00
|
|
|
|
//将seg端点作为边缘点。做了地面调平后,垂直孔的内侧在XY平面上均为边缘点。
|
2026-03-24 00:43:58 +08:00
|
|
|
|
std::vector<SWD_segFeature> line_gaps;
|
2026-01-26 15:57:19 +08:00
|
|
|
|
for (int i = 0, i_max = (int)segs.size(); i < i_max; i++)
|
|
|
|
|
|
{
|
2026-03-24 00:43:58 +08:00
|
|
|
|
int ptIdx_1 = segs[i].start;
|
|
|
|
|
|
int ptIdx_2 = segs[i].start + segs[i].len - 1;
|
|
|
|
|
|
SWD_segFeature a_gap;
|
|
|
|
|
|
a_gap.lineIdx = line;
|
|
|
|
|
|
a_gap.startPtIdx = ptIdx_1;
|
|
|
|
|
|
a_gap.endPtIdx = ptIdx_2;
|
|
|
|
|
|
a_gap.startPt = lineData[ptIdx_1].pt3D;
|
|
|
|
|
|
a_gap.endPt = lineData[ptIdx_2].pt3D;
|
|
|
|
|
|
a_gap.featureValue = abs(a_gap.startPt.y - a_gap.endPt.y);
|
|
|
|
|
|
line_gaps.push_back(a_gap);
|
|
|
|
|
|
}
|
|
|
|
|
|
holeGaps_h.push_back(line_gaps);
|
|
|
|
|
|
}
|
|
|
|
|
|
//特征生长
|
|
|
|
|
|
wd_getSegFeatureGrowingTrees_2(holeGaps_h, segTrees_h, growParam);
|
|
|
|
|
|
|
|
|
|
|
|
//创建Tree所在孔洞的Mask
|
|
|
|
|
|
std::vector<std::vector<int>> treeMask_v;
|
|
|
|
|
|
treeMask_v.resize(lineNum);
|
|
|
|
|
|
std::vector<std::vector<int>> treeMask_h;
|
|
|
|
|
|
treeMask_h.resize(lineNum);
|
|
|
|
|
|
for (int i = 0; i < lineNum; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
treeMask_v[i].resize(linePtNum);
|
|
|
|
|
|
std::fill(treeMask_v[i].begin(), treeMask_v[i].end(), -1);
|
|
|
|
|
|
treeMask_h[i].resize(linePtNum);
|
|
|
|
|
|
std::fill(treeMask_h[i].begin(), treeMask_h[i].end(), -1);
|
|
|
|
|
|
}
|
|
|
|
|
|
//标注
|
|
|
|
|
|
std::vector<SSG_treeInfo> treeInfo_v;
|
|
|
|
|
|
treeInfo_v.resize(segTrees_v.size());
|
|
|
|
|
|
for (int i = 0; i < (int)segTrees_v.size(); i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
SWD_segFeatureTree& a_tree = segTrees_v[i];
|
|
|
|
|
|
treeInfo_v[i].treeIdx = i;
|
|
|
|
|
|
treeInfo_v[i].sLineIdx = a_tree.sLineIdx;
|
|
|
|
|
|
treeInfo_v[i].eLineIdx = a_tree.eLineIdx;
|
|
|
|
|
|
treeInfo_v[i].vTreeFlag = 0;
|
|
|
|
|
|
treeInfo_v[i].treeType = 0;
|
2026-03-25 18:24:56 +08:00
|
|
|
|
treeInfo_v[i].roi = { -1, -1, -1, -1 };
|
2026-03-24 00:43:58 +08:00
|
|
|
|
int nullPtSize = 0;
|
2026-03-25 18:24:56 +08:00
|
|
|
|
SSG_ROIRectD roi = { -1, -1, -1, -1 };
|
2026-03-24 00:43:58 +08:00
|
|
|
|
SVzNLRange ptIdxRange = { -1, -1 };
|
2026-03-26 07:17:19 +08:00
|
|
|
|
if (a_tree.treeNodes.size() > 0)
|
2026-03-24 00:43:58 +08:00
|
|
|
|
{
|
|
|
|
|
|
for (int m = 0; m < (int)a_tree.treeNodes.size(); m++)
|
2026-01-26 15:57:19 +08:00
|
|
|
|
{
|
2026-03-24 00:43:58 +08:00
|
|
|
|
SWD_segFeature& a_seg = a_tree.treeNodes[m];
|
|
|
|
|
|
for (int n = a_seg.startPtIdx; n <= a_seg.endPtIdx; n++)
|
|
|
|
|
|
treeMask_v[a_seg.lineIdx][n] = i;
|
|
|
|
|
|
|
|
|
|
|
|
nullPtSize += a_seg.endPtIdx - a_seg.startPtIdx + 1;
|
|
|
|
|
|
_updateROI(roi, scanLines[a_seg.lineIdx][a_seg.startPtIdx].pt3D);
|
|
|
|
|
|
_updateROI(roi, scanLines[a_seg.lineIdx][a_seg.endPtIdx].pt3D);
|
|
|
|
|
|
_updateRenge(ptIdxRange, a_seg.startPtIdx);
|
|
|
|
|
|
_updateRenge(ptIdxRange, a_seg.endPtIdx);
|
|
|
|
|
|
//scanLinesInput[a_seg.lineIdx][a_seg.endPtIdx].nPointIdx = 0x01;
|
|
|
|
|
|
//scanLinesInput[a_seg.lineIdx][a_seg.startPtIdx].nPointIdx = 0x01;
|
2026-01-26 15:57:19 +08:00
|
|
|
|
}
|
2026-03-24 00:43:58 +08:00
|
|
|
|
treeInfo_v[i].treeType = nullPtSize;
|
|
|
|
|
|
treeInfo_v[i].roi = roi;
|
|
|
|
|
|
treeInfo_v[i].ptIdxRange = ptIdxRange;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
std::vector<SSG_treeInfo> treeInfo_h;
|
|
|
|
|
|
treeInfo_h.resize(segTrees_h.size());
|
|
|
|
|
|
for (int i = 0; i < (int)segTrees_h.size(); i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
SWD_segFeatureTree& a_tree = segTrees_h[i];
|
|
|
|
|
|
treeInfo_h[i].treeIdx = i;
|
|
|
|
|
|
treeInfo_h[i].sLineIdx = a_tree.sLineIdx;
|
|
|
|
|
|
treeInfo_h[i].eLineIdx = a_tree.eLineIdx;
|
|
|
|
|
|
treeInfo_h[i].vTreeFlag = 0;
|
|
|
|
|
|
treeInfo_h[i].treeType = 0;
|
|
|
|
|
|
treeInfo_h[i].roi = { -1, -1, -1, -1 };
|
|
|
|
|
|
int nullPtSize = 0;
|
|
|
|
|
|
SSG_ROIRectD roi = { -1, -1, -1, -1 };
|
|
|
|
|
|
SVzNLRange ptIdxRange = { -1, -1 };
|
2026-03-26 07:17:19 +08:00
|
|
|
|
if (a_tree.treeNodes.size() > 0)
|
2026-03-24 00:43:58 +08:00
|
|
|
|
{
|
|
|
|
|
|
for (int m = 0; m < (int)a_tree.treeNodes.size(); m++)
|
2026-01-26 15:57:19 +08:00
|
|
|
|
{
|
2026-03-24 00:43:58 +08:00
|
|
|
|
SWD_segFeature& a_seg = a_tree.treeNodes[m];
|
|
|
|
|
|
for (int n = a_seg.startPtIdx; n <= a_seg.endPtIdx; n++)
|
|
|
|
|
|
treeMask_h[n][a_seg.lineIdx] = i;
|
|
|
|
|
|
|
|
|
|
|
|
nullPtSize += a_seg.endPtIdx - a_seg.startPtIdx + 1;
|
|
|
|
|
|
_updateROI(roi, scanLines[a_seg.startPtIdx][a_seg.lineIdx].pt3D);
|
|
|
|
|
|
_updateROI(roi, scanLines[a_seg.endPtIdx][a_seg.lineIdx].pt3D);
|
|
|
|
|
|
_updateRenge(ptIdxRange, a_seg.startPtIdx);
|
|
|
|
|
|
_updateRenge(ptIdxRange, a_seg.endPtIdx);
|
|
|
|
|
|
//scanLinesInput[a_seg.startPtIdx][a_seg.lineIdx].nPointIdx |= 0x02;
|
|
|
|
|
|
//scanLinesInput[a_seg.endPtIdx][a_seg.lineIdx].nPointIdx |= 0x02;
|
2026-01-26 15:57:19 +08:00
|
|
|
|
}
|
2026-03-24 00:43:58 +08:00
|
|
|
|
treeInfo_h[i].treeType = nullPtSize;
|
|
|
|
|
|
treeInfo_h[i].roi = roi;
|
|
|
|
|
|
treeInfo_h[i].ptIdxRange = ptIdxRange;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
//水平和垂直目标合并
|
|
|
|
|
|
int vTreeSize = (int)segTrees_v.size();
|
|
|
|
|
|
int hTreeSize = (int)segTrees_h.size();
|
|
|
|
|
|
std::vector<std::vector<int>> treeHVInfo; //统计垂直和水平的tree的位置信息
|
|
|
|
|
|
treeHVInfo.resize(vTreeSize);
|
|
|
|
|
|
for (int i = 0; i < vTreeSize; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
treeHVInfo[i].resize(hTreeSize);
|
|
|
|
|
|
std::fill(treeHVInfo[i].begin(), treeHVInfo[i].end(), 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
for (int line = 0; line < lineNum; line++)
|
|
|
|
|
|
{
|
|
|
|
|
|
for (int ptIdx = 0; ptIdx < linePtNum; ptIdx++)
|
|
|
|
|
|
{
|
|
|
|
|
|
int idx_v = treeMask_v[line][ptIdx];
|
|
|
|
|
|
int idx_h = treeMask_h[line][ptIdx];
|
|
|
|
|
|
if ((idx_v >= 0) && (idx_h >= 0))
|
|
|
|
|
|
treeHVInfo[idx_v][idx_h]++;
|
2026-01-26 15:57:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-24 00:43:58 +08:00
|
|
|
|
//生成候选目标
|
|
|
|
|
|
std::vector<SSG_intPair> objects;
|
|
|
|
|
|
for (int i = 0; i < vTreeSize; i++)
|
2026-01-26 15:57:19 +08:00
|
|
|
|
{
|
2026-03-24 00:43:58 +08:00
|
|
|
|
SWD_segFeatureTree& a_tree = segTrees_v[i];
|
|
|
|
|
|
if ((a_tree.sLineIdx <= 1047) && (a_tree.eLineIdx >= 1047))
|
|
|
|
|
|
int kkk = 1;
|
|
|
|
|
|
|
|
|
|
|
|
int totalSize_v = treeInfo_v[i].treeType;
|
|
|
|
|
|
int commonSize = 0;
|
|
|
|
|
|
int hTreeIdx = -1;
|
|
|
|
|
|
for (int j = 0; j < hTreeSize; j++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (commonSize < treeHVInfo[i][j])
|
|
|
|
|
|
{
|
|
|
|
|
|
hTreeIdx = j;
|
|
|
|
|
|
commonSize = treeHVInfo[i][j];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (hTreeIdx >= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
int totalSize_h = treeInfo_h[hTreeIdx].treeType;
|
2026-03-27 00:01:16 +08:00
|
|
|
|
int sizeV_th = (int)((double)totalSize_v * valieCommonNumRatio);
|
|
|
|
|
|
int sizeH_th = (int)((double)totalSize_h * valieCommonNumRatio);
|
|
|
|
|
|
if ((commonSize > sizeH_th) && (commonSize > sizeV_th))
|
2026-03-24 00:43:58 +08:00
|
|
|
|
{
|
|
|
|
|
|
SSG_intPair a_obj;
|
|
|
|
|
|
a_obj.data_0 = i;
|
|
|
|
|
|
a_obj.data_1 = hTreeIdx;
|
|
|
|
|
|
a_obj.idx = commonSize;
|
|
|
|
|
|
objects.push_back(a_obj);
|
|
|
|
|
|
treeInfo_v[i].data = commonSize;
|
|
|
|
|
|
treeInfo_h[hTreeIdx].data = commonSize;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-26 15:57:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-24 00:43:58 +08:00
|
|
|
|
//滤除相邻。每个目标保留一个
|
|
|
|
|
|
for (int i = 0; i < (int)objects.size(); i++)
|
2026-01-26 15:57:19 +08:00
|
|
|
|
{
|
2026-03-24 00:43:58 +08:00
|
|
|
|
int vTreeIdx = objects[i].data_0;
|
|
|
|
|
|
if (treeInfo_v[vTreeIdx].vTreeFlag < 0)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
std::vector<int> neighbours;
|
|
|
|
|
|
_searchNeighbours(vTreeIdx, 3,
|
|
|
|
|
|
treeInfo_v[vTreeIdx].sLineIdx, treeInfo_v[vTreeIdx].eLineIdx,
|
|
|
|
|
|
treeInfo_v[vTreeIdx].ptIdxRange, treeMask_v, neighbours);
|
|
|
|
|
|
|
|
|
|
|
|
int bestIdx = vTreeIdx;
|
|
|
|
|
|
int maxValue = treeInfo_v[vTreeIdx].data;
|
|
|
|
|
|
if (neighbours.size() > 0)
|
2026-01-26 15:57:19 +08:00
|
|
|
|
{
|
2026-03-24 00:43:58 +08:00
|
|
|
|
for (int j = 0; j < (int)neighbours.size(); j++)
|
|
|
|
|
|
{
|
|
|
|
|
|
int idx = neighbours[j];
|
|
|
|
|
|
if (maxValue < treeInfo_v[idx].data)
|
|
|
|
|
|
{
|
|
|
|
|
|
maxValue = treeInfo_v[idx].data;
|
|
|
|
|
|
bestIdx = idx;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (bestIdx != vTreeIdx)
|
|
|
|
|
|
treeInfo_v[vTreeIdx].vTreeFlag = -1;
|
|
|
|
|
|
for (int j = 0; j < (int)neighbours.size(); j++)
|
2026-01-26 15:57:19 +08:00
|
|
|
|
{
|
2026-03-24 00:43:58 +08:00
|
|
|
|
int idx = neighbours[j];
|
|
|
|
|
|
if (bestIdx != idx)
|
|
|
|
|
|
treeInfo_v[idx].vTreeFlag = -1;
|
2026-01-26 15:57:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-25 18:24:56 +08:00
|
|
|
|
|
2026-03-24 00:43:58 +08:00
|
|
|
|
for (int i = 0; i < (int)objects.size(); i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
int vTreeIdx = objects[i].data_0;
|
|
|
|
|
|
if (treeInfo_v[vTreeIdx].vTreeFlag < 0)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
validObjects.push_back(objects[i]);
|
|
|
|
|
|
}
|
2026-03-25 18:24:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//工件孔定位-拓普发工件孔定位
|
|
|
|
|
|
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;
|
2026-03-27 00:01:16 +08:00
|
|
|
|
double valieCommonNumRatio = 0.25;
|
|
|
|
|
|
wd_getHoleInfo(scanLines, lineSegPara, filterParam, growParam, valieCommonNumRatio, segTrees_v, segTrees_h, validObjects);
|
2026-03-24 00:43:58 +08:00
|
|
|
|
|
2026-04-10 23:58:27 +08:00
|
|
|
|
for (int i = 0; i < lineNum; i++)
|
|
|
|
|
|
{
|
2026-04-11 00:51:17 +08:00
|
|
|
|
for (int j = 0; j < (int)scanLines[i].size(); j++)
|
2026-04-10 23:58:27 +08:00
|
|
|
|
scanLines[i][j].nPointIdx = 0; //清零
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-24 00:43:58 +08:00
|
|
|
|
//生成聚类信息,
|
2026-02-02 15:10:42 +08:00
|
|
|
|
std::vector<std::vector< SVzNL2DPoint>> clusters; //只记录位置
|
2026-01-26 15:57:19 +08:00
|
|
|
|
std::vector<SVzNL3DRangeD> clustersRoi3D;
|
2026-03-24 00:43:58 +08:00
|
|
|
|
for (int i = 0; i < (int)validObjects.size(); i++)
|
2026-01-26 15:57:19 +08:00
|
|
|
|
{
|
2026-03-24 00:43:58 +08:00
|
|
|
|
std::vector< SVzNL2DPoint> a_cluster;
|
|
|
|
|
|
SVzNL3DRangeD a_roi3D = { {-1, -1}, {-1, -1}, {-1, -1 } };
|
|
|
|
|
|
|
|
|
|
|
|
int vTreeIdx = validObjects[i].data_0;
|
|
|
|
|
|
int hTreeIdx = validObjects[i].data_1;
|
|
|
|
|
|
for (int m = 0; m < (int)segTrees_v[vTreeIdx].treeNodes.size(); m++)
|
2026-01-26 15:57:19 +08:00
|
|
|
|
{
|
2026-03-24 00:43:58 +08:00
|
|
|
|
SWD_segFeature& a_seg = segTrees_v[vTreeIdx].treeNodes[m];
|
2026-04-10 23:58:27 +08:00
|
|
|
|
if (scanLines[a_seg.lineIdx][a_seg.endPtIdx].nPointIdx == 0)
|
2026-03-24 00:43:58 +08:00
|
|
|
|
{
|
2026-04-10 23:58:27 +08:00
|
|
|
|
scanLines[a_seg.lineIdx][a_seg.endPtIdx].nPointIdx = vTreeIdx + 1; // 0x01;
|
2026-03-24 00:43:58 +08:00
|
|
|
|
scanLinesInput[a_seg.lineIdx][a_seg.endPtIdx].nPointIdx = vTreeIdx + 1; // 0x01;
|
|
|
|
|
|
SVzNL2DPoint a_pos = { a_seg.lineIdx , a_seg.endPtIdx };
|
|
|
|
|
|
a_cluster.push_back(a_pos);
|
2026-04-10 23:58:27 +08:00
|
|
|
|
_updateRoi3D(a_roi3D, scanLines[a_seg.lineIdx][a_seg.endPtIdx].pt3D);
|
2026-03-24 00:43:58 +08:00
|
|
|
|
}
|
2026-04-10 23:58:27 +08:00
|
|
|
|
if (scanLines[a_seg.lineIdx][a_seg.startPtIdx].nPointIdx == 0)
|
2026-03-24 00:43:58 +08:00
|
|
|
|
{
|
2026-04-10 23:58:27 +08:00
|
|
|
|
scanLines[a_seg.lineIdx][a_seg.startPtIdx].nPointIdx = vTreeIdx + 1; // 0x01;
|
2026-03-24 00:43:58 +08:00
|
|
|
|
scanLinesInput[a_seg.lineIdx][a_seg.startPtIdx].nPointIdx = vTreeIdx + 1; // 0x01;
|
|
|
|
|
|
SVzNL2DPoint a_pos = { a_seg.lineIdx , a_seg.startPtIdx };
|
|
|
|
|
|
a_cluster.push_back(a_pos);
|
2026-04-10 23:58:27 +08:00
|
|
|
|
_updateRoi3D(a_roi3D, scanLines[a_seg.lineIdx][a_seg.startPtIdx].pt3D);
|
2026-03-24 00:43:58 +08:00
|
|
|
|
}
|
2026-01-26 15:57:19 +08:00
|
|
|
|
}
|
2026-03-24 00:43:58 +08:00
|
|
|
|
for (int m = 0; m < (int)segTrees_h[hTreeIdx].treeNodes.size(); m++)
|
|
|
|
|
|
{
|
|
|
|
|
|
SWD_segFeature& a_seg = segTrees_h[hTreeIdx].treeNodes[m];
|
2026-04-10 23:58:27 +08:00
|
|
|
|
if (scanLines[a_seg.startPtIdx][a_seg.lineIdx].nPointIdx == 0)
|
2026-03-24 00:43:58 +08:00
|
|
|
|
{
|
2026-04-10 23:58:27 +08:00
|
|
|
|
scanLines[a_seg.startPtIdx][a_seg.lineIdx].nPointIdx = vTreeIdx + 1; // 0x02;
|
2026-03-24 00:43:58 +08:00
|
|
|
|
scanLinesInput[a_seg.startPtIdx][a_seg.lineIdx].nPointIdx = vTreeIdx + 1; // 0x02;
|
|
|
|
|
|
SVzNL2DPoint a_pos = { a_seg.startPtIdx , a_seg.lineIdx };
|
|
|
|
|
|
a_cluster.push_back(a_pos);
|
2026-04-10 23:58:27 +08:00
|
|
|
|
_updateRoi3D(a_roi3D, scanLines[a_seg.startPtIdx][a_seg.lineIdx].pt3D);
|
2026-03-24 00:43:58 +08:00
|
|
|
|
}
|
2026-04-10 23:58:27 +08:00
|
|
|
|
if (scanLines[a_seg.endPtIdx][a_seg.lineIdx].nPointIdx == 0)
|
2026-03-24 00:43:58 +08:00
|
|
|
|
{
|
2026-04-10 23:58:27 +08:00
|
|
|
|
scanLines[a_seg.endPtIdx][a_seg.lineIdx].nPointIdx = vTreeIdx + 1; // 0x02;
|
2026-03-24 00:43:58 +08:00
|
|
|
|
scanLinesInput[a_seg.endPtIdx][a_seg.lineIdx].nPointIdx = vTreeIdx + 1; // 0x02;
|
|
|
|
|
|
SVzNL2DPoint a_pos = { a_seg.endPtIdx , a_seg.lineIdx };
|
|
|
|
|
|
a_cluster.push_back(a_pos);
|
2026-04-10 23:58:27 +08:00
|
|
|
|
_updateRoi3D(a_roi3D, scanLines[a_seg.endPtIdx][a_seg.lineIdx].pt3D);
|
2026-03-24 00:43:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
clusters.push_back(a_cluster);
|
|
|
|
|
|
clustersRoi3D.push_back(a_roi3D);
|
2026-01-26 15:57:19 +08:00
|
|
|
|
}
|
2026-03-24 00:43:58 +08:00
|
|
|
|
|
2026-02-02 15:10:42 +08:00
|
|
|
|
//聚类结果分析
|
2026-01-26 15:57:19 +08:00
|
|
|
|
std::vector<int> validCluserIndexing;
|
|
|
|
|
|
int clusterSize = (int)clusters.size();
|
|
|
|
|
|
for (int i = 0; i < clusterSize; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
SVzNL3DRangeD& a_roi = clustersRoi3D[i];
|
|
|
|
|
|
double L = a_roi.xRange.max - a_roi.xRange.min;
|
|
|
|
|
|
double W = a_roi.yRange.max - a_roi.yRange.min;
|
2026-03-24 00:43:58 +08:00
|
|
|
|
if ((L > workpiecePara.holeDiameter * 0.5) && (L < workpiecePara.holeDiameter * 2.5) &&
|
|
|
|
|
|
(W > workpiecePara.holeDiameter * 0.5) && (W < workpiecePara.holeDiameter * 2.5))
|
2026-01-26 15:57:19 +08:00
|
|
|
|
validCluserIndexing.push_back(i);
|
|
|
|
|
|
}
|
2026-02-02 15:10:42 +08:00
|
|
|
|
//生成结果
|
2026-01-26 15:57:19 +08:00
|
|
|
|
std::vector< SWD_HoleInfo> holes;
|
|
|
|
|
|
int objectSize = (int)validCluserIndexing.size();
|
|
|
|
|
|
for (int objIdx = 0; objIdx < objectSize; objIdx++)
|
|
|
|
|
|
{
|
|
|
|
|
|
std::vector<SVzNL3DPoint> pointArray;
|
|
|
|
|
|
int clusterIdx = validCluserIndexing[objIdx];
|
2026-02-02 15:10:42 +08:00
|
|
|
|
//取cluster上的点
|
2026-01-26 15:57:19 +08:00
|
|
|
|
int clusterPtSize = (int)clusters[clusterIdx].size();
|
|
|
|
|
|
double minZ = DBL_MAX;
|
|
|
|
|
|
for (int i = 0; i < clusterPtSize; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
SVzNL2DPoint a_pos = clusters[clusterIdx][i];
|
2026-03-24 00:43:58 +08:00
|
|
|
|
int lineIdx = a_pos.x;
|
|
|
|
|
|
int ptIdx = a_pos.y;
|
2026-01-26 15:57:19 +08:00
|
|
|
|
SVzNL3DPoint a_pt3d = scanLines[lineIdx][ptIdx].pt3D;
|
|
|
|
|
|
if (minZ > a_pt3d.z)
|
|
|
|
|
|
minZ = a_pt3d.z;
|
|
|
|
|
|
pointArray.push_back(a_pt3d);
|
|
|
|
|
|
}
|
2026-02-02 15:10:42 +08:00
|
|
|
|
//圆拟合
|
2026-01-26 15:57:19 +08:00
|
|
|
|
SVzNL3DPoint center;
|
|
|
|
|
|
double radius;
|
|
|
|
|
|
double err = fitCircleByLeastSquare(pointArray, center, radius);
|
|
|
|
|
|
center.z = minZ;
|
|
|
|
|
|
SWD_HoleInfo a_hole;
|
|
|
|
|
|
a_hole.center = { center.x, center.y, center.z };
|
|
|
|
|
|
a_hole.radius = radius;
|
|
|
|
|
|
holes.push_back(a_hole);
|
|
|
|
|
|
}
|
2026-02-02 15:10:42 +08:00
|
|
|
|
//分割
|
|
|
|
|
|
//方法:先搜索与W最接近的点,然后条件搜索(垂直)与L最接近的点
|
|
|
|
|
|
double distDeviation = 5.0; //距离搜索的合格门限。小于此距离,认为搜索到的目标为有效
|
2026-03-24 00:43:58 +08:00
|
|
|
|
std::vector< WD_workpieceInfo> allWorkpiece;
|
2026-01-26 15:57:19 +08:00
|
|
|
|
for (int objIdx = 0; objIdx < objectSize; objIdx++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (holes[objIdx].radius < 0)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
holes[objIdx].radius = -1;
|
|
|
|
|
|
SWD_HoleInfo& p0 = holes[objIdx];
|
|
|
|
|
|
int idx1 = distanceSearchObject(p0.center, holes, workpiecePara.holeDist_W, distDeviation);
|
|
|
|
|
|
if (idx1 < 0)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
2026-02-02 15:10:42 +08:00
|
|
|
|
SVzNLRangeD angleRange = { 85, 95 }; //垂直,5度范围
|
2026-01-26 15:57:19 +08:00
|
|
|
|
SWD_HoleInfo& p1 = holes[idx1];
|
2026-02-02 15:10:42 +08:00
|
|
|
|
//搜索最接近distance且角度为angle的目标, 以角度为优先
|
2026-01-26 15:57:19 +08:00
|
|
|
|
int idx2 = angleConditionDistanceSearch(
|
|
|
|
|
|
p0.center, p1.center,
|
|
|
|
|
|
holes,
|
|
|
|
|
|
workpiecePara.holeDist_L, distDeviation,
|
|
|
|
|
|
angleRange);
|
|
|
|
|
|
if (idx2 < 0)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
SWD_HoleInfo& p2 = holes[idx2];
|
2026-02-02 15:10:42 +08:00
|
|
|
|
//搜索最接近distance且角度为angle的目标, 以角度为优先
|
2026-01-26 15:57:19 +08:00
|
|
|
|
int idx3 = angleConditionDistanceSearch(
|
|
|
|
|
|
p1.center, p0.center,
|
|
|
|
|
|
holes,
|
|
|
|
|
|
workpiecePara.holeDist_L, distDeviation,
|
|
|
|
|
|
angleRange);
|
|
|
|
|
|
if (idx3 < 0)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
SWD_HoleInfo& p3 = holes[idx3];
|
|
|
|
|
|
p1.radius = -1;
|
|
|
|
|
|
p2.radius = -1;
|
|
|
|
|
|
p3.radius = -1;
|
|
|
|
|
|
|
2026-02-02 15:10:42 +08:00
|
|
|
|
//重新计算Z值。因为沉孔的原因,Z值会不准确。取四条边的中点处的Z值的均值作为整个的Z值
|
2026-01-26 22:36:13 +08:00
|
|
|
|
SVzNL3DPoint center_p0p1 = { (p0.center.x + p1.center.x) / 2,(p0.center.y + p1.center.y) / 2, (p0.center.z + p1.center.z) / 2 };
|
|
|
|
|
|
SVzNL3DPoint center_p0p2 = { (p0.center.x + p2.center.x) / 2,(p0.center.y + p2.center.y) / 2, (p0.center.z + p2.center.z) / 2 };
|
|
|
|
|
|
SVzNL3DPoint center_p1p3 = { (p1.center.x + p3.center.x) / 2,(p1.center.y + p3.center.y) / 2, (p1.center.z + p3.center.z) / 2 };
|
|
|
|
|
|
SVzNL3DPoint center_p2p3 = { (p2.center.x + p3.center.x) / 2,(p2.center.y + p3.center.y) / 2, (p2.center.z + p3.center.z) / 2 };
|
|
|
|
|
|
double rectR = 5.0;
|
|
|
|
|
|
double z1 = _getMeanZ(quantiValue, center_p0p1, roi2D, rectR);
|
|
|
|
|
|
double z2 = _getMeanZ(quantiValue, center_p0p2, roi2D, rectR);
|
|
|
|
|
|
double z3 = _getMeanZ(quantiValue, center_p1p3, roi2D, rectR);
|
|
|
|
|
|
double z4 = _getMeanZ(quantiValue, center_p2p3, roi2D, rectR);
|
|
|
|
|
|
|
|
|
|
|
|
p0.center.z = (z1 + z2) / 2;
|
|
|
|
|
|
p1.center.z = (z1 + z3) / 2;
|
|
|
|
|
|
p2.center.z = (z2 + z4) / 2;
|
|
|
|
|
|
p3.center.z = (z3 + z4) / 2;
|
2026-01-26 15:57:19 +08:00
|
|
|
|
WD_workpieceInfo a_workpiece;
|
|
|
|
|
|
a_workpiece.workpieceType = workpiecePara.workpieceType;
|
|
|
|
|
|
a_workpiece.holes.push_back(p0.center);
|
|
|
|
|
|
a_workpiece.holes.push_back(p1.center);
|
|
|
|
|
|
a_workpiece.holes.push_back(p2.center);
|
|
|
|
|
|
a_workpiece.holes.push_back(p3.center);
|
2026-01-26 22:36:13 +08:00
|
|
|
|
|
|
|
|
|
|
for (int m = 0; m < 4; m++)
|
|
|
|
|
|
{
|
|
|
|
|
|
SVzNL3DPoint a_pt = a_workpiece.holes[m];
|
2026-02-02 15:10:42 +08:00
|
|
|
|
a_pt.z = a_pt.z + 20; //法向,因为做过地面高平,所以法向只在z向
|
2026-01-26 22:36:13 +08:00
|
|
|
|
a_workpiece.holesDir.push_back(a_pt);
|
|
|
|
|
|
}
|
2026-01-26 15:57:19 +08:00
|
|
|
|
a_workpiece.center = { (p0.center.x + p1.center.x + p2.center.x + p3.center.x) / 4,
|
|
|
|
|
|
(p0.center.y + p1.center.y + p2.center.y + p3.center.y) / 4,
|
2026-01-26 22:36:13 +08:00
|
|
|
|
(z1 + z2 + z3 + z4) / 4 };
|
2026-01-26 15:57:19 +08:00
|
|
|
|
|
|
|
|
|
|
SVzNL3DPoint y_dir;
|
2026-02-02 15:10:42 +08:00
|
|
|
|
if (p0.center.x < p1.center.x)
|
|
|
|
|
|
y_dir = { p1.center.x - p0.center.x, p1.center.y - p0.center.y, 0 };
|
2026-02-01 15:28:17 +08:00
|
|
|
|
else
|
2026-02-02 15:10:42 +08:00
|
|
|
|
y_dir = { p0.center.x - p1.center.x, p0.center.y - p1.center.y, 0 };
|
2026-01-26 22:36:13 +08:00
|
|
|
|
double modLen = sqrt(pow(y_dir.x, 2) + pow(y_dir.y, 2));
|
|
|
|
|
|
y_dir = { y_dir.x / modLen, y_dir.y / modLen, 0 };
|
|
|
|
|
|
a_workpiece.y_dir = { y_dir.x * 20 + a_workpiece.center.x, y_dir.y * 20 + a_workpiece.center.y, a_workpiece.center.z };
|
2026-02-01 15:28:17 +08:00
|
|
|
|
a_workpiece.z_dir = { a_workpiece.center.x, a_workpiece.center.y, a_workpiece.center.z + 20 };
|
2026-01-30 00:10:14 +08:00
|
|
|
|
|
2026-03-24 00:43:58 +08:00
|
|
|
|
allWorkpiece.push_back(a_workpiece);
|
|
|
|
|
|
}
|
|
|
|
|
|
int workpieceNum = (int)allWorkpiece.size();
|
|
|
|
|
|
if (workpieceNum == 0)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
//排序
|
|
|
|
|
|
//z方向排序
|
|
|
|
|
|
std::vector< WD_workpieceInfo> zSortWorkpiece;
|
|
|
|
|
|
double minZ = allWorkpiece[0].center.z;
|
|
|
|
|
|
for (int i = 1; i < workpieceNum; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (minZ > allWorkpiece[i].center.z)
|
|
|
|
|
|
minZ = allWorkpiece[i].center.z;
|
|
|
|
|
|
}
|
|
|
|
|
|
double topLayerTh = minZ + workpiecePara.H / 2;
|
|
|
|
|
|
for (int i = 0; i < workpieceNum; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (allWorkpiece[i].center.z < topLayerTh)
|
|
|
|
|
|
zSortWorkpiece.push_back(allWorkpiece[i]);
|
|
|
|
|
|
}
|
|
|
|
|
|
//水平方向排序
|
|
|
|
|
|
while (zSortWorkpiece.size() > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
std::vector< WD_workpieceInfo> firstLine;
|
|
|
|
|
|
std::vector< WD_workpieceInfo> restWorkpiece;
|
|
|
|
|
|
_getYTopLine(zSortWorkpiece, firstLine, restWorkpiece, workpiecePara.yLen / 2);
|
|
|
|
|
|
workpiecePositioning.insert(workpiecePositioning.end(), firstLine.begin(), firstLine.end());
|
|
|
|
|
|
zSortWorkpiece.clear();
|
|
|
|
|
|
zSortWorkpiece.insert(zSortWorkpiece.end(), restWorkpiece.begin(), restWorkpiece.end());
|
2026-01-26 15:57:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-02 15:10:42 +08:00
|
|
|
|
//旋转回去
|
2026-03-24 00:43:58 +08:00
|
|
|
|
workpieceNum = (int)workpiecePositioning.size();
|
2026-01-26 15:57:19 +08:00
|
|
|
|
for (int i = 0; i < workpieceNum; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
SVzNL3DPoint rpt;
|
|
|
|
|
|
rpt = _ptRotate(workpiecePositioning[i].center, groundCalibPara.invRMatrix);
|
|
|
|
|
|
workpiecePositioning[i].center = rpt;
|
|
|
|
|
|
rpt = _ptRotate(workpiecePositioning[i].y_dir, groundCalibPara.invRMatrix);
|
|
|
|
|
|
workpiecePositioning[i].y_dir = rpt;
|
|
|
|
|
|
rpt = _ptRotate(workpiecePositioning[i].z_dir, groundCalibPara.invRMatrix);
|
|
|
|
|
|
workpiecePositioning[i].z_dir = rpt;
|
|
|
|
|
|
for (int j = 0, j_max = (int)workpiecePositioning[i].holes.size(); j < j_max; j++)
|
|
|
|
|
|
{
|
|
|
|
|
|
rpt = _ptRotate(workpiecePositioning[i].holes[j], groundCalibPara.invRMatrix);
|
|
|
|
|
|
workpiecePositioning[i].holes[j] = rpt;
|
2026-01-26 22:36:13 +08:00
|
|
|
|
rpt = _ptRotate(workpiecePositioning[i].holesDir[j], groundCalibPara.invRMatrix);
|
|
|
|
|
|
workpiecePositioning[i].holesDir[j] = rpt;
|
2026-01-26 15:57:19 +08:00
|
|
|
|
}
|
2026-01-30 00:10:14 +08:00
|
|
|
|
|
|
|
|
|
|
SVzNL3DPoint vector_z = { workpiecePositioning[i].z_dir.x - workpiecePositioning[i].center.x,
|
|
|
|
|
|
workpiecePositioning[i].z_dir.y - workpiecePositioning[i].center.y,
|
|
|
|
|
|
workpiecePositioning[i].z_dir.z - workpiecePositioning[i].center.z };
|
|
|
|
|
|
SVzNL3DPoint vector_y = { workpiecePositioning[i].y_dir.x - workpiecePositioning[i].center.x,
|
|
|
|
|
|
workpiecePositioning[i].y_dir.y - workpiecePositioning[i].center.y,
|
|
|
|
|
|
workpiecePositioning[i].y_dir.z - workpiecePositioning[i].center.z };
|
|
|
|
|
|
double mod_vz = sqrt(pow(vector_z.x, 2) + pow(vector_z.y, 2) + pow(vector_z.z, 2));
|
2026-02-02 15:10:42 +08:00
|
|
|
|
vector_z = { vector_z.x / mod_vz, vector_z.y / mod_vz, vector_z.z / mod_vz }; //归一化
|
2026-01-30 00:10:14 +08:00
|
|
|
|
double mod_vy = sqrt(pow(vector_y.x, 2) + pow(vector_y.y, 2) + pow(vector_y.z, 2));
|
2026-02-02 15:10:42 +08:00
|
|
|
|
vector_y = { vector_y.x / mod_vy, vector_y.y / mod_vy, vector_y.z / mod_vy }; //归一化
|
|
|
|
|
|
//叉乘出vector_x
|
2026-01-30 00:10:14 +08:00
|
|
|
|
SVzNL3DPoint vector_x;
|
|
|
|
|
|
vector_x.x = vector_y.y * vector_z.z - vector_z.y * vector_y.z;
|
|
|
|
|
|
vector_x.y = vector_y.z * vector_z.x - vector_z.z * vector_y.x;
|
|
|
|
|
|
vector_x.z = vector_y.x * vector_z.y - vector_z.x * vector_y.y;
|
2026-01-31 14:36:54 +08:00
|
|
|
|
workpiecePositioning[i].x_dir = vector_x;
|
|
|
|
|
|
workpiecePositioning[i].y_dir = vector_y;
|
|
|
|
|
|
workpiecePositioning[i].z_dir = vector_z;
|
|
|
|
|
|
#if 0
|
2026-02-02 15:10:42 +08:00
|
|
|
|
//得到旋转矩阵
|
2026-01-30 00:10:14 +08:00
|
|
|
|
double R[3][3];
|
|
|
|
|
|
R[0][0] = vector_x.x;
|
|
|
|
|
|
R[1][0] = vector_x.y;
|
|
|
|
|
|
R[2][0] = vector_x.z;
|
|
|
|
|
|
|
|
|
|
|
|
R[0][1] = vector_y.x;
|
|
|
|
|
|
R[1][1] = vector_y.y;
|
|
|
|
|
|
R[2][1] = vector_y.z;
|
|
|
|
|
|
|
|
|
|
|
|
R[0][2] = vector_z.x;
|
|
|
|
|
|
R[1][2] = vector_z.y;
|
|
|
|
|
|
R[2][2] = vector_z.z;
|
|
|
|
|
|
SSG_EulerAngles eulerAngle = rotationMatrixToEulerZYX(R);
|
|
|
|
|
|
workpiecePositioning[i].workpiecePose = eulerAngle;
|
2026-01-31 14:36:54 +08:00
|
|
|
|
#endif
|
2026-01-26 15:57:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
2026-03-25 18:24:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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,
|
2026-03-27 00:01:16 +08:00
|
|
|
|
double pntCornenrMax,
|
2026-04-01 22:14:56 +08:00
|
|
|
|
double minZHeight,
|
2026-03-25 18:24:56 +08:00
|
|
|
|
SVzNLRect& roi
|
|
|
|
|
|
)
|
|
|
|
|
|
{
|
|
|
|
|
|
int lineNum = (int)pntDirAngles_v.size();
|
|
|
|
|
|
int ptNum = (int)pntDirAngles_h.size();
|
|
|
|
|
|
|
2026-04-01 22:14:56 +08:00
|
|
|
|
double seed_z = pntDirAngles_v[seedPos.x][seedPos.y].curr_z;
|
2026-03-25 18:24:56 +08:00
|
|
|
|
//向左搜索
|
|
|
|
|
|
int left = -1;
|
|
|
|
|
|
for (int line = seedPos.x; line >= 0; line--)
|
|
|
|
|
|
{
|
2026-04-01 22:14:56 +08:00
|
|
|
|
double z_diff = seed_z - pntDirAngles_v[line][seedPos.y].curr_z;
|
|
|
|
|
|
if ((pntDirAngles_v[line][seedPos.y].type >= 0) && (abs(pntDirAngles_v[line][seedPos.y].corner) < pntCornenrMax)
|
|
|
|
|
|
&& (z_diff > minZHeight))
|
2026-03-25 18:24:56 +08:00
|
|
|
|
{
|
|
|
|
|
|
left = line;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
//向右搜索
|
|
|
|
|
|
int right = -1;
|
|
|
|
|
|
for (int line = seedPos.x; line < lineNum; line++)
|
|
|
|
|
|
{
|
2026-04-01 22:14:56 +08:00
|
|
|
|
double z_diff = seed_z - pntDirAngles_v[line][seedPos.y].curr_z;
|
|
|
|
|
|
if ((pntDirAngles_v[line][seedPos.y].type >= 0) && (abs(pntDirAngles_v[line][seedPos.y].corner) < pntCornenrMax)
|
|
|
|
|
|
&& (z_diff > minZHeight))
|
2026-03-25 18:24:56 +08:00
|
|
|
|
{
|
|
|
|
|
|
right = line;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
//向上搜索
|
|
|
|
|
|
int top = -1;
|
|
|
|
|
|
for (int ptIdx = seedPos.y; ptIdx >= 0; ptIdx--)
|
|
|
|
|
|
{
|
2026-04-01 22:14:56 +08:00
|
|
|
|
double z_diff = seed_z - pntDirAngles_h[ptIdx][seedPos.x].curr_z;
|
|
|
|
|
|
if ((pntDirAngles_h[ptIdx][seedPos.x].type >= 0) && (abs(pntDirAngles_h[ptIdx][seedPos.x].corner) < pntCornenrMax)
|
|
|
|
|
|
&& (z_diff > minZHeight))
|
2026-03-25 18:24:56 +08:00
|
|
|
|
{
|
|
|
|
|
|
top = ptIdx;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
//向下搜索
|
|
|
|
|
|
int bottom = -1;
|
|
|
|
|
|
for (int ptIdx = seedPos.y; ptIdx < ptNum; ptIdx++)
|
|
|
|
|
|
{
|
2026-04-01 22:14:56 +08:00
|
|
|
|
double z_diff = seed_z - pntDirAngles_h[ptIdx][seedPos.x].curr_z;
|
|
|
|
|
|
if ((pntDirAngles_h[ptIdx][seedPos.x].type >= 0) && (abs(pntDirAngles_h[ptIdx][seedPos.x].corner) < pntCornenrMax)
|
|
|
|
|
|
&& (z_diff > minZHeight))
|
2026-03-25 18:24:56 +08:00
|
|
|
|
{
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 00:01:16 +08:00
|
|
|
|
//添加3x3平滑
|
|
|
|
|
|
scanLinesSmooting3x3(scanLinesInput, scanLines);
|
|
|
|
|
|
|
2026-03-25 18:24:56 +08:00
|
|
|
|
//内部参数
|
2026-03-27 00:01:16 +08:00
|
|
|
|
double zPeakScale = 10.0; //计算ZPeak时的尺度
|
|
|
|
|
|
double minZPeakHeight = 2.0; //最小的凹坑深度
|
|
|
|
|
|
double planeInlierDistTh = 1.5; //平面点距离平面的距离。超出此距离被判别为离群点
|
|
|
|
|
|
double flagCornerMax = 3.0; //直线上点的拐角最大值
|
|
|
|
|
|
double maxHoleDiameter = 30.0;//最大的孔直径
|
|
|
|
|
|
double valieCommonNumRatio = 0.125; //1/8
|
2026-03-25 18:24:56 +08:00
|
|
|
|
//计算dirAngle
|
|
|
|
|
|
std::vector<std::vector<SSG_pntDirAngle>> pntDirAngles_v;
|
|
|
|
|
|
std::vector<std::vector<SSG_basicFeature1D>> zMaxPeaks_v;
|
|
|
|
|
|
for (int line = 0; line < lineNum; line++)
|
|
|
|
|
|
{
|
2026-04-01 22:14:56 +08:00
|
|
|
|
if (line == 1062)
|
2026-03-25 18:24:56 +08:00
|
|
|
|
int kkk = 1;
|
|
|
|
|
|
|
|
|
|
|
|
std::vector<SVzNL3DPosition>& lineData = scanLines[line];
|
|
|
|
|
|
//滤波,滤除异常点
|
2026-03-26 07:17:19 +08:00
|
|
|
|
//sg_lineDataRemoveOutlier_changeOriginData(&lineData[0], linePtNum, filterParam);
|
2026-03-25 18:24:56 +08:00
|
|
|
|
|
|
|
|
|
|
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++)
|
|
|
|
|
|
{
|
2026-03-26 07:17:19 +08:00
|
|
|
|
if (line == 234)
|
2026-03-25 18:24:56 +08:00
|
|
|
|
int kkk = 1;
|
|
|
|
|
|
std::vector<SVzNL3DPosition>& lineData = hLines_raw[line];
|
|
|
|
|
|
//滤波,滤除异常点
|
|
|
|
|
|
int ptNum = (int)lineData.size();
|
2026-03-26 07:17:19 +08:00
|
|
|
|
//sg_lineDataRemoveOutlier_changeOriginData(&lineData[0], ptNum, filterParam);
|
2026-03-25 18:24:56 +08:00
|
|
|
|
|
|
|
|
|
|
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++)
|
|
|
|
|
|
{
|
2026-03-26 07:17:19 +08:00
|
|
|
|
if (line == 144)
|
|
|
|
|
|
int kkk = 1;
|
2026-03-25 18:24:56 +08:00
|
|
|
|
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;
|
2026-04-01 22:14:56 +08:00
|
|
|
|
bool validPk = _getZMaxPeakROI(a_lineZMax[j].jumpPos2D, pntDirAngles_v, pntDirAngles_h, flagCornerMax, minZPeakHeight, a_roi);
|
2026-03-25 18:24:56 +08:00
|
|
|
|
if (true == validPk)
|
|
|
|
|
|
{
|
|
|
|
|
|
double meanZ = scanLines[a_roi.left][idx_pt].pt3D.z;
|
2026-04-01 22:14:56 +08:00
|
|
|
|
double minZ = scanLines[a_roi.left][idx_pt].pt3D.z;
|
|
|
|
|
|
double maxZ = scanLines[a_roi.left][idx_pt].pt3D.z;
|
2026-03-25 18:24:56 +08:00
|
|
|
|
meanZ += scanLines[a_roi.right][idx_pt].pt3D.z;
|
2026-04-01 22:14:56 +08:00
|
|
|
|
minZ = minZ < scanLines[a_roi.right][idx_pt].pt3D.z ? minZ : scanLines[a_roi.right][idx_pt].pt3D.z;
|
|
|
|
|
|
maxZ = maxZ > scanLines[a_roi.right][idx_pt].pt3D.z ? maxZ : scanLines[a_roi.right][idx_pt].pt3D.z;
|
2026-03-25 18:24:56 +08:00
|
|
|
|
meanZ += scanLines[idx_line][a_roi.top].pt3D.z;
|
2026-04-01 22:14:56 +08:00
|
|
|
|
minZ = minZ < scanLines[idx_line][a_roi.top].pt3D.z ? minZ : scanLines[idx_line][a_roi.top].pt3D.z;
|
|
|
|
|
|
maxZ = maxZ > scanLines[idx_line][a_roi.top].pt3D.z ? maxZ : scanLines[idx_line][a_roi.top].pt3D.z;
|
2026-03-25 18:24:56 +08:00
|
|
|
|
meanZ += scanLines[idx_line][a_roi.bottom].pt3D.z;
|
2026-04-01 22:14:56 +08:00
|
|
|
|
minZ = minZ < scanLines[idx_line][a_roi.bottom].pt3D.z ? minZ : scanLines[idx_line][a_roi.bottom].pt3D.z;
|
|
|
|
|
|
maxZ = maxZ > scanLines[idx_line][a_roi.bottom].pt3D.z ? maxZ : scanLines[idx_line][a_roi.bottom].pt3D.z;
|
|
|
|
|
|
double zMaxMin = maxZ - minZ;
|
2026-03-25 18:24:56 +08:00
|
|
|
|
meanZ = meanZ / 4.0;
|
|
|
|
|
|
double z_diff = a_lineZMax[j].jumpPos.z - meanZ;
|
2026-03-27 00:01:16 +08:00
|
|
|
|
|
|
|
|
|
|
double xLen = abs(scanLines[a_roi.right][idx_pt].pt3D.x - scanLines[a_roi.left][idx_pt].pt3D.x);
|
|
|
|
|
|
double yLen = abs(scanLines[idx_line][a_roi.bottom].pt3D.y - scanLines[idx_line][a_roi.top].pt3D.y);
|
2026-04-01 22:14:56 +08:00
|
|
|
|
if ( (z_diff > minZPeakHeight) && (xLen < maxHoleDiameter) && (yLen < maxHoleDiameter) && (zMaxMin < planeInlierDistTh*2))
|
2026-03-25 18:24:56 +08:00
|
|
|
|
{
|
|
|
|
|
|
_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;
|
|
|
|
|
|
}
|
2026-03-26 07:17:19 +08:00
|
|
|
|
objPeaks[j].flag = -1;
|
2026-03-25 18:24:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
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;
|
2026-03-27 00:01:16 +08:00
|
|
|
|
#if 0
|
2026-03-25 18:24:56 +08:00
|
|
|
|
vzCaculateLaserPlane(Points3ds, res);
|
2026-03-27 00:01:16 +08:00
|
|
|
|
#else
|
|
|
|
|
|
#if 0
|
|
|
|
|
|
double delta = 1.0;
|
|
|
|
|
|
int maxIter = 20;
|
|
|
|
|
|
Plane robustPlane = robustFitPlane(
|
|
|
|
|
|
Points3ds, TUKEY,
|
|
|
|
|
|
delta , // 阈值:>此值视为离群点(mm)
|
|
|
|
|
|
maxIter // 迭代次数
|
|
|
|
|
|
);
|
|
|
|
|
|
#else
|
|
|
|
|
|
float dist_thresh = 0.5f;
|
|
|
|
|
|
int max_iter = 1000;
|
|
|
|
|
|
int stop_no_improve = 150;
|
|
|
|
|
|
std::vector<cv::Point3f> out_inliers;
|
|
|
|
|
|
Plane robustPlane = ransacFitPlane(
|
|
|
|
|
|
Points3ds,
|
|
|
|
|
|
out_inliers,
|
|
|
|
|
|
dist_thresh, // 内点距离阈值
|
|
|
|
|
|
max_iter, // 最大迭代
|
|
|
|
|
|
stop_no_improve // 连续多少次无提升就提前退出
|
|
|
|
|
|
);
|
|
|
|
|
|
#endif
|
|
|
|
|
|
res.resize(4);
|
|
|
|
|
|
if (robustPlane.C > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
res[0] = -robustPlane.A;
|
|
|
|
|
|
res[1] = -robustPlane.B;
|
|
|
|
|
|
res[2] = -robustPlane.C;
|
|
|
|
|
|
res[3] = -robustPlane.D;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
res[0] = robustPlane.A;
|
|
|
|
|
|
res[1] = robustPlane.B;
|
|
|
|
|
|
res[2] = robustPlane.C;
|
|
|
|
|
|
res[3] = robustPlane.D;
|
|
|
|
|
|
}
|
|
|
|
|
|
#endif
|
2026-03-25 18:24:56 +08:00
|
|
|
|
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);
|
2026-03-27 00:01:16 +08:00
|
|
|
|
|
2026-03-25 18:24:56 +08:00
|
|
|
|
//生成面上点
|
|
|
|
|
|
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;
|
2026-03-27 00:01:16 +08:00
|
|
|
|
wd_getHoleInfo(roi_scanLines, lineSegPara, filterParam, growParam, valieCommonNumRatio, segTrees_v, segTrees_h, validObjects);
|
2026-03-25 18:24:56 +08:00
|
|
|
|
if (validObjects.size() > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
SSG_intPair a_hvPair = validObjects[0];
|
|
|
|
|
|
if (validObjects.size() > 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
for (int j = 1; j < (int)validObjects.size(); j++)
|
|
|
|
|
|
{
|
2026-03-27 00:01:16 +08:00
|
|
|
|
if (a_hvPair.idx < validObjects[j].idx)
|
2026-03-25 18:24:56 +08:00
|
|
|
|
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;
|
2026-01-26 15:57:19 +08:00
|
|
|
|
}
|