SX_lapWeldDetection

version 1.0.0 :焊缝(平缝)识别检测算法初始发布
This commit is contained in:
jerryzeng 2026-08-13 16:22:03 +08:00
parent 8354c4163c
commit f4a25aae11
10 changed files with 873 additions and 32 deletions

View File

@ -164,11 +164,13 @@
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="..\sourceCode\WD_convolveGauss.h" />
<ClInclude Include="..\sourceCode\SG_baseAlgo_Export.h" />
<ClInclude Include="..\sourceCode\SG_baseDataType.h" />
<ClInclude Include="..\sourceCode\SG_errCode.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\sourceCode\WD_convolveGauss.cpp" />
<ClCompile Include="..\sourceCode\dataFitting.cpp">
<AdditionalOptions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">/bigobj %(AdditionalOptions)</AdditionalOptions>
<AdditionalOptions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">/bigobj %(AdditionalOptions)</AdditionalOptions>

View File

@ -7,6 +7,7 @@
#include <VZNL_Types.h>
#include "direct.h"
#include <string>
#include "SG_baseAlgo_Export.h"
#include "SX_lapWeldDetection_Export.h"
#include <opencv2/opencv.hpp>
#include <Windows.h>
@ -206,7 +207,7 @@ void _outputRGBDScanLapWeld_RGBD(
}
else if (LINE_FEATURE_L_JUMP_L2H == featureType_v)
{
rgb = objColor[7];
rgb = { 0, 250, 0 };
size = 5;
}
else if (LINE_FEATURE_L_JUMP_H2L == featureType_h)
@ -271,26 +272,36 @@ void _outputRGBDScanLapWeld_RGBD(
double exLen = 5.0;
rgb = objColor[0];
size = 3;
int polyIndx = 0;
for (int i = 0; i < weldNum; i++)
{
SVzNL3DPoint pt0 = weldOPs[i][0];
SVzNL3DPoint pt1 = weldOPs[i].back();
double len = sqrt(pow(pt0.x - pt1.x, 2) + pow(pt0.y - pt1.y, 2));
double k = exLen / len;
sw << "Poly_" << i << "_2" << std::endl;
double x = -k * (pt1.x - pt0.x) + pt0.x;
double y = -k * (pt1.y - pt0.y) + pt0.y;
double z = -k * (pt1.z - pt0.z) + pt0.z;
sw << "{" << (float)x << "," << (float)y << "," << (float)z << "}-";
sw << "{0,0}-{0,0}-";
sw << "{" << (int)rgb.r << "," << (int)rgb.g << "," << (int)rgb.b << "," << size << "}" << std::endl;
for (int j = 0; j < (int)weldOPs[i].size()-1; j++)
{
SVzNL3DPoint pt0 = weldOPs[i][j];
SVzNL3DPoint pt1 = weldOPs[i][j +1];
double len = sqrt(pow(pt0.x - pt1.x, 2) + pow(pt0.y - pt1.y, 2));
double k1 = exLen / len;
if (j > 0)
k1 = 0;
sw << "Poly_" << polyIndx << "_2" << std::endl;
polyIndx++;
double x = -k1 * (pt1.x - pt0.x) + pt0.x;
double y = -k1 * (pt1.y - pt0.y) + pt0.y;
double z = -k1 * (pt1.z - pt0.z) + pt0.z;
sw << "{" << (float)x << "," << (float)y << "," << (float)z << "}-";
sw << "{0,0}-{0,0}-";
sw << "{" << (int)rgb.r << "," << (int)rgb.g << "," << (int)rgb.b << "," << size << "}" << std::endl;
x = -k * (pt0.x - pt1.x) + pt1.x;
y = -k * (pt0.y - pt1.y) + pt1.y;
z = -k * (pt0.z - pt1.z) + pt1.z;
sw << "{" << x << "," << y << "," << z << "}-";
sw << "{0,0}-{0,0}-";
sw << "{" << (int)rgb.r << "," << (int)rgb.g << "," << (int)rgb.b << "," << size << "}" << std::endl;
double k2 = exLen / len;
if (j < (int)weldOPs[i].size() - 2)
k2 = 0;
x = -k2 * (pt0.x - pt1.x) + pt1.x;
y = -k2 * (pt0.y - pt1.y) + pt1.y;
z = -k2 * (pt0.z - pt1.z) + pt1.z;
sw << "{" << x << "," << y << "," << z << "}-";
sw << "{0,0}-{0,0}-";
sw << "{" << (int)rgb.r << "," << (int)rgb.g << "," << (int)rgb.b << "," << size << "}" << std::endl;
}
}
}
sw.close();
@ -696,11 +707,48 @@ void _convertToGridData(std::vector< std::vector<SVzNL3DPosition>>& scanLines,
return;
}
int counterLinePtNum_XYZ(std::vector< SVzNL3DPosition>& lineData)
{
int num = 0;
for (int i = 0; i < (int)lineData.size(); i++)
{
if (lineData[i].pt3D.z > 1e-4)
num++;
}
return num;
}
void _removeZeroLines_XYZ(std::vector<std::vector< SVzNL3DPosition>>& scanData)
{
int lineNum = (int)scanData.size();
int firstLine = -1;
int lastLine = 0;
for (int line = 0; line < lineNum; line++)
{
int num = counterLinePtNum_XYZ(scanData[line]);
if ((num > 0) && (firstLine < 0))
firstLine = line;
if (num > 0)
lastLine = line;
}
if (firstLine < 0)
{
scanData.clear();
return;
}
if ((lastLine < (lineNum - 1)) && (lastLine > 0))
scanData.erase(scanData.begin() + lastLine + 1, scanData.end());
scanData.erase(scanData.begin(), scanData.begin() + firstLine);
return;
}
#define CONVERT_TO_GRID 0
#define TEST_COMPUTE_CALIB_PARA 0
#define TEST_COMPUTE_POSITION 1
#define TEST_GROUP 2
int main()
void lapWeldTest()
{
const char* dataPath[TEST_GROUP] = {
"F:\\ShangGu\\项目\\钢板搭接焊缝检测\\数据\\", //0
@ -866,13 +914,239 @@ int main()
#endif
}
// 运行程序: Ctrl + F5 或调试 >“开始执行(不调试)”菜单
// 调试程序: F5 或调试 >“开始调试”菜单
#define TEST_BQ_CALIB_PARA 0
#define TEST_BQ_WELD_SEAM 1
#define BQ_TEST_GROUP 1
void BQ_thinWeldSeamTest()
{
const char* dataPath[BQ_TEST_GROUP] = {
"F:/ShangGu/项目/冠钦_博清科技/焊缝识别/数据/", //0
};
// 入门使用技巧:
// 1. 使用解决方案资源管理器窗口添加/管理文件
// 2. 使用团队资源管理器窗口连接到源代码管理
// 3. 使用输出窗口查看生成输出和其他消息
// 4. 使用错误列表窗口查看错误
// 5. 转到“项目”>“添加新项”以创建新的代码文件,或转到“项目”>“添加现有项”以将现有代码文件添加到项目
// 6. 将来,若要再次打开此项目,请转到“文件”>“打开”>“项目”并选择 .sln 文件
SVzNLRange fileIdx[BQ_TEST_GROUP] = {
{1,4},
};
const char* ver = wd_weldSeamAlgoVersion();
printf("ver :%s\n", ver);
#if TEST_BQ_CALIB_PARA
char _calib_datafile[256];
sprintf_s(_calib_datafile, "F:/ShangGu/项目/冠钦_博清科技/焊缝识别/数据/1_LaserData_Jl26C077.txt");
int lineNum = 0;
float lineV = 0.0f;
int dataCalib = 0;
int maxTimeStamp = 0;
int clockPerSecond = 0;
std::vector<std::vector< SVzNL3DPosition>> scanData;
vzReadLaserScanPointFromFile_XYZ_vector(_calib_datafile, scanData);
_removeZeroLines_XYZ(scanData);
//高度过滤,保留顶板
double hTh = 490.0;
for (int line = 0; line < (int)scanData.size(); line++)
{
for (int j = 0; j < (int)scanData[line].size(); j++)
{
if (scanData[line][j].pt3D.z > hTh)
scanData[line][j].pt3D = { 0.0, 0.0, 0.0 };
}
}
lineNum = (int)scanData.size();
if (scanData.size() > 0)
{
SSG_planeCalibPara calibPara = sx_getBaseCalibPara(scanData);
//结果进行验证
for (int i = 0; i < lineNum; i++)
{
if (i == 14)
int kkk = 1;
//行处理
//调平,去除地面
sx_lineDataR(scanData[i], calibPara.planeCalib, -1); // calibPara.planeHeight);
}
char calibFile[250];
sprintf_s(calibFile, "F:/ShangGu/项目/冠钦_博清科技/焊缝识别/数据/ground_calib_para.txt");
_outputCalibPara(calibFile, calibPara);
char _out_file[256];
sprintf_s(_out_file, "F:/ShangGu/项目/冠钦_博清科技/焊缝识别/数据/scanData_ground_1_calib.txt");
int headNullLines = 0;
_outputScanDataFile_vector(_out_file, scanData, false, &headNullLines);
printf("%s: calib done!\n", _calib_datafile);
#if 1
for (int grp = 0; grp < 1; grp++)
{
for (int fidx = fileIdx[grp].nMin; fidx <= fileIdx[grp].nMax; fidx++)
{
char _scan_file[256];
sprintf_s(_scan_file, "%s%d_LaserData_Jl26C077.txt", dataPath[grp], fidx);
std::vector<std::vector< SVzNL3DPosition>> scanLines;
vzReadLaserScanPointFromFile_XYZ_vector(_scan_file, scanLines);
_removeZeroLines_XYZ(scanLines);
if (scanLines.size() == 0)
continue;
lineNum = (int)scanLines.size();
for (int i = 0; i < lineNum; i++)
sx_lineDataR(scanLines[i], calibPara.planeCalib, -1);//调平,去除地面
sprintf_s(_scan_file, "%s%d_ground_calibrated.txt", dataPath[grp], fidx);
int headNullLines = 0;
_outputScanDataFile_vector(_scan_file, scanLines, false, &headNullLines);
printf("%s: calib done!\n", _scan_file);
}
}
#endif
}
#endif
#if TEST_BQ_WELD_SEAM
//for (int group = 0; group < TEST_GROUPS; group++)
for (int grp = 0; grp < BQ_TEST_GROUP; grp++)
{
SSG_planeCalibPara groundCalibPara;
//初始化成单位阵
groundCalibPara.planeCalib[0] = 1.0;
groundCalibPara.planeCalib[1] = 0.0;
groundCalibPara.planeCalib[2] = 0.0;
groundCalibPara.planeCalib[3] = 0.0;
groundCalibPara.planeCalib[4] = 1.0;
groundCalibPara.planeCalib[5] = 0.0;
groundCalibPara.planeCalib[6] = 0.0;
groundCalibPara.planeCalib[7] = 0.0;
groundCalibPara.planeCalib[8] = 1.0;
groundCalibPara.planeHeight = -1.0;
for (int i = 0; i < 9; i++)
groundCalibPara.invRMatrix[i] = groundCalibPara.planeCalib[i];
char calibFile[250];
sprintf_s(calibFile, "%sground_calib_para.txt", dataPath[grp]);
groundCalibPara = _readCalibPara(calibFile);
double para_chkWidth_wide = 2.5f;
SSG_cornerParam cornerPara;
SVzNLRangeD gapParam;
SWD_valleyParam valleyParam;
valleyParam.chkWin_width = 2.5; //mm, 检测2.5mm宽的V型
valleyParam.maxWidth = 20.0; //最大宽度20mm用于过滤虚假信号
valleyParam.minHeight = 1.5;
SSG_treeGrowParam growParam;
growParam.maxLineSkipNum = 50;
growParam.yDeviation_max = 1.0;
growParam.maxSkipDistance = 30.0;
growParam.zDeviation_max = 5.0;//
growParam.minLTypeTreeLen = 50; //mm,
growParam.minVTypeTreeLen = 50; //mm
for (int fidx = fileIdx[grp].nMin; fidx <= fileIdx[grp].nMax; fidx++)
{
char _scan_file[256];
sprintf_s(_scan_file, "%s%d_LaserData_Jl26C077.txt", dataPath[grp], fidx);
printf("process: %s......\n", _scan_file);
std::vector<std::vector< SVzNL3DPosition>> scanLines;
vzReadLaserScanPointFromFile_XYZ_vector(_scan_file, scanLines);
_removeZeroLines_XYZ(scanLines);
if (scanLines.size() == 0)
continue;
//统计时间
long t1 = (long)GetTickCount64();//统计时间
std::vector<SSG_gapFeatureTree> trees;
for (int i = 0; i < (int)scanLines.size(); i++)
{
std::vector<SVzNL3DPosition>& a_line = scanLines[i];
std::vector< SSG_basicFeatureGap> line_valleys;
sx_lineDataR(scanLines[i], groundCalibPara.planeCalib, -1);//调平,去除地面
#if 0
sg_getLineGapFeature(
a_line, //扫描线
i, //当前扫描线序号
cornerPara,
gapParam,
line_valleys);
#else
wd_getLineVFlaws(
a_line,
i,
valleyParam,
line_valleys);
#endif
bool isLastLine = i == ((int)scanLines.size() - 1) ? true : false;
sg_lineGapsGrowing(
i,
isLastLine,
line_valleys,
trees,
growParam);
}
int weldRefPoints = 3;
int errCode = 0;
std::vector<SVzNL3DPoint> weldOps;
std::vector<SVzNL3DPosition> weldRawPoints;
BQ_getWeldSeamPose(
trees,
weldRefPoints, //焊缝中间参考点位置
weldOps, //焊缝位置
weldRawPoints,
&errCode);
long t2 = (long)GetTickCount64();
printf("%s: %d(ms)!\n", _scan_file, (int)(t2 - t1));
//生成debug信息
for (int line = 0; line < (int)scanLines.size(); line++)
{
for (int j = 0; j < (int)scanLines[line].size(); j++)
scanLines[line][j].nPointIdx = 0;
}
for (int i = 0; i < (int)trees.size(); i++)
{
for (int j = 0; j < (int)trees[i].treeNodes.size(); j++)
{
SSG_basicFeatureGap& a_node = trees[i].treeNodes[j];
scanLines[a_node.lineIdx][a_node.gapPeak.nPointIdx].nPointIdx = 2;
}
}
for (int i = 0; i < (int)weldRawPoints.size(); i++)
{
int lineIdx = weldRawPoints[i].nPointIdx >> 16;
int ptIdx = weldRawPoints[i].nPointIdx & 0xffff;
scanLines[lineIdx][ptIdx].nPointIdx = 1;
}
char outFileName[256];
//输出测试结果
sprintf_s(outFileName, "%sresult\\LaserLine%d_result.txt", dataPath[grp], fidx);
std::vector< std::vector<SVzNL3DPoint>> multiSeam;
multiSeam.push_back(weldOps);
_outputRGBDScanLapWeld_RGBD(outFileName, scanLines, multiSeam, true);
}
}
#endif
}
typedef enum
{
keSG_焊缝定位_搭接焊缝 = 0,
keSG_焊缝定位_长直细焊缝,
}ESG_testMode;
int main()
{
//ESG_testMode testMode = keSG_焊缝定位_搭接焊缝;
ESG_testMode testMode = keSG_焊缝定位_长直细焊缝;
if (keSG_焊缝定位_搭接焊缝 == testMode)
lapWeldTest();
else if (keSG_焊缝定位_长直细焊缝 == testMode)
BQ_thinWeldSeamTest();
return 0;
}

View File

@ -126,7 +126,7 @@
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>opencv_world320d.lib;lapWeldDetection.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>opencv_world320d.lib;baseAlgorithm.lib;lapWeldDetection.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\..\thirdParty\opencv320\build\x64\vc14\lib;..\build\x64\Debug;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
@ -146,7 +146,7 @@
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>opencv_world320.lib;lapWeldDetection.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>opencv_world320.lib;baseAlgorithm.lib;lapWeldDetection.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\..\thirdParty\opencv320\build\x64\vc14\lib;..\build\x64\Release;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>

View File

@ -1117,3 +1117,11 @@ SG_APISHARED_EXPORT void WD_getHoleInfo(
std::vector<SWD_segFeatureTree>& segTrees_h,
std::vector<SSG_intPair>& validObjects
);
/* 支持栅格数据(要求输入栅格数据) */
//提取扫描线上的V型Valley
SG_APISHARED_EXPORT void wd_getLineVFlaws(
std::vector<SVzNL3DPosition>& a_line,
const int lineIdx,
const SWD_valleyParam valleyParam,
std::vector< SSG_basicFeatureGap>& valleys);

View File

@ -214,6 +214,7 @@ typedef struct
double gap_depth;
SVzNL3DPosition gapPt_0; //gap的端点
SVzNL3DPosition gapPt_1; //gap的端点
SVzNL3DPosition gapPeak;
SSG_ROIRectD roi;
}SSG_basicFeatureGap;
@ -250,6 +251,13 @@ typedef struct
double outlierTh;
}SSG_outlierFilterParam;
typedef struct
{
double chkWin_width;
double maxWidth;
double minHeight;
}SWD_valleyParam;
typedef struct
{
double LSlopeZWin; //对L型Slope进行坡度计算的窗口长度

View File

@ -13,6 +13,7 @@
#define SX_ERR_ZERO_OBJECTS -1010
#define SX_ERR_NULL_GROUND_PLANE -1011
#define SX_ERR_ZERO_2D_OBJECTS -1012
#define SX_ERR_FITTING_POINTS_LESS -1013
//BQ_workpiece
#define SX_ERR_INVLD_VTREE_NUM -2001
@ -30,6 +31,7 @@
#define SX_ERR_ZERO_OBJ_BTMLAYER -2103
#define SX_ERR_GET_INVALID_PALTE -2104 //몰겼瓊혤댄轎
#define SX_ERR_BIN_EDGE_ERROR -2105
#define SX_ERR_BIN_SLICE_ZEROPOINTS -2106
//뀔관
#define SX_BAG_TRAY_EMPTY -2201

View File

@ -1,5 +1,7 @@
#include "SG_baseDataType.h"
#include "SG_baseAlgo_Export.h"
#include "WD_convolveGauss.h"
#include <vector>
const double EPS = 1e-8;
@ -9526,3 +9528,307 @@ if (i == 98)
}
return;
}
enum STATES { null = 0, UP, DOWN };
struct posAttri
{
int peakPos;
STATES peakState;
};
//peak检测
void _peakDetect(std::vector<double>& data, int dataSize, std::vector<posAttri>& peakPosition)
{
if (dataSize == 0)
return;
static float algoPara_threshold_unchange = 0.001;
//检测Peak
float preData = data[0];
STATES a_state = null;
int peakPos = 0;
for (int i = 1; i < dataSize; i++)
{
if (i == 115)
int kkk = 1;
float currData = data[i];
float delta = fabsf(currData - preData);
switch (a_state)
{
case null:
if (delta > algoPara_threshold_unchange)
{
if (currData > preData)
a_state = UP;
else
a_state = DOWN;
}
peakPos = i;
preData = currData;
break;
case UP:
if (currData > preData) //记录最大值
{
peakPos = i;
preData = currData;
}
if ((delta > algoPara_threshold_unchange) && (currData < preData))//检查拐点
{
//保存极值位置
posAttri a_pos;
a_pos.peakPos = peakPos;
a_pos.peakState = a_state;
peakPosition.push_back(a_pos);
a_state = DOWN;
//新的初值
peakPos = i;
preData = currData;
}
break;
case DOWN:
if (currData < preData) //记录最小值
{
peakPos = i;
preData = currData;
}
if ((delta > algoPara_threshold_unchange) && (currData > preData)) //检查拐点
{
//保存极值位置
posAttri a_pos;
a_pos.peakPos = peakPos;
a_pos.peakState = a_state;
peakPosition.push_back(a_pos);
a_state = UP;
//新的初值
peakPos = i;
preData = currData;
}
break;
}
}
}
//检查凹陷
//使用窄窗口
//凹陷:二阶导为负极值,在极值的两边有一正一负的一阶导极值。
float _computeHeight(SVzNL3DPoint& startPt, SVzNL3DPoint& endPt, SVzNL3DPoint& valleyPt)
{
float _a = endPt.z - startPt.z;
float _b = -(endPt.y - startPt.y);
float _c = -_a * startPt.y - _b * startPt.z;
float height = fabs(_a * valleyPt.y + _b * valleyPt.z + _c) / sqrt(pow(_a, 2) + pow(_b, 2));
return height;
}
/* startId, endId: 有效点的起止位置。对于栅格数据格式,开始的空点和结束的空点都不是有效点 */
void _valleyDetect(
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检测正peak
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))
{
//检查两侧
int preEdge = -1;
for (int j = a_pos.peakPos - 1; j > 0; j--)
{
if (buff[j].peakState == DOWN)
break;
else if (buff[j].peakState == UP)
{
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 == UP)
break;
else if (buff[j].peakState == DOWN)
{
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 / 10) //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);
}
}
}
}
}
}
}
}
/* 支持栅格数据(要求输入栅格数据) */
//提取扫描线上的V型Valley
void wd_getLineVFlaws(
std::vector<SVzNL3DPosition>& a_line,
const int lineIdx,
const SWD_valleyParam valleyParam,
std::vector< SSG_basicFeatureGap>& valleys)
{
//处理栅格数据(填补空洞,统计有效点
int startId = -1, endId = 0;
for (int i = 0; i < (int)a_line.size(); i++)
{
a_line[i].nPointIdx = 0;
if (a_line[i].pt3D.z > 1e-4)
{
if (startId < 0)
startId = i;
endId = i;
}
else
a_line[i].nPointIdx = -1; //空点标记
}
int validLinePtNum = endId - startId + 1;
if ((startId < 0) || (validLinePtNum < 10)) //空行
return;
//填充孔洞
double preZ = a_line[startId].pt3D.z;
for (int i = startId + 1; i <= endId; i++)
{
if (a_line[i].pt3D.z < 1e-4)
a_line[i].pt3D.z = preZ;
preZ = a_line[i].pt3D.z;
}
//单窗口
float total_deltaY = a_line[endId].pt3D.y - a_line[startId].pt3D.y;
float scale = total_deltaY / validLinePtNum;
#if 0
//scale = scale / 1.5;
float* deriv_1_wide = (float*)malloc(sizeof(float) * a_line->m_3DPointCount);
memset(deriv_1_wide, 0, sizeof(float) * a_line->m_3DPointCount);
float* deriv_2_wide = (float*)malloc(sizeof(float) * a_line->m_3DPointCount);
memset(deriv_2_wide, 0, sizeof(float) * a_line->m_3DPointCount);
//计算gauss一阶和二阶导
_convolveGauss_laserLine(a_line, startId, endId, deriv_1_wide, deriv_2_wide, chkWidth_wide, scale);
#endif
//单窗口:窄窗口
double chkWidth_narrow = valleyParam.chkWin_width / 2;
float narrow_scale = scale;
std::vector<double> deriv_1_narrow;
std::vector<double> deriv_2_narrow;
//计算gauss一阶和二阶导
wd_convolveGauss_laserLine(a_line, startId, endId, deriv_1_narrow, deriv_2_narrow, chkWidth_narrow, narrow_scale);
//
//peak检测: 边沿检测和凹陷检测同时需要使用
std::vector<posAttri> deriv_1_peaks;
_peakDetect(deriv_1_narrow, (int)a_line.size(), deriv_1_peaks);
std::vector<posAttri> deriv_2_peaks;
_peakDetect(deriv_2_narrow, (int)a_line.size(), deriv_2_peaks);
//检查凹陷
//使用窄窗口
//凹陷:二阶导为极值,在极值的两边有一负一正的一阶导极值。
//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);
}

View File

@ -5,6 +5,12 @@
#include <opencv2/opencv.hpp>
#include <limits>
//version 1.0.0 :焊缝(平缝)识别检测算法初始发布
std::string m_strVersion = "weldSeamAlgo_1.0.0";
const char* wd_weldSeamAlgoVersion(void)
{
return m_strVersion.c_str();
}
//计算一个平面调平参数。
//数据输入中可以有一个地平面和参考调平平面,以最高的平面进行调平
//旋转矩阵为调平参数,即将平面法向调整为垂直向量的参数
@ -88,6 +94,64 @@ void getWeldPoint(std::vector<SVzNL3DPoint>& a_weld_contour, int midPtNum, std::
a_weld.push_back(pt_1);
}
SVzNLRange _getTreesLineRange(std::vector<SSG_gapFeatureTree>& trees)
{
SVzNLRange a_rng = { 0, 0 };
if (trees.size() == 0)
return a_rng;
if (trees[0].sLineIdx < trees[0].eLineIdx)
{
a_rng.nMin = trees[0].sLineIdx;
a_rng.nMax = trees[0].eLineIdx;
}
else
{
a_rng.nMax = trees[0].sLineIdx;
a_rng.nMin = trees[0].eLineIdx;
}
for (int i = 1; i < (int)trees.size(); i++)
{
a_rng.nMin = a_rng.nMin > trees[i].sLineIdx ? trees[i].sLineIdx : a_rng.nMin;
a_rng.nMin = a_rng.nMin > trees[i].eLineIdx ? trees[i].eLineIdx : a_rng.nMin;
a_rng.nMax = a_rng.nMax < trees[i].sLineIdx ? trees[i].sLineIdx : a_rng.nMax;
a_rng.nMax = a_rng.nMax < trees[i].eLineIdx ? trees[i].eLineIdx : a_rng.nMax;
}
return a_rng;
}
SVzNLRangeD _getTreesXRange(std::vector<SSG_gapFeatureTree>& trees)
{
SVzNLRangeD a_rng = { 0.0, 0.0 };
if (trees.size() == 0)
return a_rng;
SSG_basicFeatureGap nodeFirst = trees[0].treeNodes[0];
SSG_basicFeatureGap nodeLast = trees[0].treeNodes.back();
if (nodeFirst.gapPeak.pt3D.x < nodeLast.gapPeak.pt3D.x)
{
a_rng.min = nodeFirst.gapPeak.pt3D.x;
a_rng.max = nodeLast.gapPeak.pt3D.x;
}
else
{
a_rng.max = nodeFirst.gapPeak.pt3D.x;
a_rng.min = nodeLast.gapPeak.pt3D.x;
}
for (int i = 1; i < (int)trees.size(); i++)
{
nodeFirst = trees[i].treeNodes[0];
nodeLast = trees[i].treeNodes.back();
a_rng.min = a_rng.min > nodeFirst.gapPeak.pt3D.x ? nodeFirst.gapPeak.pt3D.x : a_rng.min;
a_rng.min = a_rng.min > nodeLast.gapPeak.pt3D.x ? nodeLast.gapPeak.pt3D.x : a_rng.min;
a_rng.max = a_rng.max < nodeFirst.gapPeak.pt3D.x ? nodeFirst.gapPeak.pt3D.x : a_rng.max;
a_rng.max = a_rng.max < nodeLast.gapPeak.pt3D.x ? nodeLast.gapPeak.pt3D.x : a_rng.max;
}
return a_rng;
}
//提取搭接焊缝
void sx_getLapWeldPostion(
std::vector< std::vector<SVzNL3DPosition>>& scanLines,
@ -355,4 +419,168 @@ void sx_getLapWeldPostion(
}
//输出结果
}
double _getMeanZ(std::vector<double>& ptZ)
{
double meanZ = 0;
int num = (int)ptZ.size();
for (int i = 0; i < num; i++)
meanZ += ptZ[i];
meanZ = meanZ / num;
return meanZ;
}
bool _compareByXValue(const SVzNL3DPoint& a, const SVzNL3DPoint& b) {
return a.x < b.x;
}
//计算焊缝位置
void BQ_getWeldSeamPose(
std::vector<SSG_gapFeatureTree>& trees,
int weldRefPoints, //焊缝中间参考点位置
std::vector<SVzNL3DPoint>& weldOps, //焊缝位置
std::vector<SVzNL3DPosition>& weldRawPoints, //用于显示和debug
int* errCode)
{
*errCode = 0;
//保留最长的tree
int bestId = 0;
trees[0].treeState = 0;
for (int i = 1; i < (int)trees.size(); i++)
{
trees[i].treeState = 0;
if (trees[bestId].treeNodes.size() < trees[i].treeNodes.size())
bestId = i;
}
trees[bestId].treeState = 1; //已经被处理标志
//以最长的tree为基础进行合并
std::vector<SSG_gapFeatureTree> weldSeam;
weldSeam.push_back(trees[bestId]);
while (1)
{
int oldSize = (int)weldSeam.size();
SVzNLRange lineRange = _getTreesLineRange(weldSeam);
//拟合直线
for (int i = 0; i < (int)trees.size(); i++)
{
if (trees[i].treeState > 0)
continue;
//检查能否被合并
}
if (oldSize == (int)weldSeam.size())
break;
}
//生成显示数据
for (int i = 0; i < (int)weldSeam.size(); i++)
{
for (int j = 0; j < (int)weldSeam[i].treeNodes.size(); j++)
{
SSG_basicFeatureGap& a_node = weldSeam[i].treeNodes[j];
SVzNL3DPosition a_debugPt;
a_debugPt.nPointIdx = (a_node.lineIdx << 16) | a_node.gapPeak.nPointIdx;
a_debugPt.pt3D = a_node.gapPeak.pt3D;
weldRawPoints.push_back(a_debugPt);
}
}
int nodeSize = 0;
for(int i = 0; i < (int)weldSeam.size(); i ++)
nodeSize += weldSeam[i].treeNodes.size();
if (nodeSize < 10)
{
*errCode = SX_ERR_FITTING_POINTS_LESS;
return;
}
//生成结果信息
//中点参考点的位置计算方法以参考点的X为基准左右各取段长的一半进行直线拟合根据X计算Y坐标
int refSegNum = weldRefPoints + 1;
refSegNum = refSegNum * 2; //以段长一半进行区分
std::vector<std::vector<SVzNL3DPoint>> refSegments;
std::vector<std::vector<double>> refSegZ;
refSegments.resize(refSegNum);
refSegZ.resize(refSegNum);
SVzNLRangeD xRange = _getTreesXRange(weldSeam);
double segLen = (xRange.max - xRange.min) / refSegNum;
for (int i = 0; i < (int)weldSeam.size(); i++)
{
for (int j = 0; j < (int)weldSeam[i].treeNodes.size(); j++)
{
double x = weldSeam[i].treeNodes[j].gapPeak.pt3D.x;
double xoffset = x - xRange.min;
if (xoffset < 0)
xoffset = 0;
xoffset = xoffset / segLen;
int segIdx = (int)xoffset;
if (segIdx >= refSegNum)
segIdx = refSegNum - 1;
refSegments[segIdx].push_back(weldSeam[i].treeNodes[j].gapPeak.pt3D);
double valleyZ = (weldSeam[i].treeNodes[j].gapPt_0.pt3D.z + weldSeam[i].treeNodes[j].gapPt_1.pt3D.z) / 2;
refSegZ[segIdx].push_back(valleyZ);
}
}
//分段按X大小排序
for (int i = 0; i < refSegNum; i++)
{
if (refSegments[i].size() > 0)
std::sort(refSegments[i].begin(), refSegments[i].end(), _compareByXValue);
}
//分段拟合直线
for (int i = 0; i <= weldRefPoints+1; i++)
{
int segIdx_0 = i * 2 - 1;
int segIdx_1 = i * 2;
std::vector<SVzNL3DPoint> fittingPoints;
std::vector<double> fittingPtZ;
if (segIdx_0 >= 0)
{
fittingPoints.insert(fittingPoints.end(), refSegments[segIdx_0].begin(), refSegments[segIdx_0].end()); //左半段
fittingPtZ.insert(fittingPtZ.end(), refSegZ[segIdx_0].begin(), refSegZ[segIdx_0].end());
}
if (segIdx_1 < refSegNum)
{
fittingPoints.insert(fittingPoints.end(), refSegments[segIdx_1].begin(), refSegments[segIdx_1].end()); //右半段
fittingPtZ.insert(fittingPtZ.end(), refSegZ[segIdx_1].begin(), refSegZ[segIdx_1].end());
}
while (fittingPoints.size() < 10) //保证足够的拟合点
{
//向两边扩展
int oldSize = (int)fittingPoints.size();
segIdx_0--;
if (segIdx_0 >= 0)
{
fittingPoints.insert(fittingPoints.end(), refSegments[segIdx_0].begin(), refSegments[segIdx_0].end());
fittingPtZ.insert(fittingPtZ.end(), refSegZ[segIdx_0].begin(), refSegZ[segIdx_0].end());
}
segIdx_1++;
if (segIdx_1 < refSegNum)
{
fittingPoints.insert(fittingPoints.end(), refSegments[segIdx_1].begin(), refSegments[segIdx_1].end());
fittingPtZ.insert(fittingPtZ.end(), refSegZ[segIdx_1].begin(), refSegZ[segIdx_1].end());
}
if (oldSize == (int)fittingPoints.size())
break;
}
//直线拟合
double _a, _b, _c;
lineFitting_abc(fittingPoints, &_a, &_b, &_c);
double meanZ = _getMeanZ(fittingPtZ);
double x = segLen * i * 2.0 + xRange.min;
double y = -(_a * x + _c) / _b;
SVzNL3DPoint a_pt;
a_pt.x = x;
a_pt.y = y;
a_pt.z = meanZ;
weldOps.push_back(a_pt);
}
return;
}

View File

@ -17,6 +17,8 @@ typedef struct
ESX_WeldScanMode scanMode;
}SSX_lapWeldParam;
SG_APISHARED_EXPORT const char* wd_weldSeamAlgoVersion(void);
//计算一个平面调平参数。
//数据输入中可以有一个地平面和参考调平平面,以最高的平面进行调平
//旋转矩阵为调平参数,即将平面法向调整为垂直向量的参数
@ -39,3 +41,10 @@ SG_APISHARED_EXPORT void sx_getLapWeldPostion(
std::vector<std::vector<SVzNL3DPoint>>& objOps,
int* errCode);
//计算焊缝位置
SG_APISHARED_EXPORT void BQ_getWeldSeamPose(
std::vector<SSG_gapFeatureTree>& trees,
int weldRefPoints, //焊缝中间参考点位置
std::vector<SVzNL3DPoint>& weldOps, //焊缝位置
std::vector<SVzNL3DPosition>& weldRawPoints, //用于显示和debug
int* errCode);

View File

@ -179,11 +179,15 @@ double fitCircleByLeastSquare(
double e;
double r2 = radius * radius;
for (int pId = 0; pId < N; ++pId) {
e = pow(pointArray[pId].x - center.x, 2) + pow(pointArray[pId].y - center.y, 2) - r2;
if (e > err) {
e = abs(pow(pointArray[pId].x - center.x, 2) + pow(pointArray[pId].y - center.y, 2) - r2);
#if 0 if (e > err) {
err = e;
}
#else
err += e;
#endif
}
err = sqrt(err / N);
return err;
}