SX_lapWeldDetection

version 1.2.0 :焊缝(平缝)识别添加博清模拟数据中的凸起焊缝,通过参数选择识别类型(不支持自动识别)
This commit is contained in:
jerryzeng 2026-08-24 15:50:13 +08:00
parent 8d49089a57
commit 737d741406
5 changed files with 337 additions and 103 deletions

View File

@ -744,6 +744,19 @@ void _removeZeroLines_XYZ(std::vector<std::vector< SVzNL3DPosition>>& scanData)
return;
}
void _outputWeldInfo(char* outFileName, std::vector<SVzNL3DPoint>& weldRawPoints)
{
std::ofstream sw(outFileName);
char dataStr[250];
for (int i = 0; i < (int)weldRawPoints.size(); i++)
{
sprintf_s(dataStr, 250, "id_%d: (%g, %g, %g)", i+1, weldRawPoints[i].x, weldRawPoints[i].y, weldRawPoints[i].z);
sw << dataStr << std::endl;
}
sw.close();
}
#define CONVERT_TO_GRID 0
#define TEST_COMPUTE_CALIB_PARA 0
#define TEST_COMPUTE_POSITION 1
@ -916,15 +929,16 @@ void lapWeldTest()
#define TEST_BQ_CALIB_PARA 0
#define TEST_BQ_WELD_SEAM 1
#define BQ_TEST_GROUP 1
#define BQ_TEST_GROUP 2
void BQ_thinWeldSeamTest()
{
const char* dataPath[BQ_TEST_GROUP] = {
"F:/ShangGu/项目/冠钦_博清科技/焊缝识别/数据/", //0
"F:/ShangGu/项目/冠钦_博清科技/焊缝识别/博清实验室模拟数据/", //0
};
SVzNLRange fileIdx[BQ_TEST_GROUP] = {
{1,4},
{1,4},{1,2}
};
@ -1003,7 +1017,7 @@ void BQ_thinWeldSeamTest()
#if TEST_BQ_WELD_SEAM
//for (int group = 0; group < TEST_GROUPS; group++)
for (int grp = 0; grp < BQ_TEST_GROUP; grp++)
for (int grp = 1; grp < BQ_TEST_GROUP; grp++)
{
SSG_planeCalibPara groundCalibPara;
//初始化成单位阵
@ -1020,8 +1034,11 @@ void BQ_thinWeldSeamTest()
for (int i = 0; i < 9; i++)
groundCalibPara.invRMatrix[i] = groundCalibPara.planeCalib[i];
char calibFile[250];
if (0 == grp)
{
sprintf_s(calibFile, "%sground_calib_para.txt", dataPath[grp]);
groundCalibPara = _readCalibPara(calibFile);
}
double para_chkWidth_wide = 2.5f;
@ -1029,22 +1046,32 @@ void BQ_thinWeldSeamTest()
SVzNLRangeD gapParam;
SWD_valleyParam valleyParam;
valleyParam.chkWin_width = 2.5; //mm, 检测2.5mm宽的V型
valleyParam.chkWin_width = 5.0; // 2.5; //mm, 检测2.5mm宽的V型
valleyParam.maxWidth = 20.0; //最大宽度20mm,用于过滤虚假信号
valleyParam.minHeight = 1.0;
valleyParam.minHeight = 0.5;
SSG_treeGrowParam growParam;
growParam.maxLineSkipNum = 50;
growParam.yDeviation_max = 1.0;
growParam.yDeviation_max = 1.5;
growParam.maxSkipDistance = 30.0;
growParam.zDeviation_max = 5.0;//
growParam.minLTypeTreeLen = 50; //mm,
growParam.minVTypeTreeLen = 50; //mm
EWD_FlawType detectType;
if(0 == grp)
detectType = keWD_FlawType_Depression;
else
detectType = keWD_FlawType_Protrusion;
for (int fidx = fileIdx[grp].nMin; fidx <= fileIdx[grp].nMax; fidx++)
{
//fidx = 2;
char _scan_file[256];
if(0 == grp)
sprintf_s(_scan_file, "%s%d_LaserData_Jl26C077.txt", dataPath[grp], fidx);
else
sprintf_s(_scan_file, "%s%d_LaserData_Jh26B083.txt", dataPath[grp], fidx);
printf("process: %s......\n", _scan_file);
std::vector<std::vector< SVzNL3DPosition>> scanLines;
@ -1059,6 +1086,9 @@ void BQ_thinWeldSeamTest()
std::vector<SSG_gapFeatureTree> trees;
for (int i = 0; i < (int)scanLines.size(); i++)
{
if (i == 117)
int kkk = 1;
std::vector<SVzNL3DPosition>& a_line = scanLines[i];
std::vector< SSG_basicFeatureGap> line_valleys;
sx_lineDataR(scanLines[i], groundCalibPara.planeCalib, -1);//调平,去除地面
@ -1074,11 +1104,12 @@ void BQ_thinWeldSeamTest()
a_line,
i,
valleyParam,
detectType,
line_valleys);
#endif
bool isLastLine = i == ((int)scanLines.size() - 1) ? true : false;
sg_lineGapsGrowing(
sg_lineGapsGrowing_treeFirst(
i,
isLastLine,
line_valleys,
@ -1127,6 +1158,9 @@ void BQ_thinWeldSeamTest()
std::vector< std::vector<SVzNL3DPoint>> multiSeam;
multiSeam.push_back(weldOps);
_outputRGBDScanLapWeld_RGBD(outFileName, scanLines, multiSeam, true);
sprintf_s(outFileName, "%sresult\\LaserLine%d_weldInfo.txt", dataPath[grp], fidx);
_outputWeldInfo(outFileName, weldOps);
}
}
#endif

View File

@ -1133,4 +1133,5 @@ SG_APISHARED_EXPORT void wd_getLineVFlaws(
std::vector<SVzNL3DPosition>& a_line,
const int lineIdx,
const SWD_valleyParam valleyParam,
const EWD_FlawType detectFlawType,
std::vector< SSG_basicFeatureGap>& valleys);

View File

@ -6,6 +6,14 @@
#define PI 3.14159265358979323846
typedef enum
{
keWD_FlawType_Uknown = 0,
keWD_FlawType_Depression, //凹陷
keWD_FlawType_Protrusion, //凸起
keSG_FlawType_Dep_Prot, //凹陷和凸起混合
} EWD_FlawType;
// 定义欧拉角结构体(单位:度)
typedef struct{
double roll; // 绕X轴旋转(横滚)
@ -90,6 +98,12 @@ typedef struct
int end;
}SWD_Interval; //区间
typedef struct SWD_IndexedValue
{
int idx;
double value;
}SWD_IndexedValue;
typedef struct
{
bool validFlag; //指示结果是否有效

View File

@ -9702,7 +9702,7 @@ if (i == 98)
return;
}
enum STATES { null = 0, UP, DOWN };
enum STATES { null = 0, UP, DOWN, USED };
struct posAttri
{
int peakPos;
@ -9797,8 +9797,17 @@ float _computeHeight(SVzNL3DPoint& startPt, SVzNL3DPoint& endPt, SVzNL3DPoint& v
float height = fabs(_a * valleyPt.y + _b * valleyPt.z + _c) / sqrt(pow(_a, 2) + pow(_b, 2));
return height;
}
bool _sortFormZMaxToMin(const SWD_IndexedValue& a, const SWD_IndexedValue& b) {
return a.value > b.value;
}
bool _sortFormZMinToMax(const SWD_IndexedValue& a, const SWD_IndexedValue& b) {
return a.value < b.value;
}
/* startId, endId: 有效点的起止位置。对于栅格数据格式,开始的空点和结束的空点都不是有效点 */
void _valleyDetect(
//凹陷检测(depression)
void _depressionDetect(
std::vector<SVzNL3DPosition>& a_line,
const int lineIdx, const int startId, const int endId,
std::vector<posAttri>& deriv_1_peaks,
@ -9816,19 +9825,32 @@ void _valleyDetect(
double y_start = a_line[startId].pt3D.y;
double y_end = a_line[endId].pt3D.y;
//逐个peak检测:正peak
//按peak(DOWN)值进行排序
std::vector<SWD_IndexedValue> indexedPeaks;
for (int i = 0; i < deriv_2_peaks.size(); i++)
{
if (i == 153)
int kkk = 1;
posAttri a_pos = deriv_2_peaks[i];
if ((a_pos.peakState == DOWN) && (deriv_2[a_pos.peakPos] < 0))
{
SWD_IndexedValue a_indexdPeak;
a_indexdPeak.idx = i;
a_indexdPeak.value = a_line[a_pos.peakPos].pt3D.z;
indexedPeaks.push_back(a_indexdPeak);
}
}
std::sort(indexedPeaks.begin(), indexedPeaks.end(), _sortFormZMaxToMin);
//逐个peak检测:正peak
for (int i = 0; i < (int)indexedPeaks.size(); i++)
{
int idx = indexedPeaks[i].idx;
if (idx == 153)
int kkk = 1;
posAttri a_pos = deriv_2_peaks[idx];
//检查两侧
int preEdge = -1;
for (int j = a_pos.peakPos - 1; j > 0; j--)
{
if (buff[j].peakState == DOWN)
if ( (buff[j].peakState == DOWN)|| (buff[j].peakState == USED))
break;
else if (buff[j].peakState == UP)
{
@ -9840,7 +9862,7 @@ void _valleyDetect(
int postEdge = -1;
for (int j = a_pos.peakPos + 1; j < (int)a_line.size(); j++)
{
if (buff[j].peakState == UP)
if ((buff[j].peakState == UP)|| (buff[j].peakState == USED))
break;
else if (buff[j].peakState == DOWN)
{
@ -9924,6 +9946,9 @@ void _valleyDetect(
a_valley.gap_width = width;
a_valley.gap_depth = height;
valleys.push_back(a_valley);
buff[preEdge].peakState = USED;
buff[postEdge].peakState = USED;
}
}
}
@ -9931,14 +9956,169 @@ void _valleyDetect(
}
}
}
//凸起检测(protrusion)
void _protrusionDetect(
std::vector<SVzNL3DPosition>& a_line,
const int lineIdx, const int startId, const int endId,
std::vector<posAttri>& deriv_1_peaks,
std::vector<posAttri>& deriv_2_peaks,
std::vector<double>& deriv_1,
std::vector<double>& deriv_2,
std::vector<SSG_basicFeatureGap>& valleys,
const SWD_valleyParam valleyParam)
{
std::vector< posAttri> buff;
buff.resize(a_line.size());
for (int i = 0; i < deriv_1_peaks.size(); i++)
buff[deriv_1_peaks[i].peakPos] = deriv_1_peaks[i];
double y_start = a_line[startId].pt3D.y;
double y_end = a_line[endId].pt3D.y;
//按peak(UP)值进行排序
std::vector<SWD_IndexedValue> indexedPeaks;
for (int i = 0; i < deriv_2_peaks.size(); i++)
{
posAttri a_pos = deriv_2_peaks[i];
if ((a_pos.peakState == UP) && (deriv_2[a_pos.peakPos] > 0))
{
SWD_IndexedValue a_indexdPeak;
a_indexdPeak.idx = i;
a_indexdPeak.value = a_line[a_pos.peakPos].pt3D.z;
indexedPeaks.push_back(a_indexdPeak);
}
}
std::sort(indexedPeaks.begin(), indexedPeaks.end(), _sortFormZMinToMax);
//逐个peak检测:正peak
for (int i = 0; i < (int)indexedPeaks.size(); i++)
{
if (i == 153)
int kkk = 1;
int idx = indexedPeaks[i].idx;
posAttri a_pos = deriv_2_peaks[idx];
//检查两侧
int preEdge = -1;
for (int j = a_pos.peakPos - 1; j > 0; j--)
{
if ( (buff[j].peakState == UP)|| (buff[j].peakState == USED))
break;
else if (buff[j].peakState == DOWN)
{
if (deriv_1[j] < 0)
preEdge = j;
break;
}
}
int postEdge = -1;
for (int j = a_pos.peakPos + 1; j < (int)a_line.size(); j++)
{
if ( (buff[j].peakState == DOWN) || (buff[j].peakState == USED))
break;
else if (buff[j].peakState == UP)
{
if (deriv_1[j] > 0)
postEdge = j;
break;
}
}
//
if ((preEdge >= 0) && (postEdge >= 0)) //初步符合凸起特点
{
float edge_value_pre = deriv_1[preEdge];
float edge_value_post = deriv_1[postEdge];
//检测对称性
float min_value, max_value;
if ((-edge_value_pre) < edge_value_post)
{
min_value = -edge_value_pre;
max_value = edge_value_post;
}
else
{
max_value = -edge_value_pre;
min_value = edge_value_post;
}
if (min_value > max_value / 5) //2023.05.02: 放宽。当凹陷在斜面上时,不再是maxValue/2的理想情况
{
//检查凹陷宽度深度,滤除噪声
float th_pre = edge_value_pre * 0.5;
int edge_pos_pre = -1;
for (int j = preEdge; j >= 0; j--)
{
if ((deriv_1[j] >= th_pre) && (a_line[j].nPointIdx >= 0)) //非填充点
{
edge_pos_pre = j;
break;
}
}
float th_post = edge_value_post * 0.5;
int edge_pos_post = -1;
for (int j = postEdge; j < (int)a_line.size(); j++)
{
if ((deriv_1[j] <= th_post) && (a_line[j].nPointIdx >= 0)) //非填充点
{
edge_pos_post = j;
break;
}
}
//检测宽度
if ((edge_pos_pre >= 0) && (edge_pos_post >= 0))
{
//float width = a_line->m_p3DPositiont[edge_pos_post].pt3D.y - a_line->m_p3DPositiont[edge_pos_pre].pt3D.y;
float width = a_line[postEdge].pt3D.y - a_line[preEdge].pt3D.y;
if (width < valleyParam.maxWidth)
{
float height = _computeHeight(a_line[edge_pos_pre].pt3D,
a_line[edge_pos_post].pt3D,
a_line[a_pos.peakPos].pt3D);
if (height > valleyParam.minHeight)
{
//精确确定peak点
int realPeakPos = preEdge;
for (int m = preEdge; m <= postEdge; m++)
{
if ((a_line[realPeakPos].pt3D.z > a_line[m].pt3D.z) && (a_line[m].nPointIdx >= 0))
realPeakPos = m;
}
SSG_basicFeatureGap a_valley;
memset(&a_valley, 0, sizeof(SSG_basicFeatureGap));
a_valley.gapPeak.nPointIdx = realPeakPos;
a_valley.gapPeak.pt3D = a_line[realPeakPos].pt3D;
a_valley.gapPt_0.nPointIdx = edge_pos_pre;
a_valley.gapPt_0.pt3D = a_line[edge_pos_pre].pt3D;
a_valley.gapPt_1.nPointIdx = edge_pos_post;
a_valley.gapPt_1.pt3D = a_line[edge_pos_post].pt3D;
a_valley.lineIdx = lineIdx;
a_valley.gap_start = a_line[preEdge].pt3D.y;
a_valley.gap_width = width;
a_valley.gap_depth = height;
valleys.push_back(a_valley);
buff[preEdge].peakState = USED;
buff[postEdge].peakState = USED;
}
}
}
}
}
}
}
/* 支持栅格数据(要求输入栅格数据) */
//提取扫描线上的V型Valley
void wd_getLineVFlaws(
std::vector<SVzNL3DPosition>& a_line,
const int lineIdx,
const SWD_valleyParam valleyParam,
const EWD_FlawType detectFlawType,
std::vector< SSG_basicFeatureGap>& valleys)
{
if (lineIdx == 286)
@ -10004,7 +10184,11 @@ void wd_getLineVFlaws(
//使用窄窗口
//凹陷:二阶导为极值,在极值的两边有一负一正的一阶导极值。
//float innerPara_max_valley_width = 20.0; //内部参数。控制最大凹陷检测宽度,用于过滤虚假凹陷
_valleyDetect(a_line, lineIdx, startId, endId, deriv_1_peaks, deriv_2_peaks, deriv_1_narrow, deriv_2_narrow, valleys, valleyParam);
if(keWD_FlawType_Depression == detectFlawType)
_depressionDetect(a_line, lineIdx, startId, endId, deriv_1_peaks, deriv_2_peaks, deriv_1_narrow, deriv_2_narrow, valleys, valleyParam);
else if (keWD_FlawType_Protrusion == detectFlawType)
_protrusionDetect(a_line, lineIdx, startId, endId, deriv_1_peaks, deriv_2_peaks, deriv_1_narrow, deriv_2_narrow, valleys, valleyParam);
//else if (keSG_FlawType_Dep_Prot == detectFlawType)
}

View File

@ -7,7 +7,8 @@
//version 1.0.0 :焊缝(平缝)识别检测算法初始发布
//version 1.1.0 :焊缝(平缝)识别修正回归测试中发现的bug
std::string m_strVersion = "weldSeamAlgo_1.1.0";
//version 1.2.0 :焊缝(平缝)识别添加博清模拟数据中的凸起焊缝,通过参数选择识别类型(不支持自动识别)
std::string m_strVersion = "weldSeamAlgo_1.2.0";
const char* wd_weldSeamAlgoVersion(void)
{