迁移 Flutter 端与训练脚本,模型/训练产物移出 git(遵循纯代码约定)

This commit is contained in:
2026-08-24 12:35:24 +08:00
parent d056f01965
commit 961523d94c
218 changed files with 13391 additions and 3232 deletions
@@ -0,0 +1,92 @@
import 'package:camera/camera.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'frame_analyzer.dart';
/// camera 插件封装:后摄图像流(对应 Kotlin CameraController)。
class AppCameraController {
final List<CameraDescription> cameras;
CameraController? controller;
/// 图像流回调实际触发次数(诊断用,与 analyzer 帧计数区分)
int streamCallbacks = 0;
AppCameraController._(this.cameras);
static Future<AppCameraController?> create() async {
final cameras = await availableCameras();
if (cameras.isEmpty) return null;
return AppCameraController._(cameras);
}
bool get isInitialized => controller?.value.isInitialized ?? false;
CameraController get currentController =>
controller ?? (throw StateError('camera not initialized'));
/// 图像流送达时的旋转角(传感器 → 竖屏显示所需的顺时针旋转)。
/// 与 CameraX rotationDegrees 同公式;预览本身由平台旋转,检测框 overlay
/// 用同一角度映射即可对齐。
int get rotationDegrees {
final c = controller;
if (c == null) return 0;
final deviceDegrees = switch (c.value.deviceOrientation) {
DeviceOrientation.portraitUp => 0,
DeviceOrientation.landscapeLeft => 90,
DeviceOrientation.portraitDown => 180,
DeviceOrientation.landscapeRight => 270,
};
final sensor = c.description.sensorOrientation;
final isFront =
c.description.lensDirection == CameraLensDirection.front;
final degrees = (isFront ? sensor + deviceDegrees : sensor - deviceDegrees) % 360;
return degrees < 0 ? degrees + 360 : degrees;
}
Future<void> start(FrameAnalyzer analyzer) async {
await stop();
final desc = cameras.firstWhere(
(c) => c.lensDirection == CameraLensDirection.back,
orElse: () => cameras.first);
// iOS 用默认 bgra8888420v 在部分 iOS 版本上视频输出静默不送帧),
// Android 用 yuv420 多平面。
final fmt = defaultTargetPlatform == TargetPlatform.iOS
? ImageFormatGroup.bgra8888
: ImageFormatGroup.yuv420;
final c = CameraController(desc, ResolutionPreset.high,
enableAudio: false, imageFormatGroup: fmt);
controller = c;
await c.initialize();
// 相机(重新)启动后重置运动/背景参考与抽帧节流,避免旧场景残留
analyzer.reset();
analyzer.worker?.reset();
debugPrint('[camera] initialized, starting image stream');
try {
await c.startImageStream((image) {
streamCallbacks++;
try {
analyzer.analyze(image, rotationDegrees);
} catch (e, st) {
debugPrint('[camera] analyze error: $e\n$st');
analyzer.recordStreamError('analyze: $e');
}
});
debugPrint('[camera] startImageStream ok');
} catch (e, st) {
debugPrint('[camera] startImageStream FAILED: $e\n$st');
analyzer.recordStreamError('startImageStream: $e');
rethrow;
}
}
Future<void> stop() async {
final c = controller;
if (c == null) return;
controller = null;
try {
await c.stopImageStream();
} catch (_) {}
await c.dispose();
}
}