PHPによる画像からのコンテンツ抽出とPDFテキストの抽出処理

2018年7月7日 18:52:17

画像からテキストを抽出する場合、PHPで独自アルゴリズムを実装することも可能だが、その場合の最適化は煩雑であり、ライブラリの選定や精度調整が必要になる。

一般的なプロジェクトにおいて、OCRサービスを構築しない限り、専用ツールやAPIを構築する必要はない。

一、

ツールについて:tesseract-ocr/tesseractは現在検証中だが、完全なセットアップには時間がかかり、学習データが必要で、複数言語に対応している。

https://github.com/ShuiPingYang/tesseract-ocr-for-php

公式サイト:http://tesseract-ocr.repairfaq.org

実際のプロジェクトに導入するには少なくとも1ヶ月程度、成熟した運用には数ヶ月のオンラインデータトレーニングが必要となる。

二、

既存の成熟したプラットフォームのAPIを利用する方法もある。現在テスト済みの例として、百度(Baidu)の画像認識APIの「一般文字認識(高精度版)」があり、複雑な画像でも一定の精度を維持しており、戻り値は配列形式。不規則なテーブルやテキストに対しては別途処理が必要だが、現状のプロジェクトではこれを使用。公式サイト:http://ai.baidu.com/ 500回/日まで無料。テスト用途には便利。

他にもhttps://ai.qq.com/(騰訊)なども存在するが、登録のみで未テスト。

またアリババクラウドのAPIも存在するが未確認。

いずれも言語別のデモパッケージが提供されている。

以下は百度APIのデモコード:

/*
     * 百度OCR
     * $image は画像のバイナリデータ
     * $type = 1 一般文字認識 50000回/日無料 
     * $type = 2 一般文字認識(高精度)500回/日無料
     * $type = 3 ネットワーク画像文字認識 500回/日無料
     * $type = 4 身分証明書認識 500回/日無料
     * $type = 5 銀行カード認識 500回/日無料
     * 
     * 備考:一般文字認識(高精度)の精度が高いので推奨
     */

    public static function baidu_ai($image = '', $type = 2) {
        if (empty($image)) {
            throw new \Exception('画像のバイナリデータは空にできません');
        }

        $AppID = '';
        $API_Key = '';
        $Secret_Key = '';

        if ($type == 1) {
//            一般文字認識
            $url = 'https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic';
        } elseif ($type == 2) {
            //一般文字認識(高精度版)
            $url = 'https://aip.baidubce.com/rest/2.0/ocr/v1/accurate_basic';
        } elseif ($type == 3) {
            //ネットワーク画像文字認識
            $url = 'https://aip.baidubce.com/rest/2.0/ocr/v1/webimage';
        } elseif ($type == 4) {
            //身分証明書認識
            $url = 'https://aip.baidubce.com/rest/2.0/ocr/v1/idcard';
        } elseif ($type == 5) {
            //銀行カード認識
            $url = 'https://aip.baidubce.com/rest/2.0/ocr/v1/bankcard';
        } else {
            throw new \Exception('$type のタイプが誤っています');
        }
        //トークン取得(有効期限30日)、Redisに保存
        $baidu_key = 'BAIDU_TOKET';
        if (Redis::ttl($baidu_key) < 360) {
            $data['grant_type'] = 'client_credentials';
            $data['client_id'] = $API_Key;
            $data['client_secret'] = $Secret_Key;
            $access_token_url = 'https://aip.baidubce.com/oauth/2.0/token';
            $result = self::post($access_token_url, $data);
            if ($result['code'] != 200) {
                throw new \Exception('認証エラー');
            }

            Redis::set($baidu_key, $result['content']);
            Redis::expire($baidu_key, 29 * 24 * 3600);
        }
        $baidu_token = Redis::get($baidu_key);
        $baidu_token = json_decode($baidu_token, true);

        $access_token = $baidu_token['access_token'];

        $request_data['image'] = base64_encode($image);
        $request_url = $url . "?access_token={$access_token}&aipSdk=php&aipSdkVersion=2_2_2";
        $data_result = self::post($request_url, $request_data);
        if ($data_result['code'] != 200) {
            throw new \Exception('APIリクエストエラー');
        }
        return $data_result['content'];
    }

    public static function post($url = '', $data = [], $headers = []) {
        if (empty($url) || empty($data)) {
            throw new \Exception('リクエストURLとデータは空にできません');
        }
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_HEADER, false);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ch, CURLOPT_POSTFIELDS, is_array($data) ? http_build_query($data) : $data);
        curl_setopt($ch, CURLOPT_TIMEOUT_MS, 60000);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, 60000);
        $content = curl_exec($ch);
        $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

        if ($code === 0) {
            throw new \Exception(curl_error($ch));
        }

        curl_close($ch);
        return array(
            'code' => $code,
            'content' => $content,
        );
    }

三、

純粋なアルゴリズムによる抽出は見送る。

PDFの内容抽出

テキストベースのPDFをPHPで処理する場合、いくつかのライブラリが存在し、効果的な結果を得られる。ただし、出力されるのは非構造化されたテキスト。

https://github.com/smalot/pdfparser PHPライブラリ

精度と効率ともに良好。

プロジェクトのデモコード:

composer require smalot/pdfparser
use Smalot\PdfParser\Parser;


public static function parsing_pdf() {
 $parser = new Parser();
        $pdf = $parser->parseFile('http://www.yafco.com.hk/asp_bin/newimg/1523872385.pdf');
//        pp($pdf);
        $text = $pdf->getText();
        $result = json_encode($text, JSON_UNESCAPED_UNICODE);
}

画像形式のPDFはページごとに画像に分割してからOCR処理が必要。

データ抽出に関する考察

たとえば特定のウェブサイトから公開されたPDFファイルをダウンロードし、一部のデータを抽出する場合、まず元のPDFテキストをJSON形式に変換し、タブ文字や改行文字を適切に処理する。日本語はエンコードしないこと。

json_encode('日本語', JSON_UNESCAPED_UNICODE);

タブ文字(\t)や改行(\n)などの区切り文字を用いて、視覚的に規則正しいPDFであっても、解析後は不規則な形式になる。複数の要素を位置で特定するには、全体を走査する方法は非効率的である。

方法1:

PDFのデータ配置が概ね固定されている場合、大枠を小さな塊に分割し、それぞれの塊内での位置を細かく指定することで、多数のマッチング条件を減らすことが可能。全体走査よりも効率的。

方法2:

自然言語処理(NLP)APIを利用。百度AIや騰訊AIのテキスト解析機能はアルゴリズムを実装したインターフェースを提供しているが、完全な機能は提供されていない。また、1回の処理で扱える文字数が制限されているため、1ページあたり数千文字ある場合は不向き。

方法3:

Hadoopを使用する。しかし、直接的なデータクリーニング・抽出ツールは存在せず、大量データ分析を行う場合はこの生態系が必要。

今後の更新予定あり。

タグ: PHP OCR PDF解析 BaiduAI テキスト抽出

7月9日 00:38 投稿