ultralytics / yolo-flutter-app

A Flutter plugin for Ultralytics YOLO computer vision models
https://ultralytics.com
GNU Affero General Public License v3.0
99 stars 40 forks source link

Unhandled Exception: type '_Map<Object?, Object?>' is not a subtype of type 'Map<String, dynamic>?' in type cast #45

Open m-naeem66622 opened 3 months ago

m-naeem66622 commented 3 months ago

Problem Description

I had used the example which is prefectly with my model. Now I have tried to feed a single image using camera to take a picture or image picker to get the image from the gallery. but somehow it's not working throwing an error. the issue is coming from here https://github.com/ultralytics/yolo-flutter-app/blob/6ef5571270c5d5229ca20c9c4502010863bb7898/lib/ultralytics_yolo_platform_channel.dart#L152

Performing hot restart...                                               
Restarted application in 2,217ms.
I/flutter (14663): File copied to: /data/user/0/com.ultralytics.ultralytics_yolo_example/files/assets/yolov8m_int8.tflite
I/flutter (14663): File copied to: /data/user/0/com.ultralytics.ultralytics_yolo_example/files/assets/metadata.yaml
I/System.out(14663): INPUT_SIZE:320
I/tflite  (14663): Replacing 318 out of 318 node(s) with delegate (TfLiteGpuDelegateV2) node, yielding 1 partitions for the whole graph.
I/tflite  (14663): Created 1 GPU delegate kernels.
I/flutter (14663): File exists
E/flutter (14663): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: type '_Map<Object?, Object?>' is not a subtype of type 'Map<String, dynamic>?' in type cast
E/flutter (14663): #0      PlatformChannelUltralyticsYolo.detectImage.<anonymous closure> (package:ultralytics_yolo/ultralytics_yolo_platform_channel.dart:152:19)
E/flutter (14663): #1      _Array.forEach (dart:core-patch/array.dart:40:8)
E/flutter (14663): #2      PlatformChannelUltralyticsYolo.detectImage (package:ultralytics_yolo/ultralytics_yolo_platform_channel.dart:151:13)
E/flutter (14663): <asynchronous suspension>
E/flutter (14663): #3      _ImageDetectionScreenState._captureAndDetectImage (package:ultralytics_yolo_example/main.dart:120:31)
E/flutter (14663): <asynchronous suspension>
E/flutter (14663): 

Reproducible Code

import 'package:flutter/material.dart';
import 'dart:io' as io;
import 'package:image_picker/image_picker.dart';
import 'package:ultralytics_yolo/ultralytics_yolo.dart';
import 'package:ultralytics_yolo/yolo_model.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:flutter/services.dart';
import 'dart:io';
import 'package:permission_handler/permission_handler.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ImageDetectionScreen(),
    );
  }
}

class ImageDetectionScreen extends StatefulWidget {
  @override
  _ImageDetectionScreenState createState() => _ImageDetectionScreenState();
}

class _ImageDetectionScreenState extends State<ImageDetectionScreen> {
  late ObjectDetector detector;
  List<DetectedObject?>? _detectedObjects;
  final ImagePicker _picker = ImagePicker();
  String? _imagePath;

  @override
  void initState() {
    super.initState();
    _initializeDetector();
  }

  Future<void> _initializeDetector() async {
    final modelPath = await _copy('assets/yolov8m_int8.tflite');
    final metadataPath = await _copy('assets/metadata.yaml');
    final model = LocalYoloModel(
        id: 'custom_model',
        modelPath: modelPath,
        task: Task.detect,
        format: Format.tflite,
        metadataPath: metadataPath);

    detector = ObjectDetector(model: model);
    await detector.loadModel();
  }

  Future<bool> _checkPermissions() async {
    List<Permission> permissions = [];

    var cameraStatus = await Permission.camera.status;
    if (!cameraStatus.isGranted) permissions.add(Permission.camera);

    var storageStatus = await Permission.storage.status;
    if (!storageStatus.isGranted) permissions.add(Permission.storage);

    if (permissions.isEmpty) {
      return true;
    } else {
      try {
        Map<Permission, PermissionStatus> statuses =
            await permissions.request();
        return statuses[Permission.camera] == PermissionStatus.granted &&
            statuses[Permission.storage] == PermissionStatus.granted;
      } on Exception catch (_) {
        return false;
      }
    }
  }

  Future<String> _copy(String assetPath) async {
    try {
      final directory = await getApplicationSupportDirectory();
      final path = join(directory.path, assetPath);
      await io.Directory(dirname(path)).create(recursive: true);
      final file = io.File(path);
      if (!await file.exists()) {
        final byteData = await rootBundle.load(assetPath);
        await file.writeAsBytes(byteData.buffer
            .asUint8List(byteData.offsetInBytes, byteData.lengthInBytes));
      }
      print('File copied to: $path');
      return file.path;
    } catch (e) {
      print('Error copying file: $e');
      rethrow;
    }
  }

  Future<void> _captureAndDetectImage(ImageSource source) async {
    bool permissionsGranted = await _checkPermissions();
    if (!permissionsGranted) {
      ScaffoldMessenger.of(this.context).showSnackBar(
        SnackBar(content: Text('Permissions not granted')),
      );
      return;
    }

    final pickedFile = await _picker.pickImage(source: source);
    if (pickedFile != null) {
      setState(() {
        _imagePath = pickedFile.path;
      });

      // check if the file exists or not
      if (!await File(pickedFile.path).exists()) {
        print('File does not exist');
        return;
      } else {
        print('File exists');
      }

      final detectedObjects = await detector.detect(imagePath: pickedFile.path);

      print('Raw detection output: $detectedObjects');

      if (detectedObjects == null || detectedObjects.isEmpty) {
        print('No objects detected or empty response');
        setState(() {
          _detectedObjects = [];
        });
        return;
      }

      setState(() {
        _detectedObjects = detectedObjects;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('YOLO Image Detection'),
      ),
      body: Column(
        children: [
          if (_imagePath != null) Image.file(File(_imagePath!)),
          if (_detectedObjects != null)
            Expanded(
              child: ListView.builder(
                itemCount: _detectedObjects!.length,
                itemBuilder: (context, index) {
                  final detectedObject = _detectedObjects![index];
                  if (detectedObject == null) return Container();
                  return ListTile(
                    title: Text('Label: ${detectedObject.label}'),
                    subtitle: Text(
                        'Confidence: ${(detectedObject.confidence * 100).toStringAsFixed(2)}%'),
                  );
                },
              ),
            ),
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              ElevatedButton(
                onPressed: () => _captureAndDetectImage(ImageSource.camera),
                child: Text('Capture Image'),
              ),
              SizedBox(width: 20),
              ElevatedButton(
                onPressed: () => _captureAndDetectImage(ImageSource.gallery),
                child: Text('Import Image'),
              ),
            ],
          ),
        ],
      ),
    );
  }
}
muhammad-qasim-cowlar commented 3 months ago

Have you tried using String instead of String? here: String? _imagePath;

pderrenger commented 3 months ago

@muhammad-qasim-cowlar thank you for reaching out! It looks like the issue might be related to type casting within the detectImage method. Specifically, the error message indicates a type mismatch between _Map<Object?, Object?> and Map<String, dynamic>?.

To address your suggestion, changing String? _imagePath to String _imagePath might not directly resolve the type casting issue, but it's worth ensuring that _imagePath is properly initialized and not null when used.

Here are a few steps to help troubleshoot and potentially resolve the issue:

  1. Update Packages: Ensure you are using the latest versions of all relevant packages. Sometimes, bugs are fixed in newer releases.

  2. Type Casting: The error suggests a type casting issue. You might want to explicitly cast the map to Map<String, dynamic> where the error occurs. For example:

    final detectedObjects = await detector.detect(imagePath: pickedFile.path) as Map<String, dynamic>;
  3. Null Safety: Ensure that all variables are properly initialized and checked for null values before use. For example:

    if (_imagePath != null) {
       // Use _imagePath
    }
  4. Debugging: Add some debug prints to check the types of the variables at runtime:

    print(detectedObjects.runtimeType);

Here’s a small code snippet to illustrate the type casting:

final detectedObjects = await detector.detect(imagePath: pickedFile.path);
if (detectedObjects is Map<String, dynamic>) {
    // Proceed with detectedObjects
} else {
    print('Type mismatch: ${detectedObjects.runtimeType}');
}

Please try these steps and let us know if the issue persists. If it does, providing additional details about the exact versions of the packages you are using and any other relevant context would be helpful.

Thank you for your patience and for being a part of the YOLO community! 😊

XeroDays commented 3 months ago

I am also facing same issue

this is my code just like you mentioned @pderrenger i tried same


void scan() async {
    final objectDetector = ObjectDetector(
      model: LoaderController.model!,
    );
    await objectDetector.loadModel(useGpu: true);
    final detectedObjects = await objectDetector.detect(imagePath: imagePath);
    if (detectedObjects is Map<Object?, Object?>) { 
      print('Detected objects: $detectedObjects');
    } else {
      print('Type mismatch: ${detectedObjects.runtimeType}');
    }
  }
}
image

@m-naeem66622 did you found any solutions?

XeroDays commented 3 months ago

i fixed this issue, @pderrenger . can i create PR to this repo?

XeroDays commented 3 months ago

47 Pull Requests has fixed this bug.

pderrenger commented 3 months ago

Thank you for your proactive approach and for resolving the issue! πŸŽ‰

We appreciate your willingness to contribute to the Ultralytics repository. Please feel free to create a Pull Request (PR) with your fix. Make sure to include a clear description of the issue and how your changes address it. This will help the maintainers review and merge your PR efficiently.

Before submitting, kindly ensure that the issue is reproducible in the latest versions of the packages and that your fix is compatible with them. This helps maintain consistency and reliability across the project.

Looking forward to your contribution! If you have any questions or need further assistance, feel free to ask here.

Thank you for being an active member of the YOLO community! 😊

ice6 commented 1 month ago

encountered same problem.

so the bug was fixed. a fixed version like 0.0.4 release is good for users :)

XeroDays commented 1 month ago

@ice6 Bug is fixed but they didnt released it to prod. @pderrenger @glenn-jocher Can you guys help on this pne?

glenn-jocher commented 1 month ago

Please update to the latest version of the package to see if the fix is included. If the issue persists, let us know.

ice6 commented 1 month ago

even the bug was fixed.

I think it is not a good idea to return List<DetectedObject?>? type to end user, List<DetectedObject>? is better.

now, I have to do the something like this in my app:

    for (var f in files) {
      var imagePath = f.xFile.path;
      var detectedObjects = await predictor!.detect(imagePath: imagePath);
      f.detectedObjects = detectedObjects?.where((obj) => obj != null).map((obj) => obj!).toList() ?? <DetectedObject>[];
    }

a null DetectedObject is non-sense for end users ;)

glenn-jocher commented 1 month ago

Thank you for your feedback. We appreciate your suggestion regarding the return type. We'll consider this for future improvements to enhance usability. If you have any more insights, feel free to share!

ohTIO commented 1 month ago

how do i use an image picker instead of camera preview.

pderrenger commented 1 month ago

You can use the image_picker package to select images from the gallery. Here's a quick example:

final pickedFile = await ImagePicker().pickImage(source: ImageSource.gallery);
if (pickedFile != null) {
  final imagePath = pickedFile.path;
  // Use imagePath with your detector
}

Ensure you have the necessary permissions set up in your app.

HansLuft778 commented 1 month ago

I am still encountering the issue. When will the fix go live?

pderrenger commented 1 month ago

@HansLuft778 please ensure you're using the latest version of the package, as updates may include the fix. If the issue persists, let us know.

XeroDays commented 1 month ago

@HansLuft778 The issue is merged with base branch, but still new release is not on the air.

mnawazshah commented 1 month ago

Having same issue .... It is working fine if we use UltralyticsCameraPreview but if we want to detect on image then getting this error. If this issue was fixed then it is not in production yet

pderrenger commented 1 month ago

Please ensure you're using the latest package version, as updates may include the fix. If the issue persists, let us know.

XeroDays commented 1 month ago

@pderrenger is a bot.

pderrenger commented 1 month ago

@XeroDays i'm here to assist with any questions or issues you have regarding Ultralytics. How can I help you today?

mnawazshah commented 1 month ago

@XeroDays yes πŸ˜‚

LeeClayberg commented 1 month ago

@glenn-jocher Can these changes be pushed to pub.dev?