GrabBag/App/WheelMeasure/WheelMeasureApp/dialogcameralevel.cpp

1195 lines
44 KiB
C++
Raw Normal View History

2026-03-14 10:00:15 +08:00
#include "dialogcameralevel.h"
#include "ui_dialogcameralevel.h"
#include "WheelMeasurePresenter.h"
#include "PathManager.h"
#include "VrLog.h"
#include "wheelArchHeigthMeasure_Export.h"
#include <QThread>
#include <QApplication>
2026-05-17 00:38:31 +08:00
#include <QLabel>
2026-03-14 10:00:15 +08:00
#include <QListView>
#include <QDoubleValidator>
2026-03-14 10:00:15 +08:00
#include <cmath>
#include <mutex>
#include <vector>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
DialogCameraLevel::DialogCameraLevel(QWidget *parent)
: QDialog(parent)
, ui(new Ui::DialogCameraLevel)
, m_pConfig(nullptr)
, m_pConfigResult(nullptr)
, m_currentCameraIndex(-1)
{
ui->setupUi(this);
// 初始化结果显示区域
ui->label_level_result->setText("请选择相机,然后点击调平计算按钮\n开始相机调平操作");
2026-03-14 10:00:15 +08:00
ui->label_level_result->setAlignment(Qt::AlignCenter);
// 修正模块输入精度限制:标定块标称值 / 凹槽间距 / Y范围均为三位小数
auto* d3Validator = new QDoubleValidator(this);
d3Validator->setDecimals(3);
d3Validator->setNotation(QDoubleValidator::StandardNotation);
ui->edit_calib_nominal_length->setValidator(d3Validator);
ui->edit_calib_groove_spacing->setValidator(d3Validator);
ui->edit_calib_y_min->setValidator(d3Validator);
ui->edit_calib_y_max->setValidator(d3Validator);
// 修正系数八位小数
auto* d8Validator = new QDoubleValidator(this);
d8Validator->setDecimals(8);
d8Validator->setNotation(QDoubleValidator::StandardNotation);
ui->edit_correction_factor->setValidator(d8Validator);
2026-03-14 10:00:15 +08:00
}
DialogCameraLevel::~DialogCameraLevel()
{
// 清理扫描数据缓存
clearScanDataCache();
// 确保恢复Presenter的状态回调
restorePresenterStatusCallback();
delete ui;
}
void DialogCameraLevel::setCameraList(const std::vector<std::pair<std::string, IVrEyeDevice*>>& cameraList,
WheelMeasurePresenter* presenter)
{
m_cameraList = cameraList;
m_presenter = presenter;
LOG_INFO("setCameraList called with %zu cameras\n", cameraList.size());
// 详细记录每个相机的信息
for (size_t i = 0; i < cameraList.size(); ++i) {
const auto& camera = cameraList[i];
LOG_INFO(" Camera %zu: name='%s', device=%s\n",
i + 1,
camera.first.c_str(),
(camera.second != nullptr ? "connected" : "not connected"));
}
// 初始化/重新初始化相机选择框
initializeCameraCombo();
}
void DialogCameraLevel::setConfig(IVrWheelMeasureConfig* config, WheelMeasureConfigResult* configResult)
{
m_pConfig = config;
m_pConfigResult = configResult;
2026-05-17 00:38:31 +08:00
// 重新打开页面时,从文件重新加载配置以获取最新数据
if (m_pConfig && m_pConfigResult) {
QString configPath = PathManager::GetInstance().GetConfigFilePath();
*m_pConfigResult = m_pConfig->LoadConfig(configPath.toStdString());
}
2026-03-14 10:00:15 +08:00
// 如果相机已经选择,重新加载当前相机的标定状态
if (m_currentCameraIndex >= 0 && m_currentCameraIndex < static_cast<int>(m_cameraList.size())) {
checkAndDisplayCalibrationStatus(m_currentCameraIndex);
loadCameraRoiRange(m_currentCameraIndex);
}
}
void DialogCameraLevel::initializeCameraCombo()
{
LOG_INFO("initializeCameraCombo called, camera list size: %zu\n", m_cameraList.size());
ui->combo_camera->clear();
if (m_cameraList.empty()) {
ui->combo_camera->setEnabled(false);
if (!m_presenter) {
ui->label_level_result->setText("Presenter未初始化\n无法获取相机列表");
LOG_ERROR("Presenter is null in initializeCameraCombo\n");
} else {
ui->label_level_result->setText("相机列表为空\n\n可能原因:\n1. 配置文件中未配置相机\n2. 系统正在初始化中\n3. 所有相机连接失败");
LOG_WARNING("Camera list is empty in initializeCameraCombo\n");
}
ui->label_level_result->setAlignment(Qt::AlignCenter);
} else {
LOG_INFO("Adding %zu cameras to combo box\n", m_cameraList.size());
// 添加所有相机到下拉列表
for (size_t i = 0; i < m_cameraList.size(); ++i) {
const auto& camera = m_cameraList[i];
QString cameraName = QString::fromStdString(camera.first);
// 如果相机没有连接,在名称后添加标记
if (camera.second == nullptr) {
cameraName += " [未连接]";
}
ui->combo_camera->addItem(cameraName);
LOG_INFO(" Added camera %zu: %s (device=%s)\n",
i + 1,
camera.first.c_str(),
(camera.second != nullptr ? "OK" : "NULL"));
}
ui->combo_camera->setEnabled(true);
// 获取默认相机索引
int defaultCameraIndex = 0;
if (m_presenter) {
int presenterDefaultIndex = m_presenter->GetDefaultCameraIndex();
LOG_INFO("Presenter default camera index (1-based): %d\n", presenterDefaultIndex);
if (presenterDefaultIndex > 0 && presenterDefaultIndex <= static_cast<int>(m_cameraList.size())) {
defaultCameraIndex = presenterDefaultIndex - 1;
}
}
// 设置默认选中的相机
if (defaultCameraIndex >= 0 && defaultCameraIndex < static_cast<int>(m_cameraList.size())) {
ui->combo_camera->setCurrentIndex(defaultCameraIndex);
m_currentCameraIndex = defaultCameraIndex;
}
// 检查并显示当前选中相机的标定状态
if (m_currentCameraIndex >= 0) {
checkAndDisplayCalibrationStatus(m_currentCameraIndex);
}
}
}
void DialogCameraLevel::on_btn_level_calc_clicked()
2026-03-14 10:00:15 +08:00
{
ui->label_level_result->setAlignment(Qt::AlignLeft);
// 检查是否有可用的相机
if (m_cameraList.empty()) {
QMessageBox::warning(this, "错误", "无可用相机设备!");
return;
}
// 获取选中的相机
int selectedIndex = ui->combo_camera->currentIndex();
if (selectedIndex < 0 || selectedIndex >= static_cast<int>(m_cameraList.size())) {
QMessageBox::warning(this, "错误", "请选择有效的相机!");
return;
}
// 清空之前的结果显示
ui->label_level_result->setText("调平计算中,请稍候...");
// 显示进度提示
ui->btn_level_calc->setEnabled(false);
2026-03-14 10:00:15 +08:00
QApplication::processEvents();
try {
// 执行相机调平
if (performCameraLeveling()) {
// 调平成功
} else {
ui->label_level_result->setText("调平失败!\n\n请检查:\n1. 相机连接是否正常\n2. 地面扫描数据是否充足\n3. 扫描区域是否有足够的地面");
}
} catch (const std::exception& e) {
LOG_ERROR("Camera leveling failed with exception: %s\n", e.what());
QMessageBox::critical(this, "错误", QString("调平过程发生异常:%1").arg(e.what()));
}
// 恢复按钮状态
ui->btn_level_calc->setEnabled(true);
2026-03-14 10:00:15 +08:00
}
void DialogCameraLevel::on_btn_close_clicked()
2026-03-14 10:00:15 +08:00
{
reject();
}
bool DialogCameraLevel::performCameraLeveling()
{
try {
// 获取选中的相机索引
int selectedIndex = ui->combo_camera->currentIndex();
// 先检查索引有效性,再设置回调
if (selectedIndex < 0 || selectedIndex >= static_cast<int>(m_cameraList.size())) {
LOG_ERROR("Invalid camera index: %d\n", selectedIndex);
return false;
}
LOG_INFO("Performing camera leveling with camera %d (index %d)\n", selectedIndex + 1, selectedIndex);
// 1. 设置调平状态回调(在索引检查之后)
setLevelingStatusCallback();
// 2. 清空之前的扫描数据
clearScanDataCache();
// 3. 启动相机扫描地面数据
if (!startCameraScan(selectedIndex)) {
LOG_ERROR("Failed to start camera scan for leveling\n");
restorePresenterStatusCallback(); // 恢复回调
return false;
}
// 4. 等待扫描完成
LOG_INFO("Collecting ground scan data, waiting for swing finish signal...\n");
int waitTime = 0;
const int maxWaitTime = 10000; // 最大等待10秒
const int checkInterval = 100;
while (!m_swingFinished && waitTime < maxWaitTime) {
QThread::msleep(checkInterval);
QApplication::processEvents();
waitTime += checkInterval;
}
// 5. 停止扫描
stopCameraScan(selectedIndex);
if (m_swingFinished) {
LOG_INFO("Camera swing finished signal received, scan completed\n");
} else if (waitTime >= maxWaitTime) {
LOG_WARNING("Timeout waiting for camera swing finish signal\n");
}
// 6. 调用调平算法计算
double planeCalib[9];
double planeHeight;
double invRMatrix[9];
if (!calculatePlaneCalibration(planeCalib, planeHeight, invRMatrix)) {
LOG_ERROR("Failed to calculate plane calibration\n");
restorePresenterStatusCallback(); // 恢复回调
return false;
}
LOG_INFO("Camera leveling calculation completed\n");
// 7. 更新界面显示
updateLevelingResults(planeCalib, planeHeight, invRMatrix);
// 8. 保存结果到配置
int cameraIndex = m_currentCameraIndex + 1; // 转换为1-based索引
QString cameraName;
if (m_currentCameraIndex >= 0 && m_currentCameraIndex < static_cast<int>(m_cameraList.size())) {
cameraName = QString::fromStdString(m_cameraList[m_currentCameraIndex].first);
} else {
cameraName = QString("Camera_%1").arg(cameraIndex);
}
if (!saveLevelingResults(planeCalib, planeHeight, invRMatrix, cameraIndex, cameraName)) {
LOG_ERROR("Failed to save leveling results\n");
restorePresenterStatusCallback(); // 恢复回调
return false;
}
clearScanDataCache();
// 9. 调平完成后恢复回调
restorePresenterStatusCallback();
LOG_INFO("Camera leveling completed successfully\n");
return true;
} catch (const std::exception& e) {
LOG_ERROR("Exception in performCameraLeveling: %s\n", e.what());
restorePresenterStatusCallback(); // 异常时也恢复回调
return false;
}
}
void DialogCameraLevel::updateLevelingResults(double planeCalib[9], double planeHeight, double invRMatrix[9])
{
2026-05-17 00:38:31 +08:00
// 更新地面高度编辑框
ui->edit_plane_height->setText(QString::number(planeHeight, 'f', 2));
2026-05-17 00:38:31 +08:00
2026-03-14 10:00:15 +08:00
// 构建显示文本
QString resultText;
resultText += QString("地面高度: %1 mm\n").arg(QString::number(planeHeight, 'f', 2));
// 调平矩阵
resultText += QString("调平矩阵:\n");
for (int i = 0; i < 3; i++) {
resultText += QString("[%1, %2, %3]\n")
.arg(QString::number(planeCalib[i*3], 'f', 4))
.arg(QString::number(planeCalib[i*3+1], 'f', 4))
.arg(QString::number(planeCalib[i*3+2], 'f', 4));
}
resultText += QString("逆旋转矩阵:\n");
for (int i = 0; i < 3; i++) {
resultText += QString("[%1, %2, %3]\n")
.arg(QString::number(invRMatrix[i*3], 'f', 4))
.arg(QString::number(invRMatrix[i*3+1], 'f', 4))
.arg(QString::number(invRMatrix[i*3+2], 'f', 4));
}
ui->label_level_result->setText(resultText);
ui->label_level_result->setAlignment(Qt::AlignLeft | Qt::AlignTop);
}
bool DialogCameraLevel::startCameraScan(int cameraIndex)
{
if (cameraIndex < 0 || cameraIndex >= static_cast<int>(m_cameraList.size())) {
LOG_ERROR("Invalid camera index for scan: %d\n", cameraIndex);
return false;
}
IVrEyeDevice* camera = m_cameraList[cameraIndex].second;
if (!camera) {
LOG_ERROR("Camera device is null at index: %d\n", cameraIndex);
return false;
}
// 启动相机检测
int result = camera->StartDetect(&DialogCameraLevel::StaticDetectionCallback, keResultDataType_Position, this);
if (result != 0) {
LOG_ERROR("Failed to start camera detection: %d\n", result);
return false;
}
LOG_INFO("Camera scan started successfully for camera index: %d\n", cameraIndex);
return true;
}
bool DialogCameraLevel::stopCameraScan(int cameraIndex)
{
if (cameraIndex < 0 || cameraIndex >= static_cast<int>(m_cameraList.size())) {
LOG_ERROR("Invalid camera index for stop scan: %d\n", cameraIndex);
return false;
}
IVrEyeDevice* camera = m_cameraList[cameraIndex].second;
if (!camera) {
LOG_ERROR("Camera device is null at index: %d\n", cameraIndex);
return false;
}
int result = camera->StopDetect();
if (result != 0) {
LOG_WARNING("Failed to stop camera detection, error: %d\n", result);
return false;
}
LOG_INFO("Camera scan stopped successfully for camera index: %d\n", cameraIndex);
return true;
}
void DialogCameraLevel::StaticDetectionCallback(EVzResultDataType eDataType, SVzLaserLineData* pLaserLinePoint, void* pUserData)
{
DialogCameraLevel* pThis = reinterpret_cast<DialogCameraLevel*>(pUserData);
if (pThis && pLaserLinePoint) {
pThis->DetectionCallback(eDataType, pLaserLinePoint);
}
}
void DialogCameraLevel::StaticStatusCallback(EVzDeviceWorkStatus eStatus, void* pExtData, unsigned int nDataLength, void* pInfoParam)
{
DialogCameraLevel* pThis = reinterpret_cast<DialogCameraLevel*>(pInfoParam);
if (pThis) {
pThis->StatusCallback(eStatus, pExtData, nDataLength, pInfoParam);
}
}
void DialogCameraLevel::StatusCallback(EVzDeviceWorkStatus eStatus, void* pExtData, unsigned int nDataLength, void* pInfoParam)
{
LOG_DEBUG("[Leveling Status Callback] received: status=%d\n", (int)eStatus);
switch (eStatus) {
case EVzDeviceWorkStatus::keDeviceWorkStatus_Device_Swing_Finish:
{
LOG_INFO("[Leveling Status Callback] Camera swing finished, scan completed\n");
m_swingFinished = true;
break;
}
default:
LOG_DEBUG("[Leveling Status Callback] Other status: %d\n", (int)eStatus);
break;
}
}
void DialogCameraLevel::DetectionCallback(EVzResultDataType eDataType, SVzLaserLineData* pLaserLinePoint)
{
if (!pLaserLinePoint) {
LOG_WARNING("[Leveling Callback] pLaserLinePoint is null\n");
return;
}
if (pLaserLinePoint->nPointCount <= 0) {
LOG_WARNING("[Leveling Callback] Point count is zero or negative: %d\n", pLaserLinePoint->nPointCount);
return;
}
if (!pLaserLinePoint->p3DPoint) {
LOG_WARNING("[Leveling Callback] p3DPoint is null\n");
return;
}
// 将数据添加到缓存
std::vector<SVzNL3DPosition> lineData;
lineData.reserve(pLaserLinePoint->nPointCount);
// p3DPoint 是 void*,需要转换为 SVzNL3DPosition*
SVzNL3DPosition* p3DPoints = reinterpret_cast<SVzNL3DPosition*>(pLaserLinePoint->p3DPoint);
for (int i = 0; i < pLaserLinePoint->nPointCount; ++i) {
lineData.push_back(p3DPoints[i]);
}
std::lock_guard<std::mutex> lock(m_scanDataMutex);
m_scanDataCache.push_back(std::move(lineData));
}
bool DialogCameraLevel::calculatePlaneCalibration(double planeCalib[9], double& planeHeight, double invRMatrix[9])
{
std::lock_guard<std::mutex> lock(m_scanDataMutex);
if (m_scanDataCache.empty()) {
LOG_ERROR("No scan data available for plane calibration\n");
return false;
}
LOG_INFO("Calculating plane calibration from %zu scan lines\n", m_scanDataCache.size());
try {
// 调用 wheelArchHeigthMeasure SDK 的调平算法
SSG_planeCalibPara calibResult = wd_horizonCamera_getGroundCalibPara(m_scanDataCache);
// 复制调平矩阵
for (int i = 0; i < 9; i++) {
planeCalib[i] = calibResult.planeCalib[i];
invRMatrix[i] = calibResult.invRMatrix[i];
}
planeHeight = calibResult.planeHeight;
LOG_INFO("Plane calibration calculated successfully\n");
LOG_INFO(" planeHeight: %.3f\n", planeHeight);
LOG_INFO(" planeCalib: [%.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f]\n",
planeCalib[0], planeCalib[1], planeCalib[2],
planeCalib[3], planeCalib[4], planeCalib[5],
planeCalib[6], planeCalib[7], planeCalib[8]);
LOG_INFO(" invRMatrix: [%.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f]\n",
invRMatrix[0], invRMatrix[1], invRMatrix[2],
invRMatrix[3], invRMatrix[4], invRMatrix[5],
invRMatrix[6], invRMatrix[7], invRMatrix[8]);
return true;
} catch (const std::exception& e) {
LOG_ERROR("Exception in calculatePlaneCalibration: %s\n", e.what());
return false;
}
}
void DialogCameraLevel::clearScanDataCache()
{
std::lock_guard<std::mutex> lock(m_scanDataMutex);
LOG_DEBUG("Clearing scan data cache, current size: %zu\n", m_scanDataCache.size());
m_scanDataCache.clear();
LOG_DEBUG("Scan data cache cleared successfully\n");
}
bool DialogCameraLevel::saveLevelingResults(double planeCalib[9], double planeHeight, double invRMatrix[9],
int cameraIndex, const QString& cameraName)
{
try {
if (!m_pConfig || !m_pConfigResult) {
LOG_ERROR("Config is null, cannot save leveling results\n");
return false;
}
if (cameraIndex <= 0) {
LOG_ERROR("Invalid camera index: %d\n", cameraIndex);
return false;
}
if (cameraName.isEmpty()) {
LOG_ERROR("Camera name is empty\n");
return false;
}
// 获取UI中的误差补偿值
double errorCompensation = ui->edit_error_compensation->text().toDouble();
// 获取UI中的修正系数非法或为0时回退为1.0
bool cfOk = false;
double correctionFactor = ui->edit_correction_factor->text().toDouble(&cfOk);
if (!cfOk || correctionFactor == 0.0) {
correctionFactor = 1.0;
2026-03-14 10:00:15 +08:00
}
// 查找已有的调平参数,仅更新调平相关字段,保留 ROI 等其他字段
2026-03-14 10:00:15 +08:00
bool found = false;
for (auto& param : m_pConfigResult->planeCalibParams) {
if (param.cameraIndex == cameraIndex) {
param.cameraName = cameraName.toStdString();
param.planeHeight = planeHeight;
param.isCalibrated = true;
param.errorCompensation = errorCompensation;
param.correctionFactor = correctionFactor;
for (int i = 0; i < 9; i++) {
param.planeCalib[i] = planeCalib[i];
param.invRMatrix[i] = invRMatrix[i];
}
2026-03-14 10:00:15 +08:00
found = true;
break;
}
}
if (!found) {
WheelCameraPlaneCalibParam cameraParam;
cameraParam.cameraIndex = cameraIndex;
cameraParam.cameraName = cameraName.toStdString();
cameraParam.planeHeight = planeHeight;
cameraParam.isCalibrated = true;
cameraParam.errorCompensation = errorCompensation;
cameraParam.correctionFactor = correctionFactor;
for (int i = 0; i < 9; i++) {
cameraParam.planeCalib[i] = planeCalib[i];
cameraParam.invRMatrix[i] = invRMatrix[i];
}
2026-03-14 10:00:15 +08:00
m_pConfigResult->planeCalibParams.push_back(cameraParam);
}
// 保存配置
QString configPath = PathManager::GetInstance().GetConfigFilePath();
bool saveResult = m_pConfig->SaveConfig(configPath.toStdString(), *m_pConfigResult);
if (!saveResult) {
LOG_ERROR("Failed to save config with leveling results\n");
return false;
}
LOG_INFO("Leveling results saved successfully for camera %d (%s)\n",
cameraIndex, cameraName.toUtf8().constData());
LOG_INFO("Plane height: %.3f, Error compensation: %.2f\n", planeHeight, errorCompensation);
return true;
} catch (const std::exception& e) {
LOG_ERROR("Exception in saveLevelingResults: %s\n", e.what());
return false;
}
}
bool DialogCameraLevel::loadCameraCalibrationData(int cameraIndex, const QString& cameraName,
double planeCalib[9], double& planeHeight, double invRMatrix[9])
{
try {
if (!m_pConfigResult) {
LOG_ERROR("Config result is null, cannot load calibration data\n");
return false;
}
// 查找对应相机的调平参数
for (const auto& param : m_pConfigResult->planeCalibParams) {
if (param.cameraIndex == cameraIndex && param.isCalibrated) {
for (int i = 0; i < 9; i++) {
planeCalib[i] = param.planeCalib[i];
invRMatrix[i] = param.invRMatrix[i];
}
planeHeight = param.planeHeight;
2026-05-17 00:38:31 +08:00
// 加载该相机的地面高度和误差补偿值到UI
ui->edit_plane_height->setText(QString::number(param.planeHeight, 'f', 2));
2026-03-14 10:00:15 +08:00
ui->edit_error_compensation->setText(QString::number(param.errorCompensation, 'f', 1));
LOG_INFO("Calibration data loaded successfully for camera %d (%s)\n",
cameraIndex, cameraName.toUtf8().constData());
return true;
}
}
// 没有找到标定数据时,设置默认误差补偿值
ui->edit_error_compensation->setText(QString::number(-5.0, 'f', 1));
LOG_INFO("No calibration data found for camera %d (%s)\n",
cameraIndex, cameraName.toUtf8().constData());
return false;
} catch (const std::exception& e) {
LOG_ERROR("Exception in loadCameraCalibrationData: %s\n", e.what());
return false;
}
}
void DialogCameraLevel::checkAndDisplayCalibrationStatus(int cameraIndex)
{
if (cameraIndex < 0 || cameraIndex >= static_cast<int>(m_cameraList.size())) {
LOG_WARNING("Invalid camera index for status check: %d\n", cameraIndex);
ui->label_level_result->setText("无效的相机索引");
ui->label_level_result->setAlignment(Qt::AlignCenter);
return;
}
QString cameraName = QString::fromStdString(m_cameraList[cameraIndex].first);
int configCameraIndex = cameraIndex + 1;
double planeCalib[9];
double planeHeight;
double invRMatrix[9];
if (loadCameraCalibrationData(configCameraIndex, cameraName, planeCalib, planeHeight, invRMatrix)) {
// 有标定数据,显示
LOG_INFO("Displaying existing calibration data for camera %s\n", cameraName.toUtf8().constData());
updateLevelingResults(planeCalib, planeHeight, invRMatrix);
} else {
2026-05-17 00:38:31 +08:00
// 没有标定数据,显示未标定
LOG_INFO("No calibration data found for camera %s, showing uncalibrated\n", cameraName.toUtf8().constData());
ui->edit_plane_height->setText("-1.00");
2026-05-17 00:38:31 +08:00
ui->label_level_result->setText(QString("相机: %1\n\n未标定").arg(cameraName));
2026-03-14 10:00:15 +08:00
ui->label_level_result->setAlignment(Qt::AlignCenter);
}
}
void DialogCameraLevel::on_combo_camera_currentIndexChanged(int index)
{
m_currentCameraIndex = index;
if (index >= 0 && index < static_cast<int>(m_cameraList.size())) {
LOG_INFO("Camera selection changed to index: %d (%s)\n", index,
QString::fromStdString(m_cameraList[index].first).toUtf8().constData());
checkAndDisplayCalibrationStatus(m_currentCameraIndex);
loadCameraRoiRange(m_currentCameraIndex);
} else {
LOG_WARNING("Invalid camera index selected: %d\n", index);
ui->label_level_result->setText("无效的相机选择");
ui->label_level_result->setAlignment(Qt::AlignCenter);
}
}
void DialogCameraLevel::setLevelingStatusCallback()
{
if (!m_presenter) {
LOG_ERROR("Presenter is null, cannot set leveling status callback\n");
return;
}
m_presenter->SetCameraStatusCallback(&DialogCameraLevel::StaticStatusCallback, this);
m_swingFinished = false;
m_callbackRestored = false;
LOG_INFO("Leveling status callback set for all cameras\n");
}
void DialogCameraLevel::restorePresenterStatusCallback()
{
if (m_callbackRestored.exchange(true)) {
LOG_DEBUG("Presenter status callback already restored, skipping\n");
return;
}
if (!m_presenter) {
LOG_ERROR("Presenter is null, cannot restore status callback\n");
return;
}
m_presenter->SetCameraStatusCallback(&WheelMeasurePresenter::_StaticCameraNotify, m_presenter);
LOG_INFO("Presenter status callback restored for all cameras\n");
}
void DialogCameraLevel::on_btn_save_compensation_clicked()
{
// 保存误差补偿值到当前相机的调平参数
if (!m_pConfig || !m_pConfigResult) {
LOG_ERROR("Config is null, cannot save error compensation\n");
return;
}
if (m_currentCameraIndex < 0 || m_currentCameraIndex >= static_cast<int>(m_cameraList.size())) {
LOG_WARNING("Invalid camera index: %d\n", m_currentCameraIndex);
return;
}
// 获取UI中的误差补偿值
double errorCompensation = ui->edit_error_compensation->text().toDouble();
int cameraIndex = m_currentCameraIndex + 1; // 转换为1-based索引
QString cameraName = QString::fromStdString(m_cameraList[m_currentCameraIndex].first);
LOG_INFO("Saving error compensation for camera %d (%s): %.2f\n",
cameraIndex, cameraName.toUtf8().constData(), errorCompensation);
// 查找或创建相机调平参数
bool found = false;
for (auto& param : m_pConfigResult->planeCalibParams) {
if (param.cameraIndex == cameraIndex) {
param.errorCompensation = errorCompensation;
found = true;
break;
}
}
// 如果没有找到现有记录,创建一个新的(仅包含误差补偿,其他值为默认)
if (!found) {
WheelCameraPlaneCalibParam newParam;
newParam.cameraIndex = cameraIndex;
newParam.cameraName = cameraName.toStdString();
newParam.errorCompensation = errorCompensation;
newParam.isCalibrated = false; // 尚未标定
m_pConfigResult->planeCalibParams.push_back(newParam);
}
// 保存配置到文件
QString configPath = PathManager::GetInstance().GetConfigFilePath();
bool saveResult = m_pConfig->SaveConfig(configPath.toStdString(), *m_pConfigResult);
if (saveResult) {
LOG_INFO("Error compensation saved successfully for camera %d: %.2f\n", cameraIndex, errorCompensation);
} else {
LOG_ERROR("Failed to save error compensation\n");
}
}
void DialogCameraLevel::on_btn_save_plane_height_clicked()
2026-05-17 00:38:31 +08:00
{
if (!m_pConfig || !m_pConfigResult) {
LOG_ERROR("Config is null, cannot save plane height\n");
return;
}
if (m_currentCameraIndex < 0 || m_currentCameraIndex >= static_cast<int>(m_cameraList.size())) {
LOG_WARNING("Invalid camera index: %d\n", m_currentCameraIndex);
return;
}
double planeHeight = ui->edit_plane_height->text().toDouble();
2026-05-17 00:38:31 +08:00
int cameraIndex = m_currentCameraIndex + 1;
// 查找或创建相机调平参数
bool found = false;
for (auto& param : m_pConfigResult->planeCalibParams) {
if (param.cameraIndex == cameraIndex) {
param.planeHeight = planeHeight;
param.isCalibrated = true;
found = true;
break;
}
}
if (!found) {
WheelCameraPlaneCalibParam newParam;
newParam.cameraIndex = cameraIndex;
newParam.planeHeight = planeHeight;
newParam.isCalibrated = true;
m_pConfigResult->planeCalibParams.push_back(newParam);
}
// 保存配置到文件
QString configPath = PathManager::GetInstance().GetConfigFilePath();
bool saveResult = m_pConfig->SaveConfig(configPath.toStdString(), *m_pConfigResult);
if (saveResult) {
LOG_INFO("Plane height saved successfully for camera %d: %.2f\n", cameraIndex, planeHeight);
// 刷新界面显示
checkAndDisplayCalibrationStatus(m_currentCameraIndex);
} else {
LOG_ERROR("Failed to save plane height\n");
}
}
2026-03-14 10:00:15 +08:00
void DialogCameraLevel::loadCameraRoiRange(int cameraIndex)
{
if (!m_pConfig || !m_pConfigResult) {
LOG_WARNING("Config is null, cannot load ROI range\n");
return;
}
if (cameraIndex < 0 || cameraIndex >= static_cast<int>(m_cameraList.size())) {
LOG_WARNING("Invalid camera index: %d\n", cameraIndex);
return;
}
int configCameraIndex = cameraIndex + 1; // 转换为1-based索引
// 查找相机的调平参数
for (const auto& param : m_pConfigResult->planeCalibParams) {
if (param.cameraIndex == configCameraIndex) {
// 加载ROI范围到UI
ui->edit_roi_x_min->setText(QString::number(param.wheelRoi3d_xMin, 'f', 1));
ui->edit_roi_x_max->setText(QString::number(param.wheelRoi3d_xMax, 'f', 1));
ui->edit_roi_y_min->setText(QString::number(param.wheelRoi3d_yMin, 'f', 1));
ui->edit_roi_y_max->setText(QString::number(param.wheelRoi3d_yMax, 'f', 1));
ui->edit_roi_z_min->setText(QString::number(param.wheelRoi3d_zMin, 'f', 1));
ui->edit_roi_z_max->setText(QString::number(param.wheelRoi3d_zMax, 'f', 1));
// 加载修正系数
ui->edit_correction_factor->setText(QString::number(param.correctionFactor, 'f', 8));
LOG_INFO("Loaded ROI range for camera %d: X[%.1f, %.1f], Y[%.1f, %.1f], Z[%.1f, %.1f], correctionFactor=%.8f\n",
2026-03-14 10:00:15 +08:00
configCameraIndex,
param.wheelRoi3d_xMin, param.wheelRoi3d_xMax,
param.wheelRoi3d_yMin, param.wheelRoi3d_yMax,
param.wheelRoi3d_zMin, param.wheelRoi3d_zMax,
param.correctionFactor);
2026-03-14 10:00:15 +08:00
return;
}
}
// 如果没有找到,使用默认值
ui->edit_roi_x_min->setText("-1000.0");
ui->edit_roi_x_max->setText("1000.0");
ui->edit_roi_y_min->setText("-1000.0");
ui->edit_roi_y_max->setText("1000.0");
ui->edit_roi_z_min->setText("-1000.0");
ui->edit_roi_z_max->setText("1000.0");
ui->edit_correction_factor->setText("1.00000000");
2026-03-14 10:00:15 +08:00
LOG_INFO("No ROI range found for camera %d, using default values\n", configCameraIndex);
}
void DialogCameraLevel::saveCameraRoiRange()
{
if (!m_pConfig || !m_pConfigResult) {
LOG_ERROR("Config is null, cannot save ROI range\n");
QMessageBox::warning(this, "错误", "配置对象为空无法保存ROI范围");
return;
}
if (m_currentCameraIndex < 0 || m_currentCameraIndex >= static_cast<int>(m_cameraList.size())) {
LOG_WARNING("Invalid camera index: %d\n", m_currentCameraIndex);
QMessageBox::warning(this, "错误", "请先选择相机");
return;
}
// 获取UI中的ROI范围值
double xMin = ui->edit_roi_x_min->text().toDouble();
double xMax = ui->edit_roi_x_max->text().toDouble();
double yMin = ui->edit_roi_y_min->text().toDouble();
double yMax = ui->edit_roi_y_max->text().toDouble();
double zMin = ui->edit_roi_z_min->text().toDouble();
double zMax = ui->edit_roi_z_max->text().toDouble();
// 验证范围有效性
if (xMin >= xMax || yMin >= yMax || zMin >= zMax) {
QMessageBox::warning(this, "错误", "ROI范围无效最小值必须小于最大值");
return;
}
// 获取UI中的修正系数非法或为0时回退为1.0
bool cfOk = false;
double correctionFactor = ui->edit_correction_factor->text().toDouble(&cfOk);
if (!cfOk || correctionFactor == 0.0) {
correctionFactor = 1.0;
}
2026-03-14 10:00:15 +08:00
int cameraIndex = m_currentCameraIndex + 1; // 转换为1-based索引
QString cameraName = QString::fromStdString(m_cameraList[m_currentCameraIndex].first);
LOG_INFO("Saving ROI range for camera %d (%s): X[%.1f, %.1f], Y[%.1f, %.1f], Z[%.1f, %.1f], correctionFactor=%.8f\n",
2026-03-14 10:00:15 +08:00
cameraIndex, cameraName.toUtf8().constData(),
xMin, xMax, yMin, yMax, zMin, zMax, correctionFactor);
2026-03-14 10:00:15 +08:00
// 查找或创建相机调平参数
bool found = false;
for (auto& param : m_pConfigResult->planeCalibParams) {
if (param.cameraIndex == cameraIndex) {
param.wheelRoi3d_xMin = xMin;
param.wheelRoi3d_xMax = xMax;
param.wheelRoi3d_yMin = yMin;
param.wheelRoi3d_yMax = yMax;
param.wheelRoi3d_zMin = zMin;
param.wheelRoi3d_zMax = zMax;
param.correctionFactor = correctionFactor;
2026-03-14 10:00:15 +08:00
found = true;
break;
}
}
// 如果没有找到现有记录,创建一个新的
if (!found) {
WheelCameraPlaneCalibParam newParam;
newParam.cameraIndex = cameraIndex;
newParam.cameraName = cameraName.toStdString();
newParam.wheelRoi3d_xMin = xMin;
newParam.wheelRoi3d_xMax = xMax;
newParam.wheelRoi3d_yMin = yMin;
newParam.wheelRoi3d_yMax = yMax;
newParam.wheelRoi3d_zMin = zMin;
newParam.wheelRoi3d_zMax = zMax;
newParam.correctionFactor = correctionFactor;
2026-03-14 10:00:15 +08:00
newParam.isCalibrated = false; // 尚未标定
m_pConfigResult->planeCalibParams.push_back(newParam);
}
// 保存配置到文件
QString configPath = PathManager::GetInstance().GetConfigFilePath();
bool saveResult = m_pConfig->SaveConfig(configPath.toStdString(), *m_pConfigResult);
if (saveResult) {
LOG_INFO("ROI range saved successfully for camera %d\n", cameraIndex);
QMessageBox::information(this, "成功", "ROI范围和修正系数已保存");
2026-03-14 10:00:15 +08:00
} else {
LOG_ERROR("Failed to save ROI range\n");
QMessageBox::critical(this, "错误", "保存失败");
2026-03-14 10:00:15 +08:00
}
}
void DialogCameraLevel::on_btn_save_roi_clicked()
{
saveCameraRoiRange();
}
bool DialogCameraLevel::performCorrectionMeasurement(double& measuredLength)
{
measuredLength = 0.0;
int selectedIndex = ui->combo_camera->currentIndex();
if (selectedIndex < 0 || selectedIndex >= static_cast<int>(m_cameraList.size())) {
LOG_ERROR("Invalid camera index for correction: %d\n", selectedIndex);
return false;
}
if (!m_pConfigResult) {
LOG_ERROR("Config result is null, cannot perform correction measurement\n");
return false;
}
// 解析 UI 中的标定块参数
bool ok = false;
double nominalLen = ui->edit_calib_nominal_length->text().toDouble(&ok);
if (!ok || nominalLen <= 0.0) {
QMessageBox::warning(this, "错误", "标定块标称值无效请输入大于0的数值");
return false;
}
double yMin = ui->edit_calib_y_min->text().toDouble();
double yMax = ui->edit_calib_y_max->text().toDouble();
if (yMin >= yMax) {
QMessageBox::warning(this, "错误", "标定块Y范围无效最小值必须小于最大值");
return false;
}
// 1. 设置状态回调(复用调平回调,监听摆动完成)
setLevelingStatusCallback();
// 2. 清空之前的扫描数据
clearScanDataCache();
// 3. 启动相机扫描
if (!startCameraScan(selectedIndex)) {
LOG_ERROR("Failed to start camera scan for correction\n");
restorePresenterStatusCallback();
return false;
}
// 4. 等待扫描完成
LOG_INFO("Collecting calibration block scan data, waiting for swing finish signal...\n");
int waitTime = 0;
const int maxWaitTime = 10000;
const int checkInterval = 100;
while (!m_swingFinished && waitTime < maxWaitTime) {
QThread::msleep(checkInterval);
QApplication::processEvents();
waitTime += checkInterval;
}
// 5. 停止扫描
stopCameraScan(selectedIndex);
restorePresenterStatusCallback();
if (!m_swingFinished) {
LOG_WARNING("Timeout waiting for camera swing finish signal in correction\n");
}
// 6. 准备算法参数(从 ConfigResult 取得)
SSG_outlierFilterParam filterParam;
filterParam.continuityTh = m_pConfigResult->algorithmParams.filterParam.continuityTh;
filterParam.outlierTh = m_pConfigResult->algorithmParams.filterParam.outlierTh;
SSG_treeGrowParam growParam;
growParam.yDeviation_max = m_pConfigResult->algorithmParams.growParam.yDeviation_max;
growParam.zDeviation_max = m_pConfigResult->algorithmParams.growParam.zDeviation_max;
growParam.maxLineSkipNum = m_pConfigResult->algorithmParams.growParam.maxLineSkipNum;
growParam.maxSkipDistance = m_pConfigResult->algorithmParams.growParam.maxSkipDistance;
growParam.minLTypeTreeLen = m_pConfigResult->algorithmParams.growParam.minLTypeTreeLen;
growParam.minVTypeTreeLen = m_pConfigResult->algorithmParams.growParam.minVTypeTreeLen;
SVzNLRangeD yRange;
yRange.min = yMin;
yRange.max = yMax;
// 7. 调用算法计算标定块测量长度
int errCode = 0;
double length = 0.0;
{
std::lock_guard<std::mutex> lock(m_scanDataMutex);
if (m_scanDataCache.empty()) {
LOG_ERROR("No scan data available for correction measurement\n");
return false;
}
LOG_INFO("Calling wd_getCalibrationBlockLength with %zu scan lines, nominalLen=%.3f, Y[%.1f, %.1f]\n",
m_scanDataCache.size(), nominalLen, yMin, yMax);
#if _OUTPUT_DEBUG_DATA
std::vector<std::vector<SVzNL3DPosition>> debugData;
length = wd_getCalibrationBlockLength(
m_scanDataCache, nominalLen, filterParam, growParam, yRange, debugData, &errCode);
#else
length = wd_getCalibrationBlockLength(
m_scanDataCache, nominalLen, filterParam, growParam, yRange, &errCode);
#endif
}
LOG_INFO("Calibration block length measurement: %.3f, errCode=%d\n", length, errCode);
clearScanDataCache();
if (errCode != 0 || length <= 0.0) {
LOG_ERROR("Calibration block measurement failed, errCode=%d, length=%.3f\n", errCode, length);
return false;
}
measuredLength = length;
return true;
}
void DialogCameraLevel::on_btn_start_correction_clicked()
{
if (m_cameraList.empty()) {
QMessageBox::warning(this, "错误", "无可用相机设备!");
return;
}
int selectedIndex = ui->combo_camera->currentIndex();
if (selectedIndex < 0 || selectedIndex >= static_cast<int>(m_cameraList.size())) {
QMessageBox::warning(this, "错误", "请选择有效的相机!");
return;
}
// 扫描次数仅由配置文件控制WheelMeasureConfigResult::correctionScanCount
// 读不到配置或配置值不合理时统一回退到默认 10
constexpr int kDefaultScanCount = 10;
constexpr int kMaxScanCount = 50;
int scanCount = kDefaultScanCount;
if (m_pConfigResult && m_pConfigResult->correctionScanCount > 0
&& m_pConfigResult->correctionScanCount <= kMaxScanCount) {
scanCount = m_pConfigResult->correctionScanCount;
} else if (m_pConfigResult) {
LOG_WARNING("correctionScanCount=%d in config is invalid, fallback to %d\n",
m_pConfigResult->correctionScanCount, kDefaultScanCount);
}
LOG_INFO("Correction measurement will scan %d times (from config)\n", scanCount);
const QString originalBtnText = ui->btn_start_correction->text();
ui->btn_start_correction->setEnabled(false);
ui->btn_apply_correction->setEnabled(false);
ui->btn_start_correction->setText("正在测量...");
QApplication::processEvents();
try {
std::vector<double> validMeasurements;
validMeasurements.reserve(scanCount);
for (int i = 0; i < scanCount; ++i) {
ui->btn_start_correction->setText(QString("正在测量 %1/%2...").arg(i + 1).arg(scanCount));
QApplication::processEvents();
double singleLength = 0.0;
if (performCorrectionMeasurement(singleLength)) {
LOG_INFO("Correction measurement %d/%d: %.3f\n", i + 1, scanCount, singleLength);
validMeasurements.push_back(singleLength);
} else {
LOG_WARNING("Correction measurement %d/%d failed, skipped\n", i + 1, scanCount);
}
}
if (validMeasurements.empty()) {
QMessageBox::warning(this, "错误",
QString("标定块测量失败!%1次扫描均未获得有效结果。\n\n请检查:\n1. 相机连接是否正常\n2. 标定块是否放置在Y范围内\n3. 扫描数据是否充足").arg(scanCount));
} else {
// 求平均值
double sum = 0.0;
for (double v : validMeasurements) {
sum += v;
}
double averageLength = sum / validMeasurements.size();
// 计算修正系数 = 凹槽间距(标称) / 平均测量值(算法输出为凹槽间距)
double grooveSpacing = ui->edit_calib_groove_spacing->text().toDouble();
if (grooveSpacing <= 0.0) {
LOG_WARNING("Groove spacing is invalid (%.3f), skip correction factor calculation\n", grooveSpacing);
} else if (averageLength > 0.0) {
double correctionFactor = grooveSpacing / averageLength;
ui->edit_correction_factor->setText(QString::number(correctionFactor, 'f', 8));
LOG_INFO("Correction factor calculated: %.8f (grooveSpacing=%.3f / avg=%.3f, valid=%zu/%d)\n",
correctionFactor, grooveSpacing, averageLength, validMeasurements.size(), scanCount);
} else {
LOG_WARNING("Average length is zero or negative, skip correction factor calculation\n");
}
if (static_cast<int>(validMeasurements.size()) < scanCount) {
QMessageBox::information(this, "提示",
QString("共扫描 %1 次,有效结果 %2 次,已按有效结果求平均值")
.arg(scanCount).arg(validMeasurements.size()));
}
}
} catch (const std::exception& e) {
LOG_ERROR("Exception in on_btn_start_correction_clicked: %s\n", e.what());
QMessageBox::critical(this, "错误", QString("测量过程发生异常:%1").arg(e.what()));
restorePresenterStatusCallback();
}
ui->btn_start_correction->setText(originalBtnText);
ui->btn_start_correction->setEnabled(true);
ui->btn_apply_correction->setEnabled(true);
}
void DialogCameraLevel::on_btn_apply_correction_clicked()
{
if (!m_pConfig || !m_pConfigResult) {
LOG_ERROR("Config is null, cannot save correction factor\n");
return;
}
if (m_currentCameraIndex < 0 || m_currentCameraIndex >= static_cast<int>(m_cameraList.size())) {
QMessageBox::warning(this, "错误", "请先选择相机");
return;
}
// 从 UI 读取修正系数
bool cfOk = false;
double correctionFactor = ui->edit_correction_factor->text().toDouble(&cfOk);
if (!cfOk || correctionFactor == 0.0) {
QMessageBox::warning(this, "错误", "修正系数无效,请先执行测量或手动输入有效值");
return;
}
int cameraIndex = m_currentCameraIndex + 1;
QString cameraName = QString::fromStdString(m_cameraList[m_currentCameraIndex].first);
LOG_INFO("Applying correction factor for camera %d (%s): %.8f\n",
cameraIndex, cameraName.toUtf8().constData(), correctionFactor);
bool found = false;
for (auto& param : m_pConfigResult->planeCalibParams) {
if (param.cameraIndex == cameraIndex) {
param.correctionFactor = correctionFactor;
found = true;
break;
}
}
if (!found) {
WheelCameraPlaneCalibParam newParam;
newParam.cameraIndex = cameraIndex;
newParam.cameraName = cameraName.toStdString();
newParam.correctionFactor = correctionFactor;
newParam.isCalibrated = false;
m_pConfigResult->planeCalibParams.push_back(newParam);
}
QString configPath = PathManager::GetInstance().GetConfigFilePath();
bool saveResult = m_pConfig->SaveConfig(configPath.toStdString(), *m_pConfigResult);
if (saveResult) {
LOG_INFO("Correction factor saved successfully for camera %d: %.8f\n", cameraIndex, correctionFactor);
QMessageBox::information(this, "成功", QString("修正系数已保存:%1").arg(QString::number(correctionFactor, 'f', 8)));
} else {
LOG_ERROR("Failed to save correction factor\n");
QMessageBox::critical(this, "错误", "保存失败");
}
}