Adding isolate

This commit is contained in:
ibnubatutah 2024-05-15 00:29:08 +07:00
parent ca9f74290c
commit 9c5587df03
8 changed files with 174 additions and 25 deletions

View File

@ -0,0 +1,22 @@
import 'package:json_annotation/json_annotation.dart';
part 'detection_model.g.dart';
@JsonSerializable()
class DetectionModel {
List<int> finalClasses;
List<List<double>> finalBboxes;
List<double> finalScores;
DetectionModel({
required this.finalClasses,
required this.finalBboxes,
required this.finalScores,
});
Map<String, dynamic> toJson() => _$DetectionModelToJson(this);
factory DetectionModel.fromJson(Map<String, dynamic> json) =>
_$DetectionModelFromJson(json);
}

View File

@ -17,9 +17,9 @@ RecipeModel _$RecipeModelFromJson(Map<String, dynamic> json) => RecipeModel(
instructions: (json['instructions'] as List<dynamic>?)
?.map((e) => e as String)
.toList(),
prepTime: json['prepTime'] as int?,
cookTime: json['cookTime'] as int?,
servings: json['servings'] as int?,
prepTime: (json['prepTime'] as num?)?.toInt(),
cookTime: (json['cookTime'] as num?)?.toInt(),
servings: (json['servings'] as num?)?.toInt(),
utensils: (json['utensils'] as List<dynamic>?)
?.map((e) => e as String)
.toList(),

View File

@ -7,9 +7,9 @@ part of 'utensil_model.dart';
// **************************************************************************
Utensil _$UtensilFromJson(Map<String, dynamic> json) => Utensil(
id: json['id'] as int?,
id: (json['id'] as num?)?.toInt(),
name: json['name'] as String?,
isSelected: json['isSelected'] as int?,
isSelected: (json['isSelected'] as num?)?.toInt(),
);
Map<String, dynamic> _$UtensilToJson(Utensil instance) => <String, dynamic>{

View File

@ -13,7 +13,7 @@ BaseResponse<T> _$BaseResponseFromJson<T>(
BaseResponse<T>(
data: _$nullableGenericFromJson(json['data'], fromJsonT),
)
..code = json['code'] as int?
..code = (json['code'] as num?)?.toInt()
..message = json['message'] as String?;
Map<String, dynamic> _$BaseResponseToJson<T>(

View File

@ -65,9 +65,14 @@ class RecipeDetectionViewModel extends BaseViewModel {
hideLoadingContainer();
}
void _resetBounding(){
classes.clear();
bboxes.clear();
scores.clear();
}
Future<void> updatePostProcess() async {
detectedIngredients.clear();
print("MASUK 1");
if (inferenceOutput.isEmpty) {
return;
}
@ -76,8 +81,6 @@ class RecipeDetectionViewModel extends BaseViewModel {
List<List<double>> newBboxes = [];
List<double> newScores = [];
print("MASUK 2");
/// Wait this process with loading
(newClasses, newBboxes, newScores) = await model.postprocess(
inferenceOutput,
@ -86,7 +89,6 @@ class RecipeDetectionViewModel extends BaseViewModel {
confidenceThreshold: confidenceThreshold,
iouThreshold: iouThreshold,
);
print("MASUK 3");
debugPrint('Detected ${newClasses} classes');
debugPrint('Detected ${newBboxes.length} boxed');
@ -132,17 +134,11 @@ class RecipeDetectionViewModel extends BaseViewModel {
const CustomCameraWidget(compressionQuality: 80),
),
);
print("MASUK 9");
if (data != null) {
print("MASUK 10");
imageFile.value = data;
// _startTimer();
await Future.delayed(const Duration(milliseconds: 500));
print("MASUK 11");
_resetBounding();
_detectIngredients();
}
}
@ -152,18 +148,14 @@ class RecipeDetectionViewModel extends BaseViewModel {
}
void _detectIngredients() async {
print("MASUK SINI 1");
// showLoadingDialog();
showLoadingDialog();
final image = img.decodeImage(await imageFile.value!.readAsBytes())!;
print("MASUK SINI 2");
imageHeight.value = image.height;
imageWidth.value = image.width;
inferenceOutput.value = model.infer(image);
inferenceOutput.value = await model.inferenceImage(image);
closeLoadingDialog();
print("MASUK SINI 3");
updatePostProcess();

View File

@ -0,0 +1,114 @@
import 'dart:isolate';
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:image/image.dart' as image_lib;
import 'package:tflite_flutter/tflite_flutter.dart';
class IsolateInference {
static const String _debugName = "TFLITE_INFERENCE";
final ReceivePort _receivePort = ReceivePort();
late Isolate _isolate;
late SendPort _sendPort;
SendPort get sendPort => _sendPort;
Future<void> start() async {
_isolate = await Isolate.spawn<SendPort>(entryPoint, _receivePort.sendPort,
debugName: _debugName);
_sendPort = await _receivePort.first;
}
Future<void> close() async {
_isolate.kill();
_receivePort.close();
}
static void entryPoint(SendPort sendPort) async {
final port = ReceivePort();
sendPort.send(port.sendPort);
await for (final InferenceModel isolateModel in port) {
image_lib.Image? image;
image = isolateModel.image;
int inModelWidth = 640;
int inModelHeight = 640;
int numClasses = 6;
final imgResized = image_lib.copyResize(image!,
width: inModelWidth, height: inModelHeight);
final imgNormalized = List.generate(
inModelHeight,
(y) => List.generate(
inModelWidth,
(x) {
final pixel = imgResized.getPixel(x, y);
return [pixel.rNormalized, pixel.gNormalized, pixel.bNormalized];
},
),
);
// output shape:
// 1 : batch size
// 4 + 6: left, top, right, bottom and probabilities for each class
// 8400: num predictions
final output = [
List<List<double>>.filled(4 + numClasses, List<double>.filled(8400, 0))
];
int predictionTimeStart = DateTime.now().millisecondsSinceEpoch;
Interpreter interpreter =
Interpreter.fromAddress(isolateModel.interpreterAddress);
interpreter.run([imgNormalized], output);
debugPrint(
'Prediction time: ${DateTime.now().millisecondsSinceEpoch - predictionTimeStart} ms');
isolateModel.responsePort.send(output[0]);
}
}
static List<double> xywh2xyxy(List<double> bbox) {
double halfWidth = bbox[2] / 2;
double halfHeight = bbox[3] / 2;
return [
bbox[0] - halfWidth,
bbox[1] - halfHeight,
bbox[0] + halfWidth,
bbox[1] + halfHeight,
];
}
/// Computes the intersection over union between two bounding boxes encoded with
/// the xyxy format.
static double computeIou(List<double> bbox1, List<double> bbox2) {
assert(bbox1[0] < bbox1[2]);
assert(bbox1[1] < bbox1[3]);
assert(bbox2[0] < bbox2[2]);
assert(bbox2[1] < bbox2[3]);
// Determine the coordinate of the intersection rectangle
double xLeft = max(bbox1[0], bbox2[0]);
double yTop = max(bbox1[1], bbox2[1]);
double xRight = min(bbox1[2], bbox2[2]);
double yBottom = min(bbox1[3], bbox2[3]);
if (xRight < xLeft || yBottom < yTop) {
return 0;
}
double intersectionArea = (xRight - xLeft) * (yBottom - yTop);
double bbox1Area = (bbox1[2] - bbox1[0]) * (bbox1[3] - bbox1[1]);
double bbox2Area = (bbox2[2] - bbox2[0]) * (bbox2[3] - bbox2[1]);
double iou = intersectionArea / (bbox1Area + bbox2Area - intersectionArea);
assert(iou >= 0 && iou <= 1);
return iou;
}
}
class InferenceModel {
image_lib.Image? image;
int interpreterAddress;
late SendPort responsePort;
InferenceModel(this.image, this.interpreterAddress);
}

View File

@ -74,6 +74,7 @@ import 'dart:math';
sortedBestScores.removeAt(index);
}
}
return (finalClasses, finalBboxes, finalScores);
}

View File

@ -1,7 +1,10 @@
import 'dart:isolate';
import 'package:flutter/foundation.dart';
import 'package:tflite_flutter/tflite_flutter.dart';
import 'package:image/image.dart';
import 'isolate_inference.dart';
import 'nms.dart';
class YoloModel {
@ -10,6 +13,7 @@ class YoloModel {
final int inHeight;
final int numClasses;
Interpreter? _interpreter;
late final IsolateInference isolateInference;
YoloModel(
this.modelPath,
@ -20,11 +24,12 @@ class YoloModel {
Future<void> init() async {
_interpreter = await Interpreter.fromAsset(modelPath);
isolateInference = IsolateInference();
await isolateInference.start();
}
List<List<double>> infer(Image image) {
assert(_interpreter != null, 'The model must be initialized');
final imgResized = copyResize(image, width: inWidth, height: inHeight);
final imgNormalized = List.generate(
inHeight,
@ -51,6 +56,21 @@ class YoloModel {
return output[0];
}
Future<List<List<double>>> _inference(InferenceModel inferenceModel) async {
ReceivePort responsePort = ReceivePort();
isolateInference.sendPort
.send(inferenceModel..responsePort = responsePort.sendPort);
// get inference result.
var results = await responsePort.first;
return results;
}
// inference still image
Future<List<List<double>>> inferenceImage(Image image) async {
var isolateModel = InferenceModel(image, _interpreter?.address ?? 0);
return _inference(isolateModel);
}
Future<(List<int>, List<List<double>>, List<double>)> postprocess(
List<List<double>> unfilteredBboxes,
int imageWidth,