【FastAPI】日本語を含むファイル名のファイルをダウンロードする際のエラーと対処【備忘録】

お疲れ様です。

今回はFastAPIのファイルダウンロードで日本語のファイル名をダウンロードする際の注意点についてまとめました。

ちょうど1年前くらいにFastAPIでファイルダウンロードをするAPIを作成していました。 その際はファイル名を半角英数字のみで作成していたため気づかなかったのですが、この時のコードだと日本語を含むファイル名のファイルをダウンロードしようとするとエラーが起こります。

fallpoke-tech.hatenadiary.jp

ソースコード

今回もエラー再現のためにデモページとAPIを作成しました。
以下GitHubに残してありますので必要があればご確認ください。

github.com

実行してページを開くとこんな感じです。 demo_page

エラー内容と対処方法

エラーはFastAPIで作成したAPIエンドポイントのレスポンスの内容によるものです。
以下エラーが起こるパターンとエラーを解消したパターンを記載します。

エラーが発生する書き方

import io
from fastapi import FastAPI
from fastapi.responses import StreamingResponse

app = FastAPI()


@app.get("/download")
def download_from_df() -> StreamingResponse:
    """DataFrameを指定ファイル形式でダウンロードするAPI
       (ローカルにファイル保存せず、データをファイル化して返す)
    """
    stream = io.StringIO()
    sample_df.to_csv(stream, encoding='utf-8', index=False)
    stream.seek(0)

    filename = "日本語ファイルサンプル.csv"
    media_type = "text/csv"
    
    return StreamingResponse(
        content=stream, 
        media_type=media_type,
        headers={"Content-Disposition": f"attachment; filename={filename}"}
    )

ページ上のボタンを押してダウンロードしようとするとこのように「Internal Server Error」で表示されます。

error_app

Python側の表示を見ると下記のエラーが出ています。 これはStreamingResponseの処理中に起こっており、ファイル名に日本語を含む場合に再現します。
非ASCII文字が含まれていることが原因のようです。

UnicodeEncodeError: 'latin-1' codec can't encode characters in position 21-31: ordinal not in range(256)

error_fastapi

正しい書き方

import io
from urllib.parse import quote # 追加
from fastapi import FastAPI
from fastapi.responses import StreamingResponse

app = FastAPI()


@app.get("/download")
def download_from_df() -> StreamingResponse:
    """DataFrameを指定ファイル形式でダウンロードするAPI
       (ローカルにファイル保存せず、データをファイル化して返す)
    """
    stream = io.StringIO()
    sample_df.to_csv(stream, encoding='utf-8', index=False)
    stream.seek(0)

    filename = "日本語ファイルサンプル.csv"
    media_type = "text/csv"
    
    return StreamingResponse(
        content=stream, 
        media_type=media_type,
        headers={"Content-Disposition": f"attachment; filename*=UTF-8''{quote(filename)}"} # ここを修正
    )

urllib.parse.quote()を使用します。 こちらを適用させることで日本語を含む文字列(非ASCII文字を含む文字列)をURLエンコーディングしてファイル名に設定しています。

実際に実行するとこんな感じ。
日本語の部分が%から始まる特殊な文字に置き換えられています。

urllib.parse.quote

これで正常にダウンロードできます。実際にダウンロードすると変換する前の元の日本語ファイル名なっていることがわかります。

download

参考

下記を参考にしました。併せてご参照ください。
ありがとうございました。

qiita.com

WSL+DockerでSAM3の環境構築をしてお試し実行

お疲れ様です。

SAM3の実行環境をWSL+Dockerで作成し、実際に実行して試してみた記録です。

2025年11月にリリースされたSAM(Segment Anything Model)シリーズの最新モデルです。 SAM3では、プロンプトで画像内の検出したい物体を指示することで目的の物体のセグメンテーションとBBoxの出力ができます。

(他にも3Dオブジェクトに対応したSAM3Dもありますが今回は扱いません。)

環境構築

  • 実行環境

OS: Windows 11 Pro
CPU: Intel Core i7-13700
メモリ: 32GB
GPU: NVIDIA GeForce RTX 4060 Ti (VRAM: 16GB)

環境は上述の通りWSL+Dockerを使用しました。また、Python環境はuvを使用しています。
ベースの環境の作成については過去記事をご参考ください。

fallpoke-tech.hatenadiary.jp

Windows環境の場合、一部のライブラリがLinuxでしか使えず自前でビルドする必要があるのでWSLを使う方が良いと思います。

今回使用した環境設定を含めたリポジトリGitHubに残しています。 SAM3の公式リポジトリをforkして環境設定ファイルを追加したのみですが…。

github.com

実行

公式があげているデモ用のコードを参考に作成した下記のソースコードを実行しました。

注意点として、モデルの重みのダウンロードにはHuggingFaceのモデルページで利用申請が必要になります。

import os

from PIL import Image
import matplotlib.pyplot as plt
from sam3.model_builder import build_sam3_image_model
from sam3.model.sam3_image_processor import Sam3Processor
from sam3.visualization_utils import plot_results
from huggingface_hub import login
from dotenv import load_dotenv

load_dotenv()

login(token=os.getenv("HF_TOKEN"))

# モデルの準備
model = build_sam3_image_model()
processor = Sam3Processor(model)
# 画像の読み込み
image = Image.open("data/1624777685449_985774_photo1.jpeg")
inference_state = processor.set_image(image)
# テキストプロンプトを設定して推論を実行
output = processor.set_text_prompt(state=inference_state, prompt="tomato")

plot_results(image, output)
plt.show()
plt.close()

上記を実行するとこんな感じで出力されます。
tomato

プロンプトの指示である程度検出したい物体を絞ることも可能です。例えばprompt="red tomato"と変更すると出力が変わります。
red tomato

私の環境での話にはなりますが、VRAMを大体5GBくらい使用しているので比較的軽そうです。
また、画像1枚あたりの推論時間は0.20sほどだったのでこちらもなかなか速いです。
gpu state

参考サイト

Pytorchのモデル学習の中断と再開の処理を実装したメモ【備忘録】

お疲れ様です。

今回はPytorchで学習の途中再開をするためのコードのメモです。 長期間モデル学習を実行する際に予期せぬトラブルで処理が止まってしまった場合などにも使えると思います。

ソースコード

以前作成した画像分類のプロジェクトに実装しています。 github.com

実装

  • モデルの保存時
    下記modules/trainer.pyのTrainerクラスのモデル保存用メソッドです。 modelのパラメータに加えてoptimizerのパラメータを保存するのがポイントになっています。
    ちなみに下記は最新epochのモデルを保存するためのメソッドですが、これを毎epoch行うようにしています。 こうすることで処理を止めたor止まったタイミングから再開することができます。
def save_weight_latest(
    self,
    epoch: int
) -> None:
    """モデルの重みを保存
    """        
    # 最終epochのモデル
    model_name = "model_latest.pth"
    checkpoint = {
        'model_state_dict': self.model.state_dict(),
        'optimizer_state_dict': self.optimizer.state_dict(),
        'epoch': epoch
    }
    torch.save(checkpoint, self.output_path.joinpath(model_name))
    print(f"model saved: {model_name}")
  • 途中再開時
    学習の途中再開用にmain_train_resume.pyを作成しています。 torch.loadで読み込みmodelとoptimizerそれぞれで読み込みをする形になります。
# 途中保存した重みの読み込み
checkpoint_path = output_path.joinpath("model_latest.pth")
checkpoint = torch.load(checkpoint_path, map_location=device)

# モデルの定義
model, _ = get_model_train(
    model_name=model_name, 
    num_classes=train_dataset.num_classes, 
    use_pretrained=use_pretrained
)
# 途中保存の重みに更新
model.load_state_dict(checkpoint["model_state_dict"])
params = model.parameters()

# optimizerの定義
optimizer = RAdamScheduleFree(params, lr=lr)
optimizer.load_state_dict(checkpoint["optimizer_state_dict"])

実行するとこんな感じになります。 以下、「①中断せずに20epoch回した結果」と「②10epochで中断して再開した結果」を並べてみました。
似たような曲線を描いているので問題なく途中再開できていそうです。


  • 1


  • 2

参考

参考にさせていただいた記事を載せておきます。
qiita.com

再現性も保持したい場合は乱数の状態も一緒に保存し、再開の際に読み込むことで実現できるようです。 今回作成のコードでは実装していませんが。 qiita.com

HuggingFace Datasetsのload_datasetでダウンロードに失敗するときの対処【備忘録】

お疲れ様です。

HuggingFaceのDatasetsからデータセットを読み込み時にエラーが出たときの対応方法のメモです。

今回問題のあったデータセット

  • Food_and_Vegetables(画像分類用のデータセット

huggingface.co

通常のやり方

通常Datasetsからデータセットを読み込む際はload_dataset()を使用します。
以前作成したプログラムのソースからですが、このようにして問題なくデータセットのダウンロードができていました。

# Datasetの読み込み
# https://huggingface.co/datasets/Bingsu/Human_Action_Recognition
dataset = load_dataset("Bingsu/Human_Action_Recognition")

今回対象とした"Food_and_Vegetables"では、以下のようにエラーが出ました。 エラー内容としてはHTTP ErrorでREADMEや画像データなどDatasetに含まれるファイルをダウンロードする際にエラーになっているようです。

dataset = load_dataset("SunnyAgarwal4274/Food_and_Vegetables")

HTTP error

対処方法

load_dataset()を使用せず、直接ダウンロード(git clone)することでデータ自体をダウンロードしました。 Datasetsのページ自体がGitリポジトリとなっているのでgitがインストールされていればURLを指定してcloneすることができます。

git clone https://huggingface.co/datasets/SunnyAgarwal4274/Food_and_Vegetables

git clone

このようにページ内にあるデータをそのままクローン出来ました。データの欠損等もなさそうです。 この方法を使用した場合、load_dataset()を使用したときのように自動でHuggingFaceのDatasetクラスの形式にすることはできないので、データセットの形式に合わせてDatasetクラスを自作する必要はありそうです。

dataset

今回のデータセット(Food_and_Vegetables)の場合ImageFolder形式のフォルダ構造になっているので、load_dataset()で下記のような書き方をすればHuggingFaceのDatasetクラスで読み込むことができるようです。

dataset = load_dataset(
    "imagefolder", 
    data_dir="./dataset" # git cloneしたデータセットのパスを指定(ImageFolder形式)
)

load_datasetでエラーになった原因(推測)

対象とした"Food_and_Vegetables"ではデータセットの画像が生データのままリポジトリに格納されていました。 このデータを一挙にダウンロードしようとして一部のデータでエラーが起こっていたと推測しています。

page_Food_and_Vegetables

また正常にダウンロードできた"Human_Action_Recognition"の方はデータセットをparquetファイルに変換していることがわかりました。 ざっと見た感じ他の多くのデータセットもparquetファイルかつ決まったフォルダ構造になっているように見受けられたので、 load_dataset()がこの形式を推奨しているということもありそうです…。

page_Human_Action_Recognition

OCRモデルTrOCRの実装まとめ

お疲れ様です。

前回の記事でTrOCRの調査内容をまとめたので、今回は実装のお話。 fallpoke-tech.hatenadiary.jp



ソースコード

ソースコードはこちらのGithubリポジトリにまとめています。mainブランチの方を使用する想定です。
プロジェクト全体の構造や使用方法などはREADMEを参照ください。 GitHub Copilotに作成させたのでおかしな点などあるかもしれませんが…。 github.com

下記のサイトのソースコードをベースに日本語対応をしたものになります。
https://qiita.com/relu/items/c027c486758525c0b6b9

コード内容補足

ソースコードの細かい内容はこちらで補足として記載します。

モデル

使用したモデルはこちら。HuggingFaceで利用できる日本語対応の事前学習モデルの中で使えそうなもので採用しました。 日本語の漫画のコマのデータセットを使用して作成したモデルのようです。 huggingface.co

# model_name = "kha-white/manga-ocr-base"
model = VisionEncoderDecoderModel.from_pretrained(model_name)

今回は上記を採用しましたがtransformersのVisionEncoderDecoderModelではEncoderとDecoderに個別にモデルを指定することができます。

  • Encoder -> 画像系Transformer(ViTやDeiTなど)
  • Decoder -> 自然言語系Transformer(BERTやGPTなど)

コードにすると以下のような形になります。encoderとdecoderはそれぞれ以下を使用しています。 日本語対応にしたい場合はDecoder側を日本語対応の生成モデルにすると良さそうです。

Encoder -> https://huggingface.co/facebook/deit-base-distilled-patch16-224
Decoder -> https://huggingface.co/rinna/japanese-gpt2-xsmall

# encoder_name = "facebook/deit-base-distilled-patch16-224"
# decoder_name = "rinna/japanese-gpt2-xsmall" 
model = VisionEncoderDecoderModel.from_encoder_decoder_pretrained(encoder_name, decoder_name)

データセット

画像と正解テキストの1組を返すPutorchのDatasetクラスを作成すればOKです。 用意したデータセットに合わせて処理を書き換えてください。

デフォルトでは下記のようなフォルダ構造に対応しています。 annotations.csvに画像と正解テキストの対応を記載してそれをDatasetクラスに登録するイメージです。

.
└──dataset
  ├──annotations.csv
  ├──image1.png
  ├──image2.png
  ⋮

  • annotations.csvの中身(例)
img text
PXL_20251004_070420578_30.png 必ずまた帰ってくる
PXL_20251004_070420578_31.png 東京からまんまで宇宙へ
PXL_20251004_070420578_32.png 今年も似合いの夏

前処理

前処理は基本的にはモデルに対応するProcessorを使用することになります。 今回の例だとこのようになります。
このProcessorでは224x224のリサイズと画像の正規化、torch.Tensorへの変換処理が含まれています。 224x224のリサイズがある関係で、横長や縦長の画像を対象とする場合にデータの欠損が起こりそうではあります…。

# model_name = "kha-white/manga-ocr-base"
processor = TrOCRProcessor.from_pretrained(model_name)

ソースコードではこのProcessorのほか、Albumentationsを使った画像の前処理も加えています。 デフォルトだと以下の処理が含まれます。 modules/loader/augmentation.pyaug_listにAlbumentationsのお好きな処理を追加することもできます。 またデフォルトには含めていませんが、同じソースにカスタムの前処理をいくつか実装しています。

aug_list = [
    # 色調変化のうちいずれか1つをランダムに適用
    A.OneOf([
        A.HueSaturationValue(),  # 画像の色相・彩度・明度をランダムに変化させる(全体の色味を調整)
        A.RGBShift(),            # 各RGBチャンネルをランダムにシフトさせて色のバランスを変える(照明やカメラ特性の変化を模倣)
        A.InvertImg()            # 画像の色を反転(白→黒、黒→白)させる(ネガ画像などへの耐性向上)
    ]),
    
    # 明るさとコントラストをランダムに変化させる(明暗差や照明条件の変動に対応)
    A.RandomBrightnessContrast(
        brightness_limit=0.2,   # 明るさを±20%の範囲で変化
        contrast_limit=0.2,     # コントラストを±20%の範囲で変化
        p=0.5                   # 50%の確率で適用
    ),
    
    # CLAHE(ヒストグラム均等化)を適用し、コントラストを局所的に強調(文字や模様の視認性向上)
    A.CLAHE(p=0.2),             # 20%の確率で適用
    
    # JPEG圧縮をシミュレートして画質を劣化させる(低品質画像に対するロバスト性を向上)
    A.ImageCompression(p=0.3)   # 30%の確率で適用
]

実行結果

実際にソースコードを使用して作成した結果を載せておきます。 結果としてはあまりうまくいっていないようにも思いますが参考程度に。

学習に使用したデータセットはHuggingFaceから利用できる以下のデータセットと自作のデータセットを混ぜたものです。 huggingface.co

自作データセットの方は以前記事で紹介したYomiTokuを使ったプログラムを使って作成しています。 fallpoke-tech.hatenadiary.jp

データとしては15万枚+2000枚程度あり100epochの学習が完了するのに1週間ほどかかりました…。 それでいてあまり精度も良いとは言えない感じなのが残念です。いろいろ見直しは必要になりそうです…。

学習

学習曲線がこのようになりました。 TrainとValidationの段階でそれぞれLossを計算、またValidationではCER(Character Error Rate)も計算して表示しています。

CERについて詳しくはこちら参照。
https://qiita.com/Kchan/items/7bba1f066234ba24898b

learning_curve

推論

作成したモデルを使って推論を実行した結果を一部載せておきます。 プログラムの出力としては以下のような入力画像と正解テキスト、検出テキストをcsvファイルにまとめています。

result

  • PXL_20251001_141512690_12.png
    正解テキスト: 桜の花、舞い上がる道を
    検出テキスト: 桜 の 、 い 上 本 道
    sakuranohana

文字の一部は検出できていますが、全体で正解してはいない感じですね…。 横長の画像が多いので224x224のリサイズで文字がつぶれてしまっていそうです。

OCRモデルTrOCRについて調べたまとめ

お疲れ様です。

今月の半ばくらいまで会社の勉強会の関係でAI OCRの手法の1つのTrOCRの調査と実装をしていました。 今回はそちらの内容をまとめて備忘録を書いておきます。

論文

arxiv.org

要約(from ChatGPT)

TrOCR:Transformerベースの革新的なOCR技術

TrOCRは「Transformer Optical Character Recognition」の略で、Microsoftが開発した、Transformerアーキテクチャに基づく先進的な光学文字認識OCR)モデルです。 従来のOCR技術が主に畳み込みニューラルネットワーク(CNN)を利用していたのに対し、TrOCRは画像認識とテキスト生成の両方にTransformerを用いることで、シンプルかつ効果的なエンドツーエンドのシステムを実現しています。

主な特徴

  • Transformerのみの構成: TrOCRは、画像処理のためのCNNを必要とせず、画像Transformer(エンコーダ)とテキストTransformer(デコーダ)を組み合わせたシンプルな構成が特徴です。 これにより、入力画像特有のバイアスが少なく、高い汎用性を持ちます。
  • エンドツーエンドのシステム: 画像の入力からテキストの出力までを一つのモデルで完結させるエンドツーエンドのアプローチを採用しています。
  • 事前学習の活用: 大規模な合成データセットで事前にモデルを学習(Pre-training)させ、その後、特定のタスク(手書き文字、印刷文字など)に合わせて微調整(Fine-tuning)することで、非常に高い認識精度を達成します。
  • 高い認識精度: 手書き文字と印刷されたテキストの両方で、従来のモデルを上回る優れた性能を示しています。

仕組み

TrOCRのアーキテクチャは、主に2つの部分から構成されています。

  1. 画像エンコーダ(Image Transformer): 入力された画像を小さなパッチに分割し、それぞれのパッチから特徴を抽出して文脈を捉えます。 これは、コンピュータービジョンの分野で高い性能を示すVision Transformer(ViT)と同様のアプローチです。
  2. テキストデコーダ(Text Transformer): 画像エンコーダが抽出した特徴情報を受け取り、それを基に対応するテキストシーケンスを生成します。

このエンコーダ・デコーダ構造により、画像の内容を理解し、それを自然言語のテキストとして出力することが可能になります。

ざっくりとアーキテクチャをすべてTransformerベースで作成したOCRモデルという感じですね。 EncoderとDecoderの2つのアーキテクチャを用います。それぞれ以下のようなアーキテクチャを使用します。

  • Encoder -> 画像系Transformer(ViTやDeiTなど)
  • Decoder -> 自然言語系Transformer(BERTやGPTなど)

arch

ライセンスはMITです。個人的には実際に業務でも実装した経験があります。

公式実装は以下(2つある…?)
github.com github.com

テストコードの実装

確認用のテストコードの実装を記載します。
TrOCRはHuggingFaceから利用可能でMicrosoftが作成したモデルを使用することができます。 ただし、これらは英語に特化したモデルで日本語ではそのまま使えません。

  • 使用可能なモデル一覧
モデル名 学習段階 主なデータ 用途 備考
trocr-base-stage1 事前学習 合成文字画像 再学習ベース Fine-tuning前提
trocr-base-printed Fine-tuning 印刷体 文書OCR 通常の印刷OCR向け
trocr-base-handwritten Fine-tuning 手書き文字 手書きOCR 筆記体対応
trocr-base-str Fine-tuning 自然画像文字(STR) 看板・標識など Scene Text向け

実際のコードが以下です。
下記のHuggingFaceのTrOCRの説明ページのコードを参考に、Google Colabなどで使えるようにNotebook形式で作成しています。 huggingface.co

HuggingFaceではVisionEncoderDecoderModelがTrOCRのモデル呼び出しになります。 モデルは看板や標識など現実の画像にある文字に対応したモデルであるmicrosoft/trocr-small-strを使用しました。

import os
os.environ["HF_HOME"] = ".cache"

from transformers import TrOCRProcessor, VisionEncoderDecoderModel
from PIL import Image

# 画像読み込み
image = Image.open("image.png").convert("RGB")

# 画像の表示(Notebookの出力セル内に表示できる)
display(image)

# モデルの準備
processor = TrOCRProcessor.from_pretrained('microsoft/trocr-base-str')
model = VisionEncoderDecoderModel.from_pretrained('microsoft/trocr-base-str')
pixel_values = processor(images=image, return_tensors="pt").pixel_values

# OCR実行
generated_ids = model.generate(pixel_values)
generated_text = processor.batch_decode(generated_ids, skip_special_tokens=True)[0]

print(generated_text)

結果が以下です。検出したテキストと入力画像が表示されます。
result_1

上述の通り日本語には対応していません。日本語対応させる場合はファインチューニング必須ですね。
result_2

学習用プログラムの実装はできていますが、内容多くなりそうなので別の機会に。

追記(20251101)

実装の記事を作成しました。併せてどうぞ。 fallpoke-tech.hatenadiary.jp

日本語特化OCR「YomiToku」を自作プログラムから動かす【備忘録】

お疲れ様です。

今回はOCR光学文字認識)のお話。

OCR(Optical Character Recognition:光学的文字認識)は画像に含まれる文字をコンピュータで認識できるテキストの形で読み取る技術のこと。

最近だとOSSYomiTokuが日本語特化OCRとしてはとても優秀です。

使い方にもあるようにターミナル上からコマンドで実行することができます。
例えば以下のコマンドを実行すると、画像内のレイアウトと文字を検出し、検出した情報を可視化した画像ファイルとmarkdownでテキストをファイル出力してくれます。 ./imagesに解析したい画像ファイルを入れる形です。

yomitoku ./images -f md -v

  • 検出結果 detect

検出したテキスト

今宵の月のように

作詞 · 作曲 宮本浩次

編曲 宮本浩次 佐久間正英

くだらねえとつぶやいて
醒めたつらして歩く
いつの日か輝くだろう
あふれる熱い涙

いつまでも続くのか
吐きすてて寝転んだ
俺もまた輝くだろう
今宵の月のように

夕暮れ過ぎて きらめく町の灯りは

悲しい色に 染まって揺れた

君がいつかくれた 思い出のかけら集めて
真夏の夜空 ひとり見上げた

新しい季節の始まりは

夏の風 町に吹くのさ

このようにコマンドだけでも十分に使えるのですが、Pythonのライブラリとしても使用することができます。 自作のプログラムに組み込むこともできるということです。
ライブラリとして使用できるという情報はほとんど見当たらなかったので個人的に調べて、簡単に実装したものをメモとして残しておきます。

ソースコード

ソースコードGithubに置いてありますので必要に応じてご参照ください。   github.com

※Yomitokuのバージョンは2025年10月時点の最新バージョン0.9.5です。

import os
os.environ["HF_HOME"] = "./pretrained"
from pathlib import Path

from yomitoku import TextDetector, TextRecognizer

import cv2
import pandas as pd

td = TextDetector()
tr = TextRecognizer()

Path("./result").mkdir(parents=True, exist_ok=True)

anno_dict = {"img": [], "text": []}

image_dir_path = Path(".images")
for image_path in image_dir_path.glob("*"):
    img = cv2.imread(image_path)
    
    layout = td(img)
    
    for i, pt in enumerate(layout[0].points):
        x1, y1 = pt[0]
        x2, y2 = pt[2]
        
        try:
            crop_img = img
            crop_img = crop_img[y1:y2, x1:x2]
            crop_img_name = f"{Path(image_path).stem}_{i+1}.png"
            cv2.imwrite(f"./result/{crop_img_name}", crop_img)
        except:
            continue
        
        result = tr(crop_img)
        text = result[0].contents[0]
        text = text.replace("·", "・")
        
        anno_dict["img"].append(crop_img_name)
        anno_dict["text"].append(text)
        print(text)
    
anno_df = pd.DataFrame(anno_dict)
anno_df.to_csv("./result/annotations.csv", encoding="cp932", index=False)

YomiTokuからはTextDetectorTextRecognizerをインポートしています。

  • TextDetector
    画像から文章や文字の部分を検出する。(物体検出の部分)
    検出した矩形の座標などの情報を出力する。

  • TextRecognizer
    画像内の文字をテキストとして検出する。(OCRの部分) 入力は画像と文字部分を囲う座標情報です。上記のTextDetectorで出力した座標が使えます。
    座標情報はオプションなので入力は必須ではないです。入力なしの場合は画像全体を見ますが、おそらく座標で切り出すことを前提としているのでそのままではうまく検出できないはず。

プログラムではTextDetectorで得られた座標情報から左上座標と右下座標を取得してクロップし、クロップ後の画像を入力してテキスト出力させています。

出力はこんな感じになります。AI OCRのモデル学習に使うデータのアノテーションに使えます。というか、それを想定して作りました。
(もちろん画像によっては誤検出もあるのでそれらは人の目で確認する必要はあります…。)

output

anno

他にも機能はあるので、いろいろと使えそうです。
本当に高精度で扱いやすいのですが、商用利用は有償なので手軽には使えないのが残念…。 業務で使う機会があったら是非使いたいです。