# 工件角点检测功能 - 完整实现代码 ## 已完成的工作 ✅ ### 1. 配置结构更新 ✅ - ✅ 更新 `IVrConfig.h` 添加 `lineLen` 参数 - ✅ 更新 `config.xml` 配置文件 - ✅ 更新 `VrConfig.cpp` 支持加载和保存新参数 - ✅ 更新 `WorkpieceApp.pro` 添加SDK依赖 ### 2. 算法集成 ✅ - ✅ 更新 `DetectPresenter.cpp` 集成角点提取算法 - ✅ 调用 `sx_BQ_getWorkpieceCorners` API - ✅ 处理12个角点结果(左、右、上、下各3个) - ✅ 坐标转换和可视化 ## 待实现代码 ### 1. 参数配置界面 (dialogalgoarg) #### dialogalgoarg.ui 更新 在算法参数对话框中添加以下控件(使用Qt Designer): ```xml 150 200 100 30 100.0 20 200 120 30 直线段长度: ``` #### dialogalgoarg.cpp 更新 ```cpp #include "dialogalgoarg.h" #include "ui_dialogalgoarg.h" #include "PathManager.h" #include DialogAlgoarg::DialogAlgoarg(IVrConfig* vrConfig, QWidget *parent) : QDialog(parent) , ui(new Ui::DialogAlgoarg) , m_vrConfig(vrConfig) { ui->setupUi(this); // 加载配置文件路径 m_configFilePath = PathManager::GetConfigFilePath(); // 加载当前配置 if (m_vrConfig) { m_configData = m_vrConfig->LoadConfig(m_configFilePath.toStdString()); } // 将配置数据加载到UI LoadConfigToUI(); } DialogAlgoarg::~DialogAlgoarg() { delete ui; } void DialogAlgoarg::LoadConfigToUI() { // 加载角点参数 ui->lineEdit_cornerTh->setText( QString::number(m_configData.algorithmParams.cornerParam.cornerTh)); ui->lineEdit_scale->setText( QString::number(m_configData.algorithmParams.cornerParam.scale)); ui->lineEdit_minEndingGap->setText( QString::number(m_configData.algorithmParams.cornerParam.minEndingGap)); ui->lineEdit_minEndingGap_z->setText( QString::number(m_configData.algorithmParams.cornerParam.minEndingGap_z)); ui->lineEdit_jumpCornerTh_1->setText( QString::number(m_configData.algorithmParams.cornerParam.jumpCornerTh_1)); ui->lineEdit_jumpCornerTh_2->setText( QString::number(m_configData.algorithmParams.cornerParam.jumpCornerTh_2)); // 加载树生长参数 ui->lineEdit_maxLineSkipNum->setText( QString::number(m_configData.algorithmParams.growParam.maxLineSkipNum)); ui->lineEdit_yDeviation_max->setText( QString::number(m_configData.algorithmParams.growParam.yDeviation_max)); ui->lineEdit_maxSkipDistance->setText( QString::number(m_configData.algorithmParams.growParam.maxSkipDistance)); ui->lineEdit_zDeviation_max->setText( QString::number(m_configData.algorithmParams.growParam.zDeviation_max)); ui->lineEdit_minLTypeTreeLen->setText( QString::number(m_configData.algorithmParams.growParam.minLTypeTreeLen)); ui->lineEdit_minVTypeTreeLen->setText( QString::number(m_configData.algorithmParams.growParam.minVTypeTreeLen)); // 加载工件参数 ui->lineEdit_lapHeight->setText( QString::number(m_configData.algorithmParams.workpieceParam.lapHeight)); ui->lineEdit_weldMinLen->setText( QString::number(m_configData.algorithmParams.workpieceParam.weldMinLen)); ui->lineEdit_weldRefPoints->setText( QString::number(m_configData.algorithmParams.workpieceParam.weldRefPoints)); // 加载工件角点提取参数 ui->lineEdit_lineLen->setText( QString::number(m_configData.algorithmParams.workpieceParam.lineLen)); } bool DialogAlgoarg::SaveConfigFromUI() { // 保存角点参数 m_configData.algorithmParams.cornerParam.cornerTh = ui->lineEdit_cornerTh->text().toDouble(); m_configData.algorithmParams.cornerParam.scale = ui->lineEdit_scale->text().toDouble(); m_configData.algorithmParams.cornerParam.minEndingGap = ui->lineEdit_minEndingGap->text().toDouble(); m_configData.algorithmParams.cornerParam.minEndingGap_z = ui->lineEdit_minEndingGap_z->text().toDouble(); m_configData.algorithmParams.cornerParam.jumpCornerTh_1 = ui->lineEdit_jumpCornerTh_1->text().toDouble(); m_configData.algorithmParams.cornerParam.jumpCornerTh_2 = ui->lineEdit_jumpCornerTh_2->text().toDouble(); // 保存树生长参数 m_configData.algorithmParams.growParam.maxLineSkipNum = ui->lineEdit_maxLineSkipNum->text().toInt(); m_configData.algorithmParams.growParam.yDeviation_max = ui->lineEdit_yDeviation_max->text().toDouble(); m_configData.algorithmParams.growParam.maxSkipDistance = ui->lineEdit_maxSkipDistance->text().toDouble(); m_configData.algorithmParams.growParam.zDeviation_max = ui->lineEdit_zDeviation_max->text().toDouble(); m_configData.algorithmParams.growParam.minLTypeTreeLen = ui->lineEdit_minLTypeTreeLen->text().toDouble(); m_configData.algorithmParams.growParam.minVTypeTreeLen = ui->lineEdit_minVTypeTreeLen->text().toDouble(); // 保存工件参数 m_configData.algorithmParams.workpieceParam.lapHeight = ui->lineEdit_lapHeight->text().toDouble(); m_configData.algorithmParams.workpieceParam.weldMinLen = ui->lineEdit_weldMinLen->text().toDouble(); m_configData.algorithmParams.workpieceParam.weldRefPoints = ui->lineEdit_weldRefPoints->text().toInt(); // 保存工件角点提取参数 m_configData.algorithmParams.workpieceParam.lineLen = ui->lineEdit_lineLen->text().toDouble(); // 保存到配置文件 if (!m_vrConfig->SaveConfig(m_configFilePath.toStdString(), m_configData)) { return false; } return true; } void DialogAlgoarg::on_btn_camer_ok_clicked() { if (SaveConfigFromUI()) { QMessageBox::information(this, "提示", "参数保存成功!"); accept(); } else { QMessageBox::warning(this, "错误", "参数保存失败!"); } } void DialogAlgoarg::on_btn_camer_cancel_clicked() { reject(); } ``` ### 2. TCPServerProtocol JSON结果发送 #### TCPServerMethods.cpp 添加方法 ```cpp #include "TCPServerProtocol.h" #include "VrLog.h" #include #include #include #include int TCPServerProtocol::SendWorkpieceCornerResult( const std::vector& corners, int cameraIndex, const TCPClient* pClient) { if (!m_pTCPServer || !m_bServerRunning) { LOG_ERROR("TCP server is not running\n"); return -1; } // 构造JSON格式的检测结果 QJsonObject jsonResult; jsonResult["type"] = "workpiece_corner"; jsonResult["cameraIndex"] = cameraIndex; jsonResult["timestamp"] = QDateTime::currentMSecsSinceEpoch(); jsonResult["cornerCount"] = static_cast(corners.size()); jsonResult["success"] = true; jsonResult["message"] = "Detection completed successfully"; // 构造角点数组 QJsonArray cornersArray; for (size_t i = 0; i < corners.size(); i++) { const auto& pos = corners[i]; QJsonObject cornerObj; cornerObj["index"] = static_cast(i); cornerObj["x"] = pos.x; cornerObj["y"] = pos.y; cornerObj["z"] = pos.z; cornerObj["roll"] = pos.roll; cornerObj["pitch"] = pos.pitch; cornerObj["yaw"] = pos.yaw; // 添加角点分类信息(可选) if (i < 3) { cornerObj["position"] = "left"; } else if (i < 6) { cornerObj["position"] = "right"; } else if (i < 9) { cornerObj["position"] = "top"; } else { cornerObj["position"] = "bottom"; } cornersArray.append(cornerObj); } jsonResult["corners"] = cornersArray; // 转换为JSON字符串 QJsonDocument jsonDoc(jsonResult); QByteArray jsonData = jsonDoc.toJson(QJsonDocument::Compact); // 发送数据 int result = -1; if (pClient) { // 发送给指定客户端 result = m_pTCPServer->SendData(pClient, jsonData.data(), jsonData.size()) ? 0 : -1; } else { // 广播给所有客户端 result = m_pTCPServer->SendAllData(jsonData.data(), jsonData.size()) ? 0 : -1; } if (result == 0) { LOG_INFO("Sent workpiece corner detection result: %d corners, size: %d bytes\n", static_cast(corners.size()), jsonData.size()); } else { LOG_ERROR("Failed to send workpiece corner detection result\n"); } return result; } ``` #### TCPServerProtocol.h 添加声明 ```cpp class TCPServerProtocol { public: // ... 现有方法 ... /** * @brief 发送工件角点检测结果 * @param corners 角点位置数组 * @param cameraIndex 相机索引 * @param pClient 目标客户端(nullptr表示广播给所有客户端) * @return 0-成功,其他-失败 */ int SendWorkpieceCornerResult( const std::vector& corners, int cameraIndex, const TCPClient* pClient = nullptr); }; ``` ### 3. WorkpiecePresenter 完整业务逻辑 #### WorkpiecePresenter.cpp 中的 _DetectTask 更新 ```cpp int WorkpiecePresenter::_DetectTask() { LOG_INFO("[Algo Thread] Start workpiece corner extraction detection\n"); std::lock_guard lock(m_detectionDataMutex); // 1. 检查数据 if (m_detectionDataCache.empty()) { LOG_WARNING("No cached detection data available\n"); if (m_pStatus) { m_pStatus->OnStatusUpdate("无缓存的检测数据"); } return ERR_CODE(DEV_DATA_INVALID); } LOG_INFO("[Algo Thread] Detection data cache size: %zu lines\n", m_detectionDataCache.size()); // 2. 获取手眼标定矩阵 const CalibMatrix& calibMatrix = m_clibMatrixList[m_currentCameraIndex - 1]; // 3. 执行检测 DetectionResult detectionResult; int nRet = m_pDetectPresenter->DetectWorkpiece( m_currentCameraIndex, m_detectionDataCache, m_algorithmParams, m_debugParam, m_dataLoader, calibMatrix.clibMatrix, detectionResult ); if (nRet != SUCCESS) { LOG_ERROR("Detection failed with error: %d\n", nRet); if (m_pStatus) { m_pStatus->OnStatusUpdate(QString("检测失败,错误码: %1").arg(nRet).toStdString()); m_pStatus->OnWorkStatusChanged(WorkStatus::Error); } m_currentWorkStatus = WorkStatus::Error; return nRet; } LOG_INFO("[Algo Thread] Detection completed successfully, found %zu corners\n", detectionResult.positions.size()); // 4. 更新检测结果 detectionResult.cameraIndex = m_currentCameraIndex; // 5. 通知UI显示结果 if (m_pStatus) { m_pStatus->OnDetectionResult(detectionResult); QString statusMsg = QString("检测完成,找到 %1 个角点") .arg(detectionResult.positions.size()); m_pStatus->OnStatusUpdate(statusMsg.toStdString()); } // 6. 发送结果到TCP客户端 _SendDetectionResultToTCP(detectionResult, m_currentCameraIndex); // 7. 更新工作状态 m_currentWorkStatus = WorkStatus::Completed; if (m_pStatus) { m_pStatus->OnWorkStatusChanged(WorkStatus::Completed); } return SUCCESS; } ``` #### WorkpiecePresenter.cpp 中的 _SendDetectionResultToTCP 实现 ```cpp void WorkpiecePresenter::_SendDetectionResultToTCP( const DetectionResult& detectionResult, int cameraIndex) { if (!m_pTCPServer) { LOG_WARNING("TCP server not initialized\n"); return; } if (!m_bTCPConnected) { LOG_WARNING("TCP not connected, skip sending detection result\n"); return; } LOG_INFO("Sending workpiece corner detection result to TCP client\n"); // 调用TCPServerProtocol发送JSON格式结果 int result = m_pTCPServer->SendWorkpieceCornerResult( detectionResult.positions, cameraIndex, nullptr // 广播给所有客户端 ); if (result == 0) { LOG_INFO("Successfully sent detection result to TCP client: %zu corners\n", detectionResult.positions.size()); if (m_pStatus) { m_pStatus->OnStatusUpdate("检测结果已发送到客户端"); } } else { LOG_ERROR("Failed to send detection result to TCP client\n"); if (m_pStatus) { m_pStatus->OnStatusUpdate("发送检测结果失败"); } } } ``` ### 4. MainWindow 显示检测结果 #### mainwindow.cpp 实现 OnDetectionResult ```cpp void MainWindow::OnDetectionResult(const DetectionResult& result) { LOG_INFO("Received detection result: %zu corners from camera %d\n", result.positions.size(), result.cameraIndex); // 1. 显示检测图像 if (!result.image.isNull()) { // 缩放图像以适应显示区域 QPixmap pixmap = QPixmap::fromImage(result.image); QPixmap scaledPixmap = pixmap.scaled( ui->label_image->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation ); ui->label_image->setPixmap(scaledPixmap); LOG_DEBUG("Detection image displayed: %dx%d\n", result.image.width(), result.image.height()); } else { LOG_WARNING("No detection image to display\n"); } // 2. 更新结果列表 ui->listWidget_results->clear(); // 添加标题信息 QString headerText = QString("=== 相机 %1 检测结果 ===").arg(result.cameraIndex); ui->listWidget_results->addItem(headerText); QString countText = QString("检测到 %1 个角点:").arg(result.positions.size()); ui->listWidget_results->addItem(countText); ui->listWidget_results->addItem(""); // 空行 // 添加每个角点的详细信息 for (size_t i = 0; i < result.positions.size(); i++) { const auto& pos = result.positions[i]; // 确定角点位置(左、右、上、下) QString positionLabel; if (i < 3) { positionLabel = QString("左侧角点 %1").arg(i + 1); } else if (i < 6) { positionLabel = QString("右侧角点 %1").arg(i - 2); } else if (i < 9) { positionLabel = QString("顶部角点 %1").arg(i - 5); } else { positionLabel = QString("底部角点 %1").arg(i - 8); } QString resultText = QString("%1: X=%.2f, Y=%.2f, Z=%.2f") .arg(positionLabel) .arg(pos.x) .arg(pos.y) .arg(pos.z); ui->listWidget_results->addItem(resultText); LOG_DEBUG("Corner %zu: (%.2f, %.2f, %.2f)\n", i, pos.x, pos.y, pos.z); } // 3. 更新状态栏 QString statusText = QString("检测完成 - 找到 %1 个角点 - 相机 %2") .arg(result.positions.size()) .arg(result.cameraIndex); ui->statusBar->showMessage(statusText, 5000); // 显示5秒 // 4. 更���检测时间戳 QString timeText = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"); ui->label_detectTime->setText(timeText); LOG_INFO("Detection result display updated successfully\n"); } ``` ## JSON 通信协议格式 ### 客户端触发检测请求 ``` @,1,1,Trig,$ ``` 格式说明:`@,视觉号(相机ID),视觉模版号,启动信息,$` ### 服务端返回检测结果 ```json { "type": "workpiece_corner", "cameraIndex": 1, "timestamp": 1640000000000, "cornerCount": 12, "success": true, "message": "Detection completed successfully", "corners": [ { "index": 0, "position": "left", "x": 100.523, "y": 200.341, "z": 50.218, "roll": 0.0, "pitch": 0.0, "yaw": 0.0 }, { "index": 1, "position": "left", "x": 100.234, "y": 250.567, "z": 50.123, "roll": 0.0, "pitch": 0.0, "yaw": 0.0 }, // ... 更多角点数据(共12个)... { "index": 11, "position": "bottom", "x": 450.789, "y": 650.234, "z": 50.456, "roll": 0.0, "pitch": 0.0, "yaw": 0.0 } ] } ``` ### 角点位置说明 - **index 0-2**: 左侧角点(position: "left") - **index 3-5**: 右侧角点(position: "right") - **index 6-8**: 顶部角点(position: "top") - **index 9-11**: 底部角点(position: "bottom") ## 编译和测试 ### 编译步骤 ```bash # Windows平台 cd App/Workpiece/WorkpieceApp qmake WorkpieceApp.pro nmake # 或在Qt Creator中直接构建 # Linux/ARM平台 cd App/Workpiece/WorkpieceApp qmake WorkpieceApp.pro make -j4 ``` ### 测试步骤 #### 1. 参数配置测试 1. 运行WorkpieceApp 2. 点击"算法参数"按钮打开配置对话框 3. 修改"直线段长度"参数(默认100.0) 4. 点击"确定"保存 5. 验证 `config.xml` 文件中的 `lineLen` 参数已更新 #### 2. 算法检测测试 1. 准备点云数据文件(.txt格式) 2. 在主界面点击"加载数据" 3. 选择点云文件 4. 点击"开始检测" 5. 观察日志输出和UI显示 #### 3. TCP通信测试 使用TCP客户端工具连接服务器: ```python import socket import json # 连接服务器 client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(('127.0.0.1', 5020)) # 发送触发命令 trigger_cmd = b'@,1,1,Trig,$' client.send(trigger_cmd) # 接收JSON结果 response = client.recv(4096) result = json.loads(response.decode('utf-8')) print(f"检测到 {result['cornerCount']} 个角点") for corner in result['corners']: print(f"角点 {corner['index']}: ({corner['x']:.2f}, {corner['y']:.2f}, {corner['z']:.2f})") client.close() ``` ## 调试建议 ### 1. 日志级别设置 在 `config.xml` 中启用调试模式: ```xml ``` ### 2. 查看调试输出 - 点云数据:`./debug/Laserline_1_YYYYMMDDHHMMSS.txt` - 检测图像:`./debug/Image_1_YYYYMMDDHHMMSS.png` ### 3. 常见问题排查 #### 问题1:SDK库找不到 **症状**:编译或运行时提示找不到 `BQ_workpieceCornerExtraction.dll` **解决方案**: - Windows: 确保 `SDK/workpieceCornerExtraction/Windows/x64/Release` 路径正确 - Linux: 设置 `LD_LIBRARY_PATH` 环境变量 #### 问题2:检测结果为空 **症状**:`cornerCount` 为 0 **解决方案**: - 检查点云数据质量 - 调整 `lineLen` 参数(尝试50-200范围) - 检查调平参数是否正确 #### 问题3:坐标转换错误 **症状**:角点坐标异常 **解决方案**: - 验证手眼标定矩阵 - 检查相机调平参数 - 查看日志中的原始坐标和转换后坐标 ## 性能优化建议 1. **大数据量处理**:启用多线程处理 2. **网络传输**:压缩JSON数据 3. **图像显示**:使用异步更新 4. **内存管理**:及时释放点云缓存 ## 总结 ✅ **已完成**: - 配置结构和文件更新 - 算法SDK集成 - 检测结果处理 - 项目配置文件更新 📝 **待实现**: - 参数配置UI界面(需要Qt Designer编辑.ui文件) - TCP JSON结果发送方法 - 主窗口结果显示逻辑 所有核心算法逻辑已经完成,剩余工作主要是UI界面调整和TCP通信完善。参考本文档中的代码示例即可快速完成剩余实现。