📝

ImagePickerとOCRとSelectableText

2024/01/31に公開

マンガをスクショして単語や表現を抽出

Flutter大学の第十五期共同開発で
英単語帳を作る企画に参加している。
担当しているのは、スクショ画像から単語を拾う機能。

使うのはこの三つ

dependencies:

  image_picker: ^1.0.7
  google_mlkit_text_recognition: ^0.11.0
  provider: ^6.1.1

mlkitにはいろんな機能があるらしいが、これは文字列に特化したタイプ。
いろんな言語に対応させたければ+αの設定が必要だけれど
今回は英語を英語として読むだけなので、シンプルに。

裏側?の設定

ここが一番難関だったPodfile、
documentや記事の情報、単独では動かず、継ぎ接ぎしているので怪しげ。
でも、動く。

# add this line:
$iOSVersion = '12.0'

post_install do |installer|

  xcode_base_version = `xcodebuild -version | grep 'Xcode' | awk '{print $2}' | cut -d . -f 1`
    # add these lines:
  installer.pods_project.build_configurations.each do |config|
    config.build_settings["EXCLUDED_ARCHS[sdk=*]"] = "armv7"
    config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = $iOSVersion
  end

  installer.pods_project.targets.each do |target|
    flutter_additional_ios_build_settings(target)

     # add these lines:
    target.build_configurations.each do |config|
      if Gem::Version.new($iOSVersion) > Gem::Version.new(config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'])
         config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = $iOSVersion
      end
      if config.base_configuration_reference && Integer(xcode_base_version) >= 15
      		xcconfig_path = config.base_configuration_reference.real_path
      		xcconfig = File.read(xcconfig_path)
      		xcconfig_mod = xcconfig.gsub(/DT_TOOLCHAIN_DIR/, "TOOLCHAIN_DIR")
      		File.open(xcconfig_path, "w") { |file| file << xcconfig_mod }
      end
    end

  end
end

こちらはRunnerのinfo.plist、ライブラリへのアクセス許可

	<key>NSPhotoLibraryUsageDescription</key>
	<string>選択した画像から文字列の抽出を行います</string>

Android側はappのbuild.gradleで
compileSdkVersionとminSdkVersionが21以上であることを確認
英語以外の言語を使うなら、+αの設定が必要。

View

imagepickerで画像を取得してOCRにかける。
もちろん画像にもよるのだけれど、
英文がいくつかのブロックとして取得されるので、それをListで表示。
ListTileの中をSelectableTextにすることで、
任意の単語や表現だけを選択的にコピーできる。
それを単語帳の主機能に貼り付けよう、というわけ。

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'ocr_model.dart'; 

class OcrPage extends StatelessWidget {
  const OcrPage({super.key});

  
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<OcrModel>(
      create: (context) => OcrModel(),
      child: Consumer<OcrModel>(
        builder: (_, model, child) {
          return Scaffold(
            appBar: AppBar(
              title: const Text('OCR Page'),
            ),
            body: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  ElevatedButton(
                    onPressed:  () => model.getImage(),
                    child: const Text('ライブラリから選択する'),
                  ),
                  model.image == null
                      ? const Text('No image selected.')
                      : Image.file(model.image!), // 選択した画像を表示
                  ElevatedButton(
                    onPressed: () => model.processImage(),
                    child: const Text('OCRで読み込む'),
                  ),
                  Expanded(
                      child: ListView.builder(
                          itemCount: model.texts.length,
                          itemBuilder: (context, index) {
                            return ListTile(
                              title: SelectableText(model.texts[index]),
                            );
                          },
                        ),
                      ),
                ],
              ),
            ),
          );
        }
      ),
    );
  }
}

Model

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

class OcrModel with ChangeNotifier {
  File? _image;
  List<String> _texts = [];
  final picker = ImagePicker();

  File? get image => _image;
  List<String> get texts => _texts;

  Future<void> getImage() async {
    final pickedFile = await picker.pickImage(source: ImageSource.gallery);
    if (pickedFile != null) {
      _image = File(pickedFile.path);
      notifyListeners(); 
    } else {
      print('画像が選択できませんでした');
    }
  }

  Future<void> processImage() async {
    if (_image != null) {
      final inputImage = InputImage.fromFile(_image!);
      final textRecognizer = TextRecognizer(script: TextRecognitionScript.latin);
      final RecognizedText recognizedText = await textRecognizer.processImage(inputImage);
      _texts = recognizedText.blocks.map((block) => block.text).toList();
      notifyListeners(); 
    }
  }
}

値をコピペで受け渡すって、楽すぎないか!?

https://youtube.com/shorts/19FZw7iWKEc?feature=share

Flutter大学

Discussion