# 工件角点检测功能 - 完整实现代码
## 已完成的工作 ✅
### 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通信完善。参考本文档中的代码示例即可快速完成剩余实现。