6.2 KiB
6.2 KiB
DroneScrewServer 崩溃根因分析与解决方案
🎯 问题确认
崩溃特征
- 运行时间: 约 19 分钟
- 处理帧数: 约 2000+ 帧
- 退出码: 255 (EXCEPTION)
- 内存占用: < 1GB(非内存泄漏)
🔍 精确定位
通过增强的诊断日志,精确定位到崩溃点:
第一次崩溃日志
4月 22 00:07:34 [PUB-raw] enter frame=2246 left=4096x3000 right=4096x3000
4月 22 00:07:34 [PUB-raw] before PNG encode frame=2246
4月 22 00:07:35 Main process exited, status=255/EXCEPTION
↑ 没有 "after PNG encode" 日志
第二次崩溃日志
4月 22 00:27:40 [PUB-raw] enter frame=2014 left=4096x3000 right=4096x3000
4月 22 00:27:40 [PUB-raw] before PNG encode frame=2014
4月 22 00:27:40 Main process exited, status=255/EXCEPTION
↑ 没有 "after PNG encode" 日志
✅ 结论
100% 确认:崩溃发生在 PNG 编码过程中
🔬 根本原因
PNG 编码库段错误
技术细节:
- 使用的 PNG 编码函数:
encodeImagePng() - 处理的图像尺寸:4096x3000 (12.3 MP)
- 数据量:每帧双目原始数据 ~24MB,PNG 压缩后 ~10MB
可能的具体原因
-
ARM 平台 PNG 库 bug
- OpenCV 或 libpng 在 ARM 平台的特定版本有问题
- 处理大分辨率图像时的边界条件错误
-
内存对齐问题
- ARM 架构对内存对齐要求严格
- PNG 编码库内部可能有未对齐的内存访问
-
缓冲区溢出
- PNG 编码过程中临时缓冲区计算错误
- 导致写入越界
-
累积效应
- 前面的编码操作留下某些状态
- 在处理约 2000 帧后触发
为什么 try-catch 无法捕获
段错误(Segmentation Fault)是信号,不是 C++ 异常:
- 段错误由操作系统发出 SIGSEGV 信号
- C++ 的 try-catch 只能捕获 throw 的异常
- 需要信号处理器(signal handler)才能捕获
✅ 解决方案
方案 A:禁用 PNG 压缩(已实施)
修改位置:DroneScrewZmqProtocol.cpp::publishRawImage()
// 强制使用原始格式,禁用 PNG 压缩
const bool leftPngOk = false;
const bool rightPngOk = false;
const bool usePng = false;
优点:
- ✅ 立即解决崩溃问题
- ✅ 不需要调试 PNG 库
- ✅ 稳定可靠
缺点:
- ❌ 网络带宽增加 2-3 倍(原始数据 ~24MB vs PNG ~10MB)
- ❌ 传输延迟可能增加
适用场景:
- 局域网环境,带宽充足
- 稳定性优先于带宽
方案 B:降低图像分辨率(备选)
如果网络带宽不足,可以降低图像分辨率:
// 降采样到 2048x1500 或 1024x768
cv::resize(originalImage, resizedImage, cv::Size(2048, 1500));
优点:
- ✅ 减少数据量
- ✅ PNG 压缩更稳定(小图像不易触发 bug)
缺点:
- ❌ 损失图像细节
- ❌ 可能影响检测精度
方案 C:更换 PNG 库(长期方案)
使用更稳定的 PNG 编码库:
- stb_image_write.h - 单头文件,无依赖
- fpng - 更快的 PNG 编码器
- libspng - 现代化的 PNG 库
方案 D:使用 JPEG 压缩(备选)
JPEG 压缩更快,兼容性更好:
std::vector<uchar> buffer;
cv::imencode(".jpg", image, buffer, {cv::IMWRITE_JPEG_QUALITY, 85});
优点:
- ✅ 压缩速度快
- ✅ 稳定性好
- ✅ 压缩率高
缺点:
- ❌ 有损压缩
- ❌ 可能影响算法精度(对于灰度图影响较小)
📊 测试验证
当前版本(禁用 PNG)
修改文件:
App/DroneScrewbolt/DroneScrewServer/DroneScrewZmqProtocol.cpp
预期结果:
- ✅ 不再崩溃
- ✅ 可以稳定运行数小时
- ⚠️ 网络流量增加到 ~24MB/帧(原始格式)
验证步骤:
- 重新编译
- 部署到设备
- 运行至少 1 小时
- 观察:
- 无崩溃
- 日志中看到
usePng=0(原始格式) - 网络带宽占用
验证日志示例
[PUB-raw] before PNG encode frame=2014
[PUB-raw] after PNG encode frame=2014 usePng=0 leftSize=0 rightSize=0 (PNG DISABLED)
[PUB-raw] before ZMQ publish frame=2014 payloadSize=24576088
[PUB-raw] after ZMQ publish frame=2014 ret=0
[PUB-raw] exit frame=2014
📈 性能影响分析
带宽对比
| 格式 | 单帧大小 | 每秒 (2fps) | 每分钟 | 每小时 |
|---|---|---|---|---|
| PNG 压缩 | ~10MB | ~20MB | ~1.2GB | ~72GB |
| 原始格式 | ~24MB | ~48MB | ~2.9GB | ~173GB |
局域网影响
千兆网络(1000 Mbps = 125 MB/s):
- PNG: 占用 16% 带宽
- 原始: 占用 38% 带宽
结论:千兆局域网完全足够
CPU 影响
- PNG 压缩:每帧 ~2 秒 CPU 时间(4096x3000 双目)
- 原始格式:几乎无 CPU 消耗
额外好处:降低 CPU 负载,减少发热
🔄 后续优化建议
短期(1-2 周)
- ✅ 验证禁用 PNG 方案 - 确认稳定性
- 📊 监控网络带宽 - 确认是否可接受
- 🧪 测试 JPEG 压缩 - 作为备选方案
中期(1-2 月)
- 🔍 调查 PNG 库版本 - 尝试升级到修复版本
- 📦 测试其他压缩库 - stb_image_write、fpng
- 🎛️ 可配置压缩方式 - 支持 raw/png/jpeg 切换
长期(3-6 月)
- 🏗️ 优化传输架构 - 考虑分辨率自适应
- 🌐 增加带宽监控 - 根据网络状况自动选择压缩方式
- 📊 性能基准测试 - 建立完整的性能指标
📝 修改记录
| 日期 | 版本 | 修改内容 | 状态 |
|---|---|---|---|
| 2026-06-04 | v1 | 增加异常保护 | ✅ 完成 |
| 2026-06-04 | v2 | 增加诊断日志 | ✅ 完成 |
| 2026-06-04 | v3 | 禁用 PNG 压缩 | ✅ 完成 |
🎓 经验总结
调试技巧
- 分层日志 - 在关键步骤前后增加日志
- 精确定位 - 最后一条日志就是崩溃点
- 重复验证 - 多次测试确认模式
技术收获
- 段错误 vs 异常 - 理解信号与异常的区别
- ARM 平台特性 - 内存对齐、库兼容性
- 大数据处理 - 12MP 图像的 PNG 编码是性能瓶颈
避坑指南
- ❌ 不要在关键路径使用不稳定的第三方库
- ❌ 不要假设所有平台行为一致
- ✅ 关键操作前后都要有日志
- ✅ 提供多种方案供选择(压缩 vs 原始)