Files
observer/flutter_app/lib/camera/frame_analyzer.dart
T
admin a0b115d954 训练体系整合与标注单阶段化
- 标注:AI 预标注直写 labels_json(去候选确认两阶段);重叠去重(minIoU);全量标注按钮
- 训练:脚本迁移入 server/training/(Go 化 prepare_yolo/analyze_rfdetr,保留 train_server.py);tflite 产物自检并入训练流程(check_tflite)
- 数据目录/权重不进 git;.gitignore 迁移至仓库根
2026-08-26 18:22:56 +08:00

132 lines
3.3 KiB
Dart
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.
import 'dart:typed_data';
import 'package:camera/camera.dart';
import '../detection/detection_result.dart';
import '../detection/detector_worker.dart';
import 'camera_view_model.dart';
/// 抽帧节流 + 后台推理(对应 Kotlin FrameAnalyzer)。
/// 推理在后台 isolateDetectorWorker)执行,主 isolate 只投递帧与收结果。
class FrameAnalyzer {
/// 连续检测:100ms 一帧
int intervalMs = 100;
/// null = 模型加载失败,仅预览不分析
final DetectorWorker? worker;
final CameraViewModel viewModel;
int _lastDetectMs = 0;
/// 诊断计数:推理调用/异常次数
int detectCalls = 0;
int detectErrors = 0;
String? lastError;
/// 最近一次完整处理(预处理+推理+运动检测)耗时 ms(worker 侧)
int lastProcessMs = 0;
/// 图像流回调是否到达(诊断用)
int framesReceived = 0;
FrameAnalyzer({required this.worker, required this.viewModel}) {
worker?.onResult = _onResult;
worker?.onError = _onError;
}
void recordStreamError(String msg) {
lastError = msg;
detectErrors++;
}
void _onResult(
List<DetectionResult> results,
List<MotionRegion> motion,
List<MotionRegion> novelty,
int rotation,
int width,
int height,
int processMs,
String yuvDiag) {
detectCalls++;
lastProcessMs = processMs;
viewModel.onFramesAnalyzed(
results,
rotation,
width,
height,
motion,
novelty,
detectCalls: detectCalls,
detectErrors: detectErrors,
lastError: lastError,
framesReceived: framesReceived,
lastProcessMs: lastProcessMs,
yuvDiag: yuvDiag,
);
}
void _onError(String msg) {
detectErrors++;
lastError = msg;
viewModel.onFramesAnalyzed(
const [],
90,
0,
0,
const [],
const [],
detectCalls: detectCalls,
detectErrors: detectErrors,
lastError: lastError,
framesReceived: framesReceived,
lastProcessMs: lastProcessMs,
);
}
/// 节流与 busy 丢帧判定(两入口共用);通过后才允许投递
bool _canSend() {
framesReceived++;
final w = worker;
if (w == null) return false;
final now = DateTime.now().millisecondsSinceEpoch;
if (now - _lastDetectMs < intervalMs) return false;
_lastDetectMs = now;
if (w.busy) return false; // 上一帧未返回则丢帧,避免在途积压
return true;
}
void analyze(CameraImage image, int rotationDegrees,
{bool rgbaOrder = false}) {
if (!_canSend()) return;
worker!.analyze(image, rotationDegrees, rgbaOrder: rgbaOrder);
}
/// 原生相机通道帧(Android):字节已在 Kotlin 侧旋转成竖屏,rotation=0。
/// isBgra=true + rgbaOrder 与插件路径同语义:false=BGRA(rOff=2)/true=RGBA(rOff=0)
void analyzeRaw({
required List<Uint8List> planes,
required List<int> strides,
required int width,
required int height,
required bool isBgra,
required bool rgbaOrder,
int rotationDegrees = 0,
}) {
if (!_canSend()) return;
worker!.analyzeRaw(
planes: planes,
strides: strides,
width: width,
height: height,
isBgra: isBgra,
rgbaOrder: rgbaOrder,
rotationDegrees: rotationDegrees,
);
}
void reset() => _lastDetectMs = 0;
void dispose() => worker?.dispose();
}