迁移 Flutter 端与训练脚本,模型/训练产物移出 git(遵循纯代码约定)
This commit is contained in:
@@ -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 用默认 bgra8888(420v 在部分 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user