From efd8a7cc20d3571992cf77197b7b6a35cc1786bf Mon Sep 17 00:00:00 2001 From: GloamXun Date: Wed, 14 Jan 2026 18:07:26 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20v0.1.0=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 12 + CMakeLists.txt | 160 +++ README.md | 277 +++++ build_installer.bat | 51 + configure.bat | 62 ++ include/1225pc_viewer.h | 17 + include/1225raw_image_viewer.h | 42 + include/core/GVSPParser.h | 121 +++ include/core/Logger.h | 41 + include/core/PointCloudProcessor.h | 64 ++ include/dkam_asyncqueue.h | 143 +++ include/dkam_base_type.h | 308 ++++++ include/dkam_camera_error.h | 130 +++ include/dkam_common_socket.h | 32 + include/dkam_discovery.h | 54 + include/dkam_ftpserver.h | 50 + include/dkam_gige_camera.h | 385 +++++++ include/dkam_gige_stream.h | 258 +++++ include/dkam_log.h | 41 + include/dkam_zhicamera_api.h | 394 +++++++ include/dkam_zhicamera_api_csharp.h | 93 ++ include/gui/PointCloudGLWidget.h | 68 ++ include/gui/PointCloudWidget.h | 27 + installer/BinFiles.wxs | 173 ++++ installer/UpperControl.wxs | 79 ++ resources/app_icon.ico | Bin 0 -> 5105 bytes resources/app_icon.rc | 1 + resources/convert_to_ico.py | 16 + resources/generate_icons.py | 85 ++ resources/icons/app_icon.png | Bin 0 -> 599 bytes resources/icons/camera.png | Bin 0 -> 442 bytes resources/icons/clear.png | Bin 0 -> 437 bytes resources/icons/connect.png | Bin 0 -> 316 bytes resources/icons/folder.png | Bin 0 -> 192 bytes resources/icons/refresh.png | Bin 0 -> 382 bytes resources/icons/save.png | Bin 0 -> 206 bytes resources/icons/start.png | Bin 0 -> 331 bytes resources/icons/stop.png | Bin 0 -> 180 bytes resources/resources.qrc | 13 + src/config/ConfigManager.cpp | 106 ++ src/config/ConfigManager.h | 49 + src/core/DeviceScanner.cpp | 183 ++++ src/core/DeviceScanner.h | 62 ++ src/core/DeviceScanner_getLocalSubnet.cpp | 59 ++ src/core/GVSPParser.cpp | 229 +++++ src/core/Logger.cpp | 130 +++ src/core/NetworkManager.cpp | 174 ++++ src/core/NetworkManager.h | 55 + src/core/PointCloudProcessor.cpp | 277 +++++ src/gui/MainWindow.cpp | 1138 +++++++++++++++++++++ src/gui/MainWindow.h | 159 +++ src/gui/MainWindow_data_handler.cpp | 9 + src/gui/PointCloudGLWidget.cpp | 259 +++++ src/gui/PointCloudWidget.cpp | 56 + src/main.cpp | 58 ++ 55 files changed, 6200 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 build_installer.bat create mode 100644 configure.bat create mode 100644 include/1225pc_viewer.h create mode 100644 include/1225raw_image_viewer.h create mode 100644 include/core/GVSPParser.h create mode 100644 include/core/Logger.h create mode 100644 include/core/PointCloudProcessor.h create mode 100644 include/dkam_asyncqueue.h create mode 100644 include/dkam_base_type.h create mode 100644 include/dkam_camera_error.h create mode 100644 include/dkam_common_socket.h create mode 100644 include/dkam_discovery.h create mode 100644 include/dkam_ftpserver.h create mode 100644 include/dkam_gige_camera.h create mode 100644 include/dkam_gige_stream.h create mode 100644 include/dkam_log.h create mode 100644 include/dkam_zhicamera_api.h create mode 100644 include/dkam_zhicamera_api_csharp.h create mode 100644 include/gui/PointCloudGLWidget.h create mode 100644 include/gui/PointCloudWidget.h create mode 100644 installer/BinFiles.wxs create mode 100644 installer/UpperControl.wxs create mode 100644 resources/app_icon.ico create mode 100644 resources/app_icon.rc create mode 100644 resources/convert_to_ico.py create mode 100644 resources/generate_icons.py create mode 100644 resources/icons/app_icon.png create mode 100644 resources/icons/camera.png create mode 100644 resources/icons/clear.png create mode 100644 resources/icons/connect.png create mode 100644 resources/icons/folder.png create mode 100644 resources/icons/refresh.png create mode 100644 resources/icons/save.png create mode 100644 resources/icons/start.png create mode 100644 resources/icons/stop.png create mode 100644 resources/resources.qrc create mode 100644 src/config/ConfigManager.cpp create mode 100644 src/config/ConfigManager.h create mode 100644 src/core/DeviceScanner.cpp create mode 100644 src/core/DeviceScanner.h create mode 100644 src/core/DeviceScanner_getLocalSubnet.cpp create mode 100644 src/core/GVSPParser.cpp create mode 100644 src/core/Logger.cpp create mode 100644 src/core/NetworkManager.cpp create mode 100644 src/core/NetworkManager.h create mode 100644 src/core/PointCloudProcessor.cpp create mode 100644 src/gui/MainWindow.cpp create mode 100644 src/gui/MainWindow.h create mode 100644 src/gui/MainWindow_data_handler.cpp create mode 100644 src/gui/PointCloudGLWidget.cpp create mode 100644 src/gui/PointCloudWidget.cpp create mode 100644 src/main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aceeeea --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +.vscode/** +bin/** +build/** + +installer/output/ +installer/*.wixobj +installer/*.msi +installer/*.wixpdb + +*/.venv + +CLAUDE.md \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..03269a7 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,160 @@ +cmake_minimum_required(VERSION 3.15) +project(D330Viewer VERSION 1.0.0 LANGUAGES CXX C) + +# 设置C++标准 +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Qt6特定配置 +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) + +# 设置输出目录 +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_SOURCE_DIR}/bin) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_SOURCE_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG ${CMAKE_SOURCE_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE ${CMAKE_SOURCE_DIR}/bin) + +# 添加include目录 +include_directories(${CMAKE_SOURCE_DIR}/include) +include_directories(${CMAKE_SOURCE_DIR}/src) + +# ==================== 查找依赖库 ==================== +# 查找Qt6 +find_package(Qt6 REQUIRED COMPONENTS + Core + Widgets + OpenGL + OpenGLWidgets + Network + Concurrent +) + +# 查找PCL +find_package(PCL REQUIRED COMPONENTS common io visualization) +if(PCL_FOUND) + include_directories(${PCL_INCLUDE_DIRS}) + link_directories(${PCL_LIBRARY_DIRS}) + add_definitions(${PCL_DEFINITIONS}) + message(STATUS "PCL found: ${PCL_VERSION}") +endif() + +# 查找OpenCV +find_package(OpenCV REQUIRED COMPONENTS core highgui imgproc) +if(OpenCV_FOUND) + include_directories(${OpenCV_INCLUDE_DIRS}) + message(STATUS "OpenCV found: ${OpenCV_VERSION}") +endif() + +# 查找OpenCL +find_package(OpenCL REQUIRED) +if(OpenCL_FOUND) + include_directories(${OpenCL_INCLUDE_DIRS}) + message(STATUS "OpenCL found") +endif() + +# ==================== 源文件配置 ==================== +set(GUI_SOURCES + src/main.cpp + src/gui/MainWindow.cpp + src/gui/PointCloudWidget.cpp + src/gui/PointCloudGLWidget.cpp +) + +set(CONFIG_SOURCES + src/config/ConfigManager.cpp +) +set(CORE_SOURCES + src/core/NetworkManager.cpp + src/core/DeviceScanner.cpp + src/core/GVSPParser.cpp + src/core/Logger.cpp + src/core/PointCloudProcessor.cpp +) +set(HEADERS + src/gui/MainWindow.h + include/gui/PointCloudWidget.h + include/gui/PointCloudGLWidget.h + src/config/ConfigManager.h + src/core/NetworkManager.h + src/core/DeviceScanner.h + include/core/Logger.h + include/core/GVSPParser.h + include/core/PointCloudProcessor.h +) + +# 资源文件 +set(RESOURCES + resources/resources.qrc + resources/app_icon.rc +) + +# 创建可执行文件 +add_executable(${PROJECT_NAME} WIN32 + ${GUI_SOURCES} + ${CONFIG_SOURCES} + ${CORE_SOURCES} + ${HEADERS} + ${RESOURCES} +) + +# 链接库 +target_link_libraries(${PROJECT_NAME} + Qt6::Core + Qt6::Widgets + Qt6::OpenGL + Qt6::OpenGLWidgets + Qt6::Network + Qt6::Concurrent + ${PCL_LIBRARIES} + ${OpenCV_LIBS} + ${OpenCL_LIBRARIES} + ws2_32 +) + +# Windows特定配置 +if(WIN32) + target_compile_definitions(${PROJECT_NAME} PRIVATE + _WINSOCK_DEPRECATED_NO_WARNINGS + _CRT_SECURE_NO_WARNINGS + ) +endif() + +# ==================== 安装配置 ==================== +# 安装可执行文件 +install(TARGETS ${PROJECT_NAME} + RUNTIME DESTINATION bin +) + +# 安装所有DLL +install(DIRECTORY ${CMAKE_SOURCE_DIR}/bin/ + DESTINATION bin + FILES_MATCHING PATTERN "*.dll" +) + +# 安装Qt平台插件 +install(DIRECTORY ${CMAKE_SOURCE_DIR}/bin/platforms/ + DESTINATION bin/platforms + FILES_MATCHING PATTERN "*.dll" +) + +# ==================== CPack配置 - MSI安装程序 ==================== +set(CPACK_PACKAGE_NAME "D330Viewer") +set(CPACK_PACKAGE_VENDOR "Lorenzo Zhao") +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "D330M Depth Camera Control System") +set(CPACK_PACKAGE_VERSION "0.1.0") +set(CPACK_PACKAGE_VERSION_MAJOR "0") +set(CPACK_PACKAGE_VERSION_MINOR "1") +set(CPACK_PACKAGE_VERSION_PATCH "0") +set(CPACK_PACKAGE_INSTALL_DIRECTORY "D330Viewer") + +# WiX生成器配置(用于MSI) +set(CPACK_GENERATOR "WIX") +set(CPACK_WIX_UPGRADE_GUID "12345678-1234-1234-1234-123456789012") +set(CPACK_WIX_PROGRAM_MENU_FOLDER "D330Viewer") + +# 包含CPack模块 +include(CPack) diff --git a/README.md b/README.md new file mode 100644 index 0000000..60e181d --- /dev/null +++ b/README.md @@ -0,0 +1,277 @@ +# D330Viewer - D330M深度相机控制系统 + +## 项目简介 + +D330Viewer 是一个基于Qt6的深度相机控制和可视化系统,用于D330M深度相机的实时数据采集、处理和显示。系统采用OpenCL GPU加速进行点云计算,支持实时显示深度图像和3D点云。 + +## 技术栈 + +### 核心库版本 +- **Qt6**: 6.10.1 (msvc2022_64) +- **PCL**: 1.15.1 (Point Cloud Library) +- **OpenCV**: 4.13.0 +- **OpenCL**: 3.0 (GPU加速) +- **DkamSDK**: 1.6.83 (D330M相机SDK) + +### 开发环境 +- **操作系统**: Windows 10/11 (64位) +- **编译器**: Visual Studio 2026 (MSVC) +- **构建工具**: CMake 3.15+ +- **GPU**: 支持OpenCL的GPU(NVIDIA/AMD/Intel) + +## 快速开始 + +### 1. 环境准备 + +确保已安装以下软件: +- Visual Studio 2022 以上(包含C++桌面开发工作负载) +- CMake 3.15或更高版本 +- 支持OpenCL的GPU驱动 + +### 2. 库依赖安装 + +**必需的库**: +- Qt6 6.10.1 (msvc2022_64) +- PCL 1.15.1 +- OpenCV 4.13.0 +- OpenCL 3.0(通常随GPU驱动自动安装) + +**安装路径**(默认): +- Qt6: `C:\Qt\6.10.1\msvc2022_64` +- PCL: `C:\Program Files\PCL 1.15.1` +- OpenCV: `C:\opencv\build` + +### 3. 配置项目 + +**方式1:使用配置脚本(推荐)** +```bash +# 需根据本地情况修改库路径,编辑configure.bat +configure.bat +``` + +**方式2:手动配置** +```bash +cmake -B build -S . ^ + -G "Visual Studio 18 2026" ^ + -A x64 ^ + -DPCL_DIR="C:/Program Files/PCL 1.15.1/cmake" ^ + -DOpenCV_DIR="C:/opencv/build" ^ + -DCMAKE_PREFIX_PATH="C:/Qt/6.10.1/msvc2022_64" +``` + +**注意**: +- 如果系统安装了VS 2022,可使用 `-G "Visual Studio 17 2022"` + +### 4. 编译项目 + +```bash +# Release版本(推荐) +cmake --build build --config Release + +# Debug版本 需自行复制Debug版本库文件 +cmake --build build --config Debug +``` + +编译输出: +- Release: `bin/D330Viewer.exe` +- Debug: `bin/Debug/D330Viewer.exe` + +### 5. 运行程序 + +```bash +cd bin +D330Viewer.exe +``` + +**注意**:首次运行需要确保所有DLL文件都在bin目录下。 + +### 6. 生成MSI安装程序(可选) + +项目支持生成Windows MSI安装程序,方便在其他电脑上部署。 + +**前置要求**:需要安装 WiX Toolset v3.x + +**安装WiX Toolset**: +1. 访问 GitHub Release 页面:https://github.com/wixtoolset/wix3/releases +2. 下载最新版本的安装包: + - `wix3xx-binaries.zip` + - 或 `wix3xx.exe` +3. 如果下载zip文件: + - 解压到 `C:\Program Files (x86)\WiX Toolset v3.14\` + - 添加bin目录到系统PATH:`C:\Program Files (x86)\WiX Toolset v3.14\bin` +4. 如果下载exe文件: + - 直接运行安装程序,安装后会自动添加到PATH + +**验证安装**: +```bash +candle.exe -? +``` + +**生成MSI安装程序**: + +**方式1:使用CPack(推荐)** +```bash +# 编译完成后,在build目录运行 +cd build +cpack -G WIX -C Release +``` +生成的安装程序:`build/D330Viewer-0.1.0-win64.msi` + +**方式2:使用WiX Toolset手动构建** +```bash +build_installer.bat +``` +生成的安装程序:`installer/output/D330Viewer.msi` + +**MSI安装程序包含**: +- D330Viewer.exe(主程序) +- 所有必需的DLL(37个) +- Qt平台插件(platforms目录) +- 开始菜单快捷方式 + +## 功能特性 + +### ✅ 已完成功能 + +#### 网络通信 +- ✅ UDP网络通信(GVSP协议解析) +- ✅ 自动设备扫描和发现 +- ✅ 相机连接管理(连接/断开) +- ✅ 命令发送(START/STOP/ONCE) + +#### 可视化 +- ✅ 实时深度图显示(OpenCV,垂直翻转校正) +- ✅ 实时3D点云显示(自定义OpenGL渲染) +- ✅ 正交投影视角(平面视图) +- ✅ 交互式点云操作: + - 左键拖动:旋转视角 + - 右键拖动:平移 + - 滚轮:缩放(范围100-10000) + +#### 用户界面 +- ✅ Qt6 GUI主窗口(三栏布局) +- ✅ 相机控制面板(START/STOP/ONCE按钮) +- ✅ 曝光时间调节(滑块,范围460-100000μs) +- ✅ 网络配置(IP地址、端口设置) +- ✅ 连接状态指示 +- ✅ 配置持久化(QSettings) + +### 🚧 当前开发计划 + +根据需求文档和用户反馈,后续待添加功能如下: + +- 录制功能(连续保存多帧) +- 点云颜色映射(深度着色) +- 点云滤波选项(降噪、平滑) +- 测量工具(距离、角度测量) +- 多视角预设(正视、侧视、俯视) +- 性能监控(CPU/GPU使用率、内存使用) +- 其他相机参数调节(增益、白平衡等) + +## 项目结构 + +``` +d330viewer/ +├── src/ # 源代码 +│ ├── main.cpp # 程序入口 +│ ├── gui/ # GUI相关 +│ │ ├── MainWindow.cpp # 主窗口 +│ │ ├── PointCloudWidget.cpp # 点云显示组件 +│ │ └── PointCloudGLWidget.cpp # OpenGL点云渲染 +│ ├── core/ # 核心功能 +│ │ ├── NetworkManager.cpp # 网络通信 +│ │ ├── GVSPParser.cpp # GVSP协议解析 +│ │ ├── PointCloudProcessor.cpp # OpenCL点云计算 +│ │ └── DeviceScanner.cpp # 设备扫描 +│ └── config/ # 配置管理 +│ └── ConfigManager.cpp # 配置持久化 +├── include/ # 头文件 +│ ├── gui/ # GUI头文件 +│ └── core/ # 核心功能头文件 +├── bin/ # 可执行文件和DLL +│ ├── D330Viewer.exe # 主程序 +│ └── *.dll # 依赖库(37个) +├── build/ # CMake构建目录 +├── installer/ # MSI安装程序配置 +├── CMakeLists.txt # CMake配置 +├── configure.bat # 快速配置脚本 +├── build_installer.bat # MSI安装程序构建脚本 +└── README.md # 本文件 +``` + +## 相机配置 + +### 硬件信息 +- **相机型号**: D330M +- **控制端口**: 6790 (UDP) +- **数据端口**: 3957 (UDP) +- **分辨率**: 1224 x 1024 +- **深度范围**: 155mm - 3000mm + +### 网络配置 +确保PC和相机在同一网段即可。 + +### 相机参数 +- **曝光时间**: 460 - 100000 μs +- **Z缩放因子**: 0.2 (深度值需除以5) +- **相机内参**: + - fx = 1432.8957 + - fy = 1432.6590 + - cx = 637.5117 + - cy = 521.8720 + +### 下位机程序修改 +下位机程序保存在`/root/msk/20260113/` 。具体安装和使用详见根目录内`INSTALL.md`文件。当前版本程序修改功能如下: + - 添加`DISCOVER`命令,允许上位机对相机进行自动扫描。保持上位机下位机均DHCP能够正常使用。 + - 将程序添加到系统服务中,可以使用`systemctl`进行运行和管理 + +## 使用说明 + +### 基本操作流程 + +1. **启动程序** + - 运行 `bin/D330Viewer.exe` + - 程序会自动扫描网络中的D330M相机 + +2. **连接相机** + - 在设备列表中选择相机 + - 点击"连接"按钮 + - 状态指示变为绿色"已连接" + +3. **开始采集** + - 点击"启动"按钮开始实时采集 + - 深度图显示在中间区域 + - 点云显示在右侧区域 + +4. **调整参数** + - 拖动曝光滑块调整曝光时间 + - 使用鼠标操作点云视角 + +5. **停止采集** + - 点击"停止"按钮停止采集 + - 点击"单次"按钮采集单帧 + +### 点云交互操作 + +- **旋转视角**: 左键拖动 +- **平移**: 右键拖动 +- **缩放**: 滚轮上下滚动 +- **复位**: 重新启动采集 + +## 故障排除 + +### 常见问题 + +#### 1. 无法接收数据 +**问题**: 点击"启动"后没有图像和点云显示 + +**解决方案**: +- 图像在长时间传输后有概率卡住,不相应任何命令,具体可监控下位机日志。重启系统服务即可解决。 + +## 开发文档 + +### 代码规范 +- 使用Qt6信号槽机制进行模块间通信 +- OpenCL kernel代码内联在C++源文件中 +- 配置使用QSettings持久化 +- 日志输出到 `bin/d330viewer.log` diff --git a/build_installer.bat b/build_installer.bat new file mode 100644 index 0000000..77de4e5 --- /dev/null +++ b/build_installer.bat @@ -0,0 +1,51 @@ +@echo off +echo ======================================== +echo D330Viewer MSI Installer Builder +echo ======================================== +echo. + +REM 检查WiX Toolset是否安装 +where candle.exe >nul 2>&1 +if %ERRORLEVEL% NEQ 0 ( + echo ERROR: WiX Toolset not found! + echo Please install WiX Toolset from: https://wixtoolset.org/ + echo After installation, add WiX bin directory to PATH + pause + exit /b 1 +) + +echo [1/5] Checking build output... +if not exist "bin\D330Viewer.exe" ( + echo ERROR: D330Viewer.exe not found in bin directory + echo Please build the project first: cmake --build build --config Release + pause + exit /b 1 +) + +echo [2/5] Creating installer directory... +if not exist "installer\output" mkdir installer\output + +echo [3/5] Harvesting DLL files... +REM 使用heat.exe自动收集bin目录下的所有文件 +heat.exe dir bin -cg BinFiles -gg -scom -sreg -sfrag -srd -dr INSTALLFOLDER -var var.BinDir -out installer\BinFiles.wxs + +echo [4/5] Compiling WiX source... +candle.exe -dBinDir=bin installer\UpperControl.wxs installer\BinFiles.wxs -out installer\output\ + +echo [5/5] Linking MSI package... +light.exe -ext WixUIExtension installer\output\UpperControl.wixobj installer\output\BinFiles.wixobj -out installer\output\D330Viewer.msi + +if exist "installer\output\D330Viewer.msi" ( + echo. + echo ======================================== + echo SUCCESS! MSI installer created: + echo installer\output\D330Viewer.msi + echo ======================================== +) else ( + echo. + echo ERROR: Failed to create MSI installer + pause + exit /b 1 +) + +pause diff --git a/configure.bat b/configure.bat new file mode 100644 index 0000000..5f12286 --- /dev/null +++ b/configure.bat @@ -0,0 +1,62 @@ +@echo off +chcp 65001 >nul +REM CMake配置脚本 - Windows版本 +REM 请根据实际安装路径修改以下变量 + +echo ======================================== +echo D330Viewer CMake配置脚本 +echo ======================================== +echo. + +REM 设置库路径(请根据实际安装路径修改) +set PCL_DIR=C:\Program Files\PCL 1.15.1\cmake +set OpenCV_DIR=C:\opencv\build +set Qt6_DIR=C:\Qt\6.10.1\msvc2022_64 + +echo 检查库路径... +if not exist "%PCL_DIR%" ( + echo [警告] PCL路径不存在: %PCL_DIR% + echo 请修改configure.bat中的PCL_DIR变量 +) + +if not exist "%OpenCV_DIR%" ( + echo [警告] OpenCV路径不存在: %OpenCV_DIR% + echo 请修改configure.bat中的OpenCV_DIR变量 +) + +if not exist "%Qt6_DIR%" ( + echo [警告] Qt6路径不存在: %Qt6_DIR% + echo 请修改configure.bat中的Qt6_DIR变量 +) + +echo. +echo 开始配置CMake... +echo. + +cmake -B build -S . ^ + -DPCL_DIR="%PCL_DIR%" ^ + -DOpenCV_DIR="%OpenCV_DIR%" ^ + -DCMAKE_PREFIX_PATH="%Qt6_DIR%" ^ + -G "Visual Studio 18 2026" ^ + -A x64 + +if %ERRORLEVEL% EQU 0 ( + echo. + echo ======================================== + echo CMake配置成功! + echo ======================================== + echo. + echo 下一步: + echo 1. 打开 build\D330Viewer.sln 使用Visual Studio编译 + echo 2. 或运行: cmake --build build --config Release + echo. +) else ( + echo. + echo ======================================== + echo CMake配置失败! + echo ======================================== + echo 请检查库路径是否正确 + echo. +) + +pause diff --git a/include/1225pc_viewer.h b/include/1225pc_viewer.h new file mode 100644 index 0000000..191609a --- /dev/null +++ b/include/1225pc_viewer.h @@ -0,0 +1,17 @@ +#pragma once +#include +#include + +// ʼʾģ飨ڲ̣߳ +bool pc_viewer_start(); + +// ֹͣʾģ +void pc_viewer_stop(); + +// һ֡ XYZ ݣfloatxyzxyz... +// ֻʾһ֡ݻᱻ +void pc_viewer_update( + const float* xyz, + size_t point_count, + uint32_t block_id +); diff --git a/include/1225raw_image_viewer.h b/include/1225raw_image_viewer.h new file mode 100644 index 0000000..291bdfb --- /dev/null +++ b/include/1225raw_image_viewer.h @@ -0,0 +1,42 @@ +#ifndef RAW_IMAGE_VIEWER_H +#define RAW_IMAGE_VIEWER_H + +// ֹC/C++ϵʱ +#ifdef __cplusplus +extern "C" { +#endif + +// Ҫı׼֤Ϳʶ +#include + +/** + * @brief RAWͼӻ̣߳ʼ+̨ˢ£ + * @return bool ɹtrueʧܷfalse + */ + bool raw_image_viewer_start(); + + /** + * @brief ֹͣRAWͼӻ̣߳ȫ˳+Դ + */ + void raw_image_viewer_stop(); + + /** + * @brief RAWͼݣ̰߳ǫ̈̄ӻ߳̽ + * @param data 16λRAWͼָ루޸ģ + * @param width ͼȣ>0 + * @param height ͼ߶ȣ>0 + * @param block_id ǰͼIDڴڱʾ + */ + void raw_image_viewer_update(const uint16_t* data, int width, int height, uint32_t block_id); + + /** + * @brief RAWͼӻԴԭӿڣѡã + * @details ڲȵraw_image_viewer_stop()ͷͼ񻺴ڴ + */ + void raw_image_viewer_cleanup(); + +#ifdef __cplusplus +} +#endif + +#endif // RAW_IMAGE_VIEWER_H \ No newline at end of file diff --git a/include/core/GVSPParser.h b/include/core/GVSPParser.h new file mode 100644 index 0000000..7a479b1 --- /dev/null +++ b/include/core/GVSPParser.h @@ -0,0 +1,121 @@ +#ifndef GVSPPARSER_H +#define GVSPPARSER_H + +#include +#include +#include +#include + +// GVSP packet types +#define GVSP_LEADER_PACKET 0x01 +#define GVSP_PAYLOAD_PACKET 0x02 +#define GVSP_TRAILER_PACKET 0x03 + +// Payload types +#define PAYLOAD_TYPE_IMAGE 0x0001 +#define PAYLOAD_TYPE_BINARY 0x0003 + +// Image format +#define PIXEL_FORMAT_12BIT_GRAY 0x010C0001 + +// Image dimensions +#define IMAGE_WIDTH 1224 +#define IMAGE_HEIGHT 1024 + +#pragma pack(push, 1) + +// GVSP packet header +struct GVSPPacketHeader { + uint16_t status; + uint16_t block_id; + uint32_t packet_fmt_id; +}; + +// Image data leader +struct GVSPImageDataLeader { + uint16_t reserved; + uint16_t payload_type; + uint32_t timestamp_high; + uint32_t timestamp_low; + uint32_t pixel_format; + uint32_t size_x; + uint32_t size_y; + uint32_t offset_x; + uint32_t offset_y; + uint16_t padding_x; + uint16_t padding_y; +}; + +// Image data trailer +struct GVSPImageDataTrailer { + uint32_t reserved; + uint16_t payload_type; + uint32_t size_y; +}; + +// Binary data leader (for depth data) +struct GVSPBinaryDataLeader { + uint16_t reserved; + uint16_t payload_type; + uint32_t timestamp_high; + uint32_t timestamp_low; + uint32_t file_size; + uint32_t name_len; + char file_name[256]; +}; + +// Binary data trailer +struct GVSPBinaryDataTrailer { + uint32_t reserved; + uint16_t payload_type; + uint32_t checksum; +}; + +#pragma pack(pop) + +class GVSPParser : public QObject +{ + Q_OBJECT + +public: + explicit GVSPParser(QObject *parent = nullptr); + ~GVSPParser(); + + void parsePacket(const QByteArray &packet); + void reset(); + +signals: + void imageReceived(const QImage &image, uint32_t blockId); + void depthDataReceived(const QByteArray &depthData, uint32_t blockId); + void parseError(const QString &error); + +private: + void handleLeaderPacket(const uint8_t *data, size_t size); + void handlePayloadPacket(const uint8_t *data, size_t size); + void handleTrailerPacket(const uint8_t *data, size_t size); + + void processImageData(); + void processDepthData(); + +private: + // Reception state + bool m_isReceiving; + int m_dataType; // 0=unknown, 1=image, 3=depth + uint32_t m_currentBlockId; + + // Data buffer + QByteArray m_dataBuffer; + size_t m_expectedSize; + size_t m_receivedSize; + + // Image info + uint32_t m_imageWidth; + uint32_t m_imageHeight; + uint32_t m_pixelFormat; + + // Statistics + uint32_t m_lastBlockId; + int m_packetCount; +}; + +#endif // GVSPPARSER_H diff --git a/include/core/Logger.h b/include/core/Logger.h new file mode 100644 index 0000000..e73e6af --- /dev/null +++ b/include/core/Logger.h @@ -0,0 +1,41 @@ +#ifndef LOGGER_H +#define LOGGER_H + +#include +#include +#include +#include +#include + +class Logger : public QObject +{ + Q_OBJECT + +public: + static Logger* instance(); + + void setLogFile(const QString &filePath); + void setMaxLines(int maxLines); + + void log(const QString &message); + void debug(const QString &message); + void info(const QString &message); + void warning(const QString &message); + void error(const QString &message); + +private: + explicit Logger(QObject *parent = nullptr); + ~Logger(); + + void writeLog(const QString &level, const QString &message); + void checkAndTrimLog(); + + static Logger *s_instance; + + QString m_logFilePath; + int m_maxLines; + QMutex m_mutex; + int m_currentLines; +}; + +#endif // LOGGER_H diff --git a/include/core/PointCloudProcessor.h b/include/core/PointCloudProcessor.h new file mode 100644 index 0000000..e12eca8 --- /dev/null +++ b/include/core/PointCloudProcessor.h @@ -0,0 +1,64 @@ +#ifndef POINTCLOUDPROCESSOR_H +#define POINTCLOUDPROCESSOR_H + +#include +#include +#include +#include +#include + +class PointCloudProcessor : public QObject +{ + Q_OBJECT + +public: + explicit PointCloudProcessor(QObject *parent = nullptr); + ~PointCloudProcessor(); + + // 初始化OpenCL + bool initializeOpenCL(); + + // 设置相机内参 + void setCameraIntrinsics(float fx, float fy, float cx, float cy); + + // 设置Z缩放因子 + void setZScaleFactor(float scale); + + // 将深度数据转换为点云(使用OpenCL GPU加速) + void processDepthData(const QByteArray &depthData, uint32_t blockId); + +signals: + void pointCloudReady(pcl::PointCloud::Ptr cloud, uint32_t blockId); + void errorOccurred(const QString &error); + +private: + // 清理OpenCL资源 + void cleanupOpenCL(); + + // 相机内参 + float m_fx; + float m_fy; + float m_cx; + float m_cy; + + // Z缩放因子 + float m_zScale; + + // 图像尺寸 + int m_imageWidth; + int m_imageHeight; + int m_totalPoints; + + // OpenCL资源 + cl_platform_id m_platform; + cl_device_id m_device; + cl_context m_context; + cl_command_queue m_queue; + cl_program m_program; + cl_kernel m_kernel; + cl_mem m_depthBuffer; + cl_mem m_xyzBuffer; + bool m_clInitialized; +}; + +#endif // POINTCLOUDPROCESSOR_H diff --git a/include/dkam_asyncqueue.h b/include/dkam_asyncqueue.h new file mode 100644 index 0000000..ce6d22b --- /dev/null +++ b/include/dkam_asyncqueue.h @@ -0,0 +1,143 @@ +#pragma once + +#include +#include +#ifdef _WIN32 +//Head files that WIN Visual Studio needs +#include +#include + +#else +//Head files that linux system needs +//TRUE FALSE BOOL that linux not support +#include +#define TRUE 1 +#define FALSE 0 +#define BOOL _Bool + +#endif + + +#ifdef _WIN32 +#define DLLEXPORT_API extern __declspec(dllexport) +#define DLL_VOID void __stdcall +#define DLL_INT int __stdcall +#else +#define DLLEXPORT_API +#define DLL_VOID void +#define DLL_INT int +#endif + +typedef struct _List List; + +struct _List +{ + void* data; + List *next; + List *prev; +}; + +typedef struct _Queue Queue; + +struct _Queue +{ + List *head; + List *tail; + unsigned int length; +}; + +typedef struct _AsyncQueue AsyncQueue; + +#ifdef _WIN32 +//mutex and cond are different in WIN and Linux. +struct _AsyncQueue +{ + SRWLOCK mutex; + CONDITION_VARIABLE cond; + Queue queue; + unsigned int waiting_threads; + int ref_count; +}; + +#else + +struct _AsyncQueue +{ + pthread_mutex_t mutex; + pthread_cond_t cond; + Queue queue; + unsigned int waiting_threads; + int ref_count; +}; + +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif +//add a new point to the List +//向队列中增加一个指针 +DLLEXPORT_API List* list_append(List *list, void* data); + +// Initialization of the queue +// 队列初始化函数 +DLLEXPORT_API DLL_VOID queue_init(Queue *queue); + +// Push a data point to the queue head,and increase the leagth for 1. +// 将一个数据指针推送到队列头,并将队列长度增加1。 +DLLEXPORT_API DLL_VOID queue_push_head(Queue *queue, void *data); + +// Pop a data From the tail of the queue and return the point, If there isn't a data, Then return NUll. +// 从队列尾推出一个数据,并返回这个数据指针。如果没有数据,则返回NULL。 +DLLEXPORT_API void* queue_pop_tail(Queue *queue); + +// Release all the queue, but not the data point. +// If the data point is generate by malloc, it should free first. +// 释放队列,但不是队列中data指向的数据,如果队列的data是开辟的空间,应先释放。 +DLLEXPORT_API DLL_VOID queue_clear(Queue *queue); + +// Check If there is data or not in the queue. +// 查看队列是否有数据 +DLLEXPORT_API List* queue_peek_tail_link(Queue *queue); + +// Creat a AsyncQueue. +// If successed, it return a point to a AsyncQueue. +// Otherwise, it return a NULL point. +// 创建一个异步队列。如果成功,返回队列的指针,否则,返回NULL。 +DLLEXPORT_API AsyncQueue* async_queue_new(); + +// Push a point data to the AsyncQueue head. After this, the queue length will increase one. +// input AsyncQueue point; data point. +// 将数据指针推送到异步队形头,然后队列长度增加1。 输入参数: 队列指针,数据指针。 +DLLEXPORT_API DLL_VOID async_queue_push(AsyncQueue *queue, void *data); + +// Pop a point of the data from AsyncQueue tail, After this, the queue length will decrease one and return a point. +// It will waiting point until there is one. It is a blocking function. +// 将一个数据从队列中弹出,然后队列长度减少1,返回数据的指针。 这个函数会一直等到有数据,这是一个阻塞函数。 +DLLEXPORT_API void* async_queue_pop(AsyncQueue *queue); + +// Try Pop a point of the data from AsyncQueue tail, If successed, the queue length will decrease one and return a point. +// If not successed, it return NULL. This function return immediately. +// 尝试将一数据从队列中弹出,如果成功,队列长度减少1,返回数据指针。 如果无数据,就返回NULL,这个函数是立即返回。 +DLLEXPORT_API void* async_queue_try_pop(AsyncQueue *queue); + +// Pop a point of the data from AsyncQueue tail until the timeout. +// If during the timeout, there is data, the queue length will decrease one and return a point. +// If during the timeout, there is no data, it return NULL. +// the unit of timeout is us. +// 在等待的时间内尝试将一数据从队列中弹出,如果成功,队列长度减少1,返回数据指针。 如果无数据,就返回NULL。 +// 这个函数的返回时间是小于等于等待时间的。 +DLLEXPORT_API void* async_queue_timeout_pop(AsyncQueue *queue, + long long timeout); + +// return the length of AsyncQueue +// 返回异步队列的长度 +DLLEXPORT_API DLL_INT async_queue_length(AsyncQueue *queue); + +// release the AsyncQueue +// 释放异步队列资源 +DLLEXPORT_API DLL_VOID async_queue_destroy(AsyncQueue *queue); +#ifdef __cplusplus +} +#endif diff --git a/include/dkam_base_type.h b/include/dkam_base_type.h new file mode 100644 index 0000000..a93d55a --- /dev/null +++ b/include/dkam_base_type.h @@ -0,0 +1,308 @@ +/******************************************************************************************** +Copyright : XianZhisensorTechnologiesCo.,Ltd +File Name : discovery.h +Description: Privide base type of gige camera +Author : Yaming Wang +Version : 1.0 +Data : 2019-11-18 +History : +*********************************************************************************************/ +#ifndef DKAM_BASE_TYPE_H +#define DKAM_BASE_TYPE_H + +#define IP_HEADER_LEN 20 +#define UDP_HEADER_LEN 8 +#define IMAGE_PACKET_HEADER_LEN 8 +#define BLOCK_HEAD_LEN 44 + + +#define GVCP_PORT 3956 + +#define SORT_BY_IP 0 +#define SORT_BY_SERIAL_NUMBER 1 + +//发送命令宏定义 +#define PACK_START 0x42 +#define DISCOVERY_CMD 0x0002 +//#define DISCOVERY_ACK 0x0003 +#define FORCEIP_CMD 0x0004 +//#define FORCEIP_ACK 0x0005 +#define READREG_CMD 0x0080 +//#define READREG_ACK 0x0081 +#define WRITEREG_CMD 0x0082 +//#define WRITEREG_ACK 0x0083 +#define READMEM_CMD 0x0084 +//#define READMEM_ACK 0x0085 +#define WRITEMEM_CMD 0x0086 +//#define WRITEMEM_ACK 0x0087 +#define PACKETRESEND_CMD 0x0040 + + +//GIGE version协议标准寄存器 +#define Control_Channel_Privilage_Reg 0x00000A00 +#define GVCP_Capability_Reg 0x00000934 +#define Number_of_Message_Channels_Reg 0x00000900 +#define Number_of_Stream_Channels_Reg 0x00000904 +#define First_Url_Reg 0x00000200 +#define Second_Url_Reg 0x00000400 +#define Heartbeat_Timeout_Reg 0x00000938 + +#define Source_Port_Reg 0x00000D1C + +#define Stream_Channel_0_Port_Reg 0x00000D00 +#define Stream_Channel_0_Destination_Address_Reg 0x00000D18 + +//#define Stream_Channel_1_Port_Reg 0x00000D40 +//#define Stream_Channel_1_Destination_Address_Reg 0x00000D58 +// +//#define Stream_Channel_2_Port_Reg 0x00000D80 +//#define Stream_Channel_2_Destination_Address_Reg 0x00000D98 + +#define Stream_Channel_Packet_Size_Reg 0x00000D04 + +//自定义寄存器 + + +#define Model_Adder 0x00000014 +#define IP_Adder 0x0000064C +#define Mask_Adder 0x0000065C +#define Gate_Adder 0x0000066C + + +typedef struct RoiPoint { + unsigned int size_x; + unsigned int size_y; + unsigned int offset_x; + unsigned int offset_y; +}RoiPoint; + +#define RECIEVE_TIME_STATISTICS_LENGTH 1000 //统计直方图划分的尺寸,默认1000个 +typedef struct { + int offset; //初始偏量 + unsigned int bin_steps; //步长 + unsigned int n_bins; //总步数 + int max; //最大值 + int min; //最小值 + long long count; //总数量 + long long add_more; //大于统计范围的数量 + long long add_less; //小于统计范围的数量 + long long bins[RECIEVE_TIME_STATISTICS_LENGTH]; +}StatisticsData; +//C# +typedef struct { + int offset; //初始偏量 + unsigned int bin_steps; //步长 + unsigned int n_bins; //总步数 + int max; //最大值 + int min; //最小值 + long long count; //总数量 + long long add_more; //大于统计范围的数量 + long long add_less; //小于统计范围的数量 +}StatisticsDataCSharp; + +//照片信息结构体 +typedef struct PhotoInfo { + char* pixel; + unsigned int pixel_length; + unsigned int pixel_format; + unsigned int pixel_width; + unsigned int pixel_height; + unsigned int cloud_unit; + unsigned int payload_size; + unsigned long int timestamp_high; + unsigned long int timestamp_low; + RoiPoint roi; + unsigned short block_id; + unsigned short stream_channel_index_; + unsigned int gvsp_payload_size; + int buffer_status; // 当前包传输状态 +}PhotoInfo; + +//照片信息结构体C#专用 +typedef struct PhotoInfoCSharp { + unsigned int pixel_length; + unsigned int pixel_format; + unsigned int pixel_width; + unsigned int pixel_height; + unsigned int cloud_unit; + unsigned int payload_size; + unsigned long int timestamp_high; + unsigned long int timestamp_low; + RoiPoint roi; + unsigned short block_id; + unsigned short stream_channel_index_; + unsigned int gvsp_payload_size; + int buffer_status; // 当前包传输状态 +}PhotoInfoCSharp; + +//暂存区数据结构体 +typedef struct RecvDataBuff { + unsigned short status; + char head_packet_flag; + char* data; + char block_head[BLOCK_HEAD_LEN]; + struct RecvDataBuff* next; +}RecvDataBuff; + +//数据缓冲池结构体 +typedef struct RecvDataTemp { + char head_packet_flag; + char resend_check_point; + char out_of_time_check; + unsigned short block_id; + unsigned int recv_packet_num; + unsigned int all_data_size; + unsigned int all_packet_num; + char* recv_packet_id; + char* data; + char *block_head; + struct RecvDataTemp* next; +}RecvDataTemp; + +//相机信息结构体 +typedef struct DiscoveryInfo { + unsigned int camera_ip; + unsigned int camera_mask; + unsigned int camera_gateway; + unsigned int camera_mac_low; + unsigned short camera_mac_high; + char device_vendor_name[32]; + char device_model_name[32]; + char device_version[32]; + char device_manufacturer_info[48]; + char device_serial_number[16]; + char device_user_id[16]; + unsigned int computer_ip; + unsigned int computer_mask; + unsigned int computer_gateway; + int mtu; + char computer_adapter[256]; +}DiscoveryInfo; + +typedef struct PacketInfo { + unsigned short in_status; + unsigned short in_block_id; + unsigned short in_packet_id; + unsigned char packet_format; + int data_packet_len; +}PacketInfo; + +//相机参数 +typedef struct CameraParameter { + float Kc[5]; + float K[9]; + float R[9]; + float T[3]; +}CameraParameter; + +//连接相机信息 +typedef struct InstanceDevice { + unsigned int camera_ip; + unsigned int camera_mask; + unsigned int computer_ip; + unsigned int computer_mask; + int computer_mtu; + int recv_buffer_num; + char computer_adapter[256]; +}InstanceDevice; + +//命令包 +//discover_cmd +typedef struct DiscoverCmdPack { + unsigned char start; + unsigned char flag; + unsigned short command; + unsigned short length; + unsigned short req_id; +}DiscoverCmdPack; + +//force_ip_cmd +typedef struct ForceIpCmdPack { + unsigned char start; + unsigned char flag; + unsigned short command; + unsigned short length; + unsigned short req_id; + unsigned short reserved; + unsigned short mac_addr_high; + unsigned int mac_addr_low; + unsigned int reserved1[3]; + unsigned int ip; + unsigned int reserved2[3]; + unsigned int mask; + unsigned int reserved3[3]; + unsigned int gateway; +}ForceIpCmdPack; +//force_ip_reboot +typedef struct ForceIpRebootCmdPack { + unsigned char start; + unsigned char flag; + unsigned short command; + unsigned short length; + unsigned short req_id; + unsigned int model_addr; + unsigned int model; + unsigned int ip_addr; + unsigned int ip; + unsigned int mask_addr; + unsigned int mask; + unsigned int gateway_addr; + unsigned int gateway; + unsigned int restart_addr; + unsigned int restart; +}ForceIpRebootCmdPack; +//write_register_cmd +typedef struct WriteRegCmdPack { + unsigned char start; + unsigned char flag; + unsigned short command; + unsigned short length; + unsigned short req_id; + unsigned int register_addr; + unsigned int data; +}WriteRegCmdPack; +//read_register_cmd +typedef struct ReadRegCmdPack { + unsigned char start; + unsigned char flag; + unsigned short command; + unsigned short length; + unsigned short req_id; + unsigned int register_addr; +}ReadRegCmdPack; + +//read_mem_cmd +typedef struct ReadMemCmdPack { + unsigned char start; + unsigned char flag; + unsigned short command; + unsigned short length; + unsigned short req_id; + unsigned int address; + unsigned short reserved; + unsigned short count; +}ReadMemCmdPack; +//write_mem_cmd +typedef struct WriteMemCmdPack { + unsigned char start; + unsigned char flag; + unsigned short command; + unsigned short length; + unsigned short req_id; + unsigned int address; + char data[536]; +}WriteMemCmdPack; +//resend_cmd_pack +typedef struct PacketResendCmdPack { + unsigned char start; + unsigned char flag; + unsigned short command; + unsigned short length; + unsigned short req_id; + unsigned short stream_channel_index; + unsigned short block_id; + unsigned int first_packet_id; + unsigned int last_packet_id; +}PacketResendCmdPack; + +#endif //!DKAM_BASE_TYPE_H diff --git a/include/dkam_camera_error.h b/include/dkam_camera_error.h new file mode 100644 index 0000000..b1d4fcc --- /dev/null +++ b/include/dkam_camera_error.h @@ -0,0 +1,130 @@ +/******************************************************************************** +Copyright : XianZhisensorTechnologiesCo.,Ltd +File Name :camera_error +Description: Privide information of error +Author : Yaming Wang +Version : 1.0 +Data : 2019-11- +History : +*********************************************************************************/ + +#ifndef DKAM_CAMERA_ERROR_H_ +#define DKAM_CAMERA_ERROR_H_ + +//局域网内未发现相机 + +#define SUCCESS 0 + +//#define MALLOC_ERROR -1 +#define INVALID_PARAMETER -2 +#define NETWORK_INTERFACE_CONTROLLER_ERROR -3 //网卡错误 +#define FORCE_IP_TIMEOUT -4 + +#define NO_CAMERA -5 +#define INIT_SOCKET_ERROR -6 +#define SOCKET_BIND_ERROR -7 +#define TEST_PACKET_SEND_ERROR -8 +//#define GET_XML_URL_ERROR -9 +#define XML_DATA_ERROR -10 + +#define CAMERA_DISCONNECT -11 +//#define CREATE_LOG_ERROR -12 +#define GET_MTU_ERROR -13 +//#define GET_PACK_SIZE_ERROR -14 +#define USER_CONFIG_FILE_ERROR -15 +//#define SET_CAMERA_ATTRIBUTION_ERROR -16 +#define CAMERA_ATTRIBUTION_ERROR -17 +//#define TRIGGER_ERROR -18 +#define CREATE_THREAD_ERROR -19 +//#define STREAM_ON_ERROR -20 +#define NO_REGISTER -21 +#define UNIMPLEMENTED_PIXEL_FORMAT -22 +//#define ACQUISITION_START_ERROR -23 +//#define ACQUISITION_STOP_ERROR -24 +//#define SET_ROI_ERROR -25 +//#define STREAM_OFF_ERROR -26 +//#define DISCONNECT_ERROR -27 +//#define GET_NODE_MAX_VALUE_ERROR -28 +//#define GET_NODE_MIN_VALUE_ERROR -29 +//#define SET_HEART_BEAT_TIMEOUT_ERROR -30 +//#define GET_HEART_BEAT_TIMEOUT_ERROR -31 + +//gvsp +//#define SELECT_TIMEOUT -32 +//#define INVALID_CHANNEL_INDEX -33 +#define CAPTURE_TIMEOUT -34 +#define CAPTURE_ERROR -35 + +#define INET_PTON_ERROR -36 +#define WRITE_REG_TIMEOUT -37 +#define READ_REG_TIMEOUT -38 +#define WRITE_MEM_TIMEOUT -39 +#define READ_MEM_TIMEOUT -40 +//save_data +#define SAVE_ERROR -41 +#define OPEN_FILE_ERROR -42 +//#define SAVE_XML_ERROR -43 +#define FTP_OPEN_ERROR -44 +#define FTP_CONNECT_ERROR -45 +#define FTP_PUT_ERROR -46 +#define FTP_GET_ERROR -47 +//#define CAMERA_REBOOT_ERROR -48 + +#define DIFFERENT_NETWORK_SEGMENT -49 +#define NULL_PTR -50 +//#define GET_NODE_INC_VALUE_ERROR -51 + + +//#define WRITE_REG_ERROR -52 //修改完未用 +//#define READ_REG_ERROR -53 //修改完未用 +//#define WRITE_MEM_ERROR -54 //修改完未用 +//#define READ_MEM_ERROR -55 //修改完未用 +#define RECEIVE_ERROR -56 +//#define GET_CAM_INTERN_PARAM_ERROR -57 +//#define GET_CAM_EXTERN_PARAM_ERROR -58 + +//#define FUSION_ERROR -59 //修改完未用 +#define GET_CCP_STATUS_ERROR -60 +#define LAST_TRIGGER_NOT_END -61 +#define CONNECTED_BY_OTHERS -62 +#define REGISTER_ACCESS_ERROR -63 + +#define CREATE_DISCOVER_OBJ_ERROR -97 +#define CREATE_STREAM_OBJ_ERROR -98 +#define CREATE_CAMERA_OBJ_ERROR -99 + +#define GEV_STATUS_SUCCESS 0x0000 //命令执行成功 +#define GEV_STATUS_PACKET_RESEND 0x0100 //重发包 +#define GEV_STATUS_NOT_IMPLEMENTED 0x8001 //设备不支持该命令 +#define GEV_STATUS_INVALID_PARAMETER 0x8002 //参数无效 +#define GEV_STATUS_INVALID_ADDRESS 0x8003 //试图访问不存在的地址空间位置 +#define GEV_STATUS_WRITE_PROTECT 0x8004 //寄存器地址无法写入 +#define GEV_STATUS_BAD_ALIGNMENT 0x8005 //地址偏移量或数据大小对齐错误 +#define GEV_STATUS_ACCESS_DENIED 0x8006 //试图访问永久/暂时无法访问的地址 +#define GEV_STATUS_BUSY 0x8007 //请求繁忙 +#define GEV_STATUS_LOCAL_PROBLEM 0x8008 +#define GEV_STATUS_MSG_MISMATCH 0x8009 +#define GEV_STATUS_INVALID_PROTOCOL 0x800A +#define GEV_STATUS_NO_MSG 0x800B +#define GEV_STATUS_PACKET_UNAVAILABLE 0x800C //请求的数据包不可用 +#define GEV_STATUS_DATA_OVERRUN 0x800D //GVSP发送器内部存储器溢出 +#define GEV_STATUS_INVALID_HEADER 0x800E +#define GEV_STATUS_WRONG_CONFIG 0x800F +#define GEV_STATUS_PACKET_NOT_YET_AVAILABLE 0x8010 //尚未获取请求的数据包 +#define GEV_STATUS_PACKET_AND_PREV_REMOVED_FROM_MEMORY 0x8011 //请求的数据包和所有先前的数据包不再可用,并已从GVSP发送器存储器中丢失 +#define GEV_STATUS_PACKET_REMOVED_FROM_MEMORY 0x8012 //请求的数据包不可用,且从GVSP发送器存储器中丢失 +#define GEV_STATUS_NO_REF_TIME 0x8013 //设备未与主时钟同步以用作时间参考 +#define GEV_STATUS_PACKET_TEMPORARILY_UNAVAILABLE 0x8014 //由于临时宽带问题,此时无法重新发送数据包,应在将来再次请求 +#define GEV_STATUS_OVERFLOW 0x8015 //设备队列或数据包数据已溢出 +#define GEV_STATUS_ACTION_LATE 0x8016 //在已经过去的时间请求里请求的预定动作命令 +#define GEV_STATUS_LEADER_TRAILER_OVERFLOW 0x8017 //数据头包或数据尾包的数据包大小不足以放入用于传输该块的所有信息 +#define GEV_STATUS_SLERROR 0xC001 //光机异常 +#define GEV_STATUS_CMOSERROR 0xC002 //CMOS异常 +#define GEV_STATUS_GPUERROR 0xC003 //GPU异常 +#define GEV_STATUS_OTHERERROR 0xC004 //其他异常 +#define GEV_STATUS_UPDATEERROR 0xC005 +#define GEV_STATUS_ERROR 0xCFFF //一般错误 + + +#endif //!DKAM_CAMERA_ERROR_H_ + diff --git a/include/dkam_common_socket.h b/include/dkam_common_socket.h new file mode 100644 index 0000000..f89be02 --- /dev/null +++ b/include/dkam_common_socket.h @@ -0,0 +1,32 @@ +/************************************************************************************************************** +Copyright : XianZhisensorTechnologiesCo.,Ltd +File Name : common_socket.h +Description: Privide Definition of class +Author : Yaming Wang +Version : 1.0 +Data : 2019-11-3 +History : +***************************************************************************************************************/ +#ifndef DKAM_COMMON_SOCKET_H +#define DKAM_COMMON_SOCKET_H +#ifdef _WIN32 +#include +#else +#include +#include +#endif + + +class CommonSocket { +public: + CommonSocket(); + ~CommonSocket(); + int InitSocket(); + int Bind(int socket_fd, unsigned int ip, unsigned short port); + int Send(int socket_fd, unsigned int ip, unsigned short port, const char* buffer, unsigned int buffer_len); + int Receive(int socket_fd, char* buffer, int buffer_len); + int Select(int socket_fd, fd_set *rd_fds, fd_set *wd_fds); +}; + + +#endif //!DKAM_COMMON_SOCKET_H diff --git a/include/dkam_discovery.h b/include/dkam_discovery.h new file mode 100644 index 0000000..8d56ec3 --- /dev/null +++ b/include/dkam_discovery.h @@ -0,0 +1,54 @@ +/******************************************************************************************** +Copyright : XianZhisensorTechnologiesCo.,Ltd +File Name : discovery.h +Description: Privide class of discover +Author : Yaming Wang +Version : 1.0 +Data : 2019-11-18 +History : +*********************************************************************************************/ +#ifndef DKAM_DISCOVERY_H +#define DKAM_DISCOVERY_H +#include "dkam_log.h" +#include "dkam_base_type.h" +#include "dkam_common_socket.h" +#include +#include + + +class Discovery :private CommonSocket{ +public: + Discovery(); + ~Discovery(); + //设置发现设备时log日志的等级开关,决定是否开启某一个等级的日志打印,默认关闭(0:关闭 1:开启) + void SetLogLevelSwitch(int error, int debug, int warnning, int info); + //发现相机 + int DiscoverCamera(std::vector* discovery_info); + //对发现的相机排序 0 IP 1 序列号 + int CameraSort(std::vector * discovery_info, int sort_mode); + //设置相机ip + int ForceIp(DiscoveryInfo discovery_info, char* ip, char* mask, char* gateway); + //两点分十进制ip转化为int型 + unsigned int ConvertIpStringToInt(char* str_ip); + //将int型ip转换为点分十进制形式 + char *ConvertIpIntToString(unsigned int ip); + //判断相机的IP和PC的IP是否在同一网段 + bool WhetherIsSameSegment(DiscoveryInfo discovery_info); + +private: + //win32平台发现相机 + int DiscoverCameraWin32(std::vector** discovery_info); + //linux平台发现相机 + int DiscoverCameraLinux(std::vector** discovery_info); + +private: + int camera_num_; + unsigned short req_id_; + char *camera_ip_; + cameralog logq; +}; + + + +#endif //!DKAM_DISCOVERY_H + diff --git a/include/dkam_ftpserver.h b/include/dkam_ftpserver.h new file mode 100644 index 0000000..10fb163 --- /dev/null +++ b/include/dkam_ftpserver.h @@ -0,0 +1,50 @@ +#pragma once +#include +#include +#include +#include +#ifdef _WIN32 +#include +#include +#include +//链接wininet.lib库 +#pragma comment(lib,"wininet.lib") +#else +#include +#include +#include +#include +#include +#endif // _WIN32 + +class FtpServer +{ +public: + FtpServer(); + ~FtpServer(); + // ftp 文件上传 + int FtpUpload(const char* ip, const char* localPathName, const char* uploadPath); + int FtpDownload(const char* ip, const char* LocalPath, const char* LocalName, const char* downPath); + int FtpGetFileList(const char* ip, const char* filePath, std::vector* fileNames); +private: + int FtpUploadWin(const char* ip, const char* localPathName, const char* uploadPathName); + int FtpDownloadWin(const char* ip, const char* LocalPathName, const char* downPathName); + int FtpGetFileListWin(const char* ip, const char* filePath, std::vector* fileNames); + + int FtpUploadLin(const char* ip, const char* localPathName, const char* uploadPathName); + int FtpDownloadLin(const char* ip, const char* LocalPathName, const char* downPathName); + int FtpGetFileListLin(const char* ip, const char* filePath, std::vector* fileNames); + + +private: + int ftpServerProt_m_; + char* ftpUserName_m_; + char* ftpUserPwd_m_; +#ifdef _WIN32 + char* szAppname_m_; + char* szUsername_m_; + char* szPassword_m_; +#else +#endif // _WIN32 +}; + diff --git a/include/dkam_gige_camera.h b/include/dkam_gige_camera.h new file mode 100644 index 0000000..5985951 --- /dev/null +++ b/include/dkam_gige_camera.h @@ -0,0 +1,385 @@ +/************************************************************************************* +Copyright : XianZhisensorTechnologiesCo.,Ltd +File Name : gige_camera.h +Description: Privide Definition of class +Author : Yaming Wang +Version : 1.0 +Data : 2019-11-3 +History : +*************************************************************************************/ +#ifndef DKAM_GIGE_CAMERA_H +#define DKAM_GIGE_CAMERA_H + +#include "dkam_log.h" +#include "dkam_base_type.h" +#include "dkam_common_socket.h" +#include "dkam_ftpserver.h" +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#else +#include +#endif + + +typedef struct { + int socket_id; + unsigned int payload_size; + unsigned int width; + unsigned int height; + RoiPoint roi; + unsigned int pixel_length; + unsigned int pixel_format; + unsigned int pack_num; + unsigned int cloud_unit; +}DataStreamPrivateInfo; + + +typedef struct { + RecvDataBuff recv_data_buff[3];//暂存区buffer + RecvDataBuff *read_position[3]; //指向暂存区buffer的读指针 + RecvDataBuff *write_position[3]; // 指向暂存区buffer的写指针 +}DataStreamBufferInfo; + +class GigeStream; +extern char* sdk_version; + +//相机控制,接收数据类 +class GigeCamera :private CommonSocket{ +public: + GigeCamera(); + ~GigeCamera(); + //获取相机的CCP状态(返回0为可连接) + int GetCameraCCPStatus(DiscoveryInfo* discovery_info, int *data); + //连接相机 + int CameraConnect(DiscoveryInfo* discovery_info); + //建立通道和com之间对应关系 + int ChannalCorrespondComs(); + //获取相机xml所有节点名称 + int GetCameraXMLNodeNames(std::vector* node_names); + //验证ip是否合法 + bool IsIPAddressValid(unsigned int ip_int, unsigned int camera_mask_int); + //获取节点最大值 + int GetNodeMaxValue(const char* key); + //获取节点最小值 + int GetNodeMinValue(const char* key); + //获取int节点增量 + int GetNodeIncValue(const char* key); + //获取相机内参 + int GetCamInternelParameter(int camera_cnt, float *Kc, float *K); + //获取相机外参 + int GetCamExternelParameter(int camera_cnt, float *R, float *T); + //图像膨胀 + void PixelSwell(int *roi_output, PhotoInfo &target_data); + //图像腐蚀 + void PixelCorrosion(int *roi_output, PhotoInfo &target_data); + //ROI映射区域坐标 + void ROIMappingCoordinate(int *roi_output, PhotoInfo &target_data, RoiPoint &point_output); + //ROI检索映射 + void ROIPixelMapping(PhotoInfo &point_data, PhotoInfo &source_data, PhotoInfo &target_data, RoiPoint &roi_input, int *ROI_output); + //写寄存器 返回写寄存器是否成功的状态码 + int WriteRegister(unsigned int register_addr, int data); + //保存用户配置文件 + int SaveUserConfig(char* fliename); + //加载用户配置文件 + int LoadUserConfig(char* fliename); + //获取节点类型 + int GetNodeType(const char* key); + //获取节点访问模式 + int GetNodeAccessMode(const char* key); + //设置Int类型节点值 + int SetIntNodeValue(const char* key, unsigned int value); + //设置Bool类型节点值 + int SetBoolNodeValue(const char* key, int value); + //设置Command类型节点值 + int SetCommandNodeValue(const char* key); + //设置Float类型节点值 + int SetFloatNodeValue(const char* key, float value); + //设置String类型节点值 + int SetStringNodeValue(const char* key, char* value); + //设置Enumeration类型节点值 + int SetEnumNodeValue(const char* key, int value); + //获取Int类型节点值 + int GetIntNodeValue(const char* key, int* value); + //获取相机节点value + int GetRegisterAddr(const char* key); + //读string类型的寄存器 + int ReadStringRegister(const char* key, char* reg_str); + //写string类型的寄存器 + int WriteStringRegister(const char* key, unsigned short datasize, char* reg_str); + //获取Bool类型节点值 + int GetBoolNodeValue(const char* key, int* value); + //获取Command类型节点值 + int GetCommandNodeValue(const char* key, char* value); + //获取Float类型节点值 + int GetFloatNodeValue(const char* key, float* value); + //获取String类型节点值 + int GetStringNodeValue(const char* key, char* value); + //获取Enumeration类型节点值 + int GetEnumNodeValue(const char* key, int* value); + //读寄存器 返回读寄存器是否成功的状态码 register_addr:寄存器地址 data:读取的寄存器的值 + int ReadRegister(unsigned int register_addr, int *data); + //设置相机种类 (0:红外;1:RGB) + int SetCameraType(int camera_cnt); + //获取相机种类 (0:红外;1:RGB) + int GetCameraType(); + //获取相机的宽 + int GetCameraWidth(int* width, int camera_cnt); + //设置相机的宽 + int SetCameraWidth(int width, int camera_cnt); + //获取相机的高 + int GetCameraHeight(int* height, int camera_cnt); + //设置相机的高 + int SetCameraHeight(int height, int camera_cnt); + ////设置超时时间 + int SetHeartBeatTimeout(int value); + //获取超时时间 + int GetHeartBeatTimeout(void); + //设置相机曝光模式(1手动曝光,0自动曝光 camera_cnt:0是红外, 1是RGB) + int SetAutoExposure(int status, int camera_cnt); + //获取相机曝光模式 ( camera_cnt:0是红外, 1是RGB) + int GetAutoExposure(int camera_cnt); + //设置RGB摄像头自动曝光增益的级别(>=1 仅支持RGB摄像头) (camera_cnt:相机的摄像头0:红外摄像头 1:RGB摄像头 level:曝光增益等级) + int SetCamExposureGainLevel(int camera_cnt, int level); + //获取RGB摄像头自动曝光增益的级别,仅支持RGB摄像头(camera_cnt:相机的摄像头0:红外摄像头 1:RGB摄像头) + int GetCamExposureGainLevel(int camera_cnt); + //设置相机曝光类型int status > 0 多曝光 不能是0 + int SetMutipleExposure(int status); + //获取相机曝光类型 + int GetMutipleExposure(void); + //设置曝光时间,utimes:曝光时间: 红外镜头范围1000 - 100000um, RGB镜头范围1000 - 56000um, 默认16600,camera_cnt:0是红外, 1是RGB + int SetExposureTime(int utime, int camera_cnt); + //获取相机曝光次数 0是红外, 1是RGB + int GetExposureTime(int camera_cnt); + //设置多曝光模式 0:等差,1:等比 + int SetMultiExpoMode(int mode); + //获取多曝光模式 + int GetMultiExpoMode(); + //设置多曝光起点,value 范围:0-100000 + int SetMultiExpoMin(int value); + //获取多曝光起点 + int GetMultiExpoMin(); + //设置多曝光终点,value 范围:0-100000 + int SetMultiExpoMax(int value); + //获取多曝光终点 + int GetMultiExpoMax(); + //设置增益 model: 1 模拟增益量 2 数据增益量 value: 增益值 times:缺省参数,缺省为1, 第二次增益times = 2 camera_cnt:0是红外, 1是RGB + int SetGain(int mode, int value, int camera_cnt); + //获取相机增益值 mode: 1 模拟增益量 2 数字增益量 camera_cnt:0是红外, 1是RGB + int GetGain(int mode, int camera_cnt); + //设置相机触发模式mode: 0 连拍模式 1 触发模式 + int SetTriggerMode(int mode); + //设置相机触发模式信号来源: 0 软触发 1 硬触发 + int SetTriggerSource(int sourcetype); + //设置相机RGB触发模式mode: 0 连拍模式 1 触发模式 + int SetRGBTriggerMode(int mode); + //设置相机触发模式下的触发帧数 + int SetTriggerCount(); + //获取相机触发模式下的触发帧数 + int GetTriggerCount(); + //设置相机RGB触发模式下的触发帧数 + int SetRGBTriggerCount(); + //设置ROI channel_index :数据流通道索引 + int SetRoi(int channel_index, int size_x, int size_y, int offset_x, int offset_y); + //开启数据流通道 channel_index :数据流通道索引 + int StreamOn(unsigned short channel_index, GigeStream** gigestream); + // 设置激光模型 1:line, 0:plane + int SetLaserMode(int mode); + // 获取激光模型 + int GetLaserMode(); + //设置点云后处理模型 + int SetPointCloudPostProcessMode(int mode); + //获取点云后处理模型 + int GetPointCloudPostProcessMode(); + //设置点云增益值,取值范围:0-30,只有当点云自动增益等级为0时才可以设置该值 + int SetPointCloudThresholdValue(int value); + //获取点云增益值 + int GetPointCloudThresholdValue(int* value); + //设置点云自动增益等级: 0-20 + int SetPointCloudThresholdLevel(int level); + //获取点云自动增益等级 + int GetPointCloudThresholdLevel(int* level); + // 获取xml buffer size + int GetXMLBufferSize(int* size); + //获取xml buffer + int GetXMLBuffer(char* buffer); + //开启或关闭时间戳同步status: 0 关闭时间戳同步 1 开启时间戳同步 + int SetTimestamp(int status); + //获取时间戳是否开启 + int GetTimestamp(); + //获取PTPD状态码 + int GetTimestampStatus(); + //控制锁存时间戳 + int SetTimestampControlLatch(); + //获取时间戳 + unsigned long long GetTimestampValue(); + //获取时间戳频率 + unsigned long long GetTimestampTickFrequency(); + //开始接受数据 + int AcquisitionStart(void); + //停止接受数据 + int AcquisitionStop(void); + //关闭数据流通道 + int StreamOff(unsigned short channel_index, GigeStream* gigestream); + //相机断开连接 + int CameraDisconnect(); + //保存xml到本地 + int SaveXmlToLocal(std::string pathname); + //点云从char *转成float * + void Convert3DPointFromCharToFloat(PhotoInfo &raw_data, float* output); + //rawdata转RGB888图,输出的数据仍存放在PhotoInfo结构体的pixel中 + int RawdataToRgb888(PhotoInfo &rgb_data); + //点云滤波(基于空间密度的点云去噪) + void FilterPointCloud(PhotoInfo &raw_data, double level); + //空间滤波(基于空间网格的点云去噪) 20220225: 弃用, FilterPointCloud为该API的升级版 + int SpatialFilterPointcloud(PhotoInfo &raw_data, int Area_PointCloudCount); + //获取点云的X平面数据 + int GetCloudPlaneX(PhotoInfo &raw_data, short *imagedata); + //获取点云的Y平面数据 + int GetCloudPlaneY(PhotoInfo &raw_data, short *imagedata); + //获取点云的Z平面数据 + int GetCloudPlaneZ(PhotoInfo &raw_data, short *imagedata); + //保存点云某个平面数据 + int SaveCloudPlane(PhotoInfo &raw_data, short *imagedata, char* path_name); + //保存点云 to pcd 格式 + int SavePointCloudToPcd(PhotoInfo &raw_data, char* path_name); + //保存点云 to txt 格式 + int SavePointCloudToTxt(PhotoInfo &raw_data, char* path_name); + //保存点云 to ply 格式 + int SavePointCloudToPly(PhotoInfo &raw_data, char* path_name); + //保存BMP图片 + int SaveToBMP(PhotoInfo &data, char *path_name); + //保存点云深度图 to png + int SaveDepthToPng(PhotoInfo &raw_data, char* path_name); + //点云与图片融合(image_data:图片数据, raw_data:点云数据, image_cloud点云图片融合后的数据, is_filter是否进行滤波,默认滤波) + int FusionImageTo3D(PhotoInfo &image_data, PhotoInfo &raw_data, float * image_cloud); + //点云RGB进行融合(以RGB为标准重排点云,重排后的点云index对应无畸变的rgb图像) + int Fusion3DToRGBWithOutDistortion(PhotoInfo& rgb_data, PhotoInfo& raw_data, PhotoInfo& xyz); + //保存与图片融合后的点云txt + int SavePointCloudWithImageToTxt(PhotoInfo &raw_data, float * image_cloud, char *path_name); + //保存与图片融合后的点云Ply + int SavePointCloudWithImageToPly(PhotoInfo &raw_data, float * image_cloud, char *path_name); + //保存与图片融合后的点云Pcd + int SavePointCloudWithImageToPcd(PhotoInfo &raw_data, float * image_cloud, char *path_name); + //相机固件的版本号 + char* CameraVerion(DiscoveryInfo discovery_info); + //SDK的版本号 + char* SDKVersion(); + + //点云RGB进行融合(以RGB为标准重排点云) + int Fusion3DToRGB(PhotoInfo &rgb_data, PhotoInfo &raw_data, PhotoInfo &xyz); + //固件升级 + int FirmwareUpgrade(DiscoveryInfo discovery_info, const char *localfilename); + //内核升级 + int KernelUpgrade(DiscoveryInfo discovery_info, const char* localfilename); + //获取下位机日志 + int DownloadCameraLog(DiscoveryInfo discovery_info, const char* path, const char* name); + //获取下位机日志目录 + int CameraLogList(DiscoveryInfo discovery_info, std::vector* filename_s, int* len); + //根据roi对数据进行裁剪,可以传入rgb和gray数据 + int ImageRoiCrop(PhotoInfo& source_data, RoiPoint roi, PhotoInfo& target_data); + //获取相机盖状态,此接口只对特定型号相机适用, 0:相机盖关闭,1:相机盖打开, 其他:查看错误码 + int GetCameraCoverStatus(int* status); + //打开相机盖,此接口只对特定型号相机适用 + int TurnOnCameraCover(); + //关闭相机盖,此接口只对特定型号相机适用 + int TurnOffCameraCover(); + // 设置激光器正常/关闭接口,1:关闭,0:正常,默认是正常状态 + int SetLaserStatus(int status); + // 获取激光器正常/关闭接口,1:关闭,0:正常,返回值,0:成功,其他查看错误码 + int GetLaserStatus(int *status); + +private: +#ifdef _WIN32 + static unsigned int _stdcall HeartBeat(void* arg); +#else + static void* HeartBeat(void* arg); +#endif + //读内存 + int ReadMem(unsigned int mem_addr, unsigned short count, char* recv_buf); + //写内存 + int WriteMem(unsigned int mem_addr, unsigned short count, char* recv_buf); + //获取相机xml配置文件 + int GetXMLfromCamera(); + ////获取节点int类型的属性值 + //int GetNodeProperty(const char* key, char *property); + //协商数据流包大小 + int GetPacketSize(); + //YUYV转RGB888图,输出的数据仍存放在PhotoInfo结构体的pixel中 + int YuyvToRgb888(PhotoInfo &rgb_data); + //JPEG转RGB888图,输出的数据仍存放在PhotoInfo结构体的pixel中 + int JpegToRgb888(PhotoInfo &rgb_data); + //BayerRG8转RGB888图,输出的数据仍存放在PhotoInfo结构体的pixel中 + int BayerRG8ToRgb888(PhotoInfo &rgb_data); + //获取点云的某个平面数据 + int GetCloudPlane(PhotoInfo &raw_data, short *imagedata, int plane); + //保存红外图 to bmp 格式 + int SaveGrayImageToBmp(PhotoInfo &gray_data, char *path_name); + //保存rgb图 (RGB888) + int SaveRgb888ToBmp(PhotoInfo &rgb_data, char* path_name); + //保存rgb图 (RGB565) + int SaveRgb565ToBmp(PhotoInfo &rgb_data, char* path_name); + //保存rgb图(YUYV) + int SaveYuyvRgbToBmp(PhotoInfo &rgb_data, char* path_name); + int SaveBayerRG8ToBmp(PhotoInfo &rgb_data, char* path_name); + int SaveJpegDataToJpeg(PhotoInfo &rgb_data, char* path_name); + //清空socket + void flush_socket_buffer(int skt); + char *CameraIP(unsigned int Camera_IP); + +private: +#ifdef _WIN32 + //读写寄存器信号量 + HANDLE rw_reg_sem = NULL; + //心跳线程线程句柄 + HANDLE ccp_thread_id = NULL; +#else + //读写寄存器信号量 + sem_t rw_reg_sem; + //心跳线程线程ID + pthread_t ccp_thread_id = 0; +#endif + unsigned short req_id_; + int camera_num_; + int gvcp_socket_id_; + int ccp_flag_; + //保存相机配置文件xml + char* xml_buffer_; + //相机配置文件名 + std::string xml_name_; + //相机配置文件xml的后缀: xml or zip + std::string xml_extension_; + //相机配置文件xml的文件大小 + int xml_size_; + //PC端MTU + int mtu_; + //流通道索引 + //unsigned short stream_channel_index_; + //接收数据流包大小 + int pack_size_; //包大小 + //心跳超时时间 + int heart_beat_timeout_; + //记录主机和客户端信息 + InstanceDevice device_info_; + cameralog logfile; + std::vector cam_para; + //通道和cmos对应关系 + int* streamList; + int* comsList; + int streamSize; + int comsSize; + + char *camera_ip_; + FtpServer ftpserver; +public: + void *node_map; + void *Register_Data; +}; + +#endif //!DKAM_GIGE_CAMERA_H diff --git a/include/dkam_gige_stream.h b/include/dkam_gige_stream.h new file mode 100644 index 0000000..b689f0f --- /dev/null +++ b/include/dkam_gige_stream.h @@ -0,0 +1,258 @@ +#pragma once +#include "dkam_asyncqueue.h" +#include "dkam_base_type.h" +#include "dkam_gige_camera.h" +#include "dkam_log.h" + +#define GVSP_MAX_INCOMIN_SIZE 65535 //收到数据最大尺寸,其实巨帧也不过9000多个,这最为了方便,直接用16位的最大值 。 + +#define SOCKET_SELECT_TIMEOUT_US_DEFAULT 10000 //select超时时间 +#define PACKET_TIMEOUT_US_DEFAULT 400000 //重发包超时时间 +#define BLOCK_TIMEOUT_US_DEFAULT 1000000 //block完整性检查评估的超时时间 +#define PACKET_RESEND_FLAG_DEFAILT TRUE //重发包使用 +#define PACKET_RESEND_RATIO_DEFAULT 1 //重发比例,默认100% +#define MAX_BUFFER_LENGTH_DEFAULT 4 //最长的缓冲区大小,默认4 +#define NET_SPEED_STATISTICS_UNIT_TIME_US 1000000 //网速计时的默认时间区间,1s +#define SEND_TO_SOURCE_SOURCE_TIME_US_DEFAULT 3000000 //默认发包破防火墙的间隔 3s +#define MAX_DISCARD_BLOCK_NUM 2000 //最大认为系统丢弃block数量 +//block数据类型,目前主要用IMAGE +typedef enum { + BUFFER_PAYLOAD_TYPE_UNKNOWN = -1, + BUFFER_PAYLOAD_TYPE_IMAGE = 0x0001, + BUFFER_PAYLOAD_TYPE_RAWDATA = 0x0002, + BUFFER_PAYLOAD_TYPE_FILE = 0x0003, + BUFFER_PAYLOAD_TYPE_CHUNK_DATA = 0x0004, + BUFFER_PAYLOAD_TYPE_EXTENDED_CHUNK_DATA = 0x0005, /* Deprecated */ + BUFFER_PAYLOAD_TYPE_JPEG = 0x0006, + BUFFER_PAYLOAD_TYPE_JPEG2000 = 0x0007, + BUFFER_PAYLOAD_TYPE_H264 = 0x0008, + BUFFER_PAYLOAD_TYPE_MULTIZONE_IMAGE = 0x0009 +} BufferPayloadType; + +//block收到数据的状态 +typedef enum { + BUFFER_STATUS_SUCCESS = 0, + BUFFER_STATUS_UNKNOWN = -1, + BUFFER_STATUS_CLEARED = -2, + BUFFER_STATUS_TIMEOUT = -3, + BUFFER_STATUS_MISSING_PACKETS = -4, + BUFFER_STATUS_WRONG_PACKET_ID = -5, + BUFFER_STATUS_WRONG_PACKET = -6, + BUFFER_STATUS_SIZE_MISMATCH = -7, + BUFFER_STATUS_FILLING = -8, + BUFFER_STATUS_ABORTED = -9 +} BufferStatus; + +typedef struct { + unsigned short status; + unsigned short block_id; + unsigned int packet_infos; +} GvspHeader; + +typedef struct { + unsigned short flags; + unsigned short payload_type; + unsigned int timestamp_high; + unsigned int timestamp_low; + unsigned int pixel_format; + unsigned int width; + unsigned int height; + unsigned int x_offset; + unsigned int y_offset; + unsigned int gvsp_payload_size; +} GvspDataLeader; + +typedef struct { + unsigned int payload_type; + unsigned int data0; +} GvspDataTrailer; + +typedef struct { + GvspHeader header; + unsigned char data[1]; +} GvspPacket; + +typedef struct +{ + AsyncQueue *in_buffer; + AsyncQueue *out_buffer; +}StreamDataBuffer; + +typedef struct { + int received; //是否收到包的布尔量 + long long time_us; //重发该包请求的时间 +} StreamPacketData; + +typedef struct { + PhotoInfo *buffer; + unsigned int block_id; + + int buffer_status; //当前包的状态,最好并入到PhotoInfo中,发便用户层查看 + int last_valid_packet; //连续的有效包中的最后一包,用于重发时检查的起点 + long long first_packet_time_us; //收到首包的时间,用于后面的统计 + long long last_packet_time_us; //收到最后一包的时间,在完整性检查是查看是否超时 + + int error_packet_received; //收到错误包,以此决定是不是可以将此包送到用户层 + int packet_resend_num; //每次重发包的次数,避免过多重发包请求干扰正常的GVCP通信。 + unsigned int n_packets; //一个block的总包数 + StreamPacketData *packet_data; //每包的信息 +} StreamBlockData; + + +class GigeStream +{ +public: + GigeStream(DataStreamPrivateInfo *stream_private_info, + unsigned short channel_index, + int gvsp_socket_id, + int interface_address, + int device_address, + int source_stream_port, + int stream_port, + int packet_size, + long long timestamp_tick_frequency, + cameralog *logfile); + ~GigeStream(); + int Capture(PhotoInfo *buffer); + int TryCapture(PhotoInfo *buffer); + int TimeoutCapture(PhotoInfo *buffer, long long timeout); + void FlushBuffer(); + int SetMaxBufferLength(unsigned int length); + int GetMaxBufferLength(); + void SetPacketResendFlag(unsigned int flag); + int GetPacketResendFlag(); + int SetPacketResendRatio(double ratio); + double GetPacketResendRatio(); + int SetSocketSelectTimeout(unsigned int timeout); + int GetSocketSelectTimeout(); + int SetPacketTimeout(unsigned int timeout); + int GetPacketTimeout(); + int SetBlockTimeout(unsigned int timeout); + int GetBlockTimeout(); + int SetSendtoStreamPortBreakFirewallTimeout(unsigned int timeout); + int GetSendtoStreamPortBreakFirewallTimeout(); + + + //Statistics(); + + void GetBlockStatistics( + unsigned int *completed_buffers, + unsigned int *failures, + unsigned int *timeouts, + unsigned int *underruns, + unsigned int *aborteds, + unsigned int *missing_frames, + unsigned int *block_camera_wrong, + unsigned int *size_mismatch_errors); + + void GetPacketStatistics( + unsigned int *received_packets, + unsigned int *missing_packets, + unsigned int *error_packets, + unsigned int *ignored_packets, + unsigned int *resend_requests, + unsigned int *resent_packets, + unsigned int *duplicated_packets); + + + void GetRecieveTimeStatistics(StatisticsData *o_statistics_data); + int GetNetSpeed(); + float GetBlockRate(); + int thread_exit; +private: + #ifdef _WIN32 + //接收点云数据线程 + static unsigned int _stdcall Stream_Thread(void* arg); + #else + static void* Stream_Thread(void* arg); + #endif + void loop(); + + StreamBlockData* process_packet(GvspPacket *packet, int recv_num, long long time_us); + StreamBlockData* find_block_data(GvspPacket *packet, unsigned int block_id, unsigned int packet_id, int recv_num, long long time_us); + void process_packet_leader(StreamBlockData *block, GvspPacket *packet, unsigned int packet_id); + void process_packet_payload(StreamBlockData *block, GvspPacket *packet, unsigned int packet_id, unsigned int recv_num); + void process_packet_tailer(StreamBlockData *block, GvspPacket *packet, unsigned int packet_id); + void packet_resend_check(StreamBlockData *block, unsigned int packet_id, long long time_us); + void send_packet_request(unsigned int block_id, int resend_first_packet_id, int resend_last_packet_id); + void write_recive_data(StreamBlockData *block); + void check_block_complete(StreamBlockData *block,long long time_us); + void capture_data_process(PhotoInfo *buffer, PhotoInfo *temp_buffer); + void flush_blocks(); + void sendto_stream_source_data(); + void RecieveTimeStatistics(int data); + void NetSpeedStatistics(int recv_num, long long time_us); + void BlockRateStatistics(long long time_us); +#ifdef _WIN32 + HANDLE stream_on_thread_id; +#else + pthread_t stream_on_thread_id; +#endif + unsigned short channel_index_; + StreamDataBuffer stream_data; + StatisticsData statistics_data; + DataStreamPrivateInfo stream_private_info_; + + List *all_blocks; + + int gvsp_socket_id_; + int resend_socket_id_; + int interface_address_; + //int *interface_socket_address; + int device_address_; + //int *device_socket_address; + struct sockaddr_in resend_addr; + struct sockaddr_in sendto_source_addr; + unsigned short source_stream_port_; + unsigned short stream_port_; + long long timestamp_tick_frequency_; + + unsigned short resend_req_id; + + unsigned int last_block_id; + unsigned int max_buffer_length_; + unsigned int packet_payload_size_; + unsigned int max_payload_size_; + unsigned int current_buffer_length; + + int packet_resend_flag_; + double packet_resend_ratio_; + unsigned int socket_select_timeout_us_; + unsigned int packet_timeout_us_; + unsigned int block_timeout_us_; + unsigned int sento_stream_source_time_us_; + + /* Statistics */ + //block部分的数据统计 + unsigned int n_completed_buffers; + unsigned int n_failures; + unsigned int n_timeouts; + unsigned int n_underruns; + unsigned int n_aborteds; + unsigned int n_missing_frames; + unsigned int n_block_camera_wrong; + + unsigned int n_size_mismatch_errors; + + //packet部分的数据统计 + unsigned int n_received_packets; + unsigned int n_missing_packets; + unsigned int n_error_packets; + unsigned int n_ignored_packets; + unsigned int n_resend_requests; + unsigned int n_resent_packets; + unsigned int n_duplicated_packets; + + //网速部分的统计,这里主要是时间,这段时间收到的数据量和以此计算出来的网速; + long long n_start_time_us; + long long n_recv_num_unit_time; + unsigned int n_net_speed; + long long n_block_start_time_us; + unsigned int n_block_recv_num_unit_time; + float n_block_rate; + cameralog *logfilestream; + //重发包计时 + time_t start_time_resend; + time_t end_time_resend; +}; + diff --git a/include/dkam_log.h b/include/dkam_log.h new file mode 100644 index 0000000..6af25ee --- /dev/null +++ b/include/dkam_log.h @@ -0,0 +1,41 @@ +#pragma once + +#include + +//这可以在主函数中直接修改全局变量的值,以便可查找,也可以定义宏变量的值,然后把宏变量的值写一下。 +extern int gvsp_log_error_level; +extern int gvsp_log_warnning_level; +extern int gvsp_log_info_level; +extern int gvsp_log_debug_level; + +extern int gvcp_log_error_level; +extern int gvcp_log_warnning_level; +extern int gvcp_log_info_level; +extern int gvcp_log_debug_level; + + +class cameralog { + +public: + cameralog(); + ~cameralog(); + void log_error(int level, const char *format, ...); + int log_enable(char *logname); + void log_warnning(int level, const char *format, ...); + void log_info(int level, const char *format, ...); + void log_debug(int level, const char *format, ...); + void log_disable(void); + +private: + FILE *fd; +}; +#define log_error_gvsp(...) log_error (gvsp_log_error_level, __VA_ARGS__) +#define log_warnning_gvsp(...) log_warnning (gvsp_log_warnning_level, __VA_ARGS__) +#define log_info_gvsp(...) log_info (gvsp_log_info_level, __VA_ARGS__) +#define log_debug_gvsp(...) log_debug (gvsp_log_debug_level, __VA_ARGS__) + +#define log_error_gvcp(...) log_error (gvcp_log_error_level, __VA_ARGS__) +#define log_warnning_gvcp(...) log_warnning (gvcp_log_warnning_level, __VA_ARGS__) +#define log_info_gvcp(...) log_info (gvcp_log_info_level, __VA_ARGS__) +#define log_debug_gvcp(...) log_debug (gvcp_log_debug_level, __VA_ARGS__) + diff --git a/include/dkam_zhicamera_api.h b/include/dkam_zhicamera_api.h new file mode 100644 index 0000000..3c25e0a --- /dev/null +++ b/include/dkam_zhicamera_api.h @@ -0,0 +1,394 @@ +/******************************************************************************************** +Copyright : XianZhisensorTechnologiesCo.,Ltd +File Name : zhicamera_api.cpp +Description: APIs accessed by users +Version : +Author : ss.chen +Data : 2020-4-16 +History : +*********************************************************************************************/ +#pragma once +#include "dkam_base_type.h" +#include + + +#ifdef __cplusplus +extern "C" +{ +#endif +//using namespace std; + + + typedef struct CAMERA_OBJECT Camera_Object_C; + + +#ifdef _WIN32 +#define DLLEXPORT_API extern __declspec(dllexport) +#define DLL_INT int __stdcall +#define DLL_CHAR_POINT char* __stdcall +#define DLL_BOOL bool __stdcall +#define DLL_UNSIGNED_LONG_LONG unsigned long long __stdcall +#define DLL_UNSIGNED_INT unsigned int __stdcall +#define DLL_VOID void __stdcall +#define DLL_CAMERA_OBJECT_POINT Camera_Object_C* __stdcall +#define DLL_DISCOVERYINFO DiscoveryInfo __stdcall +#define DLL_DOUBLE double __stdcall +#define DLL_FLOAT float __stdcall +#else +#define DLLEXPORT_API +#define DLL_INT int +#define DLL_CHAR_POINT char* +#define DLL_BOOL bool +#define DLL_UNSIGNED_LONG_LONG unsigned long long +#define DLL_UNSIGNED_INT unsigned int +#define DLL_VOID void +#define DLL_CAMERA_OBJECT_POINT Camera_Object_C* +#define DLL_DISCOVERYINFO DiscoveryInfo +#define DLL_DOUBLE double +#define DLL_FLOAT float +#endif + +/*///////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////// +******************************************相机相关操作***************************************** +/////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////*/ +//发现相机 +DLLEXPORT_API DLL_INT DiscoverCamera(); +//创建相机 +DLLEXPORT_API DLL_CAMERA_OBJECT_POINT CreateCamera(int camera_index); +//销毁相机 +DLLEXPORT_API DLL_VOID DestroyCamera(Camera_Object_C* camera_obj); +//相机排序(0:IP 1:series number) +DLLEXPORT_API DLL_INT CameraSort(int sort_mode); +//获取相机的信息 +DLLEXPORT_API DLL_DISCOVERYINFO GetCameraInfo(Camera_Object_C* camera_obj); +//获取相机的CCP状态(data输出0为可连接) +DLLEXPORT_API DLL_INT GetCameraCCPStatus(Camera_Object_C* camera_obj, int *data); +//获取相机xml所有节点名称 +DLLEXPORT_API DLL_VOID GetCameraXMLNodeNames(Camera_Object_C* camera_obj, char node_names[][255], int* len); +//获取节点最大值 +DLLEXPORT_API DLL_INT GetNodeMaxValue(Camera_Object_C* camera_obj, const char* key); +//获取节点最小值 +DLLEXPORT_API DLL_INT GetNodeMinValue(Camera_Object_C* camera_obj, const char* key); +//获取int节点增量 +DLLEXPORT_API DLL_INT GetNodeIncValue(Camera_Object_C* camera_obj, const char* key); +//获取相机节点value(camera_ret:相机索引号 key:寄存器的名称) +DLLEXPORT_API DLL_INT GetRegisterAddr(Camera_Object_C* camera_obj, const char* key); +//读string类型的寄存器 +DLLEXPORT_API DLL_INT ReadStringRegister(Camera_Object_C* camera_obj, const char* key, char* reg_str); +//写string类型的寄存器 +DLLEXPORT_API DLL_INT WriteStringRegister(Camera_Object_C* camera_obj, const char* key, unsigned short datasize, char* reg_str); +//相机IP(camera_ret:相机索引号) +DLLEXPORT_API DLL_CHAR_POINT CameraIP(int camera_index); +//相机的序列号 +DLLEXPORT_API DLL_CHAR_POINT CameraSeriesNumberByIndex(int camera_index); +//设置log日志的等级开关,决定是否开启某一个等级的日志打印,默认关闭(0:关闭 1:开启) +DLLEXPORT_API DLL_VOID SetLogLevel(int error, int debug, int warnning, int info); +//连接相机(camera_ret:相机索引号) +DLLEXPORT_API DLL_INT CameraConnect(Camera_Object_C* camera_obj); +//开启数据流通道 (camera_ret:相机索引号 channel_index :数据流通道索引) +DLLEXPORT_API DLL_INT StreamOn(Camera_Object_C* camera_obj, unsigned short channel_index); +//开始接受数据 (camera_ret:相机索引号) +DLLEXPORT_API DLL_INT AcquisitionStart(Camera_Object_C* camera_obj); +//清空缓存区 +DLLEXPORT_API DLL_VOID FlushBuffer(Camera_Object_C* camera_obj, unsigned short channel_index); +//设置最大的缓冲区 +DLLEXPORT_API DLL_INT SetMaxBufferLength(Camera_Object_C* camera_obj, unsigned short channel_index, unsigned int size); +//获取buffer缓冲区大小 +DLLEXPORT_API DLL_INT GetMaxBufferLength(Camera_Object_C* camera_obj, unsigned short channel_index); +//设置重发包比例 +DLLEXPORT_API DLL_INT SetPacketResendRatio(Camera_Object_C* camera_obj, unsigned short channel_index, double ratio); +//获取重发包比例 +DLLEXPORT_API DLL_DOUBLE GetPacketResendRatio(Camera_Object_C* camera_obj, unsigned short channel_index); +//设置select的超时时间 +DLLEXPORT_API DLL_INT SetSocketSelectTimeout(Camera_Object_C* camera_obj, unsigned short channel_index, unsigned int timeout); +//获取select的超时时间 +DLLEXPORT_API DLL_INT GetSocketSelectTimeout(Camera_Object_C* camera_obj, unsigned short channel_index); +//设置重发包超时时间 +DLLEXPORT_API DLL_INT SetPacketTimeout(Camera_Object_C* camera_obj, unsigned short channel_index, unsigned int timeout); +//获取重发包超时时间 +DLLEXPORT_API DLL_INT GetPacketTimeout(Camera_Object_C* camera_obj, unsigned short channel_index); +//设置block超时时间 +DLLEXPORT_API DLL_INT SetBlockTimeout(Camera_Object_C* camera_obj, unsigned short channel_index, unsigned int timeout); +//获取block超时时间 +DLLEXPORT_API DLL_INT GetBlockTimeout(Camera_Object_C* camera_obj, unsigned short channel_index); +//获取帧方面的统计数据 +DLLEXPORT_API DLL_VOID GetBlockStatistics(Camera_Object_C* camera_obj, unsigned short channel_index, + unsigned int *completed_buffers, + unsigned int *failures, + unsigned int *timeouts, + unsigned int *underruns, + unsigned int *aborteds, + unsigned int *missing_frames, + unsigned int *block_camera_wrong, + unsigned int *size_mismatch_errors); +//获取包方面的统计数据 +DLLEXPORT_API DLL_VOID GetPacketStatistics(Camera_Object_C* camera_obj, unsigned short channel_index, + unsigned int *received_packets, + unsigned int *missing_packets, + unsigned int *error_packets, + unsigned int *ignored_packets, + unsigned int *resend_requests, + unsigned int *resent_packets, + unsigned int *duplicated_packets); +//获取接收花费时间方面的统计数据 +DLLEXPORT_API DLL_VOID GetRecieveTimeStatistics(Camera_Object_C* camera_obj, unsigned short channel_index, StatisticsData *o_statistics_data); +//获取网速的统计数据 +DLLEXPORT_API DLL_INT GetNetSpeed(Camera_Object_C* camera_obj, unsigned short channel_index); +//获取帧率的统计数据 +DLLEXPORT_API DLL_FLOAT GetBlockRate(Camera_Object_C* camera_obj, unsigned short channel_index); +//获取数据(camera_ret:相机索引号 channel_index :数据流通道索引 raw_data:接收数据的结构体 ) +/*C++*/ +//阻塞式抓取数据 +DLLEXPORT_API DLL_INT Capture(Camera_Object_C* camera_obj, unsigned short channel_index, PhotoInfo *raw_data); +//try +DLLEXPORT_API DLL_INT TryCapture(Camera_Object_C* camera_obj, unsigned short channel_index, PhotoInfo *raw_data); +//超时抓取数据 +DLLEXPORT_API DLL_INT TimeoutCapture(Camera_Object_C* camera_obj, unsigned short channel_index, PhotoInfo *raw_data, long long timeout); +//将char数据转为float类型 +DLLEXPORT_API DLL_VOID Convert3DPointFromCharToFloat(Camera_Object_C* camera_obj, PhotoInfo *raw_data, float* output); +//将Rawdata数据转换成RGB888的图像数据 +DLLEXPORT_API DLL_INT RawdataToRgb888(Camera_Object_C* camera_obj, PhotoInfo *rgb_data); +//获取点云的X平面数据 +DLLEXPORT_API DLL_INT GetCloudPlaneX(Camera_Object_C* camera_obj, PhotoInfo *raw_data, short *imagedata); +//获取点云的Y平面数据 +DLLEXPORT_API DLL_INT GetCloudPlaneY(Camera_Object_C* camera_obj, PhotoInfo *raw_data, short *imagedata); +//获取点云的Z平面数据 +DLLEXPORT_API DLL_INT GetCloudPlaneZ(Camera_Object_C* camera_obj, PhotoInfo *raw_data, short *imagedata); +//保存点云某个平面数据 +DLLEXPORT_API DLL_INT SaveCloudPlane(Camera_Object_C* camera_obj, PhotoInfo *raw_data, short *imagedata, char* path_name); +/*C++*/ + +//停止接受数据(camera_ret:相机索引号) +DLLEXPORT_API DLL_INT AcquisitionStop(Camera_Object_C* camera_obj); +//关闭数据流通道 (camera_ret:相机索引号 channel_index :数据流通道索引) +DLLEXPORT_API DLL_INT StreamOff(Camera_Object_C* camera_obj, unsigned short channel_index); +//相机断开连接 (camera_ret:相机索引号) +DLLEXPORT_API DLL_INT CameraDisconnect(Camera_Object_C* camera_obj); +/*///////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////// +****************************************GVCP相关操作******************************************* +/////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////*/ +//获取相机内参 +DLLEXPORT_API DLL_INT GetCamInternelParameter(Camera_Object_C* camera_obj, int camera_cnt, float *Kc, float *K); +//获取相机外参 +DLLEXPORT_API DLL_INT GetCamExternelParameter(Camera_Object_C* camera_obj, int camera_cnt, float *R, float *T); +//设置相机ip (camera_ret:相机索引号 ip mask gateway) +DLLEXPORT_API DLL_INT ForceIP(Camera_Object_C* camera_obj, char* ip, char* mask, char* gateway); +//判断相机的IP和PC的IP是否在同一网段 +DLLEXPORT_API DLL_BOOL WhetherIsSameSegment(Camera_Object_C* camera_obj); +//设置相机种类 (0:红外;1:RGB) +DLLEXPORT_API DLL_INT SetCameraType(Camera_Object_C* camera_obj, int camera_cnt); +//获取相机种类 (0:红外;1:RGB) +DLLEXPORT_API DLL_INT GetCameraType(Camera_Object_C* camera_obj); +//获取相机的宽 +DLLEXPORT_API DLL_INT GetCameraWidth(Camera_Object_C* camera_obj, int* width, int camera_cnt); +//设置相机的宽 +DLLEXPORT_API DLL_INT SetCameraWidth(Camera_Object_C* camera_obj, int width, int camera_cnt); +//获取相机的高 +DLLEXPORT_API DLL_INT GetCameraHeight(Camera_Object_C* camera_obj, int* height, int camera_cnt); +//设置相机的高 +DLLEXPORT_API DLL_INT SetCameraHeight(Camera_Object_C* camera_obj, int height, int camera_cnt); +//写寄存器 (camera_ret:相机索引号 register_addr:寄存器的地址 data:接收寄存器的值) +DLLEXPORT_API DLL_INT WriteRegister(Camera_Object_C* camera_obj, unsigned int register_addr, int data); +//读寄存器 (camera_ret:相机索引号 register_addr:寄存器的地址 data:接收寄存器的值) +DLLEXPORT_API DLL_INT ReadRegister(Camera_Object_C* camera_obj, unsigned int register_addr, int* data); +//设置超时时间 +DLLEXPORT_API DLL_INT SetHeartBeatTimeout(Camera_Object_C* camera_obj, int value); +//获取超时时间 +DLLEXPORT_API DLL_INT GetHeartBeatTimeout(Camera_Object_C* camera_obj); +//设置相机曝光模式(camera_index:相机索引号 status:(1手动曝光,0自动曝光) camera_cnt:相机的摄像头0:红外摄像头 1:RGB摄像头) +DLLEXPORT_API DLL_INT SetAutoExposure(Camera_Object_C* camera_obj, int status, int camera_cnt); +//获取相机曝光模式 (camera_index:相机索引号 camera_cnt:相机的摄像头0:红外摄像头 1:RGB摄像头) +DLLEXPORT_API DLL_INT GetAutoExposure(Camera_Object_C* camera_obj, int camera_cnt); +//设置RGB摄像头自动曝光增益的级别(>=1 仅支持RGB摄像头) (camera_index:相机索引号 camera_cnt:相机的摄像头0:红外摄像头 1:RGB摄像头 level:曝光增益等级) +DLLEXPORT_API DLL_INT SetCamExposureGainLevel(Camera_Object_C* camera_obj, int camera_cnt, int level); +//获取RGB摄像头自动曝光增益的级别,仅支持RGB摄像头(camera_index:相机索引号 camera_cnt:相机的摄像头0:红外摄像头 1:RGB摄像头) +DLLEXPORT_API DLL_INT GetCamExposureGainLevel(Camera_Object_C* camera_obj, int camera_cnt); +//设置相机曝光类型int status >0 多重曝光 不能是0 +DLLEXPORT_API DLL_INT SetMutipleExposure(Camera_Object_C* camera_obj, int status); +//获取相机曝光类型 +DLLEXPORT_API DLL_INT GetMutipleExposure(Camera_Object_C* camera_obj); +//设置曝光时间utimes:曝光时间: 红外镜头范围1000 - 100000um, RGB镜头范围1000 - 56000um, 默认16600, camera_cnt:0是红外, 1是RGB +DLLEXPORT_API DLL_INT SetExposureTime(Camera_Object_C* camera_obj, int utime, int camera_cnt); +//获取相机曝光时间 camera_cnt:0是红外, 1是RGB +DLLEXPORT_API DLL_INT GetExposureTime(Camera_Object_C* camera_obj, int camera_cnt); +//设置相机多曝光模式, 0:等差,1:等比 +DLLEXPORT_API DLL_INT SetMultiExpoMode(Camera_Object_C* camera_obj, int mode); +//获取相机多曝光模式 +DLLEXPORT_API DLL_INT GetMultiExpoMode(Camera_Object_C* camera_obj); +//设置多曝光起点,value 范围:0-100000 +DLLEXPORT_API DLL_INT SetMultiExpoMin(Camera_Object_C* camera_obj, int value); +//获取多曝光起点 +DLLEXPORT_API DLL_INT GetMultiExpoMin(Camera_Object_C* camera_obj); +//设置多曝光终点,value 范围:0-100000 +DLLEXPORT_API DLL_INT SetMultiExpoMax(Camera_Object_C* camera_obj, int value); +//获取多曝光终点 +DLLEXPORT_API DLL_INT GetMultiExpoMax(Camera_Object_C* camera_obj); +//设置增益 model: 1 模拟增益量 2 数据增益量 value: 增益值 times:缺省参数,缺省为1, 第二次增益times = 2 +DLLEXPORT_API DLL_INT SetGain(Camera_Object_C* camera_obj, int mode, int value, int camera_cnt); +//获取相机增益值 mode: 1 模拟增益量 2 数据增益量 +DLLEXPORT_API DLL_INT GetGain(Camera_Object_C* camera_obj, int mode, int camera_cnt); +//设置相机触发模式mode: 0 连拍模式 1 触发模式 +DLLEXPORT_API DLL_INT SetTriggerMode(Camera_Object_C* camera_obj, int mode); +//设置相机触发模式信号来源: 0 软触发 1 硬触发 +DLLEXPORT_API DLL_INT SetTriggerSource(Camera_Object_C* camera_obj, int sourcetype); +//设置相机RGB触发模式mode: 0 连拍模式 1 触发模式 +DLLEXPORT_API DLL_INT SetRGBTriggerMode(Camera_Object_C* camera_obj, int mode); +//设置相机触发模式下的触发帧数 +DLLEXPORT_API DLL_INT SetTriggerCount(Camera_Object_C* camera_obj); +//获取相机触发模式下的触发帧数 +DLLEXPORT_API DLL_INT GetTriggerCount(Camera_Object_C* camera_obj); +//设置相机RGB触发模式下的触发帧数 +DLLEXPORT_API DLL_INT SetRGBTriggerCount(Camera_Object_C* camera_obj); +//设置重发请求: 0 关闭, 1 打开(默认开启) +DLLEXPORT_API DLL_VOID SetResendRequest(Camera_Object_C* camera_obj, int channel_index, int resend_flag); +//获取重发请求: 0 关闭, 1 打开(默认开启) +DLLEXPORT_API DLL_INT GetResendRequest(Camera_Object_C* camera_obj, int channel_index); +//设置红外摄像头ROI +DLLEXPORT_API DLL_INT SetRoi(Camera_Object_C* camera_obj, int channel_index, int size_x, int size_y, int offset_x, int offset_y); +//设置激光模式: 1 line, 0 plane(默认plane) +DLLEXPORT_API DLL_INT SetLaserMode(Camera_Object_C* camera_obj, int mode); +//获取激光模式 +DLLEXPORT_API DLL_INT GetLaserMode(Camera_Object_C* camera_obj); +//设置点云后处理模型 +DLLEXPORT_API DLL_INT SetPointCloudPostProcessMode(Camera_Object_C* camera_obj, int mode); +//获取点云后处理模型 +DLLEXPORT_API DLL_INT GetPointCloudPostProcessMode(Camera_Object_C* camera_obj); +//设置点云增益值,取值范围:0-30,只有当点云自动增益等级为0时才可以设置该值 +DLLEXPORT_API DLL_INT SetPointCloudThresholdValue(Camera_Object_C* camera_obj, int value); +//获取点云增益值 +DLLEXPORT_API DLL_INT GetPointCloudThresholdValue(Camera_Object_C* camera_obj, int* value); +//设置点云自动增益等级: 0-20 +DLLEXPORT_API DLL_INT SetPointCloudThresholdLevel(Camera_Object_C* camera_obj, int level); +//获取点云自动增益等级 +DLLEXPORT_API DLL_INT GetPointCloudThresholdLevel(Camera_Object_C* camera_obj, int* level); +//获取xml buffer size +DLLEXPORT_API DLL_INT GetXMLBufferSize(Camera_Object_C* camera_obj, int* xml_size); +//获取xml buffer +DLLEXPORT_API DLL_INT GetXMLBuffer(Camera_Object_C* camera_obj, char* buffer); +//保存用户配置文件 +DLLEXPORT_API DLL_INT SaveUserConfig(Camera_Object_C* camera_obj, char* fliename); +//加载用户配置文件 +DLLEXPORT_API DLL_INT LoadUserConfig(Camera_Object_C* camera_obj,char* fliename); +//获取节点类型 +DLLEXPORT_API DLL_INT GetNodeType(Camera_Object_C* camera_obj, const char* key); +//获取节点访问模式 +DLLEXPORT_API DLL_INT GetNodeAccessMode(Camera_Object_C* camera_obj, const char* key); +//设置Int类型节点值 +DLLEXPORT_API DLL_INT SetIntNodeValue(Camera_Object_C* camera_obj, const char* key, unsigned int value); +//设置Bool类型节点值 +DLLEXPORT_API DLL_INT SetBoolNodeValue(Camera_Object_C* camera_obj, const char* key, bool value); +//设置Command类型节点值 +DLLEXPORT_API DLL_INT SetCommandNodeValue(Camera_Object_C* camera_obj, const char* key); +//设置Float类型节点值 +DLLEXPORT_API DLL_INT SetFloatNodeValue(Camera_Object_C* camera_obj, const char* key, float value); +//设置String类型节点值 +DLLEXPORT_API DLL_INT SetStringNodeValue(Camera_Object_C* camera_obj, const char* key, char* value); +//设置Enumeration类型节点值 +DLLEXPORT_API DLL_INT SetEnumNodeValue(Camera_Object_C* camera_obj, const char* key, int value); +//获取Int类型节点值 +DLLEXPORT_API DLL_INT GetIntNodeValue(Camera_Object_C* camera_obj, const char* key, int* value); +//获取Bool类型节点值 +DLLEXPORT_API DLL_INT GetBoolNodeValue(Camera_Object_C* camera_obj, const char* key, int* value); +//获取Command类型节点值 +DLLEXPORT_API DLL_INT GetCommandNodeValue(Camera_Object_C* camera_obj, const char* key, char* value); +//获取Float类型节点值 +DLLEXPORT_API DLL_INT GetFloatNodeValue(Camera_Object_C* camera_obj, const char* key, float* value); +//获取String类型节点值 +DLLEXPORT_API DLL_INT GetStringNodeValue(Camera_Object_C* camera_obj, const char* key, char* value); +//获取Enumeration类型节点值 +DLLEXPORT_API DLL_INT GetEnumNodeValue(Camera_Object_C* camera_obj, const char* key, int* value); +//开启或关闭时间戳同步status: 0 关闭时间戳同步 1 开启时间戳同步 +DLLEXPORT_API DLL_INT SetTimestamp(Camera_Object_C* camera_obj, int status); +//获取时间戳是否开启 +DLLEXPORT_API DLL_INT GetTimestamp(Camera_Object_C* camera_obj); +//获取PTPD状态码 +DLLEXPORT_API DLL_INT GetTimestampStatus(Camera_Object_C* camera_obj); +//控制锁存时间戳 +DLLEXPORT_API DLL_INT SetTimestampControlLatch(Camera_Object_C* camera_obj); +//获取时间戳 +DLLEXPORT_API DLL_UNSIGNED_LONG_LONG GetTimestampValue(Camera_Object_C* camera_obj); +//获取时间戳频率 +DLLEXPORT_API DLL_UNSIGNED_LONG_LONG GetTimestampTickFrequency(Camera_Object_C* camera_obj); +//固件升级 +DLLEXPORT_API DLL_INT FirmwareUpgrade(Camera_Object_C* camera_obj, const char *localfilename); +//内核升级 +DLLEXPORT_API DLL_INT KernelUpgrade(Camera_Object_C* camera_obj, const char* localfilename); +//获取下位置日志 +DLLEXPORT_API DLL_INT DownloadCameraLog(Camera_Object_C* camera_obj, const char* filepath, const char* filename); +//获取下位机日志目录 +DLLEXPORT_API DLL_INT CameraLogList(Camera_Object_C* camera_obj, char filename_s[][255], int* len); +//图片裁剪 +DLLEXPORT_API DLL_INT ImageRoiCrop(Camera_Object_C* camera_obj, PhotoInfo* source_data, RoiPoint roi, PhotoInfo* target_data); +//获取相机盖状态,此接口只对特定型号相机适用, 0:相机盖关闭,1:相机盖打开, 其他:查看错误码 +DLLEXPORT_API DLL_INT GetCameraCoverStatus(Camera_Object_C* camera_obj, int* status); +//相机盖状态,此接口只对特定型号相机适用 +DLLEXPORT_API DLL_INT TurnOnCameraCover(Camera_Object_C* camera_obj); +//关闭相机盖,此接口只对特定型号相机适用 +DLLEXPORT_API DLL_INT TurnOffCameraCover(Camera_Object_C* camera_obj); +// 设置激光器正常/关闭接口,1:关闭,0:正常,默认是正常状态 +DLLEXPORT_API DLL_INT SetLaserStatus(Camera_Object_C* camera_obj, int status); +// 获取激光器正常/关闭接口,1:关闭,0:正常,返回值,0:成功,其他查看错误码 +DLLEXPORT_API DLL_INT GetLaserStatus(Camera_Object_C* camera_obj, int* status); + +/*///////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////// +*******************************************数据保存******************************************** +/////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////*/ +//保存xml到本地 +DLLEXPORT_API DLL_INT SaveXmlToLocal(Camera_Object_C* camera_obj, char* pathname); +/*C++*/ +//保存点云 to pcd 格式 +DLLEXPORT_API DLL_INT SavePointCloudToPcd(Camera_Object_C* camera_obj, PhotoInfo *raw_data, char* path_name); +//保存点云 to txt 格式 +DLLEXPORT_API DLL_INT SavePointCloudToTxt(Camera_Object_C* camera_obj, PhotoInfo *raw_data, char* path_name); +//保存点云 to ply 格式 +DLLEXPORT_API DLL_INT SavePointCloudToPly(Camera_Object_C* camera_obj, PhotoInfo *raw_data, char* path_name); +//点云滤波(基于空间密度的点云去噪) +DLLEXPORT_API DLL_VOID FilterPointCloud(Camera_Object_C* camera_obj, PhotoInfo *raw_data, double level); +//空间滤波(基于空间网格的点云去噪) 20220225: 弃用, FilterPointCloud为该API的升级版 +DLLEXPORT_API DLL_INT SpatialFilterPointcloud(Camera_Object_C* camera_obj, PhotoInfo *raw_data, int Area_PointCloudCount); +//保存bmp图 +DLLEXPORT_API DLL_INT SaveToBMP(Camera_Object_C* camera_obj, PhotoInfo *data, char* path_name); +//保存点云深度图 to png +DLLEXPORT_API DLL_INT SaveDepthToPng(Camera_Object_C* camera_obj, PhotoInfo *raw_data, char* path_name); +//点云融合 +DLLEXPORT_API DLL_INT FusionImageTo3D(Camera_Object_C* camera_obj, PhotoInfo *image_data, PhotoInfo *raw_data, float * image_cloud); +//根据RGB重排点云 +DLLEXPORT_API DLL_INT Fusion3DToRGB(Camera_Object_C* camera_obj, PhotoInfo *rgb_data, PhotoInfo *raw_data, PhotoInfo *xyz); +//根据RGB重排点云 +DLLEXPORT_API DLL_INT Fusion3DToRGBWithOutDistortion(Camera_Object_C* camera_obj, PhotoInfo* rgb_data, PhotoInfo* raw_data, PhotoInfo* xyz); +//图像膨胀 +DLLEXPORT_API DLL_VOID PixelSwell(Camera_Object_C* camera_obj, int *roi_output, PhotoInfo *target_data); +//图像腐蚀 +DLLEXPORT_API DLL_VOID PixelCorrosion(Camera_Object_C* camera_obj, int *roi_output, PhotoInfo *target_data); +//ROI映射区域坐标 +DLLEXPORT_API DLL_VOID ROIMappingCoordinate(Camera_Object_C* camera_obj, int *roi_output, PhotoInfo *target_data, RoiPoint *point_output); +//ROI检索映射 +DLLEXPORT_API DLL_VOID ROIPixelMapping(Camera_Object_C* camera_obj, PhotoInfo *point_data, PhotoInfo *source_data, PhotoInfo *target_data, RoiPoint *roi_input, int *ROI_output); +//保存点云 with image to pcd +DLLEXPORT_API DLL_INT SavePointCloudWithImageToTxt(Camera_Object_C* camera_obj, PhotoInfo *raw_data, float * rgb_cloud, char *path_name); +//保存与图片融合后的点云Ply(保留无效点) +DLLEXPORT_API DLL_INT SavePointCloudWithImageToPly(Camera_Object_C* camera_obj, PhotoInfo *raw_data, float * image_cloud, char *path_name); +//保存与图片融合后的点云Pcd +DLLEXPORT_API DLL_INT SavePointCloudWithImageToPcd(Camera_Object_C* camera_obj, PhotoInfo *raw_data, float * image_cloud, char *path_name); +/*C++*/ +/*///////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////// +*******************************************获取版本号****************************************** +/////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////*/ +//相机固件的版本号 +DLLEXPORT_API DLL_CHAR_POINT CameraVerions(Camera_Object_C* camera_obj); +//SDK的版本号 +DLLEXPORT_API DLL_CHAR_POINT SDKVersion(Camera_Object_C* camera_obj); +DLLEXPORT_API DLL_CHAR_POINT sdkversion(); +//相机的序列号 +DLLEXPORT_API DLL_CHAR_POINT CameraSeriesNumber(Camera_Object_C* camera_obj); +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/include/dkam_zhicamera_api_csharp.h b/include/dkam_zhicamera_api_csharp.h new file mode 100644 index 0000000..6fb0469 --- /dev/null +++ b/include/dkam_zhicamera_api_csharp.h @@ -0,0 +1,93 @@ +#pragma once + +#include "dkam_base_type.h" +#include +#include + + +#ifdef _WIN32 +#define DLLEXPORT_API extern __declspec(dllexport) +#define DLL_VOID void __stdcall +#define DLL_INT int __stdcall +#else +#define DLLEXPORT_API +#define DLL_VOID void +#define DLL_INT int +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + /*C#*/ + DLLEXPORT_API DLL_VOID GetCameraXMLNodeNamesCSharp(Camera_Object_C* camera_obj, std::vector* node_names); + //获取下位机日志目录 + DLLEXPORT_API DLL_INT CameraLogListCSharp(Camera_Object_C* camera_obj, std::vector* file_names, int* len); + //阻塞式抓取数据 + DLLEXPORT_API DLL_INT CaptureCSharp(Camera_Object_C* camera_obj, unsigned short channel_index, PhotoInfoCSharp &raw_data_info, char *xyz, int pixel_size); + //try + DLLEXPORT_API DLL_INT TryCaptureCSharp(Camera_Object_C* camera_obj, unsigned short channel_index, PhotoInfoCSharp &raw_data_info, char *xyz, int pixel_size); + //超时抓取数据 + DLLEXPORT_API DLL_INT TimeoutCaptureCSharp(Camera_Object_C* camera_obj, unsigned short channel_index, PhotoInfoCSharp &raw_data_info, char *xyz, int pixel_size, long long timeout); + //将char数据转为float类型 + DLLEXPORT_API DLL_VOID Convert3DPointFromCharToFloatCSharp(Camera_Object_C* camera_obj, PhotoInfoCSharp &raw_data_info, char *xyz, int pixel_size, float* output,int outputsize); + //将Rawdata数据转换成RGB888的图像数据 + DLLEXPORT_API DLL_VOID RawdataToRgb888CSharp(Camera_Object_C* camera_obj, PhotoInfoCSharp &raw_data_info, char *xyz, int pixel_size); + //获取点云的X平面数据 + DLLEXPORT_API DLL_INT GetCloudPlaneXCSharp(Camera_Object_C* camera_obj, PhotoInfoCSharp &raw_data_info, char *xyz, int pixel_size, short *imagedata, int datasize); + //获取点云的Y平面数据 + DLLEXPORT_API DLL_INT GetCloudPlaneYCSharp(Camera_Object_C* camera_obj, PhotoInfoCSharp &raw_data_info, char *xyz, int pixel_size, short *imagedata, int datasize); + //获取点云的Z平面数据 + DLLEXPORT_API DLL_INT GetCloudPlaneZCSharp(Camera_Object_C* camera_obj, PhotoInfoCSharp &raw_data_info, char *xyz, int pixel_size, short *imagedata, int datasize); + //保存点云某个平面数据 + DLLEXPORT_API DLL_INT SaveCloudPlaneCSharp(Camera_Object_C* camera_obj, PhotoInfoCSharp &raw_data_info, char *xyz, int pixel_size, short *imagedata, int datasize, char* path_name); + //保存点云 to pcd 格式 + DLLEXPORT_API DLL_INT SavePointCloudToPcdCSharp(Camera_Object_C* camera_obj, PhotoInfoCSharp &raw_data_info, char *xyz, int pixel_size, char* path_name); + //保存点云 to txt 格式 + DLLEXPORT_API DLL_INT SavePointCloudToTxtCSharp(Camera_Object_C* camera_obj, PhotoInfoCSharp &raw_data_info, char *xyz, int pixel_size, char* path_name); + //保存点云 to ply 格式 + DLLEXPORT_API DLL_INT SavePointCloudToPlyCSharp(Camera_Object_C* camera_obj, PhotoInfoCSharp &raw_data_info, char *xyz, int pixel_size, char* path_name); + //点云滤波(基于空间密度的点云去噪) + DLLEXPORT_API DLL_VOID FilterPointCloudCSharp(Camera_Object_C* camera_obj, PhotoInfoCSharp &raw_data_info, char *xyz, int pixel_size, double level); + //空间滤波(基于空间网格的点云去噪) 20220225: 弃用, FilterPointCloud为该API的升级版 + DLLEXPORT_API DLL_INT SpatialFilterPointcloudCSharp(Camera_Object_C* camera_obj, PhotoInfoCSharp &raw_data_info, char *xyz, int pixel_size, int Area_PointCloudCount); + //保存bmp图 + DLLEXPORT_API DLL_INT SaveToBMPCSharp(Camera_Object_C* camera_obj, PhotoInfoCSharp &raw_data_info, char *xyz, int pixel_size, char *path_name); + //图片裁剪 + DLLEXPORT_API DLL_INT ImageRoiCropCSharp(Camera_Object_C* camera_obj, PhotoInfoCSharp& source_data, char* source_pixel, int source_size, RoiPoint roi, PhotoInfoCSharp& target_data, char* target_pixel, int target_size); + //保存点云深度图 to png + DLLEXPORT_API DLL_INT SaveDepthToPngCSharp(Camera_Object_C* camera_obj, PhotoInfoCSharp &raw_data_info, char *xyz, int pixel_size, char* path_name); + //点云融合 + DLLEXPORT_API DLL_INT FusionImageTo3DCSharp(Camera_Object_C* camera_obj, PhotoInfoCSharp &image_data_info, char *image_pixel, int image_pixel_size, + PhotoInfoCSharp &point_data_info, char *point_pixel, int point_pixel_size, float * image_cloud, int image_cloud_size); + //根据RGB重排点云 + DLLEXPORT_API DLL_INT Fusion3DToRGBCSharp(Camera_Object_C* camera_obj, PhotoInfoCSharp &image_data_info, char *image_pixel, int image_pixel_size, + PhotoInfoCSharp &point_data_info, char *point_pixel, int point_pixel_size, + PhotoInfoCSharp &xyz_data_info, char *xyz, int xyz_size); + //根据RGB重排点云 + DLLEXPORT_API DLL_INT Fusion3DToRGBWithOutDistortionCSharp(Camera_Object_C* camera_obj, PhotoInfoCSharp& image_data_info, char* image_pixel, int image_pixel_size, + PhotoInfoCSharp& point_data_info, char* point_pixel, int point_pixel_size, + PhotoInfoCSharp& xyz_data_info, char* xyz, int xyz_size); + //图像膨胀 + DLLEXPORT_API DLL_VOID PixelSwellCSharp(Camera_Object_C* camera_obj, int roi_output_size, int *roi_output, PhotoInfoCSharp &target_data, int target_pixel_size); + //图像腐蚀 + DLLEXPORT_API DLL_VOID PixelCorrosionCSharp(Camera_Object_C* camera_obj, int roi_output_size, int *roi_output, PhotoInfoCSharp &target_data, int target_pixel_size); + //ROI映射区域坐标 + DLLEXPORT_API DLL_VOID ROIMappingCoordinateCSharp(Camera_Object_C* camera_obj,int roi_output_size, int *roi_output, PhotoInfoCSharp &target_data, int target_pixel_size, RoiPoint &point_output); + //ROI检索映射 + DLLEXPORT_API DLL_VOID ROIPixelMappingCSharp(Camera_Object_C* camera_obj, PhotoInfoCSharp &point_data, char *point_pixel, int point_pixel_size, PhotoInfoCSharp &source_data, + char *source_data_pixel, int source_pixel_size, PhotoInfoCSharp &target_data, char *target_data_pixel, int target_pixel_size, + RoiPoint &roi_input, int ROI_output_size, int *ROI_output); + //保存点云 with image to pcd + DLLEXPORT_API DLL_INT SavePointCloudWithImageToTxtCSharp(Camera_Object_C* camera_obj, PhotoInfoCSharp &point_data_info, char *point_pixel, int point_pixel_size, + float * rgb_cloud,int rgb_cloud_size, char *path_name); + //保存与图片融合后的点云Ply + DLLEXPORT_API DLL_INT SavePointCloudWithImageToPlyCSharp(Camera_Object_C* camera_obj, PhotoInfoCSharp &point_data_info, char *point_pixel, int point_pixel_size, + float * rgb_cloud, int rgb_cloud_size, char *path_name); + //保存与图片融合后的点云Pcd + DLLEXPORT_API DLL_INT SavePointCloudWithImageToPcdCSharp(Camera_Object_C* camera_obj, PhotoInfoCSharp &point_data_info, char *point_pixel, int point_pixel_size, + float * rgb_cloud, int rgb_cloud_size, char *path_name); + /*C#*/ +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/include/gui/PointCloudGLWidget.h b/include/gui/PointCloudGLWidget.h new file mode 100644 index 0000000..3e9e116 --- /dev/null +++ b/include/gui/PointCloudGLWidget.h @@ -0,0 +1,68 @@ +#ifndef POINTCLOUDGLWIDGET_H +#define POINTCLOUDGLWIDGET_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class PointCloudGLWidget : public QOpenGLWidget, protected QOpenGLFunctions +{ + Q_OBJECT + +public: + explicit PointCloudGLWidget(QWidget *parent = nullptr); + ~PointCloudGLWidget(); + + void updatePointCloud(pcl::PointCloud::Ptr cloud); + +protected: + void initializeGL() override; + void resizeGL(int w, int h) override; + void paintGL() override; + + // 鼠标交互 + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void wheelEvent(QWheelEvent *event) override; + +private: + void setupShaders(); + void updateBuffers(); + +private: + // OpenGL资源 + QOpenGLShaderProgram *m_program; + QOpenGLBuffer *m_vertexBuffer; + QOpenGLVertexArrayObject *m_vao; + + // 点云数据 + std::vector m_vertices; + int m_pointCount; + + // 相机参数 + QMatrix4x4 m_projection; + QMatrix4x4 m_view; + QMatrix4x4 m_model; + + float m_orthoSize; // 正交投影视野大小(控制缩放) + float m_rotationX; // X轴旋转角度 + float m_rotationY; // Y轴旋转角度 + QVector3D m_translation; // 平移 + + // 鼠标交互状态 + QPoint m_lastMousePos; + bool m_leftButtonPressed; + bool m_rightButtonPressed; +}; + +#endif // POINTCLOUDGLWIDGET_H diff --git a/include/gui/PointCloudWidget.h b/include/gui/PointCloudWidget.h new file mode 100644 index 0000000..cd35cce --- /dev/null +++ b/include/gui/PointCloudWidget.h @@ -0,0 +1,27 @@ +#ifndef POINTCLOUDWIDGET_H +#define POINTCLOUDWIDGET_H + +#include +#include +#include +#include + +class PointCloudGLWidget; + +class PointCloudWidget : public QWidget +{ + Q_OBJECT + +public: + explicit PointCloudWidget(QWidget *parent = nullptr); + ~PointCloudWidget(); + + // 更新点云显示 + void updatePointCloud(pcl::PointCloud::Ptr cloud); + +private: + QLabel *m_statusLabel; + PointCloudGLWidget *m_glWidget; +}; + +#endif // POINTCLOUDWIDGET_H diff --git a/installer/BinFiles.wxs b/installer/BinFiles.wxs new file mode 100644 index 0000000..31e1891 --- /dev/null +++ b/installer/BinFiles.wxs @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/installer/UpperControl.wxs b/installer/UpperControl.wxs new file mode 100644 index 0000000..fb7e7c6 --- /dev/null +++ b/installer/UpperControl.wxs @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/app_icon.ico b/resources/app_icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..3d0abb79bd6ec962d02f4c91efc655ad3ab7df88 GIT binary patch literal 5105 zcmai02UL?yvwjm2nt=39fC!OZ1R^LUH0c;3f(ROrVxb6vbR?ljFVdumbWo%#MvC+f zp(stIBOuai=$G&FzwMlR|8LKpoqcBJ?4Gmp%*+A+2!I0g^uXER11P}&VDi_F?+*qb z0C4wACnfa6vdT0|11f4Yg>gIjPUIv^v@vm^1m; z4fK>}{s8aRsTBZ#`gAl@jXl%zFiO{%jhL18deg=Pm%Fm>i3twGCZnuduIXK2vR3^W zlUXs?=2*OV3>_`pV55ZS>r=8okZ4eFQ;BX9vBzgE`7&=!_e=h1#N|&ZK9eb~#JUDK z^{xP$QgSp|W?v5{&|Vh;uVt2#K1$%My3vg_q6pe0bM^eVsLX$K1)9en(V`E5TRZ4Rk+A!cemZ- zR-jxmJM^w1rIgo26kkJIR`W;_?Z~rD1V8DONDm9_7pB!&=nWyL6h^&p|E-9z-}Tp% z6n9lXse0u;r%FlF4Tl9XOgH=F^*$LH4f>ieoQ{5}>iI(Oo$vh;{)3BZu4VgUfpag~ zHj7NPMz8jmHOC%@=Kr#GL$qPbQKD`1m%Q;)Y=*?Sg-~w+Qf;E;?@&*LENO+LG=kTI z4cqTi?Rd>Pcv*1n4FF@Ik@J?|XS3ln0PjN5=CYcNvtyRsj$smjymW#}7^PS+JoJ5i zOfB1^28Knqt4i<&h}35i=2vWR$*V5e9TL4K+&CECc=z9`K!+`TpjCtGD+y0Fbr+;lYrJk^{3T z{mJ}FO`X!=kt;iOT2r_UBul3m+l32a=8qTA#C*xurS^CeE2M@O%gr@-0nZJ8z!Vz_ zx8aYpq1R^aNa8l<=f0$T=zF+@Nc~vjkv8jYc0ud;n%hT@#}hriTU+z6vcAj;u9Osa z+vLya!DrURyb2`4$}*L@-EgOCbV^#!k)ju!cR|IccTNo3K*;fcQ3aAF z(F!YUt(* z=^0O)k1_+Bcao<>UzcxxY>a0kFA^u~3nN@(fUhdN5kZDl=ZW(Hu{MSnk?SrJbaY=S zv9Fw^ujaVS;YPi9?8Vzw66q%;$+s81ApD{NEf@3z=Y}Yv!1C0dLsseOZc_de%@o$< zvjn2cIMZO&Ma1tQ$!Jrt&c~;VQ|)=c?9hx^!4?EF;`rL}y~>?;R_}GIR^u36bXKas z_yrh7?lcWm!m7xo+Gc@}=bJi(iughjIceVb!*7dJo%Dqzf!^eNztzZyYf%ofgHDw6 zI75hjlu&w_`#WiA3)VQE&bIC3rZ+ki=AZ>e)6x5dsu7GXL!gsyvLr`^3XT`DNx=!I zuJT%|vNBdHf?Cx>Jtq2&XQrS@akpii*aJP@IgW}e+)Rm#l)pqh{AwL4#jzQwT{4L1 z=N3(0@*7{)S8X^nyQRg(3$>ah>uZd?Mp|{>_dPzOd*;RHc3&0d_E`w2xWu?xtVvkk zLQ433<^^)k=hM5{J4_u-89_?bZof~UVY-9D0d>`*<~|D67JHH;hMt2Y>5L!oGu=HI z^be{=wJ+5FQbx(pa(>$V75gOI>9i(DV`2&Q>b2MSMO%M?JyOhM)#WZrTe9={?Z0fj z&$m7cJYbuvI#4;3UDKEoK7qy~+tV5=J5~N6|NrVFQh(>aDR@5w03h9e@_)r_+@AX; z^XaRrm6bJS@p3xFXg&K6ezANkMykGV0yM<}&cg$!sWpW{Nh+kNB5hu%A0`y8`;b@H zC~GN@_q?XjJzD8v=CD*7gS6w+Pg=xRDdS_kt9G9Uk*T~E$*<~o7d%$?t37Z$%X~~i z-<@$OGQFxAk*6|3-IjJ$AmdA2&QYO1-Un*pS}g4%LKT7YWb#l{Msd)WAnOdUv+|{d zVW!YbATza@`^}3)fI_^RZas;9RG=EY?(eS`KqNSq7}?WBn7DVX+oeaR+_;K$lh_>5 zl^a2U@rX992Se2Qmf$hz#-5>$^k($MY?D*HB{qxqjXm(u7(7lfKwH9!Ip@O~`R{bT z?DGO{TFt9cOpN$~y9(`zpYjlG+)$;4%w&#?-}`kJWTRB~ZrGu;GMc569B)2Qzt0*CQa#=Q7gh@~(JX*CX2j4_g+caIV7S;p|V zo*-wYn zIB<{Mx5^e0@E&pSZpce*J9^ue;>CtNU7h}zx*IlHVIOGAZc|-OMH8rrY9_l4TOX{0 zrV}O`8+GKKOlL#6Q!)8e14HDqIiHY*&nDY``pf2b%O+@)Kr~1nSZAYMf-lD~yKLje zE6r!lXT5r?J=Qkw_v1F4TV;7_DZe>V zZwM2g&QfA6L3-uFF-)XG}#qfZ^)|bG1eI+SYCu{IHH{ejdr4faqpUpK|)q znzR=BBzU@GdT@_SI>z-}-F;nihOVklC918U_q&I7ns(p;5jnBlR)r!>&c&92XI^}4 z;A5!|Cr_()Oh@O@f6p?lV~+g&TKiK1T6!3!*-$JucBe$9`zL1Nrz^J*IO+E?`80B% zH+?-L!>=UtgWW(-E7ykb3qbaE*&2t8N_X>t10Zu1wjPW3PqF*d!Ssa+U^g1 z-+A$C8dd5X%#-3Os)WR5&%r{Bq@nQthZ=}Fv!nj5fo~tWOaK7%^j|gbwm~SjCiBVN zrv`n?M2Ct>t|-JKS5!PM)W`+#RXj& zhHM?w*FuI!0fLUjRzZtRd~aX1%oi2G(rVE!VOOuJj4M@k_fYg!$lTePI9wX?DV<>M zS3j?`-QSuii@2$qcS(}rjqLo&BeZT2R@=(y?(sPtAH4qHI$8nwOa+HW{~~B4D-g*D z!Kc@3dSPI=rELUo@IuUn6DInCF5UTO+%}NGWh4z`|%(4@# zV!dWJ^eq*kz1+I^HU#?U$##Nl#iJ;W1#Sy&lk*b&6SolAPl`PrdS|I-+|;EdDes;e zFgCpYf&S6GopEP8Rxo=c0mA`*lDdgIyTG|LP7bXfJp0934}hf5Y}U)mR-0A1Nx88ge%=HE&G8)U(_6QhShcY)dW4*G{ z^{_>}Ml_&fck2&<4qx*;Fpc^G>owUm4vV5}qFDa@k1%tl`=IOAPQKiJv zCygx{UV_NJEzqF0ykIu%%96*z(Zw(hhpTOIoW;0%hx;A{XC}-VNCHNlohYGff-<4o#K7e?+^NqfuYVf9gkX z+%&8%vFSb~$g=_H0rz!vYkY6FVd0V%F1wK)!*(vV8B?AckDLaUP^?{Eew~>O2j6A9 zS>(xbX^|OZmL`lDwHE?iZEwmHB4v4R^9bS2u>{k0%Usb;SsbTJ&OB(@q$!c4P8c7y zjW8iEFAL`~c8ihh>q-7Lh)GsK zf#H*QvCPpOf%OqDUfyNeR56|5g{Y1NaM<-2-W69VNj>pywqlV+=oy(DzWrWojuH0z z4ssQl+iHA$-`5Z0aI@;cO)A#p_=agJUfz@W2VjDb4p;4p=T3J?QyX@+_}MahL%@D$ z7exY(mcV~`)8!B{##^d64vbPE{1UX655?RsMMez8A6`)MW$jMsJwwd0Kn?j4MIQth^I4&Skkzr#=bWTJM+__I2rR zZBi6etB6$5OoS*i*4GwAuEqGUalr>*XMahCJFCx+L39=Q{7hpt3rZu-Gi?hv?Y-Q@|WkoA;oi_r4UUGvB+c1WWh}IFqc?jsPvzE}& zgKJT5h;pCy0ff}YjVlawqhI9|-cHbogFLBFw_5vzNmj9w3@4M%qknQkhJ5gMC|tQ< z485BjLw}WD9C1petq|{XOdN{)hrWS3v*Z5m8_mjA-2ecF{i|>M7%*6tqQ5rHzM;+X zD>{79sc33kw(K^9E7^ZOy%hH&($7Ko-qRTMI02pioCcNAh~0_92R6k+_Z+crv8WF; z01=q__Y3RF+BQ@1#b-WKw7`A9xztp7`G{1z>Eb=Ld*W`<$o0QZkT|AE-yL%UE8A<4 zI}csnTp_yVYf2+!r#tZGF7N(ZzVjWBFV^ngnGdDq^BO?(GI5}m?ir+uO5j>FQx&nsz{3djWEl|vPo{l^ zCD+RuOfQd1_<~Bm3ja$b51rfZCPRyp;bZ{$|5^x}K%BpXW^%nEFceOidB7aI5KW1C zdJn8EPup^K1Ujk^z!*Xw5(|Mr)(|PI1rQb?>s8VLVA$+u6w5&ZlHYO_?$(Ii-MLfpchWHEhxv9mJ25{^8G$>Kpfu%6xHV+wsR%1G zFg-V;Oty3R-@p0)YaOmUKkBn)e^;~q!|=h5vNu@+2kyV)tFz?`>i5S*ykV9y{GYV? E53s&02><{9 literal 0 HcmV?d00001 diff --git a/resources/app_icon.rc b/resources/app_icon.rc new file mode 100644 index 0000000..386f6e2 --- /dev/null +++ b/resources/app_icon.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON "app_icon.ico" diff --git a/resources/convert_to_ico.py b/resources/convert_to_ico.py new file mode 100644 index 0000000..726b4de --- /dev/null +++ b/resources/convert_to_ico.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +"""将PNG图标转换为ICO格式""" + +from PIL import Image +import os + +def png_to_ico(png_path, ico_path): + """将PNG转换为ICO""" + img = Image.open(png_path) + # ICO文件支持多种尺寸,Windows会根据需要选择合适的尺寸 + img.save(ico_path, format='ICO', sizes=[(16, 16), (32, 32), (48, 48), (64, 64), (128, 128), (256, 256)]) + print(f"Created: {ico_path}") + +if __name__ == "__main__": + png_to_ico("icons/app_icon.png", "app_icon.ico") + print("\nICO file generated successfully!") diff --git a/resources/generate_icons.py b/resources/generate_icons.py new file mode 100644 index 0000000..41acf12 --- /dev/null +++ b/resources/generate_icons.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +"""生成D330Viewer所需的图标""" + +from PIL import Image, ImageDraw +import os + +def create_icon(size, bg_color, fg_color, shape, filename): + """创建一个简单的图标""" + img = Image.new('RGBA', (size, size), (0, 0, 0, 0)) + draw = ImageDraw.Draw(img) + margin = size // 8 + + if shape == 'play': + # 播放三角形 + points = [(margin, margin), (margin, size - margin), (size - margin, size // 2)] + draw.polygon(points, fill=fg_color) + + elif shape == 'stop': + # 停止方块 + draw.rectangle([margin, margin, size - margin, size - margin], fill=fg_color) + + elif shape == 'camera': + # 相机图标 + draw.ellipse([margin, margin, size - margin, size - margin], outline=fg_color, width=3) + draw.ellipse([size//3, size//3, 2*size//3, 2*size//3], fill=fg_color) + + elif shape == 'save': + # 保存图标 + draw.rectangle([margin, margin, size - margin, size - margin], outline=fg_color, width=3) + draw.rectangle([margin*2, margin, size - margin*2, margin*3], fill=fg_color) + + elif shape == 'folder': + # 文件夹图标 + draw.rectangle([margin, size//3, size - margin, size - margin], outline=fg_color, width=3) + draw.rectangle([margin, size//3, size//2, size//3 + margin], fill=fg_color) + + elif shape == 'refresh': + # 刷新图标 + draw.arc([margin, margin, size - margin, size - margin], 45, 315, fill=fg_color, width=3) + draw.polygon([(size - margin, margin*2), (size - margin, margin*3), (size - margin*2, margin*2.5)], fill=fg_color) + + elif shape == 'connect': + # 连接图标 + draw.ellipse([margin, margin, size//2, size//2], fill=fg_color) + draw.ellipse([size//2, size//2, size - margin, size - margin], fill=fg_color) + draw.line([size//4, size//4, 3*size//4, 3*size//4], fill=fg_color, width=3) + + elif shape == 'clear': + # 清除图标 + draw.line([margin, margin, size - margin, size - margin], fill=fg_color, width=3) + draw.line([size - margin, margin, margin, size - margin], fill=fg_color, width=3) + + elif shape == 'app': + # 应用图标 + draw.rectangle([margin, margin*2, size - margin, size - margin], fill=bg_color, outline=fg_color, width=3) + draw.ellipse([size//4, size//3, 3*size//4, 2*size//3], fill=fg_color) + draw.rectangle([size//3, margin, 2*size//3, margin*2], fill=fg_color) + + img.save(filename) + print(f"Created: {filename}") + +if __name__ == "__main__": + icons_dir = "icons" + os.makedirs(icons_dir, exist_ok=True) + + # 颜色方案 - 使用白色/浅色图标,确保在任何背景上都可见 + white_color = (255, 255, 255, 255) # 白色 + light_gray = (220, 220, 220, 255) # 浅灰色 + transparent = (0, 0, 0, 0) # 透明背景 + + # 为应用图标使用深色背景 + app_bg_color = (33, 150, 243, 255) # 蓝色背景 + + # 生成图标 - 所有按钮图标使用白色,透明背景 + create_icon(64, transparent, white_color, 'play', f'{icons_dir}/start.png') + create_icon(64, transparent, white_color, 'stop', f'{icons_dir}/stop.png') + create_icon(64, transparent, white_color, 'camera', f'{icons_dir}/camera.png') + create_icon(64, transparent, white_color, 'save', f'{icons_dir}/save.png') + create_icon(64, transparent, white_color, 'folder', f'{icons_dir}/folder.png') + create_icon(64, transparent, white_color, 'refresh', f'{icons_dir}/refresh.png') + create_icon(64, transparent, white_color, 'connect', f'{icons_dir}/connect.png') + create_icon(64, transparent, white_color, 'clear', f'{icons_dir}/clear.png') + create_icon(128, app_bg_color, white_color, 'app', f'{icons_dir}/app_icon.png') + + print("\nAll icons generated successfully!") diff --git a/resources/icons/app_icon.png b/resources/icons/app_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ad83c3c6eccefed89e74cf493465ceee858ea56b GIT binary patch literal 599 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrV3PB6aSW-L^Y+d~-!}#VZVx3s zdWgR4>w1y5`oUe34N)x8GwPqsS>JG{r#443skGmDl8Pq``EKr0`*!blM#|c&NxAW> z;vKf~Fq~s}fS(Y1`~HjYTJ^f>2fPgF30|l3_|An zsyTA)n5}c_j(_jn&$vq`<@#-fUu-Y-{EU6p!K+YtQSGd-q{FKu-#OZP4M$vO*~Eu1 zWOh5xFxFydWcIKUT~e9MBw*1n<+8&ZYbB771?!WarwgCzaLQ{{f_P3(Ri*n8v8mUSM|n$2G<~(O~|mY@{S${SdAZ>|yi64jJ%Gq(`}W_`zL*|7BM@*RZDZ*?HvhW#t>$ z0(Lq3+pPrLF?h=)z*AC#kjvF7#jS)Nq zc#N1=6@Z!pY7VG5pyq&@0}rDv7Z^@UFsM23I&Cq%y%E}yY~Dc4fv0hcD)8QZquS@G zwy(>fC@V+d)n8&m+Ws#)C62_-g1m2hC9tG%^-tp+eXwv=fsngFE;#C1ozNJsQU#iy zCM=z2p!JW;yLl}Z*YwzRo}C1%HwtVmL?b4{A1XdvicB_>A#L9Y9%=nj^hn#c!e`pP k7Qf5?1%_c5hJpI>1mBwwb-i=IJ^%m!07*qoM6N<$f+;q}Z~y=R literal 0 HcmV?d00001 diff --git a/resources/icons/clear.png b/resources/icons/clear.png new file mode 100644 index 0000000000000000000000000000000000000000..3ad6114171731929bb90046c7dddcab1f613b0be GIT binary patch literal 437 zcmV;m0ZRUfP)qKz4-H@>2j=5q8Q?17t(k zEx!bi1%Xk186X;gRelQ~3V~UE8^DdgE>8h)At=hz0MrPo@>Bp7g0egvfQ_IoF9Bd7 zILgZab`f0Vr2wl4&hm1ASp;`^3&1EsM0p#)H-xD2zp+VVZ6Z~J+ fU8z(mmB*AHx6F)9NV>1I00000NkvXXu0mjf&6c|! literal 0 HcmV?d00001 diff --git a/resources/icons/connect.png b/resources/icons/connect.png new file mode 100644 index 0000000000000000000000000000000000000000..aaec67ae07b3b287950db62131323c6fb7888cf3 GIT binary patch literal 316 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7T#lEU}W}maSW-L^Y->dzC#KEE*Ee9 z{{O${W9;J4S8L|lz7LSd5WW#2mE2?Kw(8c!lTq2WwGLOOrO#PyK?Sp*VNT|1u8AZulZCLhb#VB^e({Y z_FrBLDHfyR`j9~8a{?D!_euk8kt(<*8(?vVx!^jG65(rgeh8wzGu$a<7vVE~u*P~p z!HUkiyC2;B7ofb`$>I`!*0a~q_nO~&_qyKUivL^RVt7o#x%MstpLs*n)E3ddz))fE MboFyt=akR{0Nr_oy#N3J literal 0 HcmV?d00001 diff --git a/resources/icons/folder.png b/resources/icons/folder.png new file mode 100644 index 0000000000000000000000000000000000000000..51dba3af7c09693c3239cdac4bfcaaeda9f908d5 GIT binary patch literal 192 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=?Vc`zh}nT69hZ zq8*>#W!IX6nGgXSq;))J{o&0=lsiAaV*;Apw)+o*_5vP1Yhk@(KqoPHy85}Sb4q9e E0C*`*lK=n! literal 0 HcmV?d00001 diff --git a/resources/icons/refresh.png b/resources/icons/refresh.png new file mode 100644 index 0000000000000000000000000000000000000000..5f35e1999a7ed218820325b0ff04582f98116494 GIT binary patch literal 382 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7T#lEV086#aSW-L^LF;db%zyroMl)4 z|9{%2dWX>VTW*R|n9fzIZJa!9%T);h7DoY=MlG`_gQZivvd`qq(4W2Vfx2{(vAEqL zL9<;Sv|YcP_~^lWN&KgiV z7fg+LKfSDQLDIoT*|z^al~=T;Y40mjlaXtj*_AKbcAR6P^~b8D><2pG|KA>d+`9Vy zuX}C+vjcB)tlRay+O$cxbEAOj{uYZCi|{y8JsUs<^=HQ)AJML}lDW?tEew(2vKYlBSOzpeQodGqq4)qmm@IGPkV7M$i5 Y@jsBhdzFtOFkBftUHx3vIVCg!0FK?NG5`Po literal 0 HcmV?d00001 diff --git a/resources/icons/save.png b/resources/icons/save.png new file mode 100644 index 0000000000000000000000000000000000000000..df69f3d847b6ea1544608b16c259a7b46a0183a7 GIT binary patch literal 206 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=Q$1ZALn`LHy=BOC*nx-j;?0BK z_kViEqR66tBkjwpcn<|rCxc@W$vvySYHs*`&*o0l>)iQ5>V?N|2JSlCHD{&wBNab% z)K@PjJ#llxzpo}kl&7?556HG$Dv2k5%4>$6zC0vP^cLQpl{xKKEf{B`ESpAIhx zzz4%i0dQh?2>`wro*IAw!&3qr%$ z-^^F=khEE`I?}_y*w{Gqx$chdasTdEKL5D + + icons/app_icon.png + icons/start.png + icons/stop.png + icons/camera.png + icons/save.png + icons/folder.png + icons/refresh.png + icons/connect.png + icons/clear.png + + diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp new file mode 100644 index 0000000..6337235 --- /dev/null +++ b/src/config/ConfigManager.cpp @@ -0,0 +1,106 @@ +#include "config/ConfigManager.h" + +ConfigManager::ConfigManager() + : m_settings(std::make_unique("D330Viewer", "D330Viewer")) +{ + // 构造函数:初始化QSettings +} + +ConfigManager::~ConfigManager() +{ + // 析构函数:QSettings会自动保存 +} + +// ========== 网络配置 ========== +QString ConfigManager::getIpAddress() const +{ + return m_settings->value("Network/IpAddress", "192.168.0.90").toString(); +} + +void ConfigManager::setIpAddress(const QString &ip) +{ + m_settings->setValue("Network/IpAddress", ip); +} + +int ConfigManager::getControlPort() const +{ + return m_settings->value("Network/ControlPort", 6790).toInt(); +} + +void ConfigManager::setControlPort(int port) +{ + m_settings->setValue("Network/ControlPort", port); +} + +int ConfigManager::getDataPort() const +{ + return m_settings->value("Network/DataPort", 3957).toInt(); +} + +void ConfigManager::setDataPort(int port) +{ + m_settings->setValue("Network/DataPort", port); +} + +// ========== 相机配置 ========== +int ConfigManager::getExposureTime() const +{ + return m_settings->value("Camera/ExposureTime", 10000).toInt(); +} + +void ConfigManager::setExposureTime(int exposure) +{ + m_settings->setValue("Camera/ExposureTime", exposure); +} + +// ========== 拍照配置 ========== +QString ConfigManager::getSavePath() const +{ + return m_settings->value("Capture/SavePath", "").toString(); +} + +void ConfigManager::setSavePath(const QString &path) +{ + m_settings->setValue("Capture/SavePath", path); +} + +QString ConfigManager::getDepthFormat() const +{ + return m_settings->value("Capture/DepthFormat", "both").toString(); +} + +void ConfigManager::setDepthFormat(const QString &format) +{ + m_settings->setValue("Capture/DepthFormat", format); +} + +QString ConfigManager::getPointCloudFormat() const +{ + return m_settings->value("Capture/PointCloudFormat", "both").toString(); +} + +void ConfigManager::setPointCloudFormat(const QString &format) +{ + m_settings->setValue("Capture/PointCloudFormat", format); +} + +// ========== 窗口配置 ========== +QByteArray ConfigManager::getWindowGeometry() const +{ + return m_settings->value("Window/Geometry").toByteArray(); +} + +void ConfigManager::setWindowGeometry(const QByteArray &geometry) +{ + m_settings->setValue("Window/Geometry", geometry); +} + +QByteArray ConfigManager::getWindowState() const +{ + return m_settings->value("Window/State").toByteArray(); +} + +void ConfigManager::setWindowState(const QByteArray &state) +{ + m_settings->setValue("Window/State", state); +} diff --git a/src/config/ConfigManager.h b/src/config/ConfigManager.h new file mode 100644 index 0000000..5a233c5 --- /dev/null +++ b/src/config/ConfigManager.h @@ -0,0 +1,49 @@ +#ifndef CONFIGMANAGER_H +#define CONFIGMANAGER_H + +#include +#include +#include + +class ConfigManager +{ +public: + ConfigManager(); + ~ConfigManager(); + + // 网络配置 + QString getIpAddress() const; + void setIpAddress(const QString &ip); + + int getControlPort() const; + void setControlPort(int port); + + int getDataPort() const; + void setDataPort(int port); + + // 相机配置 + int getExposureTime() const; + void setExposureTime(int exposure); + + // 拍照配置 + QString getSavePath() const; + void setSavePath(const QString &path); + + QString getDepthFormat() const; + void setDepthFormat(const QString &format); + + QString getPointCloudFormat() const; + void setPointCloudFormat(const QString &format); + + // 窗口配置 + QByteArray getWindowGeometry() const; + void setWindowGeometry(const QByteArray &geometry); + + QByteArray getWindowState() const; + void setWindowState(const QByteArray &state); + +private: + std::unique_ptr m_settings; +}; + +#endif // CONFIGMANAGER_H diff --git a/src/core/DeviceScanner.cpp b/src/core/DeviceScanner.cpp new file mode 100644 index 0000000..a502315 --- /dev/null +++ b/src/core/DeviceScanner.cpp @@ -0,0 +1,183 @@ +#include "DeviceScanner.h" +#include +#include +#include + +DeviceScanner::DeviceScanner(QObject *parent) + : QObject(parent) + , m_socket(new QUdpSocket(this)) + , m_timeoutTimer(new QTimer(this)) + , m_scanTimer(new QTimer(this)) + , m_currentHost(HOST_START) + , m_totalHosts(HOST_END - HOST_START + 1) + , m_isScanning(false) +{ + connect(m_socket, &QUdpSocket::readyRead, this, &DeviceScanner::onReadyRead); + connect(m_timeoutTimer, &QTimer::timeout, this, &DeviceScanner::onTimeout); + connect(m_scanTimer, &QTimer::timeout, this, &DeviceScanner::scanNextHost); + + m_timeoutTimer->setSingleShot(true); + m_scanTimer->setSingleShot(false); + m_scanTimer->setInterval(SCAN_TIMEOUT); +} + +DeviceScanner::~DeviceScanner() +{ + stopScan(); +} + +void DeviceScanner::startScan(const QString &subnet) +{ + if (m_isScanning) { + qDebug() << "Scan already in progress"; + return; + } + + m_subnet = subnet.isEmpty() ? getLocalSubnet() : subnet; + m_currentHost = HOST_START; + m_foundDevices.clear(); + m_isScanning = true; + + if (!m_socket->bind(QHostAddress::Any, 0)) { + emit scanError("Failed to bind socket"); + m_isScanning = false; + return; + } + + qDebug() << "Starting fast device scan on subnet:" << m_subnet; + + // Send DISCOVER to all hosts at once (batch mode) + for (int host = HOST_START; host <= HOST_END; host++) { + QString ip = m_subnet + "." + QString::number(host); + sendDiscoveryPacket(ip); + } + + // Wait 3 seconds for all responses + m_timeoutTimer->start(3000); + qDebug() << "Sent discovery packets to all hosts, waiting for responses..."; +} + +void DeviceScanner::stopScan() +{ + if (!m_isScanning) { + return; + } + + m_scanTimer->stop(); + m_timeoutTimer->stop(); + m_socket->close(); + m_isScanning = false; + + qDebug() << "Scan stopped. Found" << m_foundDevices.size() << "devices"; + emit scanFinished(m_foundDevices.size()); +} + +void DeviceScanner::onReadyRead() +{ + while (m_socket->hasPendingDatagrams()) { + QNetworkDatagram datagram = m_socket->receiveDatagram(); + QHostAddress senderAddr = datagram.senderAddress(); + + // Convert IPv6-mapped IPv4 address to IPv4 + if (senderAddr.protocol() == QAbstractSocket::IPv6Protocol) { + QHostAddress ipv4Addr(senderAddr.toIPv4Address()); + if (!ipv4Addr.isNull()) { + senderAddr = ipv4Addr; + } + } + + QString senderIp = senderAddr.toString(); + QString response = QString::fromUtf8(datagram.data()); + + qDebug() << "Received response from" << senderIp << ":" << response; + + if (response.contains("D330M_CAMERA")) { + DeviceInfo device; + device.ipAddress = senderIp; + device.deviceName = "D330M Camera"; + device.port = SCAN_PORT; + device.responseTime = 0; + + m_foundDevices.append(device); + emit deviceFound(device); + } + } +} + +void DeviceScanner::onTimeout() +{ + qDebug() << "Scan timeout reached"; + stopScan(); +} + +void DeviceScanner::scanNextHost() +{ + // This function is no longer used in batch mode + // Kept for compatibility +} + +void DeviceScanner::sendDiscoveryPacket(const QString &ip) +{ + QByteArray data = "DISCOVER"; + m_socket->writeDatagram(data, QHostAddress(ip), SCAN_PORT); +} + +QString DeviceScanner::getLocalSubnet() +{ + // Get all network interfaces + QList interfaces = QNetworkInterface::allInterfaces(); + + // Priority 1: Look for Ethernet adapter (以太网) + for (const QNetworkInterface &iface : interfaces) { + QString name = iface.humanReadableName().toLower(); + + // Skip virtual adapters + if (name.contains("virtual") || name.contains("vmware") || + name.contains("virtualbox") || name.contains("hyper-v") || + name.contains("vpn") || name.contains("tap") || name.contains("tun")) { + continue; + } + + // Look for Ethernet adapter + if (name.contains("ethernet") || name.contains("以太网")) { + QList entries = iface.addressEntries(); + for (const QNetworkAddressEntry &entry : entries) { + QHostAddress addr = entry.ip(); + if (addr.protocol() == QAbstractSocket::IPv4Protocol && !addr.isLoopback()) { + QString ip = addr.toString(); + QStringList parts = ip.split('.'); + if (parts.size() == 4) { + qDebug() << "Found Ethernet adapter:" << iface.humanReadableName() << "IP:" << ip; + return parts[0] + "." + parts[1] + "." + parts[2]; + } + } + } + } + } + + // Priority 2: Any non-virtual adapter + for (const QNetworkInterface &iface : interfaces) { + QString name = iface.humanReadableName().toLower(); + + if (name.contains("virtual") || name.contains("vmware") || + name.contains("virtualbox") || name.contains("hyper-v") || + name.contains("vpn") || name.contains("tap") || name.contains("tun")) { + continue; + } + + QList entries = iface.addressEntries(); + for (const QNetworkAddressEntry &entry : entries) { + QHostAddress addr = entry.ip(); + if (addr.protocol() == QAbstractSocket::IPv4Protocol && !addr.isLoopback()) { + QString ip = addr.toString(); + QStringList parts = ip.split('.'); + if (parts.size() == 4) { + qDebug() << "Found adapter:" << iface.humanReadableName() << "IP:" << ip; + return parts[0] + "." + parts[1] + "." + parts[2]; + } + } + } + } + + return "192.168.0"; +} diff --git a/src/core/DeviceScanner.h b/src/core/DeviceScanner.h new file mode 100644 index 0000000..fa58009 --- /dev/null +++ b/src/core/DeviceScanner.h @@ -0,0 +1,62 @@ +#ifndef DEVICESCANNER_H +#define DEVICESCANNER_H + +#include +#include +#include +#include +#include +#include + +struct DeviceInfo { + QString ipAddress; + QString deviceName; + int port; + qint64 responseTime; +}; + +class DeviceScanner : public QObject +{ + Q_OBJECT + +public: + explicit DeviceScanner(QObject *parent = nullptr); + ~DeviceScanner(); + + void startScan(const QString &subnet = ""); + void stopScan(); + bool isScanning() const { return m_isScanning; } + +signals: + void deviceFound(const DeviceInfo &device); + void scanProgress(int current, int total); + void scanFinished(int devicesFound); + void scanError(const QString &error); + +private slots: + void onReadyRead(); + void onTimeout(); + void scanNextHost(); + +private: + void sendDiscoveryPacket(const QString &ip); + QString getLocalSubnet(); + + QUdpSocket *m_socket; + QTimer *m_timeoutTimer; + QTimer *m_scanTimer; + + QString m_subnet; + int m_currentHost; + int m_totalHosts; + bool m_isScanning; + + QList m_foundDevices; + + static constexpr int SCAN_PORT = 6790; // Control port for device discovery + static constexpr int SCAN_TIMEOUT = 10; + static constexpr int HOST_START = 1; + static constexpr int HOST_END = 254; +}; + +#endif // DEVICESCANNER_H diff --git a/src/core/DeviceScanner_getLocalSubnet.cpp b/src/core/DeviceScanner_getLocalSubnet.cpp new file mode 100644 index 0000000..8a7705e --- /dev/null +++ b/src/core/DeviceScanner_getLocalSubnet.cpp @@ -0,0 +1,59 @@ +QString DeviceScanner::getLocalSubnet() +{ + // Get all network interfaces + QList interfaces = QNetworkInterface::allInterfaces(); + + // Priority 1: Look for Ethernet adapter (以太网) + for (const QNetworkInterface &iface : interfaces) { + QString name = iface.humanReadableName().toLower(); + + // Skip virtual adapters + if (name.contains("virtual") || name.contains("vmware") || + name.contains("virtualbox") || name.contains("hyper-v") || + name.contains("vpn") || name.contains("tap") || name.contains("tun")) { + continue; + } + + // Look for Ethernet adapter + if (name.contains("ethernet") || name.contains("以太网")) { + QList entries = iface.addressEntries(); + for (const QNetworkAddressEntry &entry : entries) { + QHostAddress addr = entry.ip(); + if (addr.protocol() == QAbstractSocket::IPv4Protocol && !addr.isLoopback()) { + QString ip = addr.toString(); + QStringList parts = ip.split('.'); + if (parts.size() == 4) { + qDebug() << "Found Ethernet adapter:" << iface.humanReadableName() << "IP:" << ip; + return parts[0] + "." + parts[1] + "." + parts[2]; + } + } + } + } + } + + // Priority 2: Any non-virtual adapter + for (const QNetworkInterface &iface : interfaces) { + QString name = iface.humanReadableName().toLower(); + + if (name.contains("virtual") || name.contains("vmware") || + name.contains("virtualbox") || name.contains("hyper-v") || + name.contains("vpn") || name.contains("tap") || name.contains("tun")) { + continue; + } + + QList entries = iface.addressEntries(); + for (const QNetworkAddressEntry &entry : entries) { + QHostAddress addr = entry.ip(); + if (addr.protocol() == QAbstractSocket::IPv4Protocol && !addr.isLoopback()) { + QString ip = addr.toString(); + QStringList parts = ip.split('.'); + if (parts.size() == 4) { + qDebug() << "Found adapter:" << iface.humanReadableName() << "IP:" << ip; + return parts[0] + "." + parts[1] + "." + parts[2]; + } + } + } + } + + return "192.168.0"; +} diff --git a/src/core/GVSPParser.cpp b/src/core/GVSPParser.cpp new file mode 100644 index 0000000..0595bc6 --- /dev/null +++ b/src/core/GVSPParser.cpp @@ -0,0 +1,229 @@ +#include "core/GVSPParser.h" +#include +#include +#include + +GVSPParser::GVSPParser(QObject *parent) + : QObject(parent) + , m_isReceiving(false) + , m_dataType(0) + , m_currentBlockId(0) + , m_expectedSize(0) + , m_receivedSize(0) + , m_imageWidth(0) + , m_imageHeight(0) + , m_pixelFormat(0) + , m_lastBlockId(0) + , m_packetCount(0) +{ +} + +GVSPParser::~GVSPParser() +{ +} + +void GVSPParser::reset() +{ + m_isReceiving = false; + m_dataType = 0; + m_currentBlockId = 0; + m_dataBuffer.clear(); + m_expectedSize = 0; + m_receivedSize = 0; + m_packetCount = 0; +} + +void GVSPParser::parsePacket(const QByteArray &packet) +{ + if (packet.size() < sizeof(GVSPPacketHeader)) { + return; + } + + const uint8_t *data = reinterpret_cast(packet.constData()); + const GVSPPacketHeader *header = reinterpret_cast(data); + + // 注释掉调试日志以提高性能 + // static int debugCount = 0; + // if (debugCount < 3) { + // qDebug() << "Packet" << debugCount << "first 16 bytes (hex):"; + // QString hexStr; + // for (int i = 0; i < qMin(16, packet.size()); i++) { + // hexStr += QString("%1 ").arg(data[i], 2, 16, QChar('0')); + // } + // qDebug() << hexStr; + // debugCount++; + // } + + // GVSP包头格式:status(2) + block_id(2) + packet_format(4) + // 包类型在packet_format的高字节 + uint32_t packet_fmt = ntohl(header->packet_fmt_id); + uint8_t packetType = (packet_fmt >> 24) & 0xFF; + uint16_t blockId = ntohs(header->block_id); + + // 注释掉频繁的日志输出以提高性能 + // static int leaderCount = 0; + // static int trailerCount = 0; + + switch (packetType) { + case GVSP_LEADER_PACKET: + handleLeaderPacket(data, packet.size()); + break; + case GVSP_PAYLOAD_PACKET: + handlePayloadPacket(data, packet.size()); + break; + case GVSP_TRAILER_PACKET: + handleTrailerPacket(data, packet.size()); + break; + default: + break; + } +} + +void GVSPParser::handleLeaderPacket(const uint8_t *data, size_t size) +{ + if (size < sizeof(GVSPPacketHeader) + sizeof(GVSPImageDataLeader)) { + return; + } + + const GVSPPacketHeader *header = reinterpret_cast(data); + m_currentBlockId = ntohs(header->block_id); + + // Check payload type + const uint16_t *payload_type_ptr = reinterpret_cast(data + sizeof(GVSPPacketHeader) + 2); + uint16_t payload_type = ntohs(*payload_type_ptr); + + if (payload_type == PAYLOAD_TYPE_IMAGE) { + // Image data leader + const GVSPImageDataLeader *leader = reinterpret_cast(data + sizeof(GVSPPacketHeader)); + + m_dataType = 1; + m_imageWidth = ntohl(leader->size_x); + m_imageHeight = ntohl(leader->size_y); + m_pixelFormat = ntohl(leader->pixel_format); + m_expectedSize = m_imageWidth * m_imageHeight * 2; // 12-bit packed in 16-bit + + m_dataBuffer.clear(); + m_dataBuffer.reserve(m_expectedSize); + m_receivedSize = 0; + m_isReceiving = true; + m_packetCount = 0; + + // 注释掉频繁的日志输出 + // qDebug() << "Image Leader: Block" << m_currentBlockId + // << "Size:" << m_imageWidth << "x" << m_imageHeight; + } + else if (payload_type == PAYLOAD_TYPE_BINARY) { + // Depth data leader + const GVSPBinaryDataLeader *leader = reinterpret_cast(data + sizeof(GVSPPacketHeader)); + + m_dataType = 3; + m_expectedSize = ntohl(leader->file_size); + + m_dataBuffer.clear(); + m_dataBuffer.reserve(m_expectedSize); + m_receivedSize = 0; + m_isReceiving = true; + m_packetCount = 0; + + // 注释掉频繁的日志输出 + // qDebug() << "Depth Leader: Block" << m_currentBlockId + // << "Size:" << m_expectedSize << "bytes"; + } +} + +void GVSPParser::handlePayloadPacket(const uint8_t *data, size_t size) +{ + if (!m_isReceiving) { + return; + } + + if (size <= sizeof(GVSPPacketHeader)) { + return; + } + + // Extract payload data (skip header) + const uint8_t *payload = data + sizeof(GVSPPacketHeader); + size_t payload_size = size - sizeof(GVSPPacketHeader); + + // Append to buffer + m_dataBuffer.append(reinterpret_cast(payload), payload_size); + m_receivedSize += payload_size; + m_packetCount++; +} + +void GVSPParser::handleTrailerPacket(const uint8_t *data, size_t size) +{ + if (!m_isReceiving) { + return; + } + + // 注释掉频繁的日志输出 + // qDebug() << "Trailer received: Block" << m_currentBlockId + // << "Received" << m_receivedSize << "/" << m_expectedSize << "bytes" + // << "Packets:" << m_packetCount; + + // Process complete data + if (m_dataType == 1) { + processImageData(); + } else if (m_dataType == 3) { + processDepthData(); + } + + // Reset state + m_isReceiving = false; + m_lastBlockId = m_currentBlockId; +} + +void GVSPParser::processImageData() +{ + if (m_dataBuffer.size() < m_expectedSize) { + // 注释掉频繁的警告日志 + // qDebug() << "Warning: Incomplete image data" << m_dataBuffer.size() << "/" << m_expectedSize; + return; + } + + // Convert 16-bit depth data to 8-bit grayscale for display + const uint16_t *src = reinterpret_cast(m_dataBuffer.constData()); + QImage image(m_imageWidth, m_imageHeight, QImage::Format_Grayscale8); + + // Find min/max for normalization + uint16_t minVal = 65535, maxVal = 0; + for (size_t i = 0; i < m_imageWidth * m_imageHeight; i++) { + uint16_t val = src[i]; + if (val > 0) { + if (val < minVal) minVal = val; + if (val > maxVal) maxVal = val; + } + } + + // Normalize to 0-255 and flip vertically + uint8_t *dst = image.bits(); + float scale = (maxVal > minVal) ? (255.0f / (maxVal - minVal)) : 0.0f; + + for (size_t y = 0; y < m_imageHeight; y++) { + for (size_t x = 0; x < m_imageWidth; x++) { + size_t src_idx = y * m_imageWidth + x; + size_t dst_idx = (m_imageHeight - 1 - y) * m_imageWidth + x; // 垂直翻转 + + uint16_t val = src[src_idx]; + if (val == 0) { + dst[dst_idx] = 0; + } else { + dst[dst_idx] = static_cast((val - minVal) * scale); + } + } + } + + emit imageReceived(image, m_currentBlockId); +} + +void GVSPParser::processDepthData() +{ + if (m_dataBuffer.size() < m_expectedSize) { + // 注释掉频繁的警告日志 + // qDebug() << "Warning: Incomplete depth data" << m_dataBuffer.size() << "/" << m_expectedSize; + return; + } + + emit depthDataReceived(m_dataBuffer, m_currentBlockId); +} diff --git a/src/core/Logger.cpp b/src/core/Logger.cpp new file mode 100644 index 0000000..a7cbfeb --- /dev/null +++ b/src/core/Logger.cpp @@ -0,0 +1,130 @@ +#include "core/Logger.h" +#include +#include +#include + +Logger* Logger::s_instance = nullptr; + +Logger* Logger::instance() +{ + if (!s_instance) { + s_instance = new Logger(); + } + return s_instance; +} + +Logger::Logger(QObject *parent) + : QObject(parent) + , m_maxLines(10000) // 保留最新10000行 + , m_currentLines(0) +{ +} + +Logger::~Logger() +{ +} + +void Logger::setLogFile(const QString &filePath) +{ + QMutexLocker locker(&m_mutex); + m_logFilePath = filePath; + + // Count existing lines + QFile file(m_logFilePath); + if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { + m_currentLines = 0; + while (!file.atEnd()) { + file.readLine(); + m_currentLines++; + } + file.close(); + } +} + +void Logger::setMaxLines(int maxLines) +{ + QMutexLocker locker(&m_mutex); + m_maxLines = maxLines; +} + +void Logger::log(const QString &message) +{ + writeLog("LOG", message); +} + +void Logger::debug(const QString &message) +{ + writeLog("DEBUG", message); +} + +void Logger::info(const QString &message) +{ + writeLog("INFO", message); +} + +void Logger::warning(const QString &message) +{ + writeLog("WARN", message); +} + +void Logger::error(const QString &message) +{ + writeLog("ERROR", message); +} + +void Logger::writeLog(const QString &level, const QString &message) +{ + QMutexLocker locker(&m_mutex); + + if (m_logFilePath.isEmpty()) { + return; + } + + QString timestamp = QDateTime::currentDateTime().toString("[yyyy-MM-dd hh:mm:ss.zzz]"); + QString logLine = QString("%1 [%2] %3\n").arg(timestamp).arg(level).arg(message); + + QFile file(m_logFilePath); + if (file.open(QIODevice::Append | QIODevice::Text)) { + QTextStream out(&file); + out << logLine; + file.close(); + + m_currentLines++; + + // Check if we need to trim the log + if (m_currentLines > m_maxLines) { + checkAndTrimLog(); + } + } +} + +void Logger::checkAndTrimLog() +{ + // Read all lines + QFile file(m_logFilePath); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + return; + } + + QStringList lines; + QTextStream in(&file); + while (!in.atEnd()) { + lines.append(in.readLine()); + } + file.close(); + + // Keep only the last m_maxLines + if (lines.size() > m_maxLines) { + lines = lines.mid(lines.size() - m_maxLines); + } + + // Write back + if (file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { + QTextStream out(&file); + for (const QString &line : lines) { + out << line << "\n"; + } + file.close(); + m_currentLines = lines.size(); + } +} diff --git a/src/core/NetworkManager.cpp b/src/core/NetworkManager.cpp new file mode 100644 index 0000000..76e39a8 --- /dev/null +++ b/src/core/NetworkManager.cpp @@ -0,0 +1,174 @@ +#include "core/NetworkManager.h" +#include "core/GVSPParser.h" +#include +#include + +NetworkManager::NetworkManager(QObject *parent) + : QObject(parent) + , m_controlSocket(new QUdpSocket(this)) + , m_dataSocket(new QUdpSocket(this)) + , m_gvspParser(new GVSPParser(this)) + , m_controlPort(6790) + , m_dataPort(3957) + , m_isConnected(false) +{ + // 连接数据接收信号 + connect(m_dataSocket, &QUdpSocket::readyRead, this, &NetworkManager::onReadyRead); + connect(m_dataSocket, &QUdpSocket::errorOccurred, this, &NetworkManager::onError); + + // 连接GVSP解析器信号 + connect(m_gvspParser, &GVSPParser::imageReceived, this, &NetworkManager::imageReceived); + connect(m_gvspParser, &GVSPParser::depthDataReceived, this, &NetworkManager::depthDataReceived); +} + +NetworkManager::~NetworkManager() +{ + disconnectFromCamera(); +} + +// ========== 连接和断开 ========== +bool NetworkManager::connectToCamera(const QString &ip, int controlPort, int dataPort) +{ + m_cameraIp = ip; + m_controlPort = controlPort; + m_dataPort = dataPort; + + // 绑定控制Socket到任意端口(让系统自动分配) + if (!m_controlSocket->bind(QHostAddress::Any, 0)) { + QString error = QString("Failed to bind control socket: %1") + .arg(m_controlSocket->errorString()); + qDebug() << error; + emit errorOccurred(error); + return false; + } + qDebug() << "Successfully bound control socket to port" << m_controlSocket->localPort(); + + // 绑定数据接收端口 + if (!m_dataSocket->bind(QHostAddress::Any, m_dataPort)) { + QString error = QString("Failed to bind data port %1: %2") + .arg(m_dataPort) + .arg(m_dataSocket->errorString()); + qDebug() << error; + emit errorOccurred(error); + return false; + } + + // 设置UDP接收缓冲区大小为64MB(减少丢包) + m_dataSocket->setSocketOption(QAbstractSocket::ReceiveBufferSizeSocketOption, QVariant(64 * 1024 * 1024)); + + qDebug() << "Successfully bound data port" << m_dataPort; + qDebug() << "Data socket state:" << m_dataSocket->state(); + qDebug() << "UDP receive buffer size:" << m_dataSocket->socketOption(QAbstractSocket::ReceiveBufferSizeSocketOption).toInt(); + + m_isConnected = true; + qDebug() << "Connected to camera:" << m_cameraIp << "Control port:" << m_controlPort << "Data port:" << m_dataPort; + + // Send STOP command to register client IP on camera + sendStopCommand(); + qDebug() << "Sent STOP command to register client IP"; + + emit connected(); + return true; +} + +void NetworkManager::disconnectFromCamera() +{ + if (m_isConnected) { + m_controlSocket->close(); + m_dataSocket->close(); + m_isConnected = false; + qDebug() << "Disconnected from camera"; + emit disconnected(); + } +} + +bool NetworkManager::isConnected() const +{ + return m_isConnected; +} + +// ========== 发送控制命令 ========== +bool NetworkManager::sendCommand(const QString &command) +{ + if (!m_isConnected) { + qDebug() << "Not connected to camera"; + return false; + } + + QByteArray data = command.toUtf8(); + qDebug() << "Sending command to" << m_cameraIp << ":" << m_controlPort << "data:" << command; + qDebug() << "Control socket state:" << m_controlSocket->state(); + qDebug() << "Control socket error:" << m_controlSocket->errorString(); + qDebug() << "Data to send (hex):" << data.toHex(); + qDebug() << "Data size:" << data.size(); + + qint64 sent = m_controlSocket->writeDatagram(data, QHostAddress(m_cameraIp), m_controlPort); + + qDebug() << "writeDatagram returned:" << sent; + if (sent == -1) { + QString error = QString("Failed to send command: %1").arg(m_controlSocket->errorString()); + qDebug() << error; + qDebug() << "Socket error code:" << m_controlSocket->error(); + emit errorOccurred(error); + return false; + } + + qDebug() << "Command sent successfully, bytes:" << sent; + qDebug() << "Sent command:" << command; + return true; +} + +bool NetworkManager::sendStartCommand() +{ + return sendCommand("START"); +} + +bool NetworkManager::sendStopCommand() +{ + return sendCommand("STOP"); +} + +bool NetworkManager::sendOnceCommand() +{ + return sendCommand("ONCE"); +} + +bool NetworkManager::sendExposureCommand(int exposureTime) +{ + QString command = QString("EXPOSURE:%1").arg(exposureTime); + return sendCommand(command); +} + +// ========== 槽函数 ========== +void NetworkManager::onReadyRead() +{ + static int packetCount = 0; + + while (m_dataSocket->hasPendingDatagrams()) { + QByteArray datagram; + datagram.resize(m_dataSocket->pendingDatagramSize()); + + QHostAddress sender; + quint16 senderPort; + m_dataSocket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort); + + // 临时添加日志以诊断问题 + if (packetCount < 5) { + qDebug() << "[NetworkManager] Received packet" << packetCount << "from" << sender.toString() << ":" << senderPort << "size:" << datagram.size(); + } + packetCount++; + + // 将数据包传递给GVSP解析器 + m_gvspParser->parsePacket(datagram); + + // 仍然发出原始数据信号(用于调试) + emit dataReceived(datagram); + } +} + +void NetworkManager::onError(QAbstractSocket::SocketError socketError) +{ + QString error = QString("Socket error: %1").arg(m_dataSocket->errorString()); + qDebug() << error; + emit errorOccurred(error); +} diff --git a/src/core/NetworkManager.h b/src/core/NetworkManager.h new file mode 100644 index 0000000..e83d3ad --- /dev/null +++ b/src/core/NetworkManager.h @@ -0,0 +1,55 @@ +#ifndef NETWORKMANAGER_H +#define NETWORKMANAGER_H + +#include +#include +#include +#include +#include + +class GVSPParser; + +class NetworkManager : public QObject +{ + Q_OBJECT + +public: + explicit NetworkManager(QObject *parent = nullptr); + ~NetworkManager(); + + // 连接和断开 + bool connectToCamera(const QString &ip, int controlPort, int dataPort); + void disconnectFromCamera(); + bool isConnected() const; + + // 发送控制命令 + bool sendCommand(const QString &command); + bool sendStartCommand(); + bool sendStopCommand(); + bool sendOnceCommand(); + bool sendExposureCommand(int exposureTime); + +signals: + void connected(); + void disconnected(); + void errorOccurred(const QString &error); + void dataReceived(const QByteArray &data); + void imageReceived(const QImage &image, uint32_t blockId); + void depthDataReceived(const QByteArray &depthData, uint32_t blockId); + +private slots: + void onReadyRead(); + void onError(QAbstractSocket::SocketError socketError); + +private: + QUdpSocket *m_controlSocket; + QUdpSocket *m_dataSocket; + GVSPParser *m_gvspParser; + + QString m_cameraIp; + int m_controlPort; + int m_dataPort; + bool m_isConnected; +}; + +#endif // NETWORKMANAGER_H diff --git a/src/core/PointCloudProcessor.cpp b/src/core/PointCloudProcessor.cpp new file mode 100644 index 0000000..cebe7a4 --- /dev/null +++ b/src/core/PointCloudProcessor.cpp @@ -0,0 +1,277 @@ +#include "core/PointCloudProcessor.h" +#include +#include + +PointCloudProcessor::PointCloudProcessor(QObject *parent) + : QObject(parent) + , m_fx(1432.8957f) + , m_fy(1432.6590f) + , m_cx(637.5117f) + , m_cy(521.8720f) + , m_zScale(0.2f) + , m_imageWidth(1224) + , m_imageHeight(1024) + , m_totalPoints(1224 * 1024) + , m_platform(nullptr) + , m_device(nullptr) + , m_context(nullptr) + , m_queue(nullptr) + , m_program(nullptr) + , m_kernel(nullptr) + , m_depthBuffer(nullptr) + , m_xyzBuffer(nullptr) + , m_clInitialized(false) +{ +} + +PointCloudProcessor::~PointCloudProcessor() +{ + cleanupOpenCL(); +} + +void PointCloudProcessor::setCameraIntrinsics(float fx, float fy, float cx, float cy) +{ + m_fx = fx; + m_fy = fy; + m_cx = cx; + m_cy = cy; +} + +void PointCloudProcessor::setZScaleFactor(float scale) +{ + m_zScale = scale; +} + +bool PointCloudProcessor::initializeOpenCL() +{ + if (m_clInitialized) { + return true; + } + + cl_int err; + qDebug() << "[OpenCL] Starting initialization..."; + + // 1. 获取平台 + err = clGetPlatformIDs(1, &m_platform, nullptr); + if (err != CL_SUCCESS) { + emit errorOccurred("Failed to get OpenCL platform"); + return false; + } + + // 2. 获取设备(优先GPU) + err = clGetDeviceIDs(m_platform, CL_DEVICE_TYPE_GPU, 1, &m_device, nullptr); + if (err != CL_SUCCESS) { + qDebug() << "[OpenCL] GPU not available, using CPU"; + err = clGetDeviceIDs(m_platform, CL_DEVICE_TYPE_CPU, 1, &m_device, nullptr); + if (err != CL_SUCCESS) { + emit errorOccurred("Failed to get OpenCL device"); + return false; + } + } + + // 3. 创建上下文 + m_context = clCreateContext(nullptr, 1, &m_device, nullptr, nullptr, &err); + if (err != CL_SUCCESS) { + emit errorOccurred("Failed to create OpenCL context"); + return false; + } + + // 4. 创建命令队列 +#if CL_TARGET_OPENCL_VERSION >= 200 + cl_queue_properties props[] = { CL_QUEUE_ON_DEVICE_DEFAULT, 0 }; + m_queue = clCreateCommandQueueWithProperties(m_context, m_device, props, &err); +#else + m_queue = clCreateCommandQueue(m_context, m_device, 0, &err); +#endif + if (err != CL_SUCCESS) { + emit errorOccurred("Failed to create command queue"); + cleanupOpenCL(); + return false; + } + + qDebug() << "[OpenCL] Platform, device, context, and queue created successfully"; + + // 5. 创建并编译OpenCL程序 + const char* kernelSource = + "__kernel void compute_xyz(__global const float* depth, " + "__global float* xyz, int width, int height, " + "float inv_fx, float inv_fy, float cx, float cy, float z_scale) { " + "int idx = get_global_id(0); " + "if (idx >= width * height) return; " + "int y = idx / width; " + "int x = idx % width; " + "float z = depth[idx] * z_scale; " + // 完全平面的圆柱投影:X和Y直接使用像素坐标,缩放到合适的范围 + "xyz[idx*3] = (x - cx) * 2.0f; " // X坐标,缩放系数2.0 + "xyz[idx*3+1] = (y - cy) * 2.0f; " // Y坐标,缩放系数2.0 + "xyz[idx*3+2] = z; " + "}"; + + m_program = clCreateProgramWithSource(m_context, 1, &kernelSource, nullptr, &err); + if (err != CL_SUCCESS) { + emit errorOccurred("Failed to create OpenCL program"); + cleanupOpenCL(); + return false; + } + + err = clBuildProgram(m_program, 1, &m_device, nullptr, nullptr, nullptr); + if (err != CL_SUCCESS) { + char buildLog[4096]; + clGetProgramBuildInfo(m_program, m_device, CL_PROGRAM_BUILD_LOG, sizeof(buildLog), buildLog, nullptr); + emit errorOccurred(QString("Failed to build OpenCL program: %1").arg(buildLog)); + cleanupOpenCL(); + return false; + } + + qDebug() << "[OpenCL] Program compiled successfully"; + + // 6. 创建kernel + m_kernel = clCreateKernel(m_program, "compute_xyz", &err); + if (err != CL_SUCCESS) { + emit errorOccurred("Failed to create OpenCL kernel"); + cleanupOpenCL(); + return false; + } + + // 7. 创建缓冲区 + m_depthBuffer = clCreateBuffer(m_context, CL_MEM_READ_ONLY, + sizeof(float) * m_totalPoints, nullptr, &err); + if (err != CL_SUCCESS) { + emit errorOccurred("Failed to create depth buffer"); + cleanupOpenCL(); + return false; + } + + m_xyzBuffer = clCreateBuffer(m_context, CL_MEM_WRITE_ONLY, + sizeof(float) * m_totalPoints * 3, nullptr, &err); + if (err != CL_SUCCESS) { + emit errorOccurred("Failed to create XYZ buffer"); + cleanupOpenCL(); + return false; + } + + m_clInitialized = true; + qDebug() << "[OpenCL] Initialization complete"; + return true; +} + +void PointCloudProcessor::processDepthData(const QByteArray &depthData, uint32_t blockId) +{ + if (!m_clInitialized) { + if (!initializeOpenCL()) { + return; + } + } + + // 验证数据大小 + if (depthData.size() != m_totalPoints * sizeof(int16_t)) { + qDebug() << "[PointCloud] Invalid depth data size:" << depthData.size() + << "expected:" << (m_totalPoints * sizeof(int16_t)); + return; + } + + // 转换int16_t到float + const int16_t* depthShort = reinterpret_cast(depthData.constData()); + std::vector depthFloat(m_totalPoints); + for (size_t i = 0; i < m_totalPoints; i++) { + depthFloat[i] = static_cast(depthShort[i]); + } + + // 注释掉频繁的日志输出 + // qDebug() << "[PointCloud] Processing block" << blockId << "with" << m_totalPoints << "points"; + + // 上传深度数据到GPU + cl_int err = clEnqueueWriteBuffer(m_queue, m_depthBuffer, CL_TRUE, 0, + sizeof(float) * m_totalPoints, depthFloat.data(), + 0, nullptr, nullptr); + if (err != CL_SUCCESS) { + emit errorOccurred(QString("Failed to upload depth data: %1").arg(err)); + return; + } + + // 设置kernel参数 + float inv_fx = 1.0f / m_fx; + float inv_fy = 1.0f / m_fy; + int width = m_imageWidth; + int height = m_imageHeight; + + clSetKernelArg(m_kernel, 0, sizeof(cl_mem), &m_depthBuffer); + clSetKernelArg(m_kernel, 1, sizeof(cl_mem), &m_xyzBuffer); + clSetKernelArg(m_kernel, 2, sizeof(int), &width); + clSetKernelArg(m_kernel, 3, sizeof(int), &height); + clSetKernelArg(m_kernel, 4, sizeof(float), &inv_fx); + clSetKernelArg(m_kernel, 5, sizeof(float), &inv_fy); + clSetKernelArg(m_kernel, 6, sizeof(float), &m_cx); + clSetKernelArg(m_kernel, 7, sizeof(float), &m_cy); + clSetKernelArg(m_kernel, 8, sizeof(float), &m_zScale); + + // 执行kernel + size_t local = 256; + size_t global = ((m_totalPoints + local - 1) / local) * local; + err = clEnqueueNDRangeKernel(m_queue, m_kernel, 1, nullptr, + &global, &local, 0, nullptr, nullptr); + if (err != CL_SUCCESS) { + emit errorOccurred(QString("Failed to execute kernel: %1").arg(err)); + return; + } + + clFinish(m_queue); + + // 读取结果 + std::vector xyz(m_totalPoints * 3); + err = clEnqueueReadBuffer(m_queue, m_xyzBuffer, CL_TRUE, 0, + sizeof(float) * m_totalPoints * 3, xyz.data(), + 0, nullptr, nullptr); + if (err != CL_SUCCESS) { + emit errorOccurred(QString("Failed to read XYZ data: %1").arg(err)); + return; + } + + // 创建PCL点云 + pcl::PointCloud::Ptr cloud(new pcl::PointCloud); + cloud->width = m_imageWidth; + cloud->height = m_imageHeight; + cloud->is_dense = false; + cloud->points.resize(m_totalPoints); + + // 填充点云数据(过滤全零点) + for (size_t i = 0; i < m_totalPoints; i++) { + cloud->points[i].x = xyz[i * 3]; + cloud->points[i].y = xyz[i * 3 + 1]; + cloud->points[i].z = xyz[i * 3 + 2]; + } + + // 注释掉频繁的日志输出 + // qDebug() << "[PointCloud] Block" << blockId << "processed successfully"; + emit pointCloudReady(cloud, blockId); +} + +void PointCloudProcessor::cleanupOpenCL() +{ + if (m_depthBuffer) { + clReleaseMemObject(m_depthBuffer); + m_depthBuffer = nullptr; + } + if (m_xyzBuffer) { + clReleaseMemObject(m_xyzBuffer); + m_xyzBuffer = nullptr; + } + if (m_kernel) { + clReleaseKernel(m_kernel); + m_kernel = nullptr; + } + if (m_program) { + clReleaseProgram(m_program); + m_program = nullptr; + } + if (m_queue) { + clReleaseCommandQueue(m_queue); + m_queue = nullptr; + } + if (m_context) { + clReleaseContext(m_context); + m_context = nullptr; + } + m_clInitialized = false; + qDebug() << "[OpenCL] Cleanup complete"; +} diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp new file mode 100644 index 0000000..371e553 --- /dev/null +++ b/src/gui/MainWindow.cpp @@ -0,0 +1,1138 @@ +#include "gui/MainWindow.h" +#include "config/ConfigManager.h" +#include "core/NetworkManager.h" +#include "core/DeviceScanner.h" +#include "core/PointCloudProcessor.h" +#include "gui/PointCloudWidget.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , m_configManager(std::make_unique()) + , m_networkManager(std::make_unique(this)) + , m_deviceScanner(std::make_unique(this)) + , m_pointCloudProcessor(std::make_unique(this)) + , m_updateTimer(new QTimer(this)) + , m_isConnected(false) + , m_autoSaveOnNextFrame(false) + , m_currentPointCloud(new pcl::PointCloud()) + , m_currentFrameId(0) + , m_frameCount(0) + , m_totalFrameCount(0) + , m_currentFps(0.0) +{ + setupUI(); + setupConnections(); + loadSettings(); + + // 添加初始日志 + addLog("D330Viewer 启动成功", "SUCCESS"); + addLog("等待连接相机...", "INFO"); + + // 启动UI更新定时器(100ms刷新一次) + m_updateTimer->start(100); + + // 自动启动设备扫描 + QTimer::singleShot(500, this, [this]() { + qDebug() << "自动启动设备扫描"; + addLog("开始扫描网络中的相机设备", "INFO"); + m_deviceScanner->startScan(); + m_refreshBtn->setText("停止扫描"); + }); +} + +MainWindow::~MainWindow() +{ + saveSettings(); +} + +void MainWindow::setupUI() +{ + setWindowTitle("D330Viewer - D330M 相机控制"); + resize(1280, 720); // 16:9 比例 + // resize(720, 1280); // 16:9 比例 + + // 设置应用程序图标 + setWindowIcon(QIcon(":/icons/icons/app_icon.png")); + + // 移除全局样式表以支持系统深色模式 + // Qt会自动使用系统主题颜色 + + // 创建中央控件 + m_centralWidget = new QWidget(this); + setCentralWidget(m_centralWidget); + + // 创建左侧控制面板 + m_controlPanel = new QWidget(m_centralWidget); + m_controlPanel->setMinimumWidth(320); + m_controlPanel->setMaximumWidth(420); + // 移除固定背景色,使用系统默认颜色以支持深色模式 + + // 创建右侧内容区域(包含显示区和日志区) + QWidget *rightPanel = new QWidget(m_centralWidget); + QVBoxLayout *rightLayout = new QVBoxLayout(rightPanel); + rightLayout->setContentsMargins(0, 0, 0, 0); + rightLayout->setSpacing(0); + + // 创建上方显示区域的水平分割器(图像 + 点云) + QSplitter *displaySplitter = new QSplitter(Qt::Horizontal, rightPanel); + + // 创建图像显示区域 + m_imageDisplay = new QLabel(displaySplitter); + m_imageDisplay->setMinimumSize(480, 360); // 4:3 比例,减小高度 + m_imageDisplay->setStyleSheet( + "QLabel { " + " background-color: #263238; " + " color: #B0BEC5; " + " border: 2px solid #37474F; " + " border-radius: 4px; " + " font-size: 12pt; " + "}" + ); + m_imageDisplay->setAlignment(Qt::AlignCenter); + m_imageDisplay->setText("图像显示\n(等待数据...)"); + + // 创建点云显示区域 + m_pointCloudWidget = new PointCloudWidget(displaySplitter); + m_pointCloudWidget->setMinimumSize(480, 360); // 4:3 比例,减小高度 + + // 设置显示区域分割器比例 + displaySplitter->setStretchFactor(0, 1); + displaySplitter->setStretchFactor(1, 1); + + // 创建日志显示区域 + QWidget *logWidget = new QWidget(rightPanel); + QVBoxLayout *logLayout = new QVBoxLayout(logWidget); + logLayout->setContentsMargins(5, 5, 5, 5); + logLayout->setSpacing(5); + + // 日志标题和按钮 + QHBoxLayout *logHeaderLayout = new QHBoxLayout(); + QLabel *logTitle = new QLabel("实时日志", logWidget); + QFont titleFont = logTitle->font(); + titleFont.setBold(true); + titleFont.setPointSize(11); + logTitle->setFont(titleFont); + logTitle->setStyleSheet("QLabel { color: #424242; padding: 4px; }"); + + m_clearLogBtn = new QPushButton(QIcon(":/icons/icons/clear.png"), "清除日志", logWidget); + m_clearLogBtn->setMaximumWidth(110); + m_clearLogBtn->setMinimumHeight(32); + m_clearLogBtn->setStyleSheet( + "QPushButton { background-color: #757575; color: white; font-size: 9pt; }" + "QPushButton:hover { background-color: #616161; }" + "QPushButton:pressed { background-color: #424242; }" + ); + + m_saveLogBtn = new QPushButton(QIcon(":/icons/icons/save.png"), "保存日志", logWidget); + m_saveLogBtn->setMaximumWidth(110); + m_saveLogBtn->setMinimumHeight(32); + m_saveLogBtn->setStyleSheet( + "QPushButton { background-color: #4CAF50; color: white; font-size: 9pt; }" + "QPushButton:hover { background-color: #45A049; }" + "QPushButton:pressed { background-color: #388E3C; }" + ); + + logHeaderLayout->addWidget(logTitle); + logHeaderLayout->addStretch(); + logHeaderLayout->addWidget(m_clearLogBtn); + logHeaderLayout->addWidget(m_saveLogBtn); + logLayout->addLayout(logHeaderLayout); + + // 日志文本显示 + m_logDisplay = new QTextEdit(logWidget); + m_logDisplay->setReadOnly(true); + m_logDisplay->setMinimumHeight(100); + m_logDisplay->setStyleSheet("QTextEdit { background-color: #1E1E1E; color: #D4D4D4; font-family: Consolas, monospace; font-size: 9pt; }"); + logLayout->addWidget(m_logDisplay); + + // 将显示区和日志区添加到右侧面板的垂直分割器中 + QSplitter *rightVerticalSplitter = new QSplitter(Qt::Vertical, rightPanel); + rightVerticalSplitter->addWidget(displaySplitter); + rightVerticalSplitter->addWidget(logWidget); + rightVerticalSplitter->setStretchFactor(0, 3); // 显示区占更多空间 + rightVerticalSplitter->setStretchFactor(1, 1); // 日志区占较少空间 + + rightLayout->addWidget(rightVerticalSplitter); + + // 创建主水平分割器(左侧控制面板 + 右侧内容区) + m_mainSplitter = new QSplitter(Qt::Horizontal, m_centralWidget); + m_mainSplitter->addWidget(m_controlPanel); + m_mainSplitter->addWidget(rightPanel); + m_mainSplitter->setStretchFactor(0, 0); // 控制面板固定宽度 + m_mainSplitter->setStretchFactor(1, 1); // 右侧内容区自适应 + + // 主布局 + QVBoxLayout *mainLayout = new QVBoxLayout(m_centralWidget); + mainLayout->addWidget(m_mainSplitter); + mainLayout->setContentsMargins(0, 0, 0, 0); + + // 创建控制面板布局 + QVBoxLayout *controlLayout = new QVBoxLayout(m_controlPanel); + controlLayout->setSpacing(10); + controlLayout->setContentsMargins(10, 10, 10, 10); + + // ========== 相机控制组 ========== + QGroupBox *cameraGroup = new QGroupBox("相机控制", m_controlPanel); + QVBoxLayout *cameraLayout = new QVBoxLayout(cameraGroup); + + // START/STOP/ONCE按钮 + QHBoxLayout *buttonLayout = new QHBoxLayout(); + m_startBtn = new QPushButton(QIcon(":/icons/icons/start.png"), "启动", cameraGroup); + m_stopBtn = new QPushButton(QIcon(":/icons/icons/stop.png"), "停止", cameraGroup); + m_onceBtn = new QPushButton(QIcon(":/icons/icons/camera.png"), "单次", cameraGroup); + + m_startBtn->setMinimumHeight(45); + m_stopBtn->setMinimumHeight(45); + m_onceBtn->setMinimumHeight(45); + m_startBtn->setIconSize(QSize(22, 22)); + m_stopBtn->setIconSize(QSize(22, 22)); + m_onceBtn->setIconSize(QSize(22, 22)); + + // 为启动按钮设置绿色主题 + m_startBtn->setStyleSheet( + "QPushButton { background-color: #4CAF50; color: white; }" + "QPushButton:hover { background-color: #45A049; }" + "QPushButton:pressed { background-color: #388E3C; }" + ); + + // 为停止按钮设置红色主题 + m_stopBtn->setStyleSheet( + "QPushButton { background-color: #F44336; color: white; }" + "QPushButton:hover { background-color: #E53935; }" + "QPushButton:pressed { background-color: #C62828; }" + ); + + // 为单次按钮设置蓝色主题 + m_onceBtn->setStyleSheet( + "QPushButton { background-color: #2196F3; color: white; }" + "QPushButton:hover { background-color: #1E88E5; }" + "QPushButton:pressed { background-color: #1976D2; }" + ); + + buttonLayout->addWidget(m_startBtn); + buttonLayout->addWidget(m_stopBtn); + buttonLayout->addWidget(m_onceBtn); + cameraLayout->addLayout(buttonLayout); + + // 拍照按钮 + m_captureBtn = new QPushButton(QIcon(":/icons/icons/camera.png"), "拍照", cameraGroup); + m_captureBtn->setMinimumHeight(45); + m_captureBtn->setIconSize(QSize(22, 22)); + m_captureBtn->setStyleSheet( + "QPushButton { background-color: #FF9800; color: white; }" + "QPushButton:hover { background-color: #FB8C00; }" + "QPushButton:pressed { background-color: #EF6C00; }" + ); + cameraLayout->addWidget(m_captureBtn); + + controlLayout->addWidget(cameraGroup); + + // ========== 选项卡组件 ========== + QTabWidget *tabWidget = new QTabWidget(m_controlPanel); + tabWidget->setStyleSheet("QTabWidget::pane { border: 1px solid #CCCCCC; }"); + + // ========== 第一个标签页:设备列表 + 网络配置 ========== + QWidget *deviceNetworkTab = new QWidget(); + QVBoxLayout *deviceNetworkLayout = new QVBoxLayout(deviceNetworkTab); + deviceNetworkLayout->setContentsMargins(10, 10, 10, 10); + + // 设备列表组 + QGroupBox *deviceGroup = new QGroupBox("设备列表", deviceNetworkTab); + QVBoxLayout *deviceLayout = new QVBoxLayout(deviceGroup); + + // 刷新按钮 + m_refreshBtn = new QPushButton(QIcon(":/icons/icons/refresh.png"), "刷新设备", deviceGroup); + m_refreshBtn->setMinimumHeight(45); + m_refreshBtn->setIconSize(QSize(22, 22)); + m_refreshBtn->setStyleSheet( + "QPushButton { background-color: #2196F3; color: white; }" + "QPushButton:hover { background-color: #1E88E5; }" + "QPushButton:pressed { background-color: #1976D2; }" + ); + deviceLayout->addWidget(m_refreshBtn); + + // 设备列表 + m_deviceList = new QListWidget(deviceGroup); + m_deviceList->setMinimumHeight(80); + deviceLayout->addWidget(m_deviceList); + + deviceNetworkLayout->addWidget(deviceGroup); + + // 网络配置组 + QGroupBox *networkGroup = new QGroupBox("网络配置", deviceNetworkTab); + QVBoxLayout *networkLayout = new QVBoxLayout(networkGroup); + + // 控制端口输入 + QHBoxLayout *ctrlPortLayout = new QHBoxLayout(); + QLabel *ctrlPortLabel = new QLabel("控制端口:", networkGroup); + m_ctrlPortSpinBox = new QSpinBox(networkGroup); + m_ctrlPortSpinBox->setRange(1, 65535); + m_ctrlPortSpinBox->setValue(6790); + ctrlPortLayout->addWidget(ctrlPortLabel); + ctrlPortLayout->addWidget(m_ctrlPortSpinBox); + networkLayout->addLayout(ctrlPortLayout); + + // 数据端口输入 + QHBoxLayout *dataPortLayout = new QHBoxLayout(); + QLabel *dataPortLabel = new QLabel("数据端口:", networkGroup); + m_dataPortSpinBox = new QSpinBox(networkGroup); + m_dataPortSpinBox->setRange(1, 65535); + m_dataPortSpinBox->setValue(3957); + dataPortLayout->addWidget(dataPortLabel); + dataPortLayout->addWidget(m_dataPortSpinBox); + networkLayout->addLayout(dataPortLayout); + + // 连接按钮和状态指示 + QHBoxLayout *connectLayout = new QHBoxLayout(); + m_connectBtn = new QPushButton(QIcon(":/icons/icons/connect.png"), "连接", networkGroup); + m_connectBtn->setMinimumHeight(45); + m_connectBtn->setIconSize(QSize(22, 22)); + m_connectBtn->setStyleSheet( + "QPushButton { background-color: #4CAF50; color: white; }" + "QPushButton:hover { background-color: #45A049; }" + "QPushButton:pressed { background-color: #388E3C; }" + ); + m_statusLabel = new QLabel("● 未连接", networkGroup); + m_statusLabel->setStyleSheet( + "QLabel { " + " color: #F44336; " + " font-weight: bold; " + " font-size: 11pt; " + " padding: 8px; " + "}" + ); + connectLayout->addWidget(m_connectBtn); + connectLayout->addWidget(m_statusLabel); + networkLayout->addLayout(connectLayout); + + deviceNetworkLayout->addWidget(networkGroup); + deviceNetworkLayout->addStretch(); + + // 将第一个标签页添加到选项卡 + tabWidget->addTab(deviceNetworkTab, "设备与网络"); + + // ========== 第二个标签页:曝光时间 + 拍照参数 ========== + QWidget *exposureCaptureTab = new QWidget(); + QVBoxLayout *exposureCaptureLayout = new QVBoxLayout(exposureCaptureTab); + exposureCaptureLayout->setContentsMargins(10, 10, 10, 10); + + // 曝光时间控制组 + QGroupBox *exposureGroup = new QGroupBox("曝光时间", exposureCaptureTab); + QVBoxLayout *exposureGroupLayout = new QVBoxLayout(exposureGroup); + + QLabel *exposureLabel = new QLabel("曝光时间 (μs):", exposureGroup); + exposureGroupLayout->addWidget(exposureLabel); + + QHBoxLayout *exposureLayout = new QHBoxLayout(); + m_exposureSlider = new QSlider(Qt::Horizontal, exposureGroup); + m_exposureSlider->setRange(460, 100000); + m_exposureSlider->setValue(10000); + + m_exposureSpinBox = new QSpinBox(exposureGroup); + m_exposureSpinBox->setRange(460, 100000); + m_exposureSpinBox->setValue(10000); + m_exposureSpinBox->setMinimumWidth(80); + + exposureLayout->addWidget(m_exposureSlider, 3); + exposureLayout->addWidget(m_exposureSpinBox, 1); + exposureGroupLayout->addLayout(exposureLayout); + + exposureCaptureLayout->addWidget(exposureGroup); + + // 拍照参数组 + QGroupBox *captureGroup = new QGroupBox("拍照参数", exposureCaptureTab); + QVBoxLayout *captureLayout = new QVBoxLayout(captureGroup); + + // 保存路径 + QLabel *savePathLabel = new QLabel("保存路径:", captureGroup); + captureLayout->addWidget(savePathLabel); + + QHBoxLayout *pathLayout = new QHBoxLayout(); + m_savePathEdit = new QLineEdit(captureGroup); + m_savePathEdit->setPlaceholderText("默认: 用户图片文件夹"); + m_browseSavePathBtn = new QPushButton(QIcon(":/icons/icons/folder.png"), "浏览...", captureGroup); + m_browseSavePathBtn->setMaximumWidth(90); + m_browseSavePathBtn->setMinimumHeight(32); + m_browseSavePathBtn->setStyleSheet( + "QPushButton { background-color: #607D8B; color: white; }" + "QPushButton:hover { background-color: #546E7A; }" + "QPushButton:pressed { background-color: #455A64; }" + ); + pathLayout->addWidget(m_savePathEdit); + pathLayout->addWidget(m_browseSavePathBtn); + captureLayout->addLayout(pathLayout); + + // 深度图格式选择 + QLabel *depthFormatLabel = new QLabel("深度图格式:", captureGroup); + captureLayout->addWidget(depthFormatLabel); + m_depthFormatCombo = new QComboBox(captureGroup); + m_depthFormatCombo->addItem("PNG (8位灰度)", "png"); + m_depthFormatCombo->addItem("TIFF (16位原始)", "tiff"); + m_depthFormatCombo->addItem("PNG + TIFF", "both"); + m_depthFormatCombo->setCurrentIndex(2); + captureLayout->addWidget(m_depthFormatCombo); + + // 点云格式选择 + QLabel *pointCloudFormatLabel = new QLabel("点云格式:", captureGroup); + captureLayout->addWidget(pointCloudFormatLabel); + m_pointCloudFormatCombo = new QComboBox(captureGroup); + m_pointCloudFormatCombo->addItem("PCD (PCL标准)", "pcd"); + m_pointCloudFormatCombo->addItem("PLY (Polygon)", "ply"); + m_pointCloudFormatCombo->addItem("PCD + PLY", "both"); + m_pointCloudFormatCombo->setCurrentIndex(2); + captureLayout->addWidget(m_pointCloudFormatCombo); + + exposureCaptureLayout->addWidget(captureGroup); + exposureCaptureLayout->addStretch(); + + // 将第二个标签页添加到选项卡 + tabWidget->addTab(exposureCaptureTab, "曝光与拍照"); + + // 将选项卡添加到控制面板(设置伸展因子为1,使其自适应扩展) + controlLayout->addWidget(tabWidget, 1); + + // ========== 统计信息显示组 ========== + QGroupBox *statsGroup = new QGroupBox("统计信息", m_controlPanel); + QVBoxLayout *statsLayout = new QVBoxLayout(statsGroup); + + m_fpsLabel = new QLabel("帧率: 0.0 fps", statsGroup); + m_resolutionLabel = new QLabel("分辨率: 1224 x 1024", statsGroup); + m_queueLabel = new QLabel("接收帧数: 0", statsGroup); + + // 设置统计标签样式 - 移除固定颜色以支持深色模式 + QString statsLabelStyle = "QLabel { font-size: 10pt; padding: 4px; }"; + m_fpsLabel->setStyleSheet(statsLabelStyle); + m_resolutionLabel->setStyleSheet(statsLabelStyle); + m_queueLabel->setStyleSheet(statsLabelStyle); + + statsLayout->addWidget(m_fpsLabel); + statsLayout->addWidget(m_resolutionLabel); + statsLayout->addWidget(m_queueLabel); + + controlLayout->addWidget(statsGroup); + controlLayout->addStretch(); +} + +void MainWindow::setupConnections() +{ + // 相机控制按钮连接 + connect(m_startBtn, &QPushButton::clicked, this, &MainWindow::onStartClicked); + connect(m_stopBtn, &QPushButton::clicked, this, &MainWindow::onStopClicked); + connect(m_onceBtn, &QPushButton::clicked, this, &MainWindow::onOnceClicked); + connect(m_captureBtn, &QPushButton::clicked, this, &MainWindow::onCaptureClicked); + connect(m_browseSavePathBtn, &QPushButton::clicked, this, &MainWindow::onBrowseSavePathClicked); + + // 曝光时间控制连接 + connect(m_exposureSlider, &QSlider::valueChanged, m_exposureSpinBox, &QSpinBox::setValue); + connect(m_exposureSpinBox, QOverload::of(&QSpinBox::valueChanged), m_exposureSlider, &QSlider::setValue); + connect(m_exposureSlider, &QSlider::valueChanged, this, &MainWindow::onExposureChanged); + + // 网络配置连接 + connect(m_connectBtn, &QPushButton::clicked, this, &MainWindow::onConnectClicked); + connect(m_ctrlPortSpinBox, QOverload::of(&QSpinBox::valueChanged), this, &MainWindow::onControlPortChanged); + connect(m_dataPortSpinBox, QOverload::of(&QSpinBox::valueChanged), this, &MainWindow::onDataPortChanged); + + // 网络状态信号连接 + connect(m_networkManager.get(), &NetworkManager::connected, this, &MainWindow::onNetworkConnected); + connect(m_networkManager.get(), &NetworkManager::disconnected, this, &MainWindow::onNetworkDisconnected); + connect(m_networkManager.get(), &NetworkManager::dataReceived, this, &MainWindow::onDataReceived); + + // GVSP数据信号连接(从NetworkManager) + connect(m_networkManager.get(), &NetworkManager::imageReceived, this, &MainWindow::onImageReceived); + connect(m_networkManager.get(), &NetworkManager::depthDataReceived, this, &MainWindow::onDepthDataReceived); + + // 点云处理信号连接 + connect(m_pointCloudProcessor.get(), &PointCloudProcessor::pointCloudReady, this, &MainWindow::onPointCloudReady); + + // 刷新按钮连接 + connect(m_refreshBtn, &QPushButton::clicked, this, &MainWindow::onRefreshClicked); + + // 设备扫描信号连接 + connect(m_deviceScanner.get(), &DeviceScanner::deviceFound, this, &MainWindow::onDeviceFound); + connect(m_deviceScanner.get(), &DeviceScanner::scanProgress, this, &MainWindow::onScanProgress); + connect(m_deviceScanner.get(), &DeviceScanner::scanFinished, this, &MainWindow::onScanFinished); + + // 设备列表选择连接 + connect(m_deviceList, &QListWidget::itemClicked, this, &MainWindow::onDeviceSelected); + + // 日志按钮连接 + connect(m_clearLogBtn, &QPushButton::clicked, this, &MainWindow::onClearLogClicked); + connect(m_saveLogBtn, &QPushButton::clicked, this, &MainWindow::onSaveLogClicked); +} + +void MainWindow::loadSettings() +{ + // 加载曝光时间 + int exposure = m_configManager->getExposureTime(); + m_exposureSlider->setValue(exposure); + m_exposureSpinBox->setValue(exposure); + + // 加载保存路径 + QString savePath = m_configManager->getSavePath(); + m_savePathEdit->setText(savePath); + + // 加载深度图格式 + QString depthFormat = m_configManager->getDepthFormat(); + int depthIndex = m_depthFormatCombo->findData(depthFormat); + if (depthIndex >= 0) { + m_depthFormatCombo->setCurrentIndex(depthIndex); + } + + // 加载点云格式 + QString pointCloudFormat = m_configManager->getPointCloudFormat(); + int pcIndex = m_pointCloudFormatCombo->findData(pointCloudFormat); + if (pcIndex >= 0) { + m_pointCloudFormatCombo->setCurrentIndex(pcIndex); + } +} + +void MainWindow::saveSettings() +{ + // 保存曝光时间 + m_configManager->setExposureTime(m_exposureSlider->value()); + + // 保存保存路径 + m_configManager->setSavePath(m_savePathEdit->text()); + + // 保存深度图格式 + m_configManager->setDepthFormat(m_depthFormatCombo->currentData().toString()); + + // 保存点云格式 + m_configManager->setPointCloudFormat(m_pointCloudFormatCombo->currentData().toString()); +} + +// ========== 槽函数实现 ========== +void MainWindow::onStartClicked() +{ + qDebug() << "启动按钮点击"; + m_networkManager->sendStartCommand(); + addLog("发送启动命令", "INFO"); +} + +void MainWindow::onStopClicked() +{ + qDebug() << "停止按钮点击"; + m_networkManager->sendStopCommand(); + addLog("发送停止命令", "INFO"); +} + +void MainWindow::onOnceClicked() +{ + qDebug() << "单次按钮点击 - 将在接收到数据后自动保存"; + m_autoSaveOnNextFrame = true; + m_networkManager->sendOnceCommand(); + addLog("发送单次采集命令(将自动保存)", "INFO"); +} + +void MainWindow::onExposureChanged(int value) +{ + qDebug() << "曝光值改变为:" << value; + m_networkManager->sendExposureCommand(value); + addLog(QString("设置曝光时间: %1 μs").arg(value), "INFO"); +} + +void MainWindow::onConnectClicked() +{ + if (m_isConnected) { + m_networkManager->disconnectFromCamera(); + m_isConnected = false; + qDebug() << "断开相机连接"; + } else { + QString ip = m_configManager->getIpAddress(); + int ctrlPort = m_configManager->getControlPort(); + int dataPort = m_configManager->getDataPort(); + + if (m_networkManager->connectToCamera(ip, ctrlPort, dataPort)) { + m_isConnected = true; + qDebug() << "连接到相机"; + } + } +} + +void MainWindow::onIpAddressChanged(const QString &ip) +{ + qDebug() << "IP地址改变为:" << ip; + m_configManager->setIpAddress(ip); +} + +void MainWindow::onControlPortChanged(int port) +{ + qDebug() << "控制端口改变为:" << port; + m_configManager->setControlPort(port); +} + +void MainWindow::onDataPortChanged(int port) +{ + qDebug() << "数据端口改变为:" << port; + m_configManager->setDataPort(port); +} + +void MainWindow::onNetworkConnected() +{ + qDebug() << "网络已连接 - 更新UI"; + m_isConnected = true; + m_connectBtn->setText("断开"); + m_connectBtn->setStyleSheet( + "QPushButton { background-color: #F44336; color: white; }" + "QPushButton:hover { background-color: #E53935; }" + "QPushButton:pressed { background-color: #C62828; }" + ); + m_statusLabel->setText("● 已连接"); + m_statusLabel->setStyleSheet( + "QLabel { " + " color: #4CAF50; " + " font-weight: bold; " + " font-size: 11pt; " + " padding: 8px; " + "}" + ); + + QString ip = m_configManager->getIpAddress(); + addLog(QString("已连接到相机: %1").arg(ip), "SUCCESS"); +} + +void MainWindow::onNetworkDisconnected() +{ + qDebug() << "网络已断开 - 更新UI"; + m_isConnected = false; + m_connectBtn->setText("连接"); + m_connectBtn->setStyleSheet( + "QPushButton { background-color: #4CAF50; color: white; }" + "QPushButton:hover { background-color: #45A049; }" + "QPushButton:pressed { background-color: #388E3C; }" + ); + m_statusLabel->setText("● 未连接"); + m_statusLabel->setStyleSheet( + "QLabel { " + " color: #F44336; " + " font-weight: bold; " + " font-size: 11pt; " + " padding: 8px; " + "}" + ); + + addLog("相机连接已断开", "WARNING"); +} + +void MainWindow::updateUI() +{ + // 定时器更新UI +} + +// ========== 设备扫描槽函数 ========== +void MainWindow::onRefreshClicked() +{ + qDebug() << "刷新设备按钮点击"; + m_deviceList->clear(); + + if (m_deviceScanner->isScanning()) { + m_deviceScanner->stopScan(); + m_refreshBtn->setText("刷新设备"); + } else { + m_deviceScanner->startScan(); + m_refreshBtn->setText("停止扫描"); + } +} + +void MainWindow::onDeviceFound(const DeviceInfo &device) +{ + qDebug() << "发现设备:" << device.ipAddress; + + // 添加到列表 + QString itemText = QString("%1 - %2").arg(device.ipAddress).arg(device.deviceName); + QListWidgetItem *item = new QListWidgetItem(itemText); + item->setData(Qt::UserRole, device.ipAddress); + m_deviceList->addItem(item); +} + +void MainWindow::onScanProgress(int current, int total) +{ + qDebug() << "扫描进度:" << current << "/" << total; +} + +void MainWindow::onScanFinished(int devicesFound) +{ + qDebug() << "扫描完成。发现" << devicesFound << "个设备"; + m_refreshBtn->setText("刷新设备"); +} + +void MainWindow::onDeviceSelected(QListWidgetItem *item) +{ + QString ip = item->data(Qt::UserRole).toString(); + qDebug() << "选择设备:" << ip; + + // 更新配置管理器中的IP地址 + m_configManager->setIpAddress(ip); +} + +// ========== 数据接收槽函数 ========== +void MainWindow::onDataReceived(const QByteArray &data) +{ + // NetworkManager已经自动将数据传递给GVSP解析器 + // 这里只用于调试统计 + Q_UNUSED(data); +} + +// ========== GVSP数据处理槽函数 ========== +void MainWindow::onImageReceived(const QImage &image, uint32_t blockId) +{ + // 保存当前图像用于拍照 + m_currentImage = image; + m_currentFrameId = blockId; + + // 计算FPS和累计帧数 + m_frameCount++; + m_totalFrameCount++; + QDateTime currentTime = QDateTime::currentDateTime(); + if (m_lastFrameTime.isValid()) { + qint64 elapsed = m_lastFrameTime.msecsTo(currentTime); + if (elapsed >= 1000) { // 每秒更新一次FPS + m_currentFps = (m_frameCount * 1000.0) / elapsed; + m_frameCount = 0; + m_lastFrameTime = currentTime; + updateStatistics(); + } + } else { + m_lastFrameTime = currentTime; + } + + // 将图像显示在UI上 + if (m_imageDisplay) { + QPixmap pixmap = QPixmap::fromImage(image); + m_imageDisplay->setPixmap(pixmap.scaled(m_imageDisplay->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); + } +} + +void MainWindow::onDepthDataReceived(const QByteArray &depthData, uint32_t blockId) +{ + // 实时处理每一帧 + // 注释掉频繁的日志输出 + // qDebug() << "Depth data received: Block" << blockId << "Size:" << depthData.size() << "bytes"; + + // 调用PointCloudProcessor进行OpenCL计算 + m_pointCloudProcessor->processDepthData(depthData, blockId); +} + +void MainWindow::onPointCloudReady(pcl::PointCloud::Ptr cloud, uint32_t blockId) +{ + // 注释掉频繁的日志输出 + // qDebug() << "Point cloud ready: Block" << blockId << "Points:" << cloud->size(); + + // 保存当前点云用于拍照 + m_currentPointCloud = cloud; + + // 更新点云显示 + if (m_pointCloudWidget) { + m_pointCloudWidget->updatePointCloud(cloud); + } + + // 如果是单次拍照模式,自动保存 + if (m_autoSaveOnNextFrame) { + m_autoSaveOnNextFrame = false; + qDebug() << "单次拍照完成,自动保存数据..."; + + // 触发拍照保存 + QTimer::singleShot(100, this, [this]() { + onCaptureClicked(); + }); + } +} + +// ========== 拍照功能 ========== +void MainWindow::onCaptureClicked() +{ + qDebug() << "拍照按钮点击"; + + // 检查是否有可用数据 + if (m_currentImage.isNull() && (!m_currentPointCloud || m_currentPointCloud->empty())) { + QMessageBox::warning(this, "拍照失败", "没有可用的图像或点云数据。\n请先启动相机采集。"); + addLog("拍照失败:没有可用数据", "ERROR"); + return; + } + + // 获取保存路径 + QString saveDir = m_savePathEdit->text().trimmed(); + if (saveDir.isEmpty()) { + // 使用默认路径:用户图片文件夹 + saveDir = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); + qDebug() << "使用默认保存路径:" << saveDir; + } + + // 检查目录是否存在,不存在则创建 + QDir dir(saveDir); + if (!dir.exists()) { + if (!dir.mkpath(".")) { + QMessageBox::warning(this, "拍照失败", QString("无法创建保存目录:\n%1").arg(saveDir)); + return; + } + } + + // 生成时间戳文件名 + QString timestamp = QDateTime::currentDateTime().toString("yyyyMMdd_HHmmss"); + QString frameId = QString::number(m_currentFrameId); + QString baseName = QString("capture_%1_frame%2").arg(timestamp).arg(frameId); + + // 获取格式选择 + QString depthFormat = m_depthFormatCombo->currentData().toString(); + QString pointCloudFormat = m_pointCloudFormatCombo->currentData().toString(); + + // 复制当前数据(避免在后台线程中访问成员变量) + QImage imageCopy = m_currentImage.copy(); + pcl::PointCloud::Ptr cloudCopy(new pcl::PointCloud(*m_currentPointCloud)); + + qDebug() << "开始后台保存..."; + addLog(QString("开始保存: %1").arg(baseName), "INFO"); + + // 在后台线程中执行保存操作 + QtConcurrent::run([this, saveDir, baseName, depthFormat, pointCloudFormat, imageCopy, cloudCopy]() { + this->performBackgroundSave(saveDir, baseName, depthFormat, pointCloudFormat, imageCopy, cloudCopy); + }); +} + +void MainWindow::onBrowseSavePathClicked() +{ + QString dir = QFileDialog::getExistingDirectory(this, "选择保存目录", + m_savePathEdit->text().isEmpty() + ? QStandardPaths::writableLocation(QStandardPaths::PicturesLocation) + : m_savePathEdit->text(), + QFileDialog::ShowDirsOnly); + if (!dir.isEmpty()) { + m_savePathEdit->setText(dir); + qDebug() << "设置保存路径:" << dir; + } +} + +void MainWindow::performBackgroundSave(const QString &saveDir, const QString &baseName, + const QString &depthFormat, const QString &pointCloudFormat, + const QImage &image, pcl::PointCloud::Ptr cloud) +{ + qDebug() << "后台保存线程开始..."; + + bool depthSuccess = false; + bool cloudSuccess = false; + + // 保存深度图 + if (!image.isNull()) { + try { + // 转换QImage到OpenCV Mat + cv::Mat mat; + if (image.format() == QImage::Format_Grayscale8) { + mat = cv::Mat(image.height(), image.width(), CV_8UC1, + const_cast(image.bits()), image.bytesPerLine()).clone(); + } else if (image.format() == QImage::Format_Grayscale16) { + mat = cv::Mat(image.height(), image.width(), CV_16UC1, + const_cast(image.bits()), image.bytesPerLine()).clone(); + } else { + // 转换为灰度图 + QImage grayImage = image.convertToFormat(QImage::Format_Grayscale8); + mat = cv::Mat(grayImage.height(), grayImage.width(), CV_8UC1, + const_cast(grayImage.bits()), grayImage.bytesPerLine()).clone(); + } + + // 根据format参数保存 + if (depthFormat == "png" || depthFormat == "both") { + QString pngPath = QString("%1/%2_depth.png").arg(saveDir).arg(baseName); + cv::Mat mat8bit; + if (mat.type() == CV_16UC1) { + mat.convertTo(mat8bit, CV_8UC1, 255.0 / 65535.0); + } else { + mat8bit = mat; + } + cv::imwrite(pngPath.toStdString(), mat8bit); + qDebug() << "保存PNG深度图:" << pngPath; + depthSuccess = true; + } + + if (depthFormat == "tiff" || depthFormat == "both") { + QString tiffPath = QString("%1/%2_depth.tiff").arg(saveDir).arg(baseName); + if (mat.type() == CV_16UC1) { + cv::imwrite(tiffPath.toStdString(), mat); + qDebug() << "保存TIFF深度图(16位):" << tiffPath; + } else { + cv::Mat mat16bit; + mat.convertTo(mat16bit, CV_16UC1, 257.0); + cv::imwrite(tiffPath.toStdString(), mat16bit); + qDebug() << "保存TIFF深度图(从8位转换):" << tiffPath; + } + depthSuccess = true; + } + } catch (const std::exception &e) { + qDebug() << "保存深度图失败:" << e.what(); + } + } + + // 保存点云 + if (cloud && !cloud->empty()) { + try { + if (pointCloudFormat == "pcd" || pointCloudFormat == "both") { + QString pcdPath = QString("%1/%2_pointcloud.pcd").arg(saveDir).arg(baseName); + if (pcl::io::savePCDFileBinary(pcdPath.toStdString(), *cloud) == 0) { + qDebug() << "保存PCD点云:" << pcdPath; + cloudSuccess = true; + } else { + qDebug() << "保存PCD失败"; + } + } + + if (pointCloudFormat == "ply" || pointCloudFormat == "both") { + QString plyPath = QString("%1/%2_pointcloud.ply").arg(saveDir).arg(baseName); + if (pcl::io::savePLYFileASCII(plyPath.toStdString(), *cloud) == 0) { + qDebug() << "保存PLY点云:" << plyPath; + cloudSuccess = true; + } else { + qDebug() << "保存PLY失败"; + } + } + } catch (const std::exception &e) { + qDebug() << "保存点云失败:" << e.what(); + } + } + + qDebug() << "后台保存完成 - 深度图:" << (depthSuccess ? "成功" : "失败") + << ", 点云:" << (cloudSuccess ? "成功" : "失败"); +} + +bool MainWindow::saveDepthImage(const QString &dir, const QString &baseName, const QString &format) +{ + try { + // 转换QImage到OpenCV Mat + cv::Mat mat; + if (m_currentImage.format() == QImage::Format_Grayscale8) { + mat = cv::Mat(m_currentImage.height(), m_currentImage.width(), CV_8UC1, + const_cast(m_currentImage.bits()), m_currentImage.bytesPerLine()).clone(); + } else if (m_currentImage.format() == QImage::Format_Grayscale16) { + mat = cv::Mat(m_currentImage.height(), m_currentImage.width(), CV_16UC1, + const_cast(m_currentImage.bits()), m_currentImage.bytesPerLine()).clone(); + } else { + // 转换为灰度图 + QImage grayImage = m_currentImage.convertToFormat(QImage::Format_Grayscale8); + mat = cv::Mat(grayImage.height(), grayImage.width(), CV_8UC1, + const_cast(grayImage.bits()), grayImage.bytesPerLine()).clone(); + } + + // 根据format参数保存 + if (format == "png" || format == "both") { + // 保存PNG格式(8位) + QString pngPath = QString("%1/%2_depth.png").arg(dir).arg(baseName); + cv::Mat mat8bit; + if (mat.type() == CV_16UC1) { + mat.convertTo(mat8bit, CV_8UC1, 255.0 / 65535.0); + } else { + mat8bit = mat; + } + cv::imwrite(pngPath.toStdString(), mat8bit); + qDebug() << "保存PNG深度图:" << pngPath; + } + + if (format == "tiff" || format == "both") { + // 保存TIFF格式(16位原始数据) + QString tiffPath = QString("%1/%2_depth.tiff").arg(dir).arg(baseName); + if (mat.type() == CV_16UC1) { + cv::imwrite(tiffPath.toStdString(), mat); + qDebug() << "保存TIFF深度图(16位):" << tiffPath; + } else { + // 如果不是16位,转换为16位保存 + cv::Mat mat16bit; + mat.convertTo(mat16bit, CV_16UC1, 257.0); // 8位转16位:乘以257 (65535/255) + cv::imwrite(tiffPath.toStdString(), mat16bit); + qDebug() << "保存TIFF深度图(从8位转换):" << tiffPath; + } + } + + return true; + } catch (const std::exception &e) { + qDebug() << "保存深度图失败:" << e.what(); + return false; + } +} + +bool MainWindow::savePointCloud(const QString &dir, const QString &baseName, const QString &format) +{ + try { + if (!m_currentPointCloud || m_currentPointCloud->empty()) { + qDebug() << "点云数据为空"; + return false; + } + + bool success = false; + + // 根据format参数保存 + if (format == "pcd" || format == "both") { + // 保存PCD格式(PCL标准格式,二进制) + QString pcdPath = QString("%1/%2_pointcloud.pcd").arg(dir).arg(baseName); + if (pcl::io::savePCDFileBinary(pcdPath.toStdString(), *m_currentPointCloud) == 0) { + qDebug() << "保存PCD点云:" << pcdPath; + success = true; + } else { + qDebug() << "保存PCD失败"; + } + } + + if (format == "ply" || format == "both") { + // 保存PLY格式(Polygon格式,ASCII) + QString plyPath = QString("%1/%2_pointcloud.ply").arg(dir).arg(baseName); + if (pcl::io::savePLYFileASCII(plyPath.toStdString(), *m_currentPointCloud) == 0) { + qDebug() << "保存PLY点云:" << plyPath; + success = true; + } else { + qDebug() << "保存PLY失败"; + } + } + + return success; + } catch (const std::exception &e) { + qDebug() << "保存点云失败:" << e.what(); + return false; + } +} + +// ========== 日志功能 ========== +void MainWindow::addLog(const QString &message, const QString &level) +{ + if (!m_logDisplay) return; + + // 获取当前时间戳 + QString timestamp = QDateTime::currentDateTime().toString("HH:mm:ss.zzz"); + + // 根据日志级别设置颜色 + QString color; + if (level == "ERROR") { + color = "#FF6B6B"; // 红色 + } else if (level == "WARNING") { + color = "#FFA500"; // 橙色 + } else if (level == "SUCCESS") { + color = "#4CAF50"; // 绿色 + } else { + color = "#D4D4D4"; // 默认灰白色 + } + + // 格式化日志消息 + QString formattedMessage = QString("[%1] " + "[%3] " + "%5") + .arg(timestamp) + .arg(color) + .arg(level) + .arg(color) + .arg(message); + + // 添加到日志显示 + m_logDisplay->append(formattedMessage); + + // 限制日志行数(保留最近1000行) + QTextDocument *doc = m_logDisplay->document(); + if (doc->blockCount() > 1000) { + QTextCursor cursor = m_logDisplay->textCursor(); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor, doc->blockCount() - 1000); + cursor.removeSelectedText(); + } + + // 自动滚动到底部 + m_logDisplay->moveCursor(QTextCursor::End); +} + +void MainWindow::onClearLogClicked() +{ + if (m_logDisplay) { + m_logDisplay->clear(); + addLog("日志已清除", "INFO"); + } +} + +void MainWindow::onSaveLogClicked() +{ + QString fileName = QFileDialog::getSaveFileName(this, "保存日志", + QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/d330viewer_log.txt", + "文本文件 (*.txt);;所有文件 (*.*)"); + if (!fileName.isEmpty()) { + QFile file(fileName); + if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QTextStream out(&file); + out << m_logDisplay->toPlainText(); + file.close(); + addLog(QString("日志已保存到: %1").arg(fileName), "SUCCESS"); + } else { + addLog(QString("保存日志失败: %1").arg(file.errorString()), "ERROR"); + } + } +} + +// ========== 统计信息更新 ========== +void MainWindow::updateStatistics() +{ + // 更新帧率 + if (m_fpsLabel) { + m_fpsLabel->setText(QString("帧率: %1 fps").arg(m_currentFps, 0, 'f', 1)); + } + + // 更新接收帧数(显示累计总数) + if (m_queueLabel) { + m_queueLabel->setText(QString("接收帧数: %1").arg(m_totalFrameCount)); + } +} + +// 监听系统主题变化事件 +void MainWindow::changeEvent(QEvent *event) +{ + if (event->type() == QEvent::PaletteChange) { + // 系统调色板变化,说明主题可能已切换 + qDebug() << "系统主题已变化,刷新UI样式"; + + // 强制重新应用样式表 + // 保存当前样式表 + QString currentStyle = styleSheet(); + + // 清空样式表 + setStyleSheet(""); + + // 重新应用样式表 + setStyleSheet(currentStyle); + + // 强制所有子组件更新 + QList widgets = findChildren(); + for (QWidget *widget : widgets) { + widget->style()->unpolish(widget); + widget->style()->polish(widget); + widget->update(); + } + + // 触发窗口重绘 + update(); + } + + // 调用基类实现 + QMainWindow::changeEvent(event); +} diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h new file mode 100644 index 0000000..98ffe02 --- /dev/null +++ b/src/gui/MainWindow.h @@ -0,0 +1,159 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include +#include +#include +#include + +class ConfigManager; +class NetworkManager; +class DeviceScanner; +class GVSPParser; +class PointCloudProcessor; +class PointCloudWidget; +struct DeviceInfo; +class QListWidget; +class QListWidgetItem; +class QPushButton; +class QSlider; +class QSpinBox; +class QLabel; +class QImage; + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +private slots: + // 相机控制槽函数 + void onStartClicked(); + void onStopClicked(); + void onOnceClicked(); + void onExposureChanged(int value); + void onCaptureClicked(); + void onBrowseSavePathClicked(); + + // 网络配置槽函数 + void onConnectClicked(); + void onIpAddressChanged(const QString &ip); + void onControlPortChanged(int port); + void onDataPortChanged(int port); + void onNetworkConnected(); + void onNetworkDisconnected(); + void onDataReceived(const QByteArray &data); + + // GVSP数据处理槽函数 + void onImageReceived(const QImage &image, uint32_t blockId); + void onDepthDataReceived(const QByteArray &depthData, uint32_t blockId); + void onPointCloudReady(pcl::PointCloud::Ptr cloud, uint32_t blockId); + + // 设备扫描槽函数 + void onRefreshClicked(); + void onDeviceFound(const DeviceInfo &device); + void onScanProgress(int current, int total); + void onScanFinished(int devicesFound); + void onDeviceSelected(QListWidgetItem *item); + + // 定时器更新 + void updateUI(); + + // 日志槽函数 + void onClearLogClicked(); + void onSaveLogClicked(); + +protected: + // 重写事件处理函数以监听系统主题变化 + void changeEvent(QEvent *event) override; + +private: + void setupUI(); + void setupConnections(); + void loadSettings(); + void saveSettings(); + + // 拍照辅助函数 + bool saveDepthImage(const QString &dir, const QString &baseName, const QString &format); + bool savePointCloud(const QString &dir, const QString &baseName, const QString &format); + void performBackgroundSave(const QString &saveDir, const QString &baseName, + const QString &depthFormat, const QString &pointCloudFormat, + const QImage &image, pcl::PointCloud::Ptr cloud); + + // 日志辅助函数 + void addLog(const QString &message, const QString &level = "INFO"); + void updateStatistics(); + +private: + // 核心管理器 + std::unique_ptr m_configManager; + std::unique_ptr m_networkManager; + std::unique_ptr m_deviceScanner; + std::unique_ptr m_pointCloudProcessor; + + // UI更新定时器 + QTimer *m_updateTimer; + + // 状态变量 + bool m_isConnected; + bool m_autoSaveOnNextFrame; + + // 当前帧数据(用于拍照) + QImage m_currentImage; + pcl::PointCloud::Ptr m_currentPointCloud; + uint32_t m_currentFrameId; + + // UI控件指针 + class QWidget *m_centralWidget; + class QSplitter *m_mainSplitter; + class QWidget *m_controlPanel; + class QLabel *m_imageDisplay; + PointCloudWidget *m_pointCloudWidget; + + // 按钮控件 + QPushButton *m_refreshBtn; + QPushButton *m_connectBtn; + QPushButton *m_startBtn; + QPushButton *m_stopBtn; + QPushButton *m_onceBtn; + QPushButton *m_captureBtn; + + // 输入控件 + QSlider *m_exposureSlider; + QSpinBox *m_exposureSpinBox; + QSpinBox *m_ctrlPortSpinBox; + QSpinBox *m_dataPortSpinBox; + + // 拍照参数控件 + class QLineEdit *m_savePathEdit; + QPushButton *m_browseSavePathBtn; + class QComboBox *m_depthFormatCombo; + class QComboBox *m_pointCloudFormatCombo; + + // 显示控件 + QLabel *m_statusLabel; + QListWidget *m_deviceList; + + // 统计信息控件 + QLabel *m_fpsLabel; + QLabel *m_resolutionLabel; + QLabel *m_queueLabel; + + // 日志显示控件 + class QTextEdit *m_logDisplay; + QPushButton *m_clearLogBtn; + QPushButton *m_saveLogBtn; + + // 统计数据 + QDateTime m_lastFrameTime; + int m_frameCount; + int m_totalFrameCount; + double m_currentFps; +}; + +#endif // MAINWINDOW_H diff --git a/src/gui/MainWindow_data_handler.cpp b/src/gui/MainWindow_data_handler.cpp new file mode 100644 index 0000000..22dfcd1 --- /dev/null +++ b/src/gui/MainWindow_data_handler.cpp @@ -0,0 +1,9 @@ +void MainWindow::onDataReceived(const QByteArray &data) +{ + static int packetCount = 0; + packetCount++; + + if (packetCount % 100 == 0) { + qDebug() << "Received" << packetCount << "packets, latest size:" << data.size() << "bytes"; + } +} diff --git a/src/gui/PointCloudGLWidget.cpp b/src/gui/PointCloudGLWidget.cpp new file mode 100644 index 0000000..063d009 --- /dev/null +++ b/src/gui/PointCloudGLWidget.cpp @@ -0,0 +1,259 @@ +#include "gui/PointCloudGLWidget.h" +#include +#include +#include + +PointCloudGLWidget::PointCloudGLWidget(QWidget *parent) + : QOpenGLWidget(parent) + , m_program(nullptr) + , m_vertexBuffer(nullptr) + , m_vao(nullptr) + , m_pointCount(0) + , m_orthoSize(2000.0f) // 正交投影视野大小 + , m_rotationX(0.0f) // 从正面看(0度) + , m_rotationY(0.0f) // 从正面看(0度) + , m_translation(0.0f, 0.0f, 0.0f) + , m_leftButtonPressed(false) + , m_rightButtonPressed(false) +{ + setMinimumSize(400, 400); +} + +PointCloudGLWidget::~PointCloudGLWidget() +{ + makeCurrent(); + if (m_vao) { + m_vao->destroy(); + delete m_vao; + } + if (m_vertexBuffer) { + m_vertexBuffer->destroy(); + delete m_vertexBuffer; + } + if (m_program) { + delete m_program; + } + doneCurrent(); +} + +void PointCloudGLWidget::initializeGL() +{ + initializeOpenGLFunctions(); + + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glEnable(GL_DEPTH_TEST); + glEnable(GL_PROGRAM_POINT_SIZE); + + setupShaders(); + + // 创建VAO和VBO + m_vao = new QOpenGLVertexArrayObject(this); + m_vao->create(); + + m_vertexBuffer = new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer); + m_vertexBuffer->create(); + + qDebug() << "[PointCloudGLWidget] OpenGL initialized"; +} + +void PointCloudGLWidget::setupShaders() +{ + m_program = new QOpenGLShaderProgram(this); + + // 顶点着色器 + const char *vertexShaderSource = R"( + #version 330 core + layout(location = 0) in vec3 position; + uniform mat4 mvp; + void main() { + gl_Position = mvp * vec4(position, 1.0); + gl_PointSize = 2.0; + } + )"; + + // 片段着色器 + const char *fragmentShaderSource = R"( + #version 330 core + out vec4 fragColor; + void main() { + fragColor = vec4(1.0, 1.0, 1.0, 1.0); + } + )"; + + if (!m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource)) { + qDebug() << "[PointCloudGLWidget] Vertex shader error:" << m_program->log(); + } + if (!m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource)) { + qDebug() << "[PointCloudGLWidget] Fragment shader error:" << m_program->log(); + } + if (!m_program->link()) { + qDebug() << "[PointCloudGLWidget] Shader link error:" << m_program->log(); + } else { + qDebug() << "[PointCloudGLWidget] Shaders compiled and linked successfully"; + } +} + +void PointCloudGLWidget::resizeGL(int w, int h) +{ + m_projection.setToIdentity(); + + // 使用正交投影代替透视投影,避免"喷射状"效果 + float aspect = float(w) / float(h); + m_projection.ortho(-m_orthoSize * aspect, m_orthoSize * aspect, + -m_orthoSize, m_orthoSize, + -50000.0f, 50000.0f); // 近平面和远平面 +} + +void PointCloudGLWidget::paintGL() +{ + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + if (m_pointCount == 0 || !m_program) { + return; + } + + // 每帧重新计算正交投影矩阵(确保使用最新的m_orthoSize) + m_projection.setToIdentity(); + float aspect = float(width()) / float(height()); + m_projection.ortho(-m_orthoSize * aspect, m_orthoSize * aspect, + -m_orthoSize, m_orthoSize, + -50000.0f, 50000.0f); + + // 设置view矩阵 + m_view.setToIdentity(); + m_view.rotate(m_rotationX, 1.0f, 0.0f, 0.0f); + m_view.rotate(m_rotationY, 0.0f, 1.0f, 0.0f); + m_view.translate(m_translation); + + // 设置model矩阵 + m_model.setToIdentity(); + + // 计算MVP矩阵 + QMatrix4x4 mvp = m_projection * m_view * m_model; + + // 绑定shader和设置uniform + m_program->bind(); + m_program->setUniformValue("mvp", mvp); + + // 绑定VAO和绘制 + m_vao->bind(); + glDrawArrays(GL_POINTS, 0, m_pointCount); + m_vao->release(); + + m_program->release(); +} + +void PointCloudGLWidget::mousePressEvent(QMouseEvent *event) +{ + m_lastMousePos = event->pos(); + if (event->button() == Qt::LeftButton) { + m_leftButtonPressed = true; + } else if (event->button() == Qt::RightButton) { + m_rightButtonPressed = true; + } +} + +void PointCloudGLWidget::mouseMoveEvent(QMouseEvent *event) +{ + QPoint delta = event->pos() - m_lastMousePos; + m_lastMousePos = event->pos(); + + if (m_leftButtonPressed) { + // 左键:旋转 + m_rotationX += delta.y() * 0.5f; + m_rotationY += delta.x() * 0.5f; + update(); + } else if (m_rightButtonPressed) { + // 右键:平移(根据正交投影视野大小调整平移速度) + float scale = m_orthoSize * 0.002f; + m_translation.setX(m_translation.x() + delta.x() * scale); + m_translation.setY(m_translation.y() - delta.y() * scale); + update(); + } +} + +void PointCloudGLWidget::mouseReleaseEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + m_leftButtonPressed = false; + } else if (event->button() == Qt::RightButton) { + m_rightButtonPressed = false; + } +} + +void PointCloudGLWidget::wheelEvent(QWheelEvent *event) +{ + // 滚轮:缩放(调整正交投影视野大小) + float delta = event->angleDelta().y() / 120.0f; + m_orthoSize *= (1.0f - delta * 0.1f); + m_orthoSize = qMax(100.0f, qMin(m_orthoSize, 10000.0f)); // 范围:100-10000 + + update(); // 触发重绘,paintGL会使用新的m_orthoSize +} + +void PointCloudGLWidget::updatePointCloud(pcl::PointCloud::Ptr cloud) +{ + if (!cloud) { + return; + } + + // 过滤全零点并转换为顶点数组 + m_vertices.clear(); + float minX = FLT_MAX, maxX = -FLT_MAX; + float minY = FLT_MAX, maxY = -FLT_MAX; + float minZ = FLT_MAX, maxZ = -FLT_MAX; + + for (const auto& point : cloud->points) { + if (point.z > 0.01f) { + m_vertices.push_back(point.x); + m_vertices.push_back(point.y); + m_vertices.push_back(point.z); + + // 统计坐标范围 + if (point.x < minX) minX = point.x; + if (point.x > maxX) maxX = point.x; + if (point.y < minY) minY = point.y; + if (point.y > maxY) maxY = point.y; + if (point.z < minZ) minZ = point.z; + if (point.z > maxZ) maxZ = point.z; + } + } + + m_pointCount = m_vertices.size() / 3; + + // 添加调试日志 + static int updateCount = 0; + if (updateCount < 3 || updateCount % 100 == 0) { + qDebug() << "[PointCloudGLWidget] Update" << updateCount << "- Points:" << m_pointCount + << "Total cloud size:" << cloud->size(); + qDebug() << " X range:" << minX << "to" << maxX; + qDebug() << " Y range:" << minY << "to" << maxY; + qDebug() << " Z range:" << minZ << "to" << maxZ; + } + updateCount++; + + updateBuffers(); + update(); +} + +void PointCloudGLWidget::updateBuffers() +{ + if (m_vertices.empty() || !m_vao || !m_vertexBuffer) { + return; + } + + makeCurrent(); + + m_vao->bind(); + m_vertexBuffer->bind(); + m_vertexBuffer->allocate(m_vertices.data(), m_vertices.size() * sizeof(float)); + + // 设置顶点属性指针 + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), nullptr); + + m_vertexBuffer->release(); + m_vao->release(); + + doneCurrent(); +} diff --git a/src/gui/PointCloudWidget.cpp b/src/gui/PointCloudWidget.cpp new file mode 100644 index 0000000..ed6263b --- /dev/null +++ b/src/gui/PointCloudWidget.cpp @@ -0,0 +1,56 @@ +#include "gui/PointCloudWidget.h" +#include "gui/PointCloudGLWidget.h" +#include +#include + +PointCloudWidget::PointCloudWidget(QWidget *parent) + : QWidget(parent) + , m_statusLabel(nullptr) + , m_glWidget(nullptr) +{ + // 创建布局 + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(5); + + // 创建状态标签 + m_statusLabel = new QLabel("点云显示 (等待数据...)", this); + m_statusLabel->setAlignment(Qt::AlignCenter); + m_statusLabel->setStyleSheet("QLabel { background-color: #1C1C1C; color: white; font-size: 12px; padding: 5px; }"); + m_statusLabel->setMaximumHeight(30); + layout->addWidget(m_statusLabel); + + // 创建OpenGL点云显示widget + m_glWidget = new PointCloudGLWidget(this); + layout->addWidget(m_glWidget); + + setLayout(layout); + + qDebug() << "[PointCloudWidget] Embedded OpenGL widget created"; +} + +PointCloudWidget::~PointCloudWidget() +{ + // OpenGL widget会自动清理 +} + +void PointCloudWidget::updatePointCloud(pcl::PointCloud::Ptr cloud) +{ + if (!cloud || !m_glWidget) { + return; + } + + // 统计有效点数 + int validPoints = 0; + for (const auto& point : cloud->points) { + if (point.z > 0.01f) { + validPoints++; + } + } + + // 更新状态标签 + m_statusLabel->setText(QString("点云显示 | 有效点数: %1").arg(validPoints)); + + // 更新OpenGL显示 + m_glWidget->updatePointCloud(cloud); +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..6c8cca2 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,58 @@ +#include +#include +#include +#include "gui/MainWindow.h" +#include "core/Logger.h" + +// Custom message handler to redirect qDebug output to Logger +void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) +{ + Logger *logger = Logger::instance(); + + switch (type) { + case QtDebugMsg: + logger->debug(msg); + break; + case QtInfoMsg: + logger->info(msg); + break; + case QtWarningMsg: + logger->warning(msg); + break; + case QtCriticalMsg: + case QtFatalMsg: + logger->error(msg); + break; + } +} + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + + // 设置应用程序信息 + app.setOrganizationName("UpperControl"); + app.setApplicationName("UpperControl GUI"); + app.setApplicationVersion("1.0.0"); + + // 初始化Logger(在可执行文件同目录下) + QString logPath = QCoreApplication::applicationDirPath() + "/d330viewer.log"; + Logger::instance()->setLogFile(logPath); + Logger::instance()->setMaxLines(10000); // 保留最新10000行 + + // 安装消息处理器 + qInstallMessageHandler(messageHandler); + + qDebug() << "D330Viewer started"; + qDebug() << "Log file:" << logPath; + + // 创建并显示主窗口 + MainWindow mainWindow; + mainWindow.show(); + + int result = app.exec(); + + qDebug() << "D330Viewer exiting"; + + return result; +}