GrabBag/App/Workpiece/Doc/工件角点检测功能实现说明.md

475 lines
14 KiB
Markdown
Raw Normal View History

# 工件角点检测功能实现说明
## 功能概述
本文档说明如何在 WorkpieceApp 中集成工件角点提取功能,使用 SDK/workpieceCornerExtraction 算法库进行检测,并通过 JSON 格式将检测结果发送给客户端。
## 实现步骤
### 1. 配置结构更新 ✅
#### 1.1 更新 IVrConfig.h
`VrWorkpieceParam` 结构中添加了 `lineLen` 参数:
```cpp
struct VrWorkpieceParam
{
double lapHeight = 2.0; // 搭接厚度
double weldMinLen = 2.0; // 最小焊缝长度
int weldRefPoints = 2; // 输出参考点数量
WeldScanMode scanMode = WeldScanMode::ScanMode_V;
double lineLen = 100.0; // 工件角点提取:直线段长度阈值
};
```
#### 1.2 更新配置文件 config.xml
```xml
<WorkpieceParam lapHeight="2.0" weldMinLen="2.0" weldRefPoints="2" lineLen="100.0" />
```
#### 1.3 更新 VrConfig.cpp
- 加载配置时读取 `lineLen` 参数
- 保存配置时写入 `lineLen` 参数
### 2. DetectPresenter 算法集成 (待实现)
#### 2.1 添加SDK头文件引用
```cpp
#include "BQ_workpieceCornerExtraction_Export.h"
```
#### 2.2 实现角点检测方法
`DetectWorkpiece` 方法中添加工件角点提取调用:
```cpp
int DetectPresenter::DetectWorkpiece(
int cameraIndex,
std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& laserLines,
const VrAlgorithmParams& algorithmParams,
const VrDebugParam& debugParam,
LaserDataLoader& dataLoader,
const double clibMatrix[16],
DetectionResult& detectionResult)
{
// 1. 转换激光线数据为算法需要的格式
std::vector<std::vector<SVzNL3DPosition>> scanLines;
// ... 数据转换代码 ...
// 2. 准备算法参数
SSX_BQworkpiecePara workpieceParam;
workpieceParam.lineLen = algorithmParams.workpieceParam.lineLen;
SSG_cornerParam cornerParam;
cornerParam.cornerTh = algorithmParams.cornerParam.cornerTh;
cornerParam.scale = algorithmParams.cornerParam.scale;
cornerParam.minEndingGap = algorithmParams.cornerParam.minEndingGap;
cornerParam.minEndingGap_z = algorithmParams.cornerParam.minEndingGap_z;
cornerParam.jumpCornerTh_1 = algorithmParams.cornerParam.jumpCornerTh_1;
cornerParam.jumpCornerTh_2 = algorithmParams.cornerParam.jumpCornerTh_2;
SSG_outlierFilterParam filterParam;
filterParam.continuityTh = algorithmParams.filterParam.continuityTh;
filterParam.outlierTh = algorithmParams.filterParam.outlierTh;
SSG_treeGrowParam growParam;
growParam.maxLineSkipNum = algorithmParams.growParam.maxLineSkipNum;
growParam.yDeviation_max = algorithmParams.growParam.yDeviation_max;
growParam.maxSkipDistance = algorithmParams.growParam.maxSkipDistance;
growParam.zDeviation_max = algorithmParams.growParam.zDeviation_max;
growParam.minLTypeTreeLen = algorithmParams.growParam.minLTypeTreeLen;
growParam.minVTypeTreeLen = algorithmParams.growParam.minVTypeTreeLen;
// 3. 获取调平参数
SSG_planeCalibPara groundCalibPara;
const VrCameraPlaneCalibParam* cameraCalib =
algorithmParams.planeCalibParam.GetCameraCalibParam(cameraIndex);
if (cameraCalib && cameraCalib->isCalibrated) {
memcpy(groundCalibPara.planeCalib, cameraCalib->planeCalib, sizeof(double) * 9);
memcpy(groundCalibPara.invRMatrix, cameraCalib->invRMatrix, sizeof(double) * 9);
groundCalibPara.planeHeight = cameraCalib->planeHeight;
} else {
// 使用单位矩阵
double identity[9] = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0};
memcpy(groundCalibPara.planeCalib, identity, sizeof(double) * 9);
memcpy(groundCalibPara.invRMatrix, identity, sizeof(double) * 9);
groundCalibPara.planeHeight = -1.0;
}
// 4. 调用算法
SSX_debugInfo debugContours[100]; // 调试信息
int errCode = 0;
SSX_BQworkpieceResult result = sx_BQ_getWorkpieceCorners(
scanLines,
cornerParam,
filterParam,
growParam,
groundCalibPara,
workpieceParam,
#if _OUTPUT_DEBUG_DATA
debugContours,
#endif
&errCode
);
if (errCode != 0) {
LOG_ERROR("工件角点检测失败,错误码: %d\n", errCode);
return errCode;
}
// 5. 将结果转换为手眼坐标系
// 手眼标定变换矩阵应用到检测结果
// ... 坐标转换代码 ...
// 6. 填充检测结果
detectionResult.cameraIndex = cameraIndex;
detectionResult.positions.clear();
// 添加左侧角点
for (int i = 0; i < 3; i++) {
WorkpiecePosition pos;
pos.x = result.corner_L[i].x;
pos.y = result.corner_L[i].y;
pos.z = result.corner_L[i].z;
detectionResult.positions.push_back(pos);
}
// 添加右侧角点
for (int i = 0; i < 3; i++) {
WorkpiecePosition pos;
pos.x = result.corner_R[i].x;
pos.y = result.corner_R[i].y;
pos.z = result.corner_R[i].z;
detectionResult.positions.push_back(pos);
}
// 添加顶部角点
for (int i = 0; i < 3; i++) {
WorkpiecePosition pos;
pos.x = result.corner_T[i].x;
pos.y = result.corner_T[i].y;
pos.z = result.corner_T[i].z;
detectionResult.positions.push_back(pos);
}
// 添加底部角点
for (int i = 0; i < 3; i++) {
WorkpiecePosition pos;
pos.x = result.corner_B[i].x;
pos.y = result.corner_B[i].y;
pos.z = result.corner_B[i].z;
detectionResult.positions.push_back(pos);
}
// 7. 生成可视化图像(如果需要)
if (debugParam.saveDebugImage) {
// 创建点云可视化图像
// ... 图像生成代码 ...
}
return SUCCESS;
}
```
### 3. WorkpiecePresenter 业务逻辑集成 (待实现)
#### 3.1 更新检测任务方法
`_DetectTask()` 方法中调用 `DetectPresenter::DetectWorkpiece()`
```cpp
int WorkpiecePresenter::_DetectTask()
{
LOG_INFO("[Algo Thread] Start workpiece corner extraction detection\n");
std::lock_guard<std::mutex> lock(m_detectionDataMutex);
if (m_detectionDataCache.empty()) {
LOG_WARNING("No cached detection data available\n");
return ERR_CODE(DEV_DATA_INVALID);
}
// 执行检测
DetectionResult detectionResult;
int nRet = m_pDetectPresenter->DetectWorkpiece(
m_currentCameraIndex,
m_detectionDataCache,
m_algorithmParams,
m_debugParam,
m_dataLoader,
m_clibMatrixList[m_currentCameraIndex - 1].clibMatrix,
detectionResult
);
if (nRet != SUCCESS) {
LOG_ERROR("Detection failed with error: %d\n", nRet);
if (m_pStatus) {
m_pStatus->OnStatusUpdate("检测失败");
}
return nRet;
}
// 通知UI显示结果
detectionResult.cameraIndex = m_currentCameraIndex;
m_pStatus->OnDetectionResult(detectionResult);
// 发送结果到TCP客户端
_SendDetectionResultToTCP(detectionResult, m_currentCameraIndex);
// 更新状态
m_currentWorkStatus = WorkStatus::Completed;
m_pStatus->OnWorkStatusChanged(WorkStatus::Completed);
return SUCCESS;
}
```
#### 3.2 实现TCP结果发送方法
```cpp
void WorkpiecePresenter::_SendDetectionResultToTCP(
const DetectionResult& detectionResult,
int cameraIndex)
{
if (!m_pTCPServer || !m_bTCPConnected) {
LOG_WARNING("TCP not connected, skip sending detection result\n");
return;
}
// 构建JSON格式的检测结果
QJsonObject jsonResult;
jsonResult["cameraIndex"] = cameraIndex;
jsonResult["timestamp"] = QDateTime::currentMSecsSinceEpoch();
jsonResult["cornerCount"] = static_cast<int>(detectionResult.positions.size());
QJsonArray cornersArray;
for (const auto& pos : detectionResult.positions) {
QJsonObject cornerObj;
cornerObj["x"] = pos.x;
cornerObj["y"] = pos.y;
cornerObj["z"] = pos.z;
cornerObj["roll"] = pos.roll;
cornerObj["pitch"] = pos.pitch;
cornerObj["yaw"] = pos.yaw;
cornersArray.append(cornerObj);
}
jsonResult["corners"] = cornersArray;
QJsonDocument jsonDoc(jsonResult);
QByteArray jsonData = jsonDoc.toJson(QJsonDocument::Compact);
// 发送JSON数据
m_pTCPServer->SendToAll(jsonData.data(), jsonData.size());
LOG_INFO("Sent detection result to TCP client: %d corners\n",
detectionResult.positions.size());
}
```
### 4. 参数配置界面 (待实现)
#### 4.1 更新 dialogalgoarg.ui
在算法参数对话框中添加工件参数配置控件:
- `lineEdit_lineLen`: 直线段长度阈值输入框
- `label_lineLen`: 参数标签
#### 4.2 更新 dialogalgoarg.cpp
```cpp
void DialogAlgoarg::LoadConfigToUI()
{
// ... 现有加载代码 ...
// 加载工件参数
ui->lineEdit_lineLen->setText(
QString::number(m_configData.algorithmParams.workpieceParam.lineLen));
}
bool DialogAlgoarg::SaveConfigFromUI()
{
// ... 现有保存代码 ...
// 保存工件参数
m_configData.algorithmParams.workpieceParam.lineLen =
ui->lineEdit_lineLen->text().toDouble();
// 保存到配置文件
QString configPath = PathManager::GetConfigFilePath();
return m_vrConfig->SaveConfig(configPath.toStdString(), m_configData);
}
```
### 5. 主窗口显示结果 (待实现)
#### 5.1 更新 mainwindow.cpp
实现 `OnDetectionResult` 回调方法:
```cpp
void MainWindow::OnDetectionResult(const DetectionResult& result)
{
// 1. 显示检测图像
if (!result.image.isNull()) {
ui->label_image->setPixmap(QPixmap::fromImage(result.image));
}
// 2. 更新结果列表
ui->listWidget_results->clear();
for (size_t i = 0; i < result.positions.size(); i++) {
const auto& pos = result.positions[i];
QString resultText = QString("角点 %1: X=%.2f, Y=%.2f, Z=%.2f")
.arg(i + 1)
.arg(pos.x)
.arg(pos.y)
.arg(pos.z);
ui->listWidget_results->addItem(resultText);
}
// 3. 更新状态栏
QString statusText = QString("检测完成,找到 %1 个角点")
.arg(result.positions.size());
ui->statusBar->showMessage(statusText);
}
```
### 6. 项目配置更新 (待实现)
#### 6.1 更新 WorkpieceApp.pro
添加工件角点提取SDK依赖
```qmake
# 工件角点提取算法SDK
INCLUDEPATH += ../../../SDK/workpieceCornerExtraction/Inc
win32:CONFIG(release, debug|release): {
LIBS += -L$$PWD/../../../SDK/workpieceCornerExtraction/Windows/x64/Release
LIBS += -lBQ_workpieceCornerExtraction -lbaseAlgorithm
}
else:win32:CONFIG(debug, debug|release): {
LIBS += -L$$PWD/../../../SDK/workpieceCornerExtraction/Windows/x64/Debug
LIBS += -lBQ_workpieceCornerExtraction -lbaseAlgorithm
}
else:unix:!macx: {
LIBS += -L$$PWD/../../../SDK/workpieceCornerExtraction/Arm/aarch64
LIBS += -lworkpieceCornerExtraction -lbaseAlgorithm
}
```
### 7. TCP通信协议
#### 7.1 客户端触发检测
客户端发送触发命令:
```
@,1,1,Trig,$
```
格式:`@,视觉号,视觉模版号,启动信息,$`
#### 7.2 服务端返回检测结果
服务端以JSON格式返回角点检测结果
```json
{
"cameraIndex": 1,
"timestamp": 1640000000000,
"cornerCount": 12,
"corners": [
{
"x": 100.5,
"y": 200.3,
"z": 50.2,
"roll": 0.0,
"pitch": 0.0,
"yaw": 0.0
},
// ... 更多角点数据 ...
]
}
```
## 数据流程
```
客户端触发 → TCPServerProtocol → WorkpiecePresenter::StartDetection
相机采集点云数据 → _DetectionCallback → m_detectionDataCache
相机扫描完成 → _AlgoDetectThread → _DetectTask
DetectPresenter::DetectWorkpiece → sx_BQ_getWorkpieceCorners (SDK算法)
坐标转换(手眼标定)→ DetectionResult
MainWindow::OnDetectionResult (UI显示) + _SendDetectionResultToTCP (发送给客户端)
```
## 关键技术点
### 1. 点云数据转换
需要将 `SVzLaserLineData` 格式转换为 `std::vector<std::vector<SVzNL3DPosition>>` 格式
### 2. 调平参数应用
使用相机的平面校准参数对点云数据进行调平处理
### 3. 手眼标定
将相机坐标系下的检测结果转换到机械臂坐标系
### 4. JSON序列化
使用Qt的QJsonObject/QJsonDocument进行JSON格式转换
### 5. 多线程安全
- 使用 `m_detectionDataMutex` 保护检测数据缓存
- 使用 `m_algoDetectCondition` 进行线程同步
## 编译和部署
### Windows平台
1. 确保SDK库文件在正确路径
2. 使用Qt Creator打开项目
3. 选择Release或Debug配置
4. 构建项目
### ARM/Linux平台
1. 配置交叉编译环境
2. 确保ARM版本的SDK库文件存在
3. 使用qmake生成Makefile
4. 执行make编译
```bash
cd App/Workpiece/WorkpieceApp
qmake WorkpieceApp.pro
make
```
## 测试验证
### 1. 参数配置测试
- 打开算法参数配置对话框
- 修改lineLen参数
- 保存并验证config.xml文件
### 2. 算法检测测试
- 加载调试点云数据
- 触发检测
- 验证检测结果
### 3. TCP通信测试
- 启动TCP服务器
- 客户端连接并发送触发命令
- 验证JSON结果返回
### 4. UI显示测试
- 验证检测图像显示
- 验证角点结果列表
- 验证状态信息更新
## 注意事项
1. **SDK版本兼容性**确保使用的SDK版本与项目兼容
2. **内存管理**:注意释放算法分配的内存资源
3. **坐标系转换**:确保手眼标定矩阵正确应用
4. **错误处理**:完善错误检测和日志输出
5. **性能优化**:大点云数据处理时注意性能
6. **线程安全**:确保多线程访问的数据安全
## 后续优化建议
1. 增加算法参数实时调整功能
2. 支持多种工件类型的角点检测
3. 增加检测结果的3D可视化
4. 优化大数据量下的处理速度
5. 增加离线调试和参数优化工具