Adding isolate
This commit is contained in:
parent
ca9f74290c
commit
9c5587df03
|
@ -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);
|
||||||
|
}
|
|
@ -17,9 +17,9 @@ RecipeModel _$RecipeModelFromJson(Map<String, dynamic> json) => RecipeModel(
|
||||||
instructions: (json['instructions'] as List<dynamic>?)
|
instructions: (json['instructions'] as List<dynamic>?)
|
||||||
?.map((e) => e as String)
|
?.map((e) => e as String)
|
||||||
.toList(),
|
.toList(),
|
||||||
prepTime: json['prepTime'] as int?,
|
prepTime: (json['prepTime'] as num?)?.toInt(),
|
||||||
cookTime: json['cookTime'] as int?,
|
cookTime: (json['cookTime'] as num?)?.toInt(),
|
||||||
servings: json['servings'] as int?,
|
servings: (json['servings'] as num?)?.toInt(),
|
||||||
utensils: (json['utensils'] as List<dynamic>?)
|
utensils: (json['utensils'] as List<dynamic>?)
|
||||||
?.map((e) => e as String)
|
?.map((e) => e as String)
|
||||||
.toList(),
|
.toList(),
|
||||||
|
|
|
@ -7,9 +7,9 @@ part of 'utensil_model.dart';
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
Utensil _$UtensilFromJson(Map<String, dynamic> json) => Utensil(
|
Utensil _$UtensilFromJson(Map<String, dynamic> json) => Utensil(
|
||||||
id: json['id'] as int?,
|
id: (json['id'] as num?)?.toInt(),
|
||||||
name: json['name'] as String?,
|
name: json['name'] as String?,
|
||||||
isSelected: json['isSelected'] as int?,
|
isSelected: (json['isSelected'] as num?)?.toInt(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$UtensilToJson(Utensil instance) => <String, dynamic>{
|
Map<String, dynamic> _$UtensilToJson(Utensil instance) => <String, dynamic>{
|
||||||
|
|
|
@ -13,7 +13,7 @@ BaseResponse<T> _$BaseResponseFromJson<T>(
|
||||||
BaseResponse<T>(
|
BaseResponse<T>(
|
||||||
data: _$nullableGenericFromJson(json['data'], fromJsonT),
|
data: _$nullableGenericFromJson(json['data'], fromJsonT),
|
||||||
)
|
)
|
||||||
..code = json['code'] as int?
|
..code = (json['code'] as num?)?.toInt()
|
||||||
..message = json['message'] as String?;
|
..message = json['message'] as String?;
|
||||||
|
|
||||||
Map<String, dynamic> _$BaseResponseToJson<T>(
|
Map<String, dynamic> _$BaseResponseToJson<T>(
|
||||||
|
|
|
@ -65,9 +65,14 @@ class RecipeDetectionViewModel extends BaseViewModel {
|
||||||
hideLoadingContainer();
|
hideLoadingContainer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _resetBounding(){
|
||||||
|
classes.clear();
|
||||||
|
bboxes.clear();
|
||||||
|
scores.clear();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> updatePostProcess() async {
|
Future<void> updatePostProcess() async {
|
||||||
detectedIngredients.clear();
|
detectedIngredients.clear();
|
||||||
print("MASUK 1");
|
|
||||||
if (inferenceOutput.isEmpty) {
|
if (inferenceOutput.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -76,8 +81,6 @@ class RecipeDetectionViewModel extends BaseViewModel {
|
||||||
List<List<double>> newBboxes = [];
|
List<List<double>> newBboxes = [];
|
||||||
List<double> newScores = [];
|
List<double> newScores = [];
|
||||||
|
|
||||||
print("MASUK 2");
|
|
||||||
|
|
||||||
/// Wait this process with loading
|
/// Wait this process with loading
|
||||||
(newClasses, newBboxes, newScores) = await model.postprocess(
|
(newClasses, newBboxes, newScores) = await model.postprocess(
|
||||||
inferenceOutput,
|
inferenceOutput,
|
||||||
|
@ -86,7 +89,6 @@ class RecipeDetectionViewModel extends BaseViewModel {
|
||||||
confidenceThreshold: confidenceThreshold,
|
confidenceThreshold: confidenceThreshold,
|
||||||
iouThreshold: iouThreshold,
|
iouThreshold: iouThreshold,
|
||||||
);
|
);
|
||||||
print("MASUK 3");
|
|
||||||
|
|
||||||
debugPrint('Detected ${newClasses} classes');
|
debugPrint('Detected ${newClasses} classes');
|
||||||
debugPrint('Detected ${newBboxes.length} boxed');
|
debugPrint('Detected ${newBboxes.length} boxed');
|
||||||
|
@ -132,17 +134,11 @@ class RecipeDetectionViewModel extends BaseViewModel {
|
||||||
const CustomCameraWidget(compressionQuality: 80),
|
const CustomCameraWidget(compressionQuality: 80),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
print("MASUK 9");
|
|
||||||
|
|
||||||
|
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
print("MASUK 10");
|
|
||||||
|
|
||||||
imageFile.value = data;
|
imageFile.value = data;
|
||||||
// _startTimer();
|
// _startTimer();
|
||||||
await Future.delayed(const Duration(milliseconds: 500));
|
_resetBounding();
|
||||||
print("MASUK 11");
|
|
||||||
|
|
||||||
_detectIngredients();
|
_detectIngredients();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -152,18 +148,14 @@ class RecipeDetectionViewModel extends BaseViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _detectIngredients() async {
|
void _detectIngredients() async {
|
||||||
print("MASUK SINI 1");
|
showLoadingDialog();
|
||||||
|
|
||||||
// showLoadingDialog();
|
|
||||||
|
|
||||||
final image = img.decodeImage(await imageFile.value!.readAsBytes())!;
|
final image = img.decodeImage(await imageFile.value!.readAsBytes())!;
|
||||||
print("MASUK SINI 2");
|
|
||||||
|
|
||||||
imageHeight.value = image.height;
|
imageHeight.value = image.height;
|
||||||
imageWidth.value = image.width;
|
imageWidth.value = image.width;
|
||||||
inferenceOutput.value = model.infer(image);
|
inferenceOutput.value = await model.inferenceImage(image);
|
||||||
|
closeLoadingDialog();
|
||||||
|
|
||||||
print("MASUK SINI 3");
|
|
||||||
updatePostProcess();
|
updatePostProcess();
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -74,6 +74,7 @@ import 'dart:math';
|
||||||
sortedBestScores.removeAt(index);
|
sortedBestScores.removeAt(index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (finalClasses, finalBboxes, finalScores);
|
return (finalClasses, finalBboxes, finalScores);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
|
import 'dart:isolate';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:tflite_flutter/tflite_flutter.dart';
|
import 'package:tflite_flutter/tflite_flutter.dart';
|
||||||
import 'package:image/image.dart';
|
import 'package:image/image.dart';
|
||||||
|
|
||||||
|
import 'isolate_inference.dart';
|
||||||
import 'nms.dart';
|
import 'nms.dart';
|
||||||
|
|
||||||
class YoloModel {
|
class YoloModel {
|
||||||
|
@ -10,6 +13,7 @@ class YoloModel {
|
||||||
final int inHeight;
|
final int inHeight;
|
||||||
final int numClasses;
|
final int numClasses;
|
||||||
Interpreter? _interpreter;
|
Interpreter? _interpreter;
|
||||||
|
late final IsolateInference isolateInference;
|
||||||
|
|
||||||
YoloModel(
|
YoloModel(
|
||||||
this.modelPath,
|
this.modelPath,
|
||||||
|
@ -20,11 +24,12 @@ class YoloModel {
|
||||||
|
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
_interpreter = await Interpreter.fromAsset(modelPath);
|
_interpreter = await Interpreter.fromAsset(modelPath);
|
||||||
|
isolateInference = IsolateInference();
|
||||||
|
await isolateInference.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<List<double>> infer(Image image) {
|
List<List<double>> infer(Image image) {
|
||||||
assert(_interpreter != null, 'The model must be initialized');
|
assert(_interpreter != null, 'The model must be initialized');
|
||||||
|
|
||||||
final imgResized = copyResize(image, width: inWidth, height: inHeight);
|
final imgResized = copyResize(image, width: inWidth, height: inHeight);
|
||||||
final imgNormalized = List.generate(
|
final imgNormalized = List.generate(
|
||||||
inHeight,
|
inHeight,
|
||||||
|
@ -51,6 +56,21 @@ class YoloModel {
|
||||||
return output[0];
|
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(
|
Future<(List<int>, List<List<double>>, List<double>)> postprocess(
|
||||||
List<List<double>> unfilteredBboxes,
|
List<List<double>> unfilteredBboxes,
|
||||||
int imageWidth,
|
int imageWidth,
|
||||||
|
|
Loading…
Reference in New Issue