ホーム 記事一覧 ブック DH週間トピックス 検索 このサイトについて
English
Azure OpenAI GPT-4 vs Document Intelligence: 日本語縦書きOCRの比較検証

Azure OpenAI GPT-4 vs Document Intelligence: 日本語縦書きOCRの比較検証

概要 Microsoft Azureが提供する2つのOCRサービス(Azure OpenAI GPT-4 VisionとAzure Document Intelligence)を使用して、日本語の縦書き原稿用紙のOCR処理を実施し、その結果を詳細に比較検証しました。 検証対象画像 画像ソース : Canvaテンプレート(400字詰め原稿用紙) URL : https://www.canva.com/ja_jp/templates/EAFbqUoH7P8/ 画像の特徴 : 20×20の400字詰め原稿用紙 縦書きレイアウト 薄いグリッド線(マス目) タイトル欄と本文欄の区別 正解データ(Ground Truth) 原稿のタイトル 佐藤ちあき 原稿用紙に書くテキストが入ります。作文や小論文を作ったり、小説を書いたりなどにご活用ください。 このテキストを使用する場合は、日本語の全角を使うことでマスにあった文字を打つことができます。手書きで使用したい場合は、このテキストを削除し、印刷してご使用ください。 1. Azure OpenAI GPT-4.1 による認識結果 認識されたテキスト 原稿のタイトル 佐藤 ちあき 原稿用紙に書くテキストが入ります。作文や小論文を作ったり、小説を書いたりなどにご活用ください。 このテキストを使用する場合は、日本語の全角を使うことでマスにあった文字を打つことができます。手書きで使用したい場合は、このテキストを削除し、印刷してご使用ください。 評価 GPT-4.1は縦書きの原稿用紙に対して以下の特徴を示しました: ✅ タイトルと著者名の順序を正しく認識 ✅ 本文の開始部分を正確に認識 ✅ 原稿用紙のマス目に関する記述を認識 ✅ 縦書きの読み順(右から左)を完璧に理解 ✅ 文章の連続性を保持 正解データとの差異 「佐藤ちあき」→「佐藤 ちあき」(全角スペースが追加) これは画像上でスペースがあるように見えるための合理的な解釈 その他のテキストは完全に一致 精度評価: 99% 2. Azure Document Intelligence による認識結果 認識された領域の可視化 評価 Document Intelligenceは以下の特徴を示しました: ✅ 文字認識能力 - 個々の文字は正確に認識(「佐藤」「ちあき」「原稿」等) ⚠️ 文章の断片化 - マス目ごとに独立した要素として処理され、連続性が失われる ❌ 縦書き読み順の課題 - 縦書きの右から左への流れを適切に処理できない ⚠️ 後処理が必要 - 座標情報を使った再構成により、ある程度の復元は可能 ✅ 座標情報の詳細取得 - 各文字の正確な位置情報は完璧に取得 精度評価: 文字認識精度は約80%、ただし縦書きレイアウトの理解に課題あり ...

Azure Container AppsでNDL古典籍OCR Liteを用いたスケーラブルOCR処理システム

Azure Container AppsでNDL古典籍OCR Liteを用いたスケーラブルOCR処理システム

⚠️ 重要な利用上の注意 本記事で紹介するシステムは、外部サーバーに負荷をかける可能性があります。利用時は十分ご注意ください。 サーバー負荷 : 並列リクエストは対象サーバーに負荷を与えます DoS攻撃のリスク : 大量の同時アクセスはDoS攻撃と誤解される可能性があります 推奨アプローチ : 事前に画像をローカルにダウンロードし、OCR処理のみを並列実行することを推奨します 利用規約の確認 : 対象サーバーの利用規約を必ず確認し、必要に応じて事前許可を取得してください 適切なレート制限 : 実運用では慎重な並列数設定(5-10並列程度)を強く推奨します 責任ある利用 : サーバー管理者や他の利用者への配慮を忘れずに 本記事は技術的な実証実験の記録です。読者の皆様には責任を持った利用をお願いします。 はじめに 本記事では、国立国会図書館(NDL)が開発したNDL古典籍OCR Liteを活用し、Azure Container AppsでスケーラブルなOCR処理システムを構築した事例を紹介します。クラウドネイティブなアーキテクチャにより、従量課金とオートスケーリングを実現したシステムの設計と実装について解説します。 システム概要 アーキテクチャ IIIF画像 → Azure Container Apps → NDL古典籍OCR → TEI XML出力 ↓ オートスケーリング (0-30レプリカ) 主要コンポーネント OCRエンジン : NDL古典籍OCR Lite(日本古典籍特化) インフラ : Azure Container Apps(サーバーレスコンテナ) API設計 : REST API(画像URL → OCR結果) 出力形式 : TEI P5準拠XML スケーリング : 需要に応じた自動スケーリング NDL古典籍OCR Liteの特徴 日本古典籍に最適化されたOCR 縦書きレイアウト対応 : 古典籍特有の縦書き文書構造 読み順序最適化 : 右から左、上から下の日本語読み順 古典文字認識 : くずし字や変体仮名への対応 軽量実装 : Docker化によりクラウドデプロイ対応 Azure Container Appsの選択理由 サーバーレスコンテナの利点 # スケーリング設定例 scale: minReplicas: 0 # アイドル時: コスト0 maxReplicas: 30 # 需要時: 自動拡張 cooldownPeriod: 300 # 5分でスケールダウン コスト最適化 従量課金 : 使用した分のみ課金 0レプリカ : アイドル時は完全にコスト0 自動スケーリング : 需要に応じたリソース調整 システム実装 サーバーサイド実装 # Flask + NDL OCR統合 from flask import Flask, request, jsonify from flask_restx import Api, Resource from simple_ocr_service import OCRService app = Flask(__name__) api = Api(app, doc='/docs/') @api.route('/api/image') class ImageOCR(Resource): def get(self): image_url = request.args.get('image_url') # NDL OCRで画像処理 result = ocr_service.process_single_image(image_url) return result 読み順序アルゴリズム def sort_japanese_reading_order(lines): """日本古典籍の読み順序ソート""" return sorted(lines, key=lambda line: ( -line["bbox"][0], # x座標降順(右→左) line["bbox"][1] # y座標昇順(上→下) )) TEI XML出力 xml version="1.0" encoding="UTF-8"?> TEI xmlns="http://www.tei-c.org/ns/1.0"> teiHeader> fileDesc> titleStmt> title>桐壺title> titleStmt> respStmt> resp>Automated Transcriptionresp> name ref="https://github.com/ndl-lab/ndlkotenocr-lite"> NDL古典籍OCR Lite name> respStmt> fileDesc> teiHeader> facsimile> surface xml:id="surface-1"> zone xml:id="zone-1-1" ulx="3391" uly="1141" lrx="3727" lry="2924" cert="0.799"/> surface> facsimile> text> body> div type="transcription"> pb n="1" facs="#surface-1"/> lb n="1.1" corresp="#zone-1-1" cert="high"/> いづれの御時にか div> body> text> TEI> 処理結果事例 小規模テスト処理(桐壺) 対象 : 東京大学所蔵「桐壺」 ページ数 : 32ページ 処理時間 : 約30秒 成功率 : 100% 並列数 : 10並列 コスト : 約$0.05 パフォーマンス特性 処理時間 = 約1秒/ページ(並列処理時) コスト効率 = $1.5〜2.0/1000ページ スケーリング = 数秒で0→20レプリカ システムの技術的特徴 1. コールドスタート対応 async def process_with_retry(image_url, max_retries=3): """コールドスタート時の自動リトライ""" for attempt in range(max_retries + 1): try: if attempt > 0: wait_time = 2 ** (attempt - 1) await asyncio.sleep(wait_time) return await ocr_request(image_url) except (HTTPError, TimeoutError) as e: if attempt == max_retries: raise e 2. 設定の外部化 # 環境変数による設定 OCR_API_URL=https://your-ocr-service.azurecontainerapps.io DEFAULT_MAX_CONCURRENT=10 DEFAULT_CONFIDENCE_THRESHOLD=0.3 DEFAULT_OUTPUT_FORMAT=xml 3. Swagger UI統合 # API仕様の自動生成 api = Api(app, version='1.0', title='NDL古典籍OCR API', description='日本古典籍専用OCR処理API', doc='/docs/' ) デプロイメント Azure Container Appsデプロイ # コンテナアプリ作成 az containerapp create \ --name ocr-service \ --resource-group rg-ocr \ --environment container-env \ --image registry.azurecr.io/ocr-app:latest \ --target-port 80 \ --ingress external \ --min-replicas 0 \ --max-replicas 30 \ --cpu 2.0 \ --memory 4Gi Docker化 FROM python:3.11-slim # NDL OCRモデル配置 COPY model/ /app/model/ COPY config/ /app/config/ # アプリケーション設定 WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY . . EXPOSE 80 CMD ["gunicorn", "--bind", "0.0.0.0:80", "app:app"] 運用とモニタリング パフォーマンスメトリクス レスポンス時間 : 平均2-3秒/画像 スループット : 10-15画像/秒(20レプリカ時) 成功率 : 99%以上 コスト効率 : アイドル時$0、処理時のみ課金 ログ監視 # Container Appsログ確認 az containerapp logs show \ --name ocr-service \ --resource-group rg-ocr \ --follow 今後の展望 技術的改善点 画像キャッシュ : 重複処理の削減 バッチ処理 : 効率的な大量処理 GPU対応 : OCR処理の高速化 メトリクス強化 : 詳細な性能分析 応用可能性 デジタルアーカイブ : 図書館・博物館での活用 研究支援 : 人文学研究のデジタル化 教育分野 : 古典文献の教材化 文化保存 : 貴重資料のデジタル保存 まとめ NDL古典籍OCR LiteとAzure Container Appsを組み合わせることで、コスト効率とスケーラビリティを両立した古典籍OCRシステムを構築できました。サーバーレスアーキテクチャにより、従量課金と自動スケーリングを実現し、実用的なデジタルヒューマニティーズツールとして活用可能です。 ...

Cantaloupeでdelegate scriptを使ってAzure Storage上のファイルパスを動的に変換する方法

Cantaloupeでdelegate scriptを使ってAzure Storage上のファイルパスを動的に変換する方法

はじめに IIIFサーバーのCantaloupeでAzure Storageを使用している際、IIIF URLのidentifierと実際のAzure Storage上のファイルパスが異なる場合があります。本記事では、この問題をdelegate scriptを使って解決する方法を詳しく解説します。 課題 以下のようなファイル構造で画像を管理しているとします: Azure Storage Container: mycontainer ├── images/ │ ├── collection1/ │ │ ├── item001/ │ │ │ └── item001_001.jpg │ │ └── item002/ │ │ └── item002_001.jpg │ └── collection2/ │ └── ... しかし、IIIF URLでは以下のようにアクセスしたい: https://example.com/iiif/3/collection1/item001/item001_001.jpg/info.json この場合、IIIF URLのidentifier (collection1/item001/item001_001.jpg) と実際のAzure Storageのパス (images/collection1/item001/item001_001.jpg) が異なります。 AzureStorageSourceには、S3SourceのようなPATH_PREFIX設定が存在しないため、この問題を解決するためにはdelegate scriptを使用する必要があります。 解決方法 1. Docker Compose設定 services: cantaloupe: image: islandora/cantaloupe:main environment: CANTALOUPE_SOURCE_STATIC: AzureStorageSource CANTALOUPE_AZURESTORAGESOURCE_ACCOUNT_NAME: ${AZURE_STORAGE_ACCOUNT_NAME} CANTALOUPE_AZURESTORAGESOURCE_ACCOUNT_KEY: ${AZURE_STORAGE_ACCOUNT_KEY} CANTALOUPE_AZURESTORAGESOURCE_CONTAINER_NAME: ${AZURE_STORAGE_CONTAINER_NAME} CANTALOUPE_AZURESTORAGESOURCE_LOOKUP_STRATEGY: ScriptLookupStrategy # 重要 CANTALOUPE_DELEGATE_SCRIPT_ENABLED: "true" CANTALOUPE_DELEGATE_SCRIPT_PATHNAME: "/opt/cantaloupe/delegates.rb" volumes: - "./delegates.rb:/opt/cantaloupe/delegates.rb:ro" labels: - "traefik.enable=true" - "traefik.http.routers.cantaloupe.rule=Host(`example.com`)" - "traefik.http.routers.cantaloupe.entrypoints=websecure" - "traefik.http.routers.cantaloupe.tls=true" - "traefik.http.services.cantaloupe.loadbalancer.server.port=8182" restart: always 2. Delegate Script (delegates.rb) 開発・デバッグ版 最初はデバッグ出力を含む版で動作確認を行います: ...

Azure Logic Appsを試す

Azure Logic Appsを試す

概要 ノーコードまたはローコード開発の調査を目的として、Azure Logic Appsを試してみましたので備忘録です。 成果物 以下は、ロジックアプリデザイナーの画面です。HTTPリクエストを受信し、Cosmos DBにデータを保存し、成功時にメールを送信するワークフローを作成します。 Azure Cosmos DBの作成 「アカウント名」以外はデフォルトにしました。「my-first-azure-cosmos-db-account」という名前で作成しました。 「Items」コンテナを作成します。 「データエクスプローラ」を使って、データベース「ToDoList」の下に、コレクション「Items」が作成されていることを確認できます。 ロジック アプリの作成 ロジック アプリの作成を行います。 「my-first-logic-app」を作成しました。 「ロジックアプリデザイナー」に移動します。 HTTPリクエスト まず、「Request」を選びます。 そして、以下を入力します。今回は単純に「url」と「code」という項目を管理します。 { "type": "object", "properties": { "url": { "type": "string" }, "code": { "type": "string" } }, "required": [ "url", "code" ] } Cosmos DBの追加 まずコネクションを作成します。 入力すべき値は、「Azure Cosmos DB アカウント」ページの「設定 > キー」から確認することができました。 次に、パラメータを入力します。先ほど作成したコレクション「Items」を選択して、「ドキュメント」に以下を入力します。 { "id": "@{guid()}", "url": "@{triggerBody()?['url']}", "code": "@{triggerBody()?['code']}" } idとしてUUIDを入力し、フォームから受け取ったurlとcodeをDBに登録します。 メールの送信 最後に、メールの送信設定です。今回は「Office 365 Outlook」の「メールの送信(V2)」を選択しました。 ...

Azure OpenAI Assistants APIを用いたアプリをGradioとNext.jsで作成する

Azure OpenAI Assistants APIを用いたアプリをGradioとNext.jsで作成する

概要 Azure OpenAI Assistants APIを用いたアプリをGradioとNext.jsで作成したので、備忘録です。 対象データ Zennで公開している記事を対象にしました。まず以下により、一括ダウンロードしました。 import requests from bs4 import BeautifulSoup import os from tqdm import tqdm page = 1 urls = [] while 1: url = f"https://zenn.dev/api/articles?username=nakamura196&page={page}" response = requests.get(url) data = response.json() articles = data['articles'] if len(articles) == 0: break for article in articles: urls.append("https://zenn.dev" + article['path']) page += 1 for url in tqdm(urls): text_opath = f"data/text/{url.split('/')[-1]}.txt" if os.path.exists(text_opath): continue response = requests.get(url) soup = BeautifulSoup(response.text, "html.parser") html = soup.find(class_="znc") txt = html.get_text() os.makedirs(os.path.dirname(text_opath), exist_ok=True) with open(text_opath, "w") as f: f.write(txt) ベクトルストアへの登録 以下のようなコードにより、データファイルをアップロードします。 import os from dotenv import load_dotenv from openai import AzureOpenAI from glob import glob from tqdm import tqdm load_dotenv(override=True) client = AzureOpenAI( azure_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT_ZENN"), api_key=os.getenv("AZURE_OPENAI_API_KEY_ZENN"), api_version="2024-05-01-preview" ) # ベクトルストアの作成または取得 is_create_vector_store = True vector_store_name = "Vector Store" if is_create_vector_store: vector_store = client.beta.vector_stores.create(name=vector_store_name) # Create a vector store caled "Financial Statements" vector_stores = client.beta.vector_stores.list() for vector_store in vector_stores: if vector_store.name == vector_store_name: vector_store_id = vector_store.id break # 登録済みデータファイルの取得 response = client.files.list(purpose="assistants") items = response.data filenames = [] for item in items: filename = item.filename filenames.append(filename) filenames.sort() # アップロード ## 定数設定 BATCH_SIZE = 100 vector_store_id = "vs_UELnIBkcROD3o4XKX2CcpVjo" ## ファイル一覧取得とソート files = glob("./data/text/*.txt") files.sort() ## アップロード済みファイルを確認済みと仮定 file_streams = [] for file in tqdm(files): filename = os.path.basename(file) if filename in filenames: # アップロード済みのファイルをスキップ continue ## ファイルをストリームとして開く file_streams.append(open(file, "rb")) ## バッチサイズに達したらアップロード処理 if len(file_streams) == BATCH_SIZE: try: client.beta.vector_stores.file_batches.upload_and_poll( vector_store_id=vector_store_id, files=file_streams ) except Exception as e: print(f"Error processing batch: {e}") finally: file_streams = [] # ストリームリセット ## 残りのファイルを処理 if file_streams: try: client.beta.vector_stores.file_batches.upload_and_poll( vector_store_id=vector_store_id, files=file_streams ) except Exception as e: print(f"Error processing remaining files: {e}") アシスタント プレイグラウンド 「アシスタント プレイグラウンド」を用いて、挙動を確認します。 ...

Cantaloupe: Microsoft Azure Blob Storageに格納した画像を配信する

Cantaloupe: Microsoft Azure Blob Storageに格納した画像を配信する

概要 IIIFイメージサーバの一つであるCantaloupe Image Serverについて、Microsoft Azure Blob Storageに格納した画像を配信する方法の備忘録です。 以下のMicrosoft Azure Blob Storage版です。 方法 今回はDocker版を使用します。 以下のリポジトリをクローンしてください。 https://github.com/nakamura196/docker_cantaloupe 特に、.env.azure.exampleを.envにリネームして、環境変数を設定します。 # For Microsoft Azure Blob Storage CANTALOUPE_AZURESTORAGESOURCE_ACCOUNT_NAME= CANTALOUPE_AZURESTORAGESOURCE_ACCOUNT_KEY= CANTALOUPE_AZURESTORAGESOURCE_CONTAINER_NAME= # For Traefik CANTALOUPE_HOST= LETS_ENCRYPT_EMAIL= 下の二つは、Traefikを用いたHTTPS化の設定も含めています。 そして、以下を実行します。 docker compose -f docker-compose-azure.yml up まとめ セキュリティの面など、不十分な点もあるかと思いますが、参考になりましたら幸いです。

Azureの仮想マシンを用いたNDLOCRのGradioアプリ構築

Azureの仮想マシンを用いたNDLOCRのGradioアプリ構築

概要 以下の記事で、Azureの仮想マシンとNDLOCRを用いたGradioアプリについて紹介しました。 本記事では、このアプリの構築方法に関する備忘録です。 仮想マシンの構築 GPUを使用するにあたり、クォータの要求を行う必要がありました。 要求後、今回は「NC8as_T4_v3」を使用します。 Docker環境の構築 以下の記事を参考にします。 https://zenn.dev/koki_algebra/scraps/32ba86a3f867a4 Secure Boot の無効化 以下のように記載されています。 Secure Boot を無効化しないと NVIDIA Driver が正しくインストールされない. 実際、無効化しなかった場合、以下の画面が表示され、前に進めませんでした。 以下で、Secure Bootを無効化します。 NVIDIA Driver のインストール ubuntu-drivers コマンドのインストールを行い、インストール可能な NVIDIA Driver を確認する。 sudo apt-get update sudo apt install ubuntu-drivers-common ubuntu-drivers devices 以下が結果です。 vendor : NVIDIA Corporation model : TU104GL [Tesla T4] driver : nvidia-driver-535 - distro non-free recommended driver : nvidia-driver-470-server - distro non-free driver : nvidia-driver-535-server - distro non-free driver : nvidia-driver-470 - distro non-free driver : xserver-xorg-video-nouveau - distro free builtin recommendedをインストールします。 ...

ndlocr_cli(NDLOCR(ver.2.1)アプリケーションを試すことができるGradioアプリを作成しました。

ndlocr_cli(NDLOCR(ver.2.1)アプリケーションを試すことができるGradioアプリを作成しました。

概要 ndlocr_cli(NDLOCR(ver.2.1)アプリケーションを試すことができるGradioアプリを作成しました。 以下のURLからお試しください。 https://ndlocr.aws.ldas.jp/ 補足 現在は1枚の画像アップロードのみに対応しています。今後、PDFのアップロード機能などのオプションも追加したいと思います。 Azureで使用可能なVMである「NC8as_T4_v3」に搭載されている「NVIDIA Tesla T4 GPU」を使用しています。 まとめ いつまでこの形で提供できるかはわかりませんが、ndlocr_cli(NDLOCR(ver.2.1)アプリケーションの精度の確認などにあたり、ご利用いただけますと幸いです。

Azure OpenAIとLlamaIndexとGradioを用いたRAG型チャットの作成

Azure OpenAIとLlamaIndexとGradioを用いたRAG型チャットの作成

概要 Azure OpenAIとLlamaIndexとGradioを用いたRAG型チャットの作成を試みたので、備忘録です。 Azure OpenAI Azure OpenAIを作成します。 その後、「エンドポイント:エンドポイントを表示するには、ここをクリックします」をクリックして、エンドポイントとキーを控えておきます。 その後、Azure OpenAI Serviceに移動します。 「モデルカタログ」に移動して、「gpt-4o」と「text-embedding-3-small」をデプロイします。 結果、以下のように表示されます。 テキストのダウンロード 今回は、青空文庫で公開されている源氏物語を対象とします。 https://www.aozora.gr.jp/index_pages/person52.html 以下により、一括ダウンロードします。 import requests from bs4 import BeautifulSoup import os url = "https://genji.dl.itc.u-tokyo.ac.jp/data/info.json" response = requests.get(url).json() selections = response["selections"] for selection in selections: members = selection["members"] for member in members: aozora_urls = [] for metadata in member["metadata"]: if metadata["label"] == "aozora": aozora_urls = metadata["value"].split(", ") for aozora_url in aozora_urls: filename = aozora_url.split("/")[-1].split(".")[0] opath = f"data/text/{filename}.txt" if os.path.exists(opath): continue # pass response = requests.get(aozora_url) response.encoding = response.apparent_encoding soup = BeautifulSoup(response.text, "html.parser") div = soup.find("div", class_="main_text") txt = div.get_text().strip() os.makedirs(os.path.dirname(opath), exist_ok=True) with open(opath, "w") as f: f.write(txt) Indexの作成 環境変数を用意します。 ...