TextInとSpring Bootを連携させた画像認識の実装

概要

画像認識とは

画像認識は、コンピュータビジョンと機械学習技術を活用して画像内の情報を解釈・識別するプロセスです。この技術により、画像から文字、物体、シーンなどの情報を抽出し、編集可能かつ検索可能なデータに変換できます。画像認識は、監視システム、ソーシャルメディアの分析、医療画像処理、自動運転車の視覚システムなど幅広い分野で応用されています。

OCR(Optical Character Recognition、光学式文字認識)は、画像からテキスト情報を抽出することに特化した画像認識の一分野です。スキャンされた文書、PDFファイル、写真など様々な形式の画像から文字を読み取り、機械が処理可能なテキスト形式に変換します。これはデジタルアーカイブ、自動データ入力、言語翻訳などにおいて重要です。

OCRの一般的な処理フローは以下の通りです:

  1. 前処理:画像品質を向上させ、認識精度を高めるためにノイズ除去、バイナリ化、傾き補正、コントラスト調整などが行われます。
  2. 文字検出:前処理後、文字領域を特定するアルゴリズムが適用され、後の認識処理の精度に大きく影響します。
  3. 文字分割:文字が検出された後、連続した文字列を個々の文字または単語に分割します。
  4. 文字認識:分割された文字に対して、深層学習モデルなどを用いて認識を行います。フォントや筆跡の違いにより認識が困難になる場合があります。
  5. 後処理:認識結果に対して、スペル訂正、フォーマット調整、言語識別などの処理が行われ、最終的な出力品質を向上させます。

OCR技術は、初期にはルールベースの手法が主流でしたが、近年ではディープラーニング技術の発展により、CNNやRNNなどの複雑なニューラルネットワークが採用され、認識精度と耐障害性が大幅に向上しています。

TextInとは

TextInは上海合合情報科技有限公司が提供するスマート文字認識技術・製品・サービスブランドです。15年以上の歴史を持つこのプラットフォームは、企業・開発者・個人向けに文字認識エンジン、製品、クラウドサービスを提供しています。50以上の言語に対応し、多角度、高輝度、ぼやけ、折れ、影、夜間などさまざまな状況での文字認識が可能です。また、カード、領収書、業種固有文書など200以上の分野での高精度認識製品や、セキュアで安定したクラウドサービス、エッジ側SDK、プライベートデプロイメントなども提供されています。

公式サイトへのアクセス後、アカウント登録が必要になります。

応用例

  1. 請求書認識とデータ処理:ユーザーが請求書画像をアップロードすると、TextInによってキー情報を抽出(請求番号、日付、金額など)し、自動記帳や財務分析に利用できます。
  2. 本人確認と情報抽出:身分証明書やパスポートの写真をアップロードすることで、名前、性別、生年月日、証明書番号などの情報を自動抽出し、登録や認証に使用できます。
  3. 契約書の審査:契約書の分類と重要な条項(期間、金額、責任)を自動的に抽出し、法的レビューと管理を支援します。
  4. 医療記録のデジタル化:手書きの記録や紙のレポートを構造化データに変換し、医師や医療機関が患者情報を効率よく保存・検索・分析できるようにします。
  5. 自動顧客サポート:製品画像をアップロードすると、モデル名を認識し、使用ガイドやトラブルシューティングを提供するチャットボットが動作します。
  6. 教育資料の分析:教師が教材をアップロードすると、TextInが内容を検索可能にする形式に変換し、学生が必要な資料を迅速に見つけられるようになります。
  7. 不動産文書処理:不動産証明書や賃貸契約書などの文書から住所、所有権、面積などの情報を自動抽出し、登録や管理を効率化します。

Spring Bootとの連携

ステップ1: Spring Bootプロジェクト作成

まず、Spring Bootプロジェクトを作成します。

ステップ2: TextIn依存関係の追加とインターフェース確認

pom.xmlにTextIn SDKの依存関係を追加します。

<dependency>
    <groupId>com.textin</groupId>
    <artifactId>textin-sdk-java</artifactId>
    <version>バージョン番号</version>
</dependency>

バージョン番号は最新版に置き換えしてください。

ステップ3: TextIn APIキーの設定

application.propertiesまたはapplication.ymlに以下の設定を追加します:

textin.app-id=あなたのApp-ID
textin.app-secret=あなたのApp-Secret

ステップ4: TextInサービスクラスの作成

TextInService.javaを作成し、API呼び出しをカプセル化します。

@Service
public class TextInService {

    private final String appId;
    private final String appSecret;

    public TextInService(@Value("${textin.app-id}") String appId, 
                         @Value("${textin.app-secret}") String appSecret) {
        this.appId = appId;
        this.appSecret = appSecret;
    }

    public String recognizeText(MultipartFile file) throws Exception {
        // TextIn SDKを使用した文字認識処理
        // SDKのドキュメントに基づいて実装が必要
    }
}

ステップ5: コントローラーの作成

TextInController.javaを作成し、HTTPリクエストを処理します。

@RestController
@RequestMapping("/textin")
public class TextInController {

    @Autowired
    private TextInService textInService;

    @PostMapping("/recognize")
    public ResponseEntity<String> recognizeText(@RequestParam("file") MultipartFile file) {
        try {
            String result = textInService.recognizeText(file);
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            return ResponseEntity.badRequest().body("文字認識失敗: " + e.getMessage());
        }
    }
}

ステップ6: アプリケーションの実行

Spring Bootアプリケーションを起動し、ブラウザまたはPostmanなどで /textin/recognize エンドポイントに画像ファイルを含むPOSTリクエストを送信します。

ステップ7: テストとデバッグ

TextInサービスが適切にリクエストに応答し、認識結果を返すことを確認してください。

TextInによる文書変換の実装

ステップ1: Spring Bootプロジェクト作成

Spring Initializrを使用して新規プロジェクトを作成し、spring-webを追加します。

ステップ2: TextIn APIの設定

application.propertiesまたはapplication.ymlに以下の情報を設定します:

textin.app-id=あなたのApp-ID
textin.app-secret=あなたのApp-Secret
textin.api-url=https://api.textin.com/ai/service/v2/recognize

ステップ3: TextInサービスクラスの作成

TextInServiceクラスを作成し、APIとの通信を処理します。

@Service
public class TextInService {

    private final String appId;
    private final String appSecret;
    private final String apiUrl;

    public TextInService(
            @Value("${textin.app-id}") String appId,
            @Value("${textin.app-secret}") String appSecret,
            @Value("${textin.api-url}") String apiUrl) {
        this.appId = appId;
        this.appSecret = appSecret;
        this.apiUrl = apiUrl;
    }

    public String recognizeAndConvert(MultipartFile file) throws IOException {
        URL url = new URL(apiUrl);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("POST");
        conn.setRequestProperty("Content-Type", "application/octet-stream");
        conn.setRequestProperty("x-ti-app-id", appId);
        conn.setRequestProperty("x-ti-secret-code", appSecret);
        conn.setDoOutput(true);

        OutputStream os = conn.getOutputStream();
        os.write(file.getBytes());
        os.flush();
        os.close();

        BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
        String line;
        StringBuilder response = new StringBuilder();
        while ((line = reader.readLine()) != null) {
            response.append(line);
        }
        reader.close();

        // TextIn APIのレスポンスを解析し、Word形式などに変換
        return response.toString();
    }
}

@RestController
@RequestMapping("/textin")
public class TextInController {

    @Autowired
    private TextInService textInService;

    @PostMapping("/convert")
    public ResponseEntity<String> convertDocument(@RequestParam("file") MultipartFile file) {
        try {
            String convertedDocument = textInService.recognizeAndConvert(file);
            return ResponseEntity.ok(convertedDocument);
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body("文書変換エラー: " + e.getMessage());
        }
    }
}

PDFからの文字認識実装

@Service
public class TextInService {

    private final String appId;
    private final String appSecret;
    private final String apiUrl;

    public TextInService(
            @Value("${textin.app-id}") String appId,
            @Value("${textin.app-secret}") String appSecret,
            @Value("${textin.api-url}") String apiUrl) {
        this.appId = appId;
        this.appSecret = appSecret;
        this.apiUrl = apiUrl;
    }

    public String recognizeTextFromPdf(MultipartFile file) throws IOException {
        byte[] fileBytes = file.getBytes();

        URL url = new URL(apiUrl);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("POST");
        conn.setRequestProperty("Content-Type", "application/octet-stream");
        conn.setRequestProperty("x-ti-app-id", appId);
        conn.setRequestProperty("x-ti-secret-code", appSecret);
        conn.setDoOutput(true);

        OutputStream os = conn.getOutputStream();
        os.write(fileBytes);
        os.flush();
        os.close();

        BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
        String line;
        StringBuilder response = new StringBuilder();
        while ((line = reader.readLine()) != null) {
            response.append(line);
        }
        reader.close();

        // APIレスポンスの解析を行い、テキスト内容を返却
        return response.toString();
    }
}

@RestController
@RequestMapping("/textin")
public class TextInController {

    @Autowired
    private TextInService textInService;

    @PostMapping("/recognize-pdf")
    public ResponseEntity<String> recognizeTextFromPdf(@RequestParam("file") MultipartFile file) {
        if (!file.isEmpty()) {
            try {
                String recognizedText = textInService.recognizeTextFromPdf(file);
                return ResponseEntity.ok(recognizedText);
            } catch (IOException e) {
                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                        .body("PDFから文字認識エラー: " + e.getMessage());
            }
        } else {
            return ResponseEntity.badRequest().body("空のファイル");
        }
    }
}

注意点

画像URLを直接渡す場合、PILのImage.read()メソッドでエラーが発生することがあります。これはHTTPリクエストのレスポンスがバイナリストリームであるためです。対策として、一時ファイルを作成し、その中にデータを書き込む方法があります。

@RestController
public class TextRecognitionController {

    @GetMapping("/recognize-text")
    public ResponseEntity<?> recognizeText(@RequestParam("image_url") String imageUrl) {
        try {
            ResponseEntity<byte[]> response = restTemplate.getForEntity(imageUrl, byte[].class);

            if (response.getStatusCode().is2xxSuccessful()) {
                ByteArrayInputStream imageStream = new ByteArrayInputStream(response.getBody());

                File tempFile = File.createTempFile("temp_image_", ".jpg");
                tempFile.deleteOnExit();

                Files.copy(imageStream, tempFile.toPath());

                String ans = new CommonOcr(tempFile.getAbsolutePath()).recognize();

                ObjectMapper mapper = new ObjectMapper();
                Map<String, Object> data = mapper.readValue(ans, Map.class);

                List<String> textList = new ArrayList<>();
                List<Map<String, Object>> lines = (List<Map<String, Object>>) data.get("result").get("lines");
                for (Map<String, Object> line : lines) {
                    textList.add((String) line.get("text"));
                }

                return ResponseEntity.ok(textList);
            } else {
                return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                        .body("画像取得失敗、ステータスコード:" + response.getStatusCode());
            }
        } catch (IOException e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body("画像認識エラー:" + e.getMessage());
        }
    }
}

まとめ

使用感想

  1. 使いやすさ:TextInは直感的なAPIを提供しており、既存システムへの統合が容易です。HTTPリクエスト一つで画像を送信し、構造化されたテキストを得ることができます。
  2. 正確性:印刷体の文書では高い認識精度を維持しており、テーブルやリストなど複雑なレイアウトにも対応できます。
  3. 機能の豊富さ:基本的な文字認識に加え、表認識、手書き認識、多言語対応などの拡張機能が提供され、金融、法律、教育などさまざまな分野に対応できます。
  4. サポート体制:公式ドキュメントやコミュニティ、技術サポートを通じて問題解決が可能です。

注意すべき点

  1. 手書き文字認識:手書き文字の認識精度は印刷体より低い傾向があり、字が乱れていると誤認識が増える可能性があります。
  2. 複雑なレイアウト:デザインが独特な文書では、元のフォーマットが保持されないことがあります。
  3. 大量文書処理:APIの呼び出し回数制限とコスト管理が必要です。
  4. セキュリティとプライバシー:機密情報を扱う際は、暗号化通信によるデータ保護が必要です。
  5. エラーハンドリング:ネットワークエラー、パラメータエラーなどに備えて、エラーコードを適切に処理する必要があります。

タグ: OCR TextIn SpringBoot 画像認識 Java

5月25日 05:47 投稿