Flutter 에서 yolov8 사용하기 - 3
이제 카메라를 구동하고 거기서 모델을 생성하여 결과를 추출하는 모듈을 만들 것이다.
카메라 구동은 기존에 하는 방법과 비슷하다.
모델을 생성하여 탐지하는 모듈은 스레드를 4개를 설정하여 비동기 독립으로 실행하도록 한다.
isolate 를 사용한다.
이렇게 하면 속도가 빠르다.
대부분 정형화 된 상태로 샘플 코드가 되어 있으니 이걸 사용하면 된다.
이제 다음과 같이 사용한다.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class YoloPage extends StatefulWidget { | |
const YoloPage({super.key}); | |
@override | |
State<YoloPage> createState() => _YoloPageState(); | |
} | |
class _YoloPageState extends State<YoloPage> with WidgetsBindingObserver { | |
CameraController? _cameraController; | |
get _controller => _cameraController; | |
Detector? _detector; | |
StreamSubscription? _subscription; | |
int _cameraIndex = -1; | |
final CameraLensDirection initialCameraLensDirection = | |
CameraLensDirection.front; | |
List<String> classes = []; | |
List<List<double>> bboxes = []; | |
List<double> scores = []; | |
@override | |
void initState() { | |
super.initState(); | |
WidgetsBinding.instance.addObserver(this); | |
_initStateAsync(); | |
} | |
void _initStateAsync() async { | |
_initializeCamera(); | |
Detector.start().then((instance) { | |
setState(() { | |
_detector = instance; | |
_subscription = instance.resultsStream.stream.listen((values) { | |
setState(() { | |
classes = values['cls']; | |
bboxes = values['box']; | |
scores = values['conf']; | |
}); | |
}); | |
}); | |
}); | |
} | |
void _initializeCamera() async { | |
for (var i = 0; i < pcameras.length; i++) { | |
if (pcameras[i].lensDirection == initialCameraLensDirection) { | |
_cameraIndex = i; | |
break; | |
} | |
} | |
final camera = pcameras[_cameraIndex]; | |
_cameraController = CameraController( | |
camera, | |
ResolutionPreset.high, | |
enableAudio: false, | |
)..initialize().then((_) async { | |
await _controller.startImageStream(onLatestImageAvailable); | |
setState(() {}); | |
ScreenParams.previewSize = _controller.value.previewSize!; | |
}); | |
} | |
@override | |
void didChangeAppLifecycleState(AppLifecycleState state) async { | |
switch (state) { | |
case AppLifecycleState.inactive: | |
_cameraController?.stopImageStream(); | |
_detector?.stop(); | |
_subscription?.cancel(); | |
break; | |
case AppLifecycleState.resumed: | |
_initStateAsync(); | |
break; | |
default: | |
} | |
} | |
void onLatestImageAvailable(CameraImage cameraImage) async { | |
_detector?.processFrame(cameraImage); | |
} | |
Widget _boundingBoxes() { | |
List<Bbox> bboxesWidgets = []; | |
for (int i = 0; i < bboxes.length; i++) { | |
bboxesWidgets.add( | |
Bbox( | |
box: bboxes[i], | |
name: classes[i], | |
score: scores[i], | |
), | |
); | |
} | |
return Stack(children: bboxesWidgets); | |
} | |
@override | |
void dispose() { | |
WidgetsBinding.instance.removeObserver(this); | |
_cameraController?.dispose(); | |
_detector?.stop(); | |
_subscription?.cancel(); | |
super.dispose(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
if (_cameraController == null || !_controller.value.isInitialized) { | |
return const SizedBox.shrink(); | |
} | |
var aspect = 1 / _controller.value.aspectRatio; | |
return Scaffold( | |
appBar: AppBar( | |
title: const Text('RealTime Test'), | |
centerTitle: true, | |
backgroundColor: Colors.black38, | |
foregroundColor: Colors.white, | |
leading: IconButton( | |
onPressed: () => | |
Navigator.popUntil(context, ModalRoute.withName("/")), | |
icon: const Icon( | |
Icons.arrow_back, | |
color: Colors.white, | |
), | |
), | |
), | |
extendBodyBehindAppBar: true, | |
extendBody: true, | |
body: Stack( | |
fit: StackFit.expand, | |
children: [ | |
AspectRatio( | |
aspectRatio: aspect, | |
child: CameraPreview(_controller), | |
), | |
AspectRatio( | |
aspectRatio: aspect, | |
child: _boundingBoxes(), | |
), | |
], | |
), | |
); | |
} | |
} |
댓글
댓글 쓰기