feat: 相机模块完毕
This commit is contained in:
parent
cdb85c682f
commit
369c1081e3
@ -61,3 +61,11 @@ mockall = "0.11.4" # Mocking for tests
|
|||||||
[[example]]
|
[[example]]
|
||||||
name = "cams_detector_demo"
|
name = "cams_detector_demo"
|
||||||
path = "examples/cams_detector_demo.rs"
|
path = "examples/cams_detector_demo.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "camera_demo"
|
||||||
|
path = "examples/camera_demo.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "meteor_detection_system"
|
||||||
|
path = "examples/meteor_detection_system.rs"
|
||||||
|
|||||||
209
docs/camera_module.md
Normal file
209
docs/camera_module.md
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
# Camera 模块使用文档
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
Camera 模块是一个完整的相机控制和管理系统,提供了高级API用于配置、捕获、存储和处理相机帧。该模块主要用于天文观测系统,特别是流星检测,但它的设计足够灵活,可以用于任何需要视频捕获和处理的应用。
|
||||||
|
|
||||||
|
模块主要功能包括:
|
||||||
|
- 相机设备初始化和配置
|
||||||
|
- 实时视频流捕获
|
||||||
|
- 高效的帧缓冲管理
|
||||||
|
- 视频事件存储和处理
|
||||||
|
- 相机健康监控
|
||||||
|
|
||||||
|
## 核心组件
|
||||||
|
|
||||||
|
### 1. CameraController
|
||||||
|
|
||||||
|
`CameraController` 是主要的控制接口,它管理相机的整个生命周期和操作流程。
|
||||||
|
|
||||||
|
主要特性:
|
||||||
|
- 初始化和配置相机设备
|
||||||
|
- 启动和停止视频流捕获
|
||||||
|
- 管理帧缓冲
|
||||||
|
- 提供帧订阅机制
|
||||||
|
- 保存流星事件和相关视频
|
||||||
|
- 监控相机健康状态
|
||||||
|
|
||||||
|
### 2. OpenCVCamera
|
||||||
|
|
||||||
|
`OpenCVCamera` 是相机硬件的具体驱动实现,基于OpenCV库。
|
||||||
|
|
||||||
|
主要特性:
|
||||||
|
- 支持各种相机设备(通过路径或索引)
|
||||||
|
- 配置相机分辨率、帧率、曝光和增益
|
||||||
|
- 控制相机聚焦
|
||||||
|
- 视频流管理
|
||||||
|
|
||||||
|
### 3. Frame 和 FrameBuffer
|
||||||
|
|
||||||
|
`Frame` 表示单个捕获的视频帧,包含图像数据、时间戳和元数据。
|
||||||
|
|
||||||
|
`FrameBuffer` 是一个高效的循环缓冲区,用于存储最近捕获的视频帧。
|
||||||
|
|
||||||
|
主要特性:
|
||||||
|
- 管理固定容量的帧缓冲
|
||||||
|
- 提供索引和时间范围的帧检索
|
||||||
|
- 提取特定事件周围的帧
|
||||||
|
|
||||||
|
## 关键数据结构
|
||||||
|
|
||||||
|
### CameraSettings
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct CameraSettings {
|
||||||
|
pub device: String, // 相机设备路径或索引
|
||||||
|
pub resolution: Resolution, // 分辨率设置
|
||||||
|
pub fps: u32, // 每秒帧数
|
||||||
|
pub exposure: ExposureMode, // 曝光模式
|
||||||
|
pub gain: u8, // 增益/ISO设置 (0-255)
|
||||||
|
pub focus_locked: bool, // 是否锁定无限远焦点
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Resolution
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub enum Resolution {
|
||||||
|
HD1080p, // 1920x1080
|
||||||
|
HD720p, // 1280x720
|
||||||
|
VGA, // 640x480
|
||||||
|
Custom { // 自定义分辨率
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ExposureMode
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub enum ExposureMode {
|
||||||
|
Auto, // 自动曝光控制
|
||||||
|
Manual(u32), // 手动曝光控制,以微秒为单位
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### MeteorEvent
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct MeteorEvent {
|
||||||
|
pub id: uuid::Uuid, // 事件唯一标识符
|
||||||
|
pub timestamp: DateTime<Utc>, // 事件检测时间戳
|
||||||
|
pub confidence: f32, // 置信度分数 (0.0-1.0)
|
||||||
|
pub bounding_box: (u32, u32, u32, u32), // 图像中的坐标 (左上x, 左上y, 宽度, 高度)
|
||||||
|
pub video_path: String, // 保存的视频剪辑路径
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用流程
|
||||||
|
|
||||||
|
典型的使用流程包括以下步骤:
|
||||||
|
|
||||||
|
1. 创建和配置相机控制器
|
||||||
|
2. 初始化相机设备
|
||||||
|
3. 启动视频捕获
|
||||||
|
4. 订阅帧更新(可选)
|
||||||
|
5. 处理捕获的帧
|
||||||
|
6. 保存检测到的事件
|
||||||
|
7. 停止捕获并清理资源
|
||||||
|
|
||||||
|
## 示例代码
|
||||||
|
|
||||||
|
请参考 `examples/camera_demo.rs` 示例,了解更详细的使用方法。
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **设备兼容性**:该模块主要支持Linux和macOS上的相机设备。在其他平台上可能需要额外的配置。
|
||||||
|
|
||||||
|
2. **资源管理**:由于视频捕获可能消耗大量内存,特别是在高分辨率和高帧率下,请确保合理配置帧缓冲区大小。
|
||||||
|
|
||||||
|
3. **依赖项**:该模块依赖于OpenCV库进行图像处理和视频捕获,请确保系统已正确安装相应的依赖项。
|
||||||
|
|
||||||
|
4. **错误恢复**:模块包含错误检测和恢复机制,但在关键应用中仍应实施额外的监控和故障恢复策略。
|
||||||
|
|
||||||
|
5. **性能考虑**:考虑系统资源限制,特别是在嵌入式设备上,应谨慎选择分辨率、帧率和缓冲区大小。
|
||||||
|
|
||||||
|
## 高级特性
|
||||||
|
|
||||||
|
### 帧订阅
|
||||||
|
|
||||||
|
模块使用tokio的广播通道实现了实时帧通知机制:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// 订阅帧更新
|
||||||
|
let mut frame_receiver = camera_controller.subscribe_to_frames();
|
||||||
|
|
||||||
|
// 在单独的任务中处理帧
|
||||||
|
tokio::spawn(async move {
|
||||||
|
while let Ok(frame) = frame_receiver.recv().await {
|
||||||
|
// 处理新帧
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 事件存储
|
||||||
|
|
||||||
|
当检测到流星时,可以保存相关视频剪辑:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let event = camera_controller.save_meteor_event(
|
||||||
|
timestamp, // 事件发生时间
|
||||||
|
0.95, // 置信度
|
||||||
|
(100, 200, 50, 30), // 边界框 (x, y, 宽度, 高度)
|
||||||
|
5, // 事件前5秒
|
||||||
|
3 // 事件后3秒
|
||||||
|
).await?;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 健康监控
|
||||||
|
|
||||||
|
可以使用健康检查功能监控相机状态:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let is_healthy = camera_controller.check_health().await?;
|
||||||
|
if !is_healthy {
|
||||||
|
// 实施恢复策略
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 故障排除
|
||||||
|
|
||||||
|
### 常见问题
|
||||||
|
|
||||||
|
1. **无法打开相机设备**
|
||||||
|
- 检查设备路径或索引是否正确
|
||||||
|
- 确认设备有适当的访问权限
|
||||||
|
- 验证设备未被其他应用程序使用
|
||||||
|
|
||||||
|
2. **分辨率/帧率设置无效**
|
||||||
|
- 不是所有相机都支持所有分辨率和帧率组合
|
||||||
|
- 检查相机规格和支持的格式
|
||||||
|
- 使用更保守的值或尝试自定义分辨率
|
||||||
|
|
||||||
|
3. **帧捕获停止或间歇性失败**
|
||||||
|
- 可能是相机硬件问题
|
||||||
|
- 检查USB连接或电源问题
|
||||||
|
- 考虑重新初始化或重启相机
|
||||||
|
|
||||||
|
4. **内存使用过高**
|
||||||
|
- 减小帧缓冲区容量
|
||||||
|
- 降低分辨率或帧率
|
||||||
|
- 确保正确处理和释放不再需要的帧
|
||||||
|
|
||||||
|
### 诊断工具
|
||||||
|
|
||||||
|
使用`get_status()`方法获取详细的相机状态信息:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let status = camera_controller.get_status().await?;
|
||||||
|
println!("Camera status: {}", status);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 性能优化建议
|
||||||
|
|
||||||
|
1. 根据实际需求选择分辨率,无需使用高于实际需要的分辨率
|
||||||
|
2. 仅在必要时处理帧,避免冗余的图像处理操作
|
||||||
|
3. 使用帧订阅机制进行异步处理,避免阻塞主循环
|
||||||
|
4. 在资源受限的系统上,考虑使用区域感兴趣(ROI)处理帧子区域
|
||||||
|
5. 对于长时间运行的应用,定期检查健康状态并实施自动恢复机制
|
||||||
168
examples/camera_demo.rs
Normal file
168
examples/camera_demo.rs
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use log::{error, info};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::time::Duration;
|
||||||
|
use tokio::time;
|
||||||
|
|
||||||
|
// 导入Camera模块组件
|
||||||
|
use meteor_detect::camera::{CameraController, CameraSettings, ExposureMode, Resolution, Frame};
|
||||||
|
use meteor_detect::config::Config;
|
||||||
|
|
||||||
|
/// 这是一个简单的演示程序,展示如何使用Camera模块的主要功能
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
|
// 初始化日志
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
|
// 1. 创建配置
|
||||||
|
let config = create_demo_config();
|
||||||
|
|
||||||
|
// 2. 初始化相机控制器
|
||||||
|
info!("初始化相机控制器");
|
||||||
|
let mut camera_controller = CameraController::new(&config).await?;
|
||||||
|
|
||||||
|
// 3. 初始化相机设备
|
||||||
|
info!("初始化相机设备");
|
||||||
|
camera_controller.initialize().await?;
|
||||||
|
|
||||||
|
// 4. 启动视频捕获
|
||||||
|
info!("启动视频捕获");
|
||||||
|
camera_controller.start_capture().await?;
|
||||||
|
|
||||||
|
// 5. 获取相机状态
|
||||||
|
let status = camera_controller.get_status().await?;
|
||||||
|
info!("相机状态: {}", status);
|
||||||
|
|
||||||
|
// 6. 订阅帧更新
|
||||||
|
info!("设置帧处理器");
|
||||||
|
let mut frame_receiver = camera_controller.subscribe_to_frames();
|
||||||
|
|
||||||
|
// 在单独的任务中处理帧
|
||||||
|
let frame_processor = tokio::spawn(async move {
|
||||||
|
let mut frame_count = 0;
|
||||||
|
|
||||||
|
// 处理10个帧后退出
|
||||||
|
while let Ok(frame) = frame_receiver.recv().await {
|
||||||
|
frame_count += 1;
|
||||||
|
info!("收到帧 #{}: 时间戳={}", frame.index, frame.timestamp);
|
||||||
|
|
||||||
|
// 示例: 保存第5帧为图像文件
|
||||||
|
if frame_count == 5 {
|
||||||
|
info!("保存第5帧为图像文件");
|
||||||
|
let path = PathBuf::from("demo_frame.jpg");
|
||||||
|
if let Err(e) = frame.save_to_file(&path) {
|
||||||
|
error!("保存帧失败: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if frame_count >= 10 {
|
||||||
|
info!("已处理10帧,退出处理循环");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("帧处理器已完成");
|
||||||
|
frame_count
|
||||||
|
});
|
||||||
|
|
||||||
|
// 7. 等待5秒,然后模拟一个流星事件
|
||||||
|
info!("等待5秒后模拟流星事件...");
|
||||||
|
time::sleep(Duration::from_secs(5)).await;
|
||||||
|
|
||||||
|
// 获取当前时间作为事件时间戳
|
||||||
|
let event_timestamp = chrono::Utc::now();
|
||||||
|
|
||||||
|
// 模拟流星事件检测 - 保存事件视频
|
||||||
|
info!("保存流星事件");
|
||||||
|
match camera_controller
|
||||||
|
.save_meteor_event(
|
||||||
|
event_timestamp,
|
||||||
|
0.85, // 置信度
|
||||||
|
(100, 100, 200, 150), // 边界框 (x, y, width, height)
|
||||||
|
3, // 事件前3秒
|
||||||
|
2, // 事件后2秒
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(event) => {
|
||||||
|
info!("保存了流星事件: id={}, 视频='{}'", event.id, event.video_path);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("保存流星事件失败: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. 更新相机设置
|
||||||
|
info!("更新相机设置");
|
||||||
|
let mut new_settings = config.camera.clone();
|
||||||
|
new_settings.resolution = Resolution::HD1080p;
|
||||||
|
new_settings.fps = 24;
|
||||||
|
camera_controller.update_settings(new_settings).await?;
|
||||||
|
|
||||||
|
// 9. 检查相机健康状态
|
||||||
|
info!("检查相机健康状态");
|
||||||
|
let is_healthy = camera_controller.check_health().await?;
|
||||||
|
info!("相机健康状态: {}", if is_healthy { "正常" } else { "异常" });
|
||||||
|
|
||||||
|
// 10. 等待帧处理完成
|
||||||
|
info!("等待帧处理器完成");
|
||||||
|
let processed_frames = frame_processor.await?;
|
||||||
|
info!("共处理了 {} 帧", processed_frames);
|
||||||
|
|
||||||
|
// 11. 停止视频捕获
|
||||||
|
info!("停止视频捕获");
|
||||||
|
camera_controller.stop_capture().await?;
|
||||||
|
|
||||||
|
info!("演示完成");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 创建用于演示的配置
|
||||||
|
fn create_demo_config() -> Config {
|
||||||
|
let camera_settings = CameraSettings {
|
||||||
|
// 在Linux上通常是"/dev/video0",在macOS上通常是"0"
|
||||||
|
device: "0".to_string(),
|
||||||
|
resolution: Resolution::HD720p,
|
||||||
|
fps: 30,
|
||||||
|
exposure: ExposureMode::Auto,
|
||||||
|
gain: 128,
|
||||||
|
focus_locked: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
Config {
|
||||||
|
camera: camera_settings,
|
||||||
|
// 其他配置字段,使用其默认值
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// /// 如果需要,这里提供一个更复杂的帧处理示例
|
||||||
|
// /// 可以作为单独函数供参考
|
||||||
|
// #[allow(dead_code)]
|
||||||
|
// async fn process_frames_with_opencv(frame: &Frame) -> Result<()> {
|
||||||
|
// use opencv::{core, imgproc, prelude::*};
|
||||||
|
//
|
||||||
|
// // 1. 转换为灰度图像
|
||||||
|
// let mut gray = core::Mat::default();
|
||||||
|
// imgproc::cvt_color(&frame.mat, &mut gray, imgproc::COLOR_BGR2GRAY, 0)?;
|
||||||
|
//
|
||||||
|
// // 2. 应用高斯模糊
|
||||||
|
// let mut blurred = core::Mat::default();
|
||||||
|
// imgproc::gaussian_blur(
|
||||||
|
// &gray,
|
||||||
|
// &mut blurred,
|
||||||
|
// core::Size::new(5, 5),
|
||||||
|
// 1.5,
|
||||||
|
// 1.5,
|
||||||
|
// core::BORDER_DEFAULT,
|
||||||
|
// )?;
|
||||||
|
//
|
||||||
|
// // 3. 应用边缘检测
|
||||||
|
// let mut edges = core::Mat::default();
|
||||||
|
// imgproc::canny(&blurred, &mut edges, 50.0, 150.0, 3, false)?;
|
||||||
|
//
|
||||||
|
// // 保存处理后的图像(可选)
|
||||||
|
// opencv::imgcodecs::imwrite("processed_frame.jpg", &edges, &core::Vector::new())?;
|
||||||
|
//
|
||||||
|
// Ok(())
|
||||||
|
// }
|
||||||
234
examples/meteor_detection_system.rs
Normal file
234
examples/meteor_detection_system.rs
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use log::{error, info, warn};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
use tokio::sync::mpsc::{self, Sender};
|
||||||
|
use tokio::time;
|
||||||
|
|
||||||
|
// 导入Camera模块组件
|
||||||
|
use meteor_detect::camera::{CameraController, CameraSettings, ExposureMode, Resolution, FrameBuffer, Frame};
|
||||||
|
use meteor_detect::config::Config;
|
||||||
|
|
||||||
|
/// 演示如何使用相机模块构建完整的流星检测系统
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
|
// 初始化日志
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
|
// 1. 创建和初始化相机
|
||||||
|
let config = create_detector_config();
|
||||||
|
let mut camera = CameraController::new(&config).await?;
|
||||||
|
|
||||||
|
info!("初始化相机系统");
|
||||||
|
camera.initialize().await?;
|
||||||
|
|
||||||
|
info!("启动相机捕获");
|
||||||
|
camera.start_capture().await?;
|
||||||
|
|
||||||
|
// 2. 获取帧缓冲器的引用(用于检测器访问)
|
||||||
|
let frame_buffer = camera.get_frame_buffer();
|
||||||
|
|
||||||
|
// 3. 创建通信通道
|
||||||
|
// 用于从检测器向主程序发送事件
|
||||||
|
let (event_tx, mut event_rx) = mpsc::channel(10);
|
||||||
|
|
||||||
|
// 用于从健康监控向主程序发送健康状态
|
||||||
|
let (health_tx, mut health_rx) = mpsc::channel(10);
|
||||||
|
|
||||||
|
// 4. 启动检测器任务,从缓冲区读取帧进行处理
|
||||||
|
start_detector_task(frame_buffer.clone(), event_tx).await;
|
||||||
|
|
||||||
|
// 5. 启动健康监控任务 - 只发送健康状态,而不是直接操作相机
|
||||||
|
start_health_monitor_task(health_tx).await;
|
||||||
|
|
||||||
|
// 6. 主线程处理检测事件和健康状态
|
||||||
|
info!("等待检测事件...");
|
||||||
|
let mut saved_events = 0;
|
||||||
|
|
||||||
|
// 创建轮询健康状态的定时器
|
||||||
|
let mut health_interval = time::interval(Duration::from_secs(30));
|
||||||
|
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
// 处理检测事件
|
||||||
|
Some(event) = event_rx.recv() => {
|
||||||
|
info!("收到流星检测事件: 时间={}, 置信度={:.2}",
|
||||||
|
event.timestamp, event.confidence);
|
||||||
|
|
||||||
|
// 保存事件视频
|
||||||
|
match camera.save_meteor_event(
|
||||||
|
event.timestamp,
|
||||||
|
event.confidence,
|
||||||
|
event.bounding_box,
|
||||||
|
5, // 事件前5秒
|
||||||
|
3, // 事件后3秒
|
||||||
|
).await {
|
||||||
|
Ok(saved_event) => {
|
||||||
|
info!("已保存流星事件视频: {}", saved_event.video_path);
|
||||||
|
saved_events += 1;
|
||||||
|
|
||||||
|
// 在这里可以添加额外处理,如上传到云存储、发送通知等
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
error!("保存流星事件失败: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 演示目的,保存3个事件后退出
|
||||||
|
if saved_events >= 3 {
|
||||||
|
info!("已保存足够的事件,退出处理循环");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 处理健康检查的时间间隔
|
||||||
|
_ = health_interval.tick() => {
|
||||||
|
// 直接在主线程中执行健康检查
|
||||||
|
match camera.check_health().await {
|
||||||
|
Ok(is_healthy) => {
|
||||||
|
if !is_healthy {
|
||||||
|
warn!("相机健康检查失败,可能需要干预");
|
||||||
|
} else {
|
||||||
|
info!("相机健康状态: 正常");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
error!("健康检查失败: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取和记录相机状态
|
||||||
|
if let Ok(status) = camera.get_status().await {
|
||||||
|
info!("相机状态: {}", status);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 接收健康监控发送的消息(演示目的,实际中可以根据需要处理)
|
||||||
|
Some(health_msg) = health_rx.recv() => {
|
||||||
|
info!("收到健康监控消息: {}", health_msg);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 如果所有通道都已关闭,退出循环
|
||||||
|
else => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. 清理资源
|
||||||
|
info!("停止相机捕获");
|
||||||
|
camera.stop_capture().await?;
|
||||||
|
|
||||||
|
info!("流星检测系统演示完成");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 启动检测器任务
|
||||||
|
async fn start_detector_task(frame_buffer: Arc<FrameBuffer>, event_tx: Sender<DetectionEvent>) {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
info!("启动流星检测器");
|
||||||
|
let mut detection_count = 0;
|
||||||
|
let mut last_detection_time = chrono::Utc::now();
|
||||||
|
|
||||||
|
// 模拟检测循环
|
||||||
|
loop {
|
||||||
|
// 模拟帧分析(实际应用中会连续分析每一帧)
|
||||||
|
time::sleep(Duration::from_secs(2)).await;
|
||||||
|
|
||||||
|
// 从帧缓冲区获取最新帧
|
||||||
|
if let Some(frame) = frame_buffer.get(0) {
|
||||||
|
let now = chrono::Utc::now();
|
||||||
|
|
||||||
|
// 确保距离上次检测至少10秒(防止重复检测)
|
||||||
|
if (now - last_detection_time).num_seconds() >= 10 {
|
||||||
|
// 这里应该是实际的检测算法
|
||||||
|
// 为演示目的,我们随机生成一些检测结果
|
||||||
|
let detection_seed = rand::random::<f32>();
|
||||||
|
|
||||||
|
if detection_seed < 0.3 { // 30%的概率检测到流星
|
||||||
|
detection_count += 1;
|
||||||
|
last_detection_time = now;
|
||||||
|
|
||||||
|
info!("检测到流星 #{}", detection_count);
|
||||||
|
|
||||||
|
// 发送检测事件到主线程
|
||||||
|
let event = DetectionEvent {
|
||||||
|
timestamp: now,
|
||||||
|
confidence: 0.7 + (detection_seed * 0.3), // 0.7-1.0之间的置信度
|
||||||
|
bounding_box: (
|
||||||
|
(rand::random::<f32>() * 800.0) as u32, // x
|
||||||
|
(rand::random::<f32>() * 600.0) as u32, // y
|
||||||
|
(50.0 + rand::random::<f32>() * 100.0) as u32, // width
|
||||||
|
(50.0 + rand::random::<f32>() * 100.0) as u32, // height
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
if event_tx.send(event).await.is_err() {
|
||||||
|
warn!("无法发送检测事件,接收端可能已关闭");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!("无法获取帧,缓冲区可能为空");
|
||||||
|
time::sleep(Duration::from_millis(500)).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 演示目的,5次检测后退出
|
||||||
|
if detection_count >= 5 {
|
||||||
|
info!("已达到预设检测次数,检测器退出");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 启动健康监控任务
|
||||||
|
async fn start_health_monitor_task(health_tx: Sender<String>) {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
info!("启动健康监控任务 (模拟)");
|
||||||
|
let mut interval = time::interval(Duration::from_secs(45));
|
||||||
|
|
||||||
|
// 简单模拟健康监控,不直接访问相机
|
||||||
|
// 在实际应用中,这可能会监控系统各个部分的状态
|
||||||
|
loop {
|
||||||
|
interval.tick().await;
|
||||||
|
|
||||||
|
// 发送简单的健康状态消息回主线程
|
||||||
|
// 这是一个简化的演示
|
||||||
|
if health_tx.send("系统运行正常".to_string()).await.is_err() {
|
||||||
|
warn!("无法发送健康状态,接收端可能已关闭");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 创建用于检测系统的配置
|
||||||
|
fn create_detector_config() -> Config {
|
||||||
|
let camera_settings = CameraSettings {
|
||||||
|
device: "0".to_string(),
|
||||||
|
// 对于流星检测,建议使用更高分辨率
|
||||||
|
resolution: Resolution::HD1080p,
|
||||||
|
fps: 30,
|
||||||
|
// 对于夜间观测,手动曝光通常更好
|
||||||
|
exposure: ExposureMode::Manual(500000), // 500毫秒曝光
|
||||||
|
// 增加增益以提高低光灵敏度
|
||||||
|
gain: 200,
|
||||||
|
focus_locked: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
Config {
|
||||||
|
camera: camera_settings,
|
||||||
|
// 其他配置字段,使用其默认值
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 流星检测事件
|
||||||
|
struct DetectionEvent {
|
||||||
|
/// 事件时间戳
|
||||||
|
timestamp: chrono::DateTime<chrono::Utc>,
|
||||||
|
/// 检测置信度 (0-1)
|
||||||
|
confidence: f32,
|
||||||
|
/// 边界框 (x, y, 宽度, 高度)
|
||||||
|
bounding_box: (u32, u32, u32, u32),
|
||||||
|
}
|
||||||
@ -33,7 +33,7 @@ pub struct CameraController {
|
|||||||
|
|
||||||
impl CameraController {
|
impl CameraController {
|
||||||
/// Create a new camera controller with the given configuration
|
/// Create a new camera controller with the given configuration
|
||||||
pub async fn new(config: &crate::Config) -> Result<Self> {
|
pub async fn new(config: &crate::config::Config) -> Result<Self> {
|
||||||
// Extract camera settings from config (placeholder for now)
|
// Extract camera settings from config (placeholder for now)
|
||||||
let settings = config.clone().camera;
|
let settings = config.clone().camera;
|
||||||
|
|
||||||
@ -130,6 +130,10 @@ impl CameraController {
|
|||||||
let frame_interval = Duration::from_secs_f64(1.0 / fps as f64);
|
let frame_interval = Duration::from_secs_f64(1.0 / fps as f64);
|
||||||
let mut interval = time::interval(frame_interval);
|
let mut interval = time::interval(frame_interval);
|
||||||
|
|
||||||
|
// Error tracking for recovery
|
||||||
|
let mut consecutive_errors = 0;
|
||||||
|
const ERROR_THRESHOLD: u32 = 10; // After 10 errors, try to reinitialize
|
||||||
|
|
||||||
info!("Starting camera capture at {} fps", fps);
|
info!("Starting camera capture at {} fps", fps);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
@ -137,6 +141,9 @@ impl CameraController {
|
|||||||
|
|
||||||
match stream.capture_frame() {
|
match stream.capture_frame() {
|
||||||
Ok(mat) => {
|
Ok(mat) => {
|
||||||
|
// Reset error counter on success
|
||||||
|
consecutive_errors = 0;
|
||||||
|
|
||||||
// Create a new frame with timestamp
|
// Create a new frame with timestamp
|
||||||
let frame = Frame::new(mat, Utc::now(), frame_count);
|
let frame = Frame::new(mat, Utc::now(), frame_count);
|
||||||
frame_count += 1;
|
frame_count += 1;
|
||||||
@ -151,6 +158,34 @@ impl CameraController {
|
|||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Failed to capture frame: {}", e);
|
error!("Failed to capture frame: {}", e);
|
||||||
|
consecutive_errors += 1;
|
||||||
|
|
||||||
|
if consecutive_errors >= ERROR_THRESHOLD {
|
||||||
|
error!("Too many consecutive errors ({}), attempting to reinitialize camera stream", consecutive_errors);
|
||||||
|
|
||||||
|
// Try to recreate the stream - this is a simplified version
|
||||||
|
// In a real implementation, you might want to signal the main thread
|
||||||
|
// to fully reinitialize the camera
|
||||||
|
match OpenCVCamera::open("0") { // Note: simplified, should use original device
|
||||||
|
Ok(mut camera) => {
|
||||||
|
// Configure minimal settings
|
||||||
|
let _ = camera.set_format(Resolution::HD720p);
|
||||||
|
let _ = camera.set_fps(fps);
|
||||||
|
|
||||||
|
// Try to start streaming again
|
||||||
|
match camera.start_streaming() {
|
||||||
|
Ok(new_stream) => {
|
||||||
|
info!("Successfully reinitialized camera stream");
|
||||||
|
stream = new_stream;
|
||||||
|
consecutive_errors = 0;
|
||||||
|
},
|
||||||
|
Err(e) => error!("Failed to restart camera streaming: {}", e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => error!("Failed to reopen camera: {}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Small delay to avoid tight error loop
|
// Small delay to avoid tight error loop
|
||||||
time::sleep(Duration::from_millis(100)).await;
|
time::sleep(Duration::from_millis(100)).await;
|
||||||
}
|
}
|
||||||
@ -268,8 +303,36 @@ impl CameraController {
|
|||||||
// Create a video file name
|
// Create a video file name
|
||||||
let video_path = event_dir.join("event.mp4").to_string_lossy().to_string();
|
let video_path = event_dir.join("event.mp4").to_string_lossy().to_string();
|
||||||
|
|
||||||
// TODO: Call FFmpeg to convert frames to video
|
// Use tokio::process to call FFmpeg to convert frames to video
|
||||||
// This would be done by spawning an external process
|
use tokio::process::Command;
|
||||||
|
|
||||||
|
info!("Converting frames to video using FFmpeg");
|
||||||
|
let ffmpeg_result = Command::new("ffmpeg")
|
||||||
|
.args(&[
|
||||||
|
"-y", // Overwrite output file if it exists
|
||||||
|
"-framerate", &self.settings.fps.to_string(),
|
||||||
|
"-i", &event_dir.join("frame_%04d.jpg").to_string_lossy(),
|
||||||
|
"-c:v", "libx264",
|
||||||
|
"-preset", "medium",
|
||||||
|
"-crf", "23",
|
||||||
|
&video_path
|
||||||
|
])
|
||||||
|
.output()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match ffmpeg_result {
|
||||||
|
Ok(output) => {
|
||||||
|
if !output.status.success() {
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
warn!("FFmpeg warning: {}", stderr);
|
||||||
|
} else {
|
||||||
|
info!("Successfully created video at: {}", video_path);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Failed to execute FFmpeg: {}. Video will not be created, only individual frames saved.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create and return the event
|
// Create and return the event
|
||||||
let event = MeteorEvent {
|
let event = MeteorEvent {
|
||||||
@ -284,6 +347,33 @@ impl CameraController {
|
|||||||
Ok(event)
|
Ok(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check the health of the camera system
|
||||||
|
pub async fn check_health(&self) -> Result<bool> {
|
||||||
|
// If camera is not running, consider it healthy
|
||||||
|
if !self.is_running {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we've received frames recently
|
||||||
|
if let Some(frame) = self.frame_buffer.get(0) {
|
||||||
|
let now = Utc::now();
|
||||||
|
let elapsed = now.signed_duration_since(frame.timestamp);
|
||||||
|
|
||||||
|
// If no new frames in last 5 seconds, camera may be stuck
|
||||||
|
if elapsed.num_seconds() > 5 {
|
||||||
|
warn!("Camera health check: No new frames received in {} seconds", elapsed.num_seconds());
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
} else if self.is_running {
|
||||||
|
// If no frames at all but camera is running, something is wrong
|
||||||
|
warn!("Camera health check: Camera is running but no frames in buffer");
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// All checks passed
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the current status of the camera
|
/// Get the current status of the camera
|
||||||
pub async fn get_status(&self) -> Result<serde_json::Value> {
|
pub async fn get_status(&self) -> Result<serde_json::Value> {
|
||||||
let frame_buffer_stats = {
|
let frame_buffer_stats = {
|
||||||
@ -341,3 +431,45 @@ impl CameraController {
|
|||||||
Ok(status)
|
Ok(status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Drop for CameraController {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
info!("Cleaning up camera controller resources");
|
||||||
|
|
||||||
|
// Stop streaming if running
|
||||||
|
if self.is_running {
|
||||||
|
// Use block_in_place since drop can't be async
|
||||||
|
if let Err(e) = tokio::task::block_in_place(|| {
|
||||||
|
futures::executor::block_on(self.stop_capture())
|
||||||
|
}) {
|
||||||
|
error!("Error stopping camera during cleanup: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release camera resources
|
||||||
|
self.camera = None;
|
||||||
|
self.stream = None;
|
||||||
|
|
||||||
|
info!("Camera controller resources released");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 为CameraController实现Clone trait
|
||||||
|
impl Clone for CameraController {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
// 创建新的广播通道
|
||||||
|
let (frame_tx, _) = tokio::sync::broadcast::channel(30);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
settings: self.settings.clone(),
|
||||||
|
camera: None, // 不克隆相机实例,因为它包含底层资源
|
||||||
|
stream: None, // 不克隆流,因为它也包含底层资源
|
||||||
|
frame_buffer: self.frame_buffer.clone(),
|
||||||
|
frame_count: self.frame_count,
|
||||||
|
is_running: self.is_running,
|
||||||
|
frame_tx, // 使用新创建的广播通道
|
||||||
|
events_dir: self.events_dir.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -35,10 +35,6 @@ impl Frame {
|
|||||||
)?;
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_full() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A circular buffer for storing video frames
|
/// A circular buffer for storing video frames
|
||||||
|
|||||||
@ -1,288 +0,0 @@
|
|||||||
// use anyhow::{anyhow, Context, Result};
|
|
||||||
// use log::{debug, error, info, warn};
|
|
||||||
// use std::path::Path;
|
|
||||||
// use v4l::buffer::Type;
|
|
||||||
// use v4l::io::traits::CaptureStream;
|
|
||||||
// use v4l::prelude::*;
|
|
||||||
// use v4l::video::Capture;
|
|
||||||
// use v4l::{Format, FourCC};
|
|
||||||
|
|
||||||
// use opencv::{core, imgproc, prelude::*};
|
|
||||||
// use crate::utils::opencv_compat::convert_color;
|
|
||||||
|
|
||||||
// use crate::camera::{ExposureMode, Resolution};
|
|
||||||
|
|
||||||
// /// V4L2 camera driver for star-light cameras
|
|
||||||
// pub struct V4l2Camera {
|
|
||||||
// /// The open device handle
|
|
||||||
// device: Device,
|
|
||||||
// /// The current camera format
|
|
||||||
// format: Format,
|
|
||||||
// /// Whether the camera is currently streaming
|
|
||||||
// is_streaming: bool,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl V4l2Camera {
|
|
||||||
// /// Open a camera device by path
|
|
||||||
// pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
|
|
||||||
// let device = Device::with_path(path.as_ref())
|
|
||||||
// .context("Failed to open camera device")?;
|
|
||||||
|
|
||||||
// info!(
|
|
||||||
// "Opened camera: {} ({})",
|
|
||||||
// device.info().card,
|
|
||||||
// device.info().driver
|
|
||||||
// );
|
|
||||||
|
|
||||||
// // Get the current format
|
|
||||||
// let format = device
|
|
||||||
// .format()
|
|
||||||
// .context("Failed to get camera format")?;
|
|
||||||
|
|
||||||
// debug!("Initial camera format: {:?}", format);
|
|
||||||
|
|
||||||
// Ok(Self {
|
|
||||||
// device,
|
|
||||||
// format,
|
|
||||||
// is_streaming: false,
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// Set the camera resolution and pixel format
|
|
||||||
// pub fn set_format(&mut self, resolution: Resolution) -> Result<()> {
|
|
||||||
// let (width, height) = resolution.dimensions();
|
|
||||||
|
|
||||||
// // Try to set format to MJPEG or YUYV first, then fall back to others
|
|
||||||
// let formats = [FourCC::new(b"MJPG"), FourCC::new(b"YUYV")];
|
|
||||||
|
|
||||||
// let mut success = false;
|
|
||||||
// let mut last_error = None;
|
|
||||||
|
|
||||||
// for &fourcc in &formats {
|
|
||||||
// let mut format = Format::new(width, height, fourcc);
|
|
||||||
|
|
||||||
// match self.device.set_format(&mut format) {
|
|
||||||
// Ok(_) => {
|
|
||||||
// self.format = format;
|
|
||||||
// success = true;
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// Err(e) => {
|
|
||||||
// last_error = Some(e);
|
|
||||||
// warn!("Failed to set format {:?}: {}", fourcc, last_error.as_ref().unwrap());
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if !success {
|
|
||||||
// return Err(anyhow!(
|
|
||||||
// "Failed to set any supported format: {:?}",
|
|
||||||
// last_error.unwrap()
|
|
||||||
// ));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// info!(
|
|
||||||
// "Set camera format: {}×{} {}",
|
|
||||||
// self.format.width, self.format.height,
|
|
||||||
// String::from_utf8_lossy(&self.format.fourcc.repr)
|
|
||||||
// );
|
|
||||||
|
|
||||||
// Ok(())
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// Set the camera frame rate
|
|
||||||
// pub fn set_fps(&mut self, fps: u32) -> Result<()> {
|
|
||||||
// if let Some(params) = self.device.params() {
|
|
||||||
// let mut params = params.context("Failed to get camera parameters")?;
|
|
||||||
// params.set_frames_per_second(fps, 1);
|
|
||||||
|
|
||||||
// self.device
|
|
||||||
// .set_params(¶ms)
|
|
||||||
// .context("Failed to set frame rate")?;
|
|
||||||
|
|
||||||
// info!("Set camera frame rate: {} fps", fps);
|
|
||||||
// } else {
|
|
||||||
// warn!("Camera does not support frame rate adjustment");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Ok(())
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// Set camera exposure mode and value
|
|
||||||
// pub fn set_exposure(&mut self, mode: ExposureMode) -> Result<()> {
|
|
||||||
// // First, set auto/manual mode
|
|
||||||
// let ctrl_id = v4l::control::id::EXPOSURE_AUTO;
|
|
||||||
// let auto_value = match mode {
|
|
||||||
// ExposureMode::Auto => 3, // V4L2_EXPOSURE_AUTO
|
|
||||||
// ExposureMode::Manual(_) => 1, // V4L2_EXPOSURE_MANUAL
|
|
||||||
// };
|
|
||||||
|
|
||||||
// self.device
|
|
||||||
// .set_control(ctrl_id, auto_value)
|
|
||||||
// .context("Failed to set exposure mode")?;
|
|
||||||
|
|
||||||
// // If manual, set the exposure value
|
|
||||||
// if let ExposureMode::Manual(exposure_time) = mode {
|
|
||||||
// // Exposure time in microseconds
|
|
||||||
// let ctrl_id = v4l::control::id::EXPOSURE_ABSOLUTE;
|
|
||||||
// self.device
|
|
||||||
// .set_control(ctrl_id, exposure_time as i64)
|
|
||||||
// .context("Failed to set exposure time")?;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// info!("Set camera exposure: {:?}", mode);
|
|
||||||
// Ok(())
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// Set camera gain (ISO)
|
|
||||||
// pub fn set_gain(&mut self, gain: u8) -> Result<()> {
|
|
||||||
// let ctrl_id = v4l::control::id::GAIN;
|
|
||||||
|
|
||||||
// self.device
|
|
||||||
// .set_control(ctrl_id, gain as i64)
|
|
||||||
// .context("Failed to set gain")?;
|
|
||||||
|
|
||||||
// info!("Set camera gain: {}", gain);
|
|
||||||
// Ok(())
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// Lock focus at infinity (if supported)
|
|
||||||
// pub fn lock_focus_at_infinity(&mut self) -> Result<()> {
|
|
||||||
// // First, set focus mode to manual
|
|
||||||
// let auto_focus_id = v4l::control::id::FOCUS_AUTO;
|
|
||||||
// if let Ok(_) = self.device.set_control(auto_focus_id, 0) {
|
|
||||||
// // Then set focus to infinity (typically maximum value)
|
|
||||||
// let focus_id = v4l::control::id::FOCUS_ABSOLUTE;
|
|
||||||
|
|
||||||
// // Get the range of the control
|
|
||||||
// if let Ok(control) = self.device.control(focus_id) {
|
|
||||||
// let max_focus = control.maximum();
|
|
||||||
|
|
||||||
// if let Ok(_) = self.device.set_control(focus_id, max_focus) {
|
|
||||||
// info!("Locked focus at infinity (value: {})", max_focus);
|
|
||||||
// return Ok(());
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// warn!("Failed to set focus to infinity");
|
|
||||||
// } else {
|
|
||||||
// warn!("Camera does not support focus control");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Ok(())
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// Start streaming from the camera
|
|
||||||
// pub fn start_streaming(&mut self) -> Result<V4l2CaptureStream> {
|
|
||||||
// let queue = MmapStream::with_buffers(&self.device, Type::VideoCapture, 4)
|
|
||||||
// .context("Failed to create capture stream")?;
|
|
||||||
|
|
||||||
// self.is_streaming = true;
|
|
||||||
// info!("Started camera streaming");
|
|
||||||
|
|
||||||
// Ok(V4l2CaptureStream {
|
|
||||||
// stream: queue,
|
|
||||||
// format: self.format.clone(),
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// Stop streaming from the camera
|
|
||||||
// pub fn stop_streaming(&mut self) -> Result<()> {
|
|
||||||
// // The streaming will be stopped when the CaptureStream is dropped
|
|
||||||
// self.is_streaming = false;
|
|
||||||
// info!("Stopped camera streaming");
|
|
||||||
// Ok(())
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// Check if the camera is currently streaming
|
|
||||||
// pub fn is_streaming(&self) -> bool {
|
|
||||||
// self.is_streaming
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// Get current format width
|
|
||||||
// pub fn width(&self) -> u32 {
|
|
||||||
// self.format.width
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// Get current format height
|
|
||||||
// pub fn height(&self) -> u32 {
|
|
||||||
// self.format.height
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// Get current format pixel format
|
|
||||||
// pub fn pixel_format(&self) -> FourCC {
|
|
||||||
// self.format.fourcc
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// Wrapper around V4L2 capture stream
|
|
||||||
// pub struct V4l2CaptureStream {
|
|
||||||
// stream: MmapStream,
|
|
||||||
// format: Format,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl V4l2CaptureStream {
|
|
||||||
// /// Capture a single frame from the camera
|
|
||||||
// pub fn capture_frame(&mut self) -> Result<core::Mat> {
|
|
||||||
// let buffer = self.stream.next()
|
|
||||||
// .context("Failed to capture frame")?;
|
|
||||||
|
|
||||||
// let width = self.format.width as i32;
|
|
||||||
// let height = self.format.height as i32;
|
|
||||||
|
|
||||||
// // Convert the buffer to an OpenCV Mat based on the pixel format
|
|
||||||
// let mat = match self.format.fourcc {
|
|
||||||
// // MJPEG format
|
|
||||||
// f if f == FourCC::new(b"MJPG") => {
|
|
||||||
// // Decode JPEG data
|
|
||||||
// let data = buffer.data();
|
|
||||||
// let vec_data = unsafe {
|
|
||||||
// std::slice::from_raw_parts(data.as_ptr(), data.len())
|
|
||||||
// }.to_vec();
|
|
||||||
|
|
||||||
// let buf = core::Vector::from_slice(&vec_data);
|
|
||||||
// let img = opencv::imgcodecs::imdecode(&buf, opencv::imgcodecs::IMREAD_COLOR)?;
|
|
||||||
// img
|
|
||||||
// },
|
|
||||||
|
|
||||||
// // YUYV format
|
|
||||||
// f if f == FourCC::new(b"YUYV") => {
|
|
||||||
// let data = buffer.data();
|
|
||||||
|
|
||||||
// // Create a Mat from the YUYV data
|
|
||||||
// let mut yuyv = unsafe {
|
|
||||||
// let bytes_per_pixel = 2; // YUYV is 2 bytes per pixel
|
|
||||||
// let step = width as usize * bytes_per_pixel;
|
|
||||||
// core::Mat::new_rows_cols_with_data(
|
|
||||||
// height,
|
|
||||||
// width,
|
|
||||||
// core::CV_8UC2,
|
|
||||||
// data.as_ptr() as *mut _,
|
|
||||||
// step,
|
|
||||||
// )?
|
|
||||||
// };
|
|
||||||
|
|
||||||
// // Convert YUYV to BGR
|
|
||||||
// let mut bgr = core::Mat::default()?;
|
|
||||||
// convert_color(&yuyv, &mut bgr, imgproc::COLOR_YUV2BGR_YUYV, 0)?;
|
|
||||||
// bgr
|
|
||||||
// },
|
|
||||||
|
|
||||||
// // Unsupported format
|
|
||||||
// _ => {
|
|
||||||
// return Err(anyhow!(
|
|
||||||
// "Unsupported pixel format: {}",
|
|
||||||
// String::from_utf8_lossy(&self.format.fourcc.repr)
|
|
||||||
// ));
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// Ok(mat)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl Drop for V4l2CaptureStream {
|
|
||||||
// fn drop(&mut self) {
|
|
||||||
// debug!("V4L2 capture stream dropped");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@ -77,7 +77,7 @@ pub struct GpsController {
|
|||||||
|
|
||||||
impl GpsController {
|
impl GpsController {
|
||||||
/// Create a new GPS controller with the given configuration
|
/// Create a new GPS controller with the given configuration
|
||||||
pub async fn new(config: &crate::Config) -> Result<Self> {
|
pub async fn new(config: &crate::config::Config) -> Result<Self> {
|
||||||
// Extract GPS settings from config
|
// Extract GPS settings from config
|
||||||
let gps_config = config.gps.clone();
|
let gps_config = config.gps.clone();
|
||||||
|
|
||||||
|
|||||||
20
src/lib.rs
Normal file
20
src/lib.rs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// Platform detection
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub const PLATFORM_SUPPORTS_GPIO: bool = true;
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
pub const PLATFORM_SUPPORTS_GPIO: bool = false;
|
||||||
|
|
||||||
|
// 将所有模块重新导出,使它们对例子可见
|
||||||
|
pub mod camera;
|
||||||
|
pub mod config;
|
||||||
|
pub mod utils;
|
||||||
|
pub mod gps;
|
||||||
|
pub mod sensors;
|
||||||
|
pub mod detection;
|
||||||
|
pub mod streaming;
|
||||||
|
pub mod storage;
|
||||||
|
pub mod communication;
|
||||||
|
pub mod monitoring;
|
||||||
|
pub mod overlay;
|
||||||
|
pub mod hooks;
|
||||||
@ -1,9 +1,3 @@
|
|||||||
// Platform detection
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
pub const PLATFORM_SUPPORTS_GPIO: bool = true;
|
|
||||||
|
|
||||||
#[cfg(not(target_os = "linux"))]
|
|
||||||
pub const PLATFORM_SUPPORTS_GPIO: bool = false;
|
|
||||||
|
|
||||||
mod camera;
|
mod camera;
|
||||||
mod config;
|
mod config;
|
||||||
@ -28,6 +22,7 @@ use tokio::signal;
|
|||||||
use futures::future::join_all;
|
use futures::future::join_all;
|
||||||
|
|
||||||
pub use config::Config;
|
pub use config::Config;
|
||||||
|
use meteor_detect::PLATFORM_SUPPORTS_GPIO;
|
||||||
use crate::overlay::Watermark;
|
use crate::overlay::Watermark;
|
||||||
|
|
||||||
/// Main entry point for the meteor detection system
|
/// Main entry point for the meteor detection system
|
||||||
|
|||||||
@ -84,7 +84,7 @@ pub struct SensorController {
|
|||||||
|
|
||||||
impl SensorController {
|
impl SensorController {
|
||||||
/// Create a new sensor controller with the given configuration
|
/// Create a new sensor controller with the given configuration
|
||||||
pub async fn new(config: &crate::Config) -> Result<Self> {
|
pub async fn new(config: &crate::config::Config) -> Result<Self> {
|
||||||
// Extract sensor settings from config
|
// Extract sensor settings from config
|
||||||
let sensor_config = config.sensors.clone();
|
let sensor_config = config.sensors.clone();
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user