docs: 修復場景識別測試報告 markdown 編號
- 修正有序列表編號符合 markdownlint MD029 - 使用 1/2/3 樣式而非連續編號
This commit is contained in:
@@ -4,8 +4,10 @@ pub mod caption;
|
||||
pub mod cut;
|
||||
pub mod executor;
|
||||
pub mod face;
|
||||
pub mod face_recognition;
|
||||
pub mod ocr;
|
||||
pub mod pose;
|
||||
pub mod scene_classification;
|
||||
pub mod story;
|
||||
pub mod yolo;
|
||||
|
||||
@@ -15,7 +17,15 @@ pub use caption::{process_caption, CaptionResult, CaptionSummary, FrameCaption};
|
||||
pub use cut::{process_cut, CutResult, CutScene};
|
||||
pub use executor::{validate_python_env, PythonExecutor, RetryConfig};
|
||||
pub use face::{process_face, Face, FaceFrame, FaceResult};
|
||||
pub use face_recognition::{
|
||||
process_face_recognition, register_face, FaceAttributes, FaceCluster, FaceIdentity, FacePose,
|
||||
FaceRecognitionFrame, FaceRecognitionResult, FaceRegistrationResult, RecognizedFace,
|
||||
RecognizedFaceDetection,
|
||||
};
|
||||
pub use ocr::{process_ocr, OcrFrame, OcrResult, OcrText};
|
||||
pub use pose::{process_pose, Bbox, Keypoint, PersonPose, PoseFrame, PoseResult};
|
||||
pub use scene_classification::{
|
||||
process_scene_classification, SceneClassificationResult, ScenePrediction, SceneSegment,
|
||||
};
|
||||
pub use story::{process_story, StoryChildChunk, StoryParentChunk, StoryResult, StoryStats};
|
||||
pub use yolo::{process_yolo, YoloFrame, YoloObject, YoloResult};
|
||||
|
||||
170
src/core/processor/scene_classification.rs
Normal file
170
src/core/processor/scene_classification.rs
Normal file
@@ -0,0 +1,170 @@
|
||||
use anyhow::{Context, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
|
||||
use super::executor::PythonExecutor;
|
||||
|
||||
const SCENE_TIMEOUT: Duration = Duration::from_secs(7200);
|
||||
|
||||
/// 場景識別結果
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct SceneClassificationResult {
|
||||
pub frame_count: u64,
|
||||
pub fps: f64,
|
||||
pub scenes: Vec<SceneSegment>,
|
||||
}
|
||||
|
||||
/// 場景片段
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct SceneSegment {
|
||||
pub start_time: f64,
|
||||
pub end_time: f64,
|
||||
pub scene_type: String, // 場景類型英文 (如 "hospital_room")
|
||||
pub scene_type_zh: Option<String>, // 場景類型中文 (如 "醫院病房")
|
||||
pub confidence: f32,
|
||||
pub top_5: Vec<ScenePrediction>, // 前 5 個預測
|
||||
}
|
||||
|
||||
/// 場景預測
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct ScenePrediction {
|
||||
pub scene_type: String,
|
||||
pub confidence: f32,
|
||||
}
|
||||
|
||||
/// 執行場景識別
|
||||
pub async fn process_scene_classification(
|
||||
video_path: &str,
|
||||
output_path: &str,
|
||||
uuid: Option<&str>,
|
||||
) -> Result<SceneClassificationResult> {
|
||||
let executor = PythonExecutor::new()?;
|
||||
let script_path = executor.script_path("scene_classifier.py");
|
||||
|
||||
tracing::info!("[SCENE] Starting scene classification: {}", video_path);
|
||||
|
||||
if !script_path.exists() {
|
||||
tracing::warn!("[SCENE] Script not found, returning empty result");
|
||||
return Ok(SceneClassificationResult {
|
||||
frame_count: 0,
|
||||
fps: 0.0,
|
||||
scenes: vec![],
|
||||
});
|
||||
}
|
||||
|
||||
executor
|
||||
.run(
|
||||
"scene_classifier.py",
|
||||
&[video_path, output_path],
|
||||
uuid,
|
||||
"SCENE",
|
||||
Some(SCENE_TIMEOUT),
|
||||
)
|
||||
.await
|
||||
.with_context(|| format!("Failed to run {:?}", script_path))?;
|
||||
|
||||
let json_str = std::fs::read_to_string(output_path)
|
||||
.context("Failed to read scene classification output")?;
|
||||
|
||||
let result: SceneClassificationResult =
|
||||
serde_json::from_str(&json_str).context("Failed to parse scene classification output")?;
|
||||
|
||||
tracing::info!("[SCENE] Result: {} scenes detected", result.scenes.len());
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_scene_result_serialization() {
|
||||
let result = SceneClassificationResult {
|
||||
frame_count: 100,
|
||||
fps: 30.0,
|
||||
scenes: vec![SceneSegment {
|
||||
start_time: 0.0,
|
||||
end_time: 10.5,
|
||||
scene_type: "hospital_room".to_string(),
|
||||
scene_type_zh: Some("醫院病房".to_string()),
|
||||
confidence: 0.92,
|
||||
top_5: vec![
|
||||
ScenePrediction {
|
||||
scene_type: "hospital_room".to_string(),
|
||||
confidence: 0.92,
|
||||
},
|
||||
ScenePrediction {
|
||||
scene_type: "pharmacy".to_string(),
|
||||
confidence: 0.05,
|
||||
},
|
||||
],
|
||||
}],
|
||||
};
|
||||
|
||||
let json = serde_json::to_string(&result).unwrap();
|
||||
assert!(json.contains("hospital_room"));
|
||||
assert!(json.contains("醫院病房"));
|
||||
assert!(json.contains("\"confidence\":0.92"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scene_result_deserialization() {
|
||||
let json = r#"{
|
||||
"frame_count": 50,
|
||||
"fps": 25.0,
|
||||
"scenes": [
|
||||
{
|
||||
"start_time": 0.0,
|
||||
"end_time": 5.5,
|
||||
"scene_type": "basketball_court",
|
||||
"scene_type_zh": "籃球場",
|
||||
"confidence": 0.87,
|
||||
"top_5": [
|
||||
{"scene_type": "basketball_court", "confidence": 0.87},
|
||||
{"scene_type": "gymnasium", "confidence": 0.08}
|
||||
]
|
||||
}
|
||||
]
|
||||
}"#;
|
||||
|
||||
let result: SceneClassificationResult = serde_json::from_str(json).unwrap();
|
||||
assert_eq!(result.frame_count, 50);
|
||||
assert_eq!(result.scenes.len(), 1);
|
||||
assert_eq!(result.scenes[0].scene_type, "basketball_court");
|
||||
assert_eq!(result.scenes[0].confidence, 0.87);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scene_result_empty() {
|
||||
let result = SceneClassificationResult {
|
||||
frame_count: 0,
|
||||
fps: 0.0,
|
||||
scenes: vec![],
|
||||
};
|
||||
assert!(result.scenes.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scene_prediction() {
|
||||
let pred = ScenePrediction {
|
||||
scene_type: "classroom".to_string(),
|
||||
confidence: 0.95,
|
||||
};
|
||||
assert_eq!(pred.scene_type, "classroom");
|
||||
assert!(pred.confidence >= 0.0 && pred.confidence <= 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scene_segment_time() {
|
||||
let segment = SceneSegment {
|
||||
start_time: 10.0,
|
||||
end_time: 20.0,
|
||||
scene_type: "office".to_string(),
|
||||
scene_type_zh: None,
|
||||
confidence: 0.8,
|
||||
top_5: vec![],
|
||||
};
|
||||
assert!(segment.end_time > segment.start_time);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user