Flutter 에서 yolov8 사용하기 - 1

Flutter 에서 yolov8 을 사용하려면 이 방법이 가장 주효하다.

물론 더 나은 라이브러리가 제공된다면 좋을 것이다.

tflite_flutter 라이브러리가 현재 공식 지원이다.

예제 등을 찾아보면 없을 것이다.

어찌하여 이미지를 추론하는 것은 찾을 수 있다.

문제는 동영상을 추론하는 게 필요하다.

그에 대한 예제는 ssd_mobilenet 모델을 제공하고 있다.


ultralytics 에서 제공하는 export 방법을 사용하여 모바일용 모델을 생성한다.

format='tflite' 옵션을 사용하면 3가지 속성이 default 로 설정된다.

half=False, int8=False, imgsz=640 이다.

각각의 설정을 변경하면서 테스트 하여 속도를 체감해보길 권장한다.

나의 경우는 half 와 int8 은 양자화인데 속도에 크게 좌우되지 않고 정밀도를 낮추는거 같았다.

imgsz 를 조정하였는데 224, 320, 4800, 640 순으로 진행하였는데

224 가 속도가 가장 빨랐다.


export 하는 중에 input 텐서의 속성을 알려주는데 이게 중요하다.

[1, 224, 224, 3] 으로 나올 것이다.

output 텐서는 [1, 6, 1029] 이런 식으로 나올 것이다.

이 텐서의 3 ~ 4차원 행렬을 무시해서 삽질을 오래 하였다.


그리고 이런 부분에 대한 설명이 자세하지 않아서

인공지능 및 딥러닝을 전공하지 않은 개발자들에게 허들로 작용할 수 있다.

모델은 여기서 가져와서 이렇게 사용한다.

enum _Codes { init, busy, ready, detect, result }
class _Command {
const _Command(this.code, {this.args});
final _Codes code;
final List<Object>? args;
}
class Detector {
static const String _modelPath = 'assets/models/elf.tflite';
static const String _labelPath = 'assets/models/elf.txt';
Detector._(this._isolate, this._interpreter, this._labels);
final Isolate _isolate;
late final Interpreter _interpreter;
late final List<String> _labels;
late final SendPort _sendPort;
bool _isReady = false;
final StreamController<Map<String, dynamic>> resultsStream =
StreamController<Map<String, dynamic>>();
static Future<Detector> start() async {
final ReceivePort receivePort = ReceivePort();
final Isolate isolate =
await Isolate.spawn(_DetectorServer._run, receivePort.sendPort);
final Detector result = Detector._(
isolate,
await _loadModel(),
await _loadLabels(),
);
receivePort.listen((message) {
result._handleCommand(message as _Command);
});
return result;
}
static Future<Interpreter> _loadModel() async {
final interpreterOptions = InterpreterOptions();
// Use XNNPACK Delegate
if (Platform.isAndroid) {
interpreterOptions.addDelegate(XNNPackDelegate());
}
return Interpreter.fromAsset(
_modelPath,
options: interpreterOptions..threads = 4,
);
}
static Future<List<String>> _loadLabels() async {
return (await rootBundle.loadString(_labelPath)).split('\n');
}
void processFrame(CameraImage cameraImage) {
if (_isReady) {
_sendPort.send(_Command(_Codes.detect, args: [cameraImage]));
}
}
void _handleCommand(_Command command) {
switch (command.code) {
case _Codes.init:
_sendPort = command.args?[0] as SendPort;
RootIsolateToken rootIsolateToken = RootIsolateToken.instance!;
_sendPort.send(_Command(_Codes.init,
args: [rootIsolateToken, _interpreter.address, _labels]));
case _Codes.ready:
_isReady = true;
case _Codes.busy:
_isReady = false;
case _Codes.result:
_isReady = true;
resultsStream.add(command.args?[0] as Map<String, dynamic>);
default:
debugPrint('Detector unrecognized command: ${command.code}');
}
}
void stop() {
_isolate.kill();
}
}
class _DetectorServer {
int mlModelInputSize = 640;
Interpreter? _interpreter;
List<String>? _labels;
_DetectorServer(this._sendPort);
final SendPort _sendPort;
static void _run(SendPort sendPort) {
ReceivePort receivePort = ReceivePort();
final _DetectorServer server = _DetectorServer(sendPort);
receivePort.listen((message) async {
final _Command command = message as _Command;
await server._handleCommand(command);
});
sendPort.send(_Command(_Codes.init, args: [receivePort.sendPort]));
}
Future<void> _handleCommand(_Command command) async {
switch (command.code) {
case _Codes.init:
RootIsolateToken rootIsolateToken =
command.args?[0] as RootIsolateToken;
BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken);
_interpreter = Interpreter.fromAddress(command.args?[1] as int);
mlModelInputSize = _interpreter?.getInputTensors().first.shape[1] ?? 640;
_labels = command.args?[2] as List<String>;
_sendPort.send(const _Command(_Codes.ready));
case _Codes.detect:
_sendPort.send(const _Command(_Codes.busy));
_convertCameraImage(command.args?[0] as CameraImage);
default:
debugPrint('_DetectorService unrecognized command ${command.code}');
}
}
void _convertCameraImage(CameraImage cameraImage) {
var preConversionTime = DateTime.now().millisecondsSinceEpoch;
convertCameraImageToImage(cameraImage).then((image) {
if (image != null) {
if (Platform.isAndroid) {
image = image_lib.copyRotate(image, angle: 90);
}
final results = analyseImage(image, preConversionTime);
_sendPort.send(_Command(_Codes.result, args: [results]));
}
});
}
Map<String, dynamic> analyseImage(
image_lib.Image? image, int preConversionTime) {
var conversionElapsedTime =
DateTime.now().millisecondsSinceEpoch - preConversionTime;
var preProcessStart = DateTime.now().millisecondsSinceEpoch;
final imageInput = image_lib.copyResize(image!,
width: mlModelInputSize, height: mlModelInputSize);
final imageMatrix = List.generate(
imageInput.height,
(y) => List.generate(
imageInput.width,
(x) {
final pixel = imageInput.getPixel(x, y);
return [pixel.rNormalized, pixel.gNormalized, pixel.bNormalized];
},
),
);
var preProcessElapsedTime =
DateTime.now().millisecondsSinceEpoch - preProcessStart;
var inferenceTimeStart = DateTime.now().millisecondsSinceEpoch;
final output = _runInference(imageMatrix);
List<List<double>> rawOutput = (output.first as List).first as List<List<double>>;
List<int> idx = [];
List<String> cls = [];
List<List<double>> box = [];
List<double> conf = [];
final numOfLabels = _labels?.length ?? 0;
final count = numOfLabels + 4;
(idx, box, conf) = nms(rawOutput, count, confidenceThreshold: 0.1, iouThreshold: 0.4);
if (idx.isNotEmpty) {
cls = idx.map((e) => _labels![e]).toList();
}
var inferenceElapsedTime =
DateTime.now().millisecondsSinceEpoch - inferenceTimeStart;
var totalElapsedTime =
DateTime.now().millisecondsSinceEpoch - preConversionTime;
return {
"cls": cls,
"box": box,
"conf": conf,
"stats": <String, String>{
'Conversion time:': conversionElapsedTime.toString(),
'Pre-processing time:': preProcessElapsedTime.toString(),
'Inference time:': inferenceElapsedTime.toString(),
'Total prediction time:': totalElapsedTime.toString(),
'Frame': '${image.width} X ${image.height}',
},
};
}
List<Object> _runInference(List<List<List<num>>> imageMatrix) {
final input = [imageMatrix];
final numOut = _interpreter?.getOutputTensors().first.shape[0] ?? 1;
final numOut1 = _interpreter?.getOutputTensors().first.shape[1] ?? 1;
final numOut2 = _interpreter?.getOutputTensors().first.shape[2] ?? 1;
final outputs = List<num>.filled(numOut * numOut1 * numOut2, 0).reshape([numOut, numOut1, numOut2]);
var map = <int, Object>{};
map[0] = outputs;
_interpreter!.runForMultipleInputs([input], map);
return map.values.toList();
}
}



댓글

이 블로그의 인기 게시물

한글 2010 에서 Ctrl + F10 누르면 특수문자 안뜰 때

맥 화면이 안나올때 조치방법

아이폰에서 RFID 사용하는 방법