371 lines
12 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <iostream>
#include <iomanip>
#include <thread>
#include <chrono>
#include <atomic>
#include <mutex>
#include <string>
#include <ctime>
#include <sstream>
#include <vector>
#include <memory>
#include <cstring>
#include <cmath>
#include <limits>
#define NOMINMAX
#include <windows.h>
#include "IRsLidarDevice.h"
#include "ICloudShow.h"
#include <pcl/point_types.h>
#include <pcl/point_cloud.h>
/// 全局控制标志
static std::atomic<bool> g_bExit{false};
/// 统计信息
static std::atomic<uint64_t> g_nFrameCount{0};
static std::atomic<uint64_t> g_nTotalPoints{0};
static std::chrono::steady_clock::time_point g_startTime;
/// 当前帧追踪
static std::atomic<int> g_nFrameBeginCount{0};
/// 控制台输出锁
static std::mutex g_consoleMutex;
static bool g_bVerbose = false;
static bool g_bDisplay = true;
static std::string g_saveDir;
static std::atomic<int> g_nSaveSeq{0};
static std::unique_ptr<ICloudShow> g_pCloudShow;
static void PrintLog(const std::string& message)
{
std::lock_guard<std::mutex> lock(g_consoleMutex);
auto now = std::time(nullptr);
char timeStr[20];
std::strftime(timeStr, sizeof(timeStr), "%H:%M:%S", std::localtime(&now));
std::cout << "[" << timeStr << "] " << message << std::endl;
}
/// 每收到一个原始包触发 — 通过 is_frame_begin 感知扫描起始
static void OnPacket(const RsPacketInfo& pkt)
{
if (pkt.is_frame_begin)
{
g_nFrameBeginCount++;
if (g_bVerbose)
{
std::ostringstream oss;
oss << ">>> is_frame_begin=1"
<< " | pkt_seq=" << pkt.seq
<< " | 第 " << g_nFrameBeginCount << "";
PrintLog(oss.str());
}
}
}
/// 每收到一帧完整点云触发 — 可在此做后处理
static void OnPointCloud(const RsPointCloudData& data)
{
if (data.pointCount == 0) return;
g_nFrameCount++;
g_nTotalPoints += data.pointCount;
if (g_bVerbose)
{
const auto& first = data.points[0];
const auto& last = data.points[data.pointCount - 1];
std::ostringstream oss;
oss << " └─ 完整帧 #" << data.seq
<< " | " << data.pointCount << ""
<< " | ts=" << std::fixed << std::setprecision(3) << data.timestamp
<< " | (" << first.x << "," << first.y << "," << first.z << ")"
<< "→(" << last.x << "," << last.y << "," << last.z << ")";
PrintLog(oss.str());
}
// 转换为 PCL 格式 — RsPointXYZI(packed 13B) 与 pcl::PointXYZI(20B) 布局不同,需逐字段拷贝
// NaN→0 和 m→mm 已在 RsLidarDevice 层处理
auto pclCloud = std::make_shared<pcl::PointCloud<pcl::PointXYZI>>();
pclCloud->height = data.height;
pclCloud->width = data.width;
pclCloud->is_dense = data.isDense;
pclCloud->points.resize(data.pointCount);
const float nan = std::numeric_limits<float>::quiet_NaN();
for (size_t i = 0; i < data.pointCount; ++i)
{
float x = data.points[i].x;
float y = data.points[i].y;
float z = data.points[i].z;
if (std::abs(x) < 0.005f && std::abs(y) < 0.005f && std::abs(z) < 0.005f)
{
pclCloud->points[i].x = nan;
pclCloud->points[i].y = nan;
pclCloud->points[i].z = nan;
pclCloud->is_dense = false;
}
else
{
pclCloud->points[i].x = x;
pclCloud->points[i].y = y;
pclCloud->points[i].z = z;
}
pclCloud->points[i].intensity = static_cast<float>(data.points[i].intensity);
}
// 持续保存到目录(--save dir
if (!g_saveDir.empty() && g_pCloudShow)
{
int seq = ++g_nSaveSeq;
std::ostringstream oss;
oss << g_saveDir << "/LaserData_" << seq << ".txt";
PrintLog("开始存储: " + oss.str());
g_pCloudShow->SaveToTxt(oss.str(), pclCloud);
PrintLog("存储完成: " + oss.str() + " | " + std::to_string(static_cast<int>(data.pointCount)) + "");
}
// 单次保存PCL 窗口按键 S/s 触发)
if (g_bDisplay && g_pCloudShow && g_pCloudShow->IsSaveRequested())
{
g_pCloudShow->ClearSaveRequest();
int seq = ++g_nSaveSeq;
std::string dir = g_saveDir.empty() ? "." : g_saveDir;
std::ostringstream oss;
oss << dir << "/LaserData_" << seq << ".txt";
PrintLog("开始存储: " + oss.str());
g_pCloudShow->SaveToTxt(oss.str(), pclCloud);
PrintLog("存储完成: " + oss.str() + " | " + std::to_string(static_cast<int>(data.pointCount)) + "");
}
// 推送点云到显示窗口
if (g_bDisplay && g_pCloudShow)
{
g_pCloudShow->UpdateCloud(pclCloud);
}
}
static void OnException(const RsExceptionInfo& info)
{
PrintLog("[异常 " + std::to_string(info.code) + "] " + info.message);
}
static void StatsLoop()
{
while (!g_bExit)
{
std::this_thread::sleep_for(std::chrono::seconds(5));
auto now = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - g_startTime).count();
if (elapsed == 0) elapsed = 1;
std::ostringstream oss;
oss << "== " << elapsed << "s"
<< " | 帧=" << g_nFrameCount
<< " | 帧起始=" << g_nFrameBeginCount
<< " | fps=" << std::fixed << std::setprecision(1)
<< (static_cast<double>(g_nFrameCount) / elapsed)
<< " | pps=" << std::fixed << std::setprecision(0)
<< (static_cast<double>(g_nTotalPoints) / elapsed);
PrintLog(oss.str());
}
}
static void PrintUsage(const char* prog)
{
std::cout << "用法: " << prog << " [选项]" << std::endl;
std::cout << std::endl;
std::cout << "选项:" << std::endl;
std::cout << " --host <IP> 雷达主机地址 (默认: 0.0.0.0)" << std::endl;
std::cout << " --msop <port> MSOP 端口 (默认: 6699)" << std::endl;
std::cout << " --difop <port> DIFOP 端口 (默认: 7788)" << std::endl;
std::cout << " --type <name> 雷达型号 (默认: RSAIRY)" << std::endl;
std::cout << " --time <seconds> 运行时长 (默认: 30, 0=持续)" << std::endl;
std::cout << " --verbose 逐帧打印扫描生命周期" << std::endl;
std::cout << " --display 实时点云显示非阻塞PCL窗口 S=保存 Q=退出)" << std::endl;
std::cout << " --save <dir> 保存点云到指定目录frame_<seq>.txt" << std::endl;
std::cout << std::endl;
std::cout << "雷达型号: RS16 RS32 RSBP RSAIRY RSHELIOS RS128 RS80 RS48 RSM1 RSM2 RSM3 等" << std::endl;
std::cout << std::endl;
std::cout << "示例:" << std::endl;
std::cout << " " << prog << " --host 192.168.1.100 --type RSHELIOS --time 60 --verbose" << std::endl;
std::cout << " " << prog << " --host 192.168.1.100 --type RSAIRY --display --save ./data --time 120" << std::endl;
}
int main(int argc, char* argv[])
{
SetConsoleOutputCP(CP_UTF8);
RsLidarConfig config;
int runSeconds = 0;
for (int i = 1; i < argc; ++i)
{
std::string arg = argv[i];
if (arg == "--help" || arg == "-h")
{
PrintUsage(argv[0]);
return 0;
}
else if (arg == "--host" && i + 1 < argc)
config.hostAddress = argv[++i];
else if (arg == "--msop" && i + 1 < argc)
config.msopPort = static_cast<uint16_t>(std::stoi(argv[++i]));
else if (arg == "--difop" && i + 1 < argc)
config.difopPort = static_cast<uint16_t>(std::stoi(argv[++i]));
else if (arg == "--type" && i + 1 < argc)
config.lidarType = argv[++i];
else if (arg == "--time" && i + 1 < argc)
runSeconds = std::stoi(argv[++i]);
else if (arg == "--verbose")
g_bVerbose = true;
else if (arg == "--display")
g_bDisplay = true;
else if (arg == "--save" && i + 1 < argc)
g_saveDir = argv[++i];
}
std::cout << "============================================" << std::endl;
std::cout << " RsLidarDevice 测试" << std::endl;
std::cout << "============================================" << std::endl;
std::cout << "雷达: " << config.lidarType << std::endl;
std::cout << "地址: " << config.hostAddress << std::endl;
std::cout << "端口: MSOP=" << config.msopPort << " DIFOP=" << config.difopPort << std::endl;
std::cout << "时长: " << (runSeconds > 0 ? std::to_string(runSeconds) + "s" : "持续") << std::endl;
std::cout << "详细: " << (g_bVerbose ? "ON" : "OFF") << std::endl;
std::cout << "============================================" << std::endl;
// 1. 创建
IRsLidarDevice* pDevice = nullptr;
if (IRsLidarDevice::CreateObject(&pDevice) != 0 || !pDevice)
{
std::cerr << "创建设备失败" << std::endl;
return -1;
}
PrintLog("设备实例创建完成");
PrintLog("驱动版本: " + pDevice->GetVersion());
// CloudShow 实例
g_pCloudShow = ICloudShow::Create();
// 2. 初始化
if (pDevice->InitDevice() != 0)
{
std::cerr << "SDK 初始化失败" << std::endl;
delete pDevice;
return -1;
}
PrintLog("SDK 初始化完成");
// 3. 打开
if (pDevice->OpenDevice(config) != 0)
{
std::cerr << "打开设备失败" << std::endl;
pDevice->CloseDevice();
delete pDevice;
return -1;
}
PrintLog("设备打开完成");
// 4. 注册回调
pDevice->SetPacketCallback(OnPacket);
pDevice->SetPointCloudCallback(OnPointCloud);
pDevice->SetExceptionCallback(OnException);
// 5. 启动
if (pDevice->Start() != 0)
{
std::cerr << "启动失败" << std::endl;
pDevice->CloseDevice();
delete pDevice;
return -1;
}
g_startTime = std::chrono::steady_clock::now();
PrintLog("数据采集已启动");
// 6. 统计线程
std::thread statsThread(StatsLoop);
// 7. 启动显示窗口(主线程创建 viewer + 驱动渲染)
if (g_bDisplay && g_pCloudShow)
{
if (g_pCloudShow->Start("LiDAR PointCloud") == 0)
{
PrintLog("PCL 窗口已打开 — S=保存下一帧 Q=退出");
}
else
{
PrintLog("PCL 窗口创建失败,继续无显示运行");
g_bDisplay = false;
}
}
// 8. 主循环:驱动 PCL 渲染 + 轮询键盘 + 超时
auto deadline = (runSeconds > 0)
? std::chrono::steady_clock::now() + std::chrono::seconds(runSeconds)
: std::chrono::steady_clock::time_point::max();
while (!g_bExit)
{
if (g_bDisplay && g_pCloudShow && g_pCloudShow->IsRunning())
{
if (g_pCloudShow->SpinOnce(10) < 0)
{
PrintLog("PCL 窗口已关闭");
break;
}
}
else
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
if (g_bDisplay && g_pCloudShow && g_pCloudShow->IsQuitRequested())
{
PrintLog("用户退出 (Q)");
break;
}
if (std::chrono::steady_clock::now() >= deadline)
break;
}
// 9. 停止
g_bExit = true;
pDevice->Stop();
if (statsThread.joinable()) statsThread.join();
auto endTime = std::chrono::steady_clock::now();
auto totalElapsed = std::chrono::duration_cast<std::chrono::seconds>(endTime - g_startTime).count();
if (totalElapsed == 0) totalElapsed = 1;
std::cout << std::endl;
std::cout << "============================================" << std::endl;
std::cout << " 最终统计" << std::endl;
std::cout << "============================================" << std::endl;
std::cout << "运行: " << totalElapsed << "s" << std::endl;
std::cout << "is_frame_begin: " << g_nFrameBeginCount << "" << std::endl;
std::cout << "完整帧: " << g_nFrameCount << std::endl;
std::cout << "总点数: " << g_nTotalPoints << std::endl;
std::cout << "帧率: " << std::fixed << std::setprecision(2)
<< (static_cast<double>(g_nFrameCount) / totalElapsed) << " fps" << std::endl;
std::cout << "点率: " << std::fixed << std::setprecision(0)
<< (static_cast<double>(g_nTotalPoints) / totalElapsed) << " pts/s" << std::endl;
std::cout << "============================================" << std::endl;
pDevice->CloseDevice();
delete pDevice;
if (g_pCloudShow)
{
g_pCloudShow->Stop();
g_pCloudShow.reset();
}
return 0;
}