TC_neva
TC_neva

Reputation: 71

Having trouble getting OSD in Flutter app using flutter_tesseract_ocr package

I'm also new to flutter and tesseract so might be a problem with my understanding.

I am wanting to make an app that takes an image from my gallery and can read the text from them.

I'm using Google ML Kit Text Recognition V2 for this, however I want to account for cases where text is not correctly facing and so need the OSD, specifically the orientation. I have been able to get this using the CLI and pytesseract with a simple python script but have been having trouble doing the same with flutter.

I'm pretty sure that the tessdata is in the correct directory. I've created a assets/tessdata/tessdata_config.json and added the necessary traineddata files into assets/tessdata directory and added both to the pubspec.yaml.

"main.dart"

import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'dart:io';
import 'package:google_mlkit_text_recognition/google_mlkit_text_recognition.dart';
import 'package:flutter_application_cards/preprocess_img.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key, this.imgPicker});

  final ImagePicker? imgPicker;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'FF photos',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: MyHomePage(
        title: 'FF photos',
        imagePicker: imgPicker ?? ImagePicker(),
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title, required this.imagePicker});

  final String title;
  final ImagePicker imagePicker;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  XFile? _mediaFile;
  String _recognizedText = '';

  /// Picks an image from the selected source (Gallery or Camera)
  Future<void> _pickImage(ImageSource source) async {
    try {
      final XFile? pickedFile = await widget.imagePicker.pickImage(source: source);
      if (pickedFile != null) {
        setState(() {
          _mediaFile = pickedFile;
        });
        _processImage(File(pickedFile.path));
      }
    } catch (e) {
      print('Error picking image: $e');
    }
  }

  /// Processes the image and extracts text using Google ML Kit
  Future<void> _processImage(File imageFile) async {
    // Put preprocessing function in here
    await isImgBlurry(imageFile);
    final inputImage = InputImage.fromFile(imageFile);
    final textRecognizer = TextRecognizer();

    try {
      final RecognizedText recognizedText = await textRecognizer.processImage(inputImage);
      setState(() {
        _recognizedText = recognizedText.text;
      });
      print('Recognized Text:\n$_recognizedText');
    } catch (e) {
      print('Error recognizing text: $e');
    } finally {
      textRecognizer.close();
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            _mediaFile == null
                ? const Text('No image selected.')
                : Image.file(File(_mediaFile!.path), height: 250),
            const SizedBox(height: 20),
            ElevatedButton.icon(
              onPressed: () => _pickImage(ImageSource.gallery),
              icon: const Icon(Icons.photo_library),
              label: const Text('Pick from Gallery'),
            ),
            const SizedBox(height: 10),
            ElevatedButton.icon(
              onPressed: () => _pickImage(ImageSource.camera),
              icon: const Icon(Icons.camera_alt),
              label: const Text('Take Photo'),
            ),
            const SizedBox(height: 20),
            _recognizedText.isNotEmpty
                ? Padding(
                    padding: const EdgeInsets.all(16.0),
                    child: Text(_recognizedText),
                  )
                : Container(),
          ],
        ),
      ),
    );
  }
}

"preprocess_img.dart"

import 'dart:io';

import 'package:opencv_core/opencv.dart';
import 'package:flutter_tesseract_ocr/flutter_tesseract_ocr.dart';

Future<void> isImgBlurry(File img, {double threshold = 100.0}) async {
  final test = imread(img.path);

  // Convert to grayscale
  final Mat grayImg = cvtColor(test, COLOR_BGR2GRAY);

  // Use laplacian method
  Mat lapImg = laplacian(grayImg, MatType.CV_64F);
  final double variance = lapImg.variance().val1;

  if (variance < threshold) {
    print("$variance, blurry");
  }
  else {
    print("Not blurry");
  }

  String osdData = await FlutterTesseractOcr.extractText(img.path, language: 'eng', args: {"psm": "0"});
  // psm 0 = Orientation and script detection (OSD) only.
  // String is empty
  print("Image osd:\n$osdData");
  
  int orientation = 0;
  int rotate = 0;
  String script = "N/A";

  RegExp orientationRegExp = RegExp(r'Orientation in degrees: (\d+)');
  RegExp rotateRegExp = RegExp(r'Rotate: (\d+)');
  RegExp scriptRegExp = RegExp(r'Script: (\w+)');

  Match? orientationMatch = orientationRegExp.firstMatch(osdData);
  Match? rotateMatch = rotateRegExp.firstMatch(osdData);
  Match? scriptMatch = scriptRegExp.firstMatch(osdData);

  if (orientationMatch != null) {
    orientation = int.parse(orientationMatch.group(1)!);
  }
  if (rotateMatch != null) {
    rotate = int.parse(rotateMatch.group(1)!);
  }
  if (scriptMatch != null) {
    script = scriptMatch.group(1)!;
  }

  // Output the extracted information
  print("Detected Orientation: $orientation degrees");
  print("Rotate by: $rotate degrees to correct");
  print("Detected Script: $script");
}

Upvotes: 0

Views: 28

Answers (0)

Related Questions