GoogleのDocument AIのAPIを呼び出してOCRを実行してみます。
GoogleのDocument AIですが、料金のページにあるようにCloud Vision、Cloud Natural Language API、Vertex AIの料金が適用されます。ドキュメントOCRプロセッサではCloud Visionが使用されるため、Cloud Visionの料金を確認します。
Cloud Visionの料金を確認すると、すべての機能について最初の 1,000 ユニット/月は無料となっています。APEXのアプリケーションを作ってAPIの呼び出しを確認する程度であれば、費用が発生することはなさそうです。
将来に無料枠が無くなる、または、条件が変わる可能性はあります。お金が関わることですし、Googleが提供している情報はご自身で確認してください。
Document AIのAPIは、サービス・アカウントを作成して、その権限で呼び出します。サービス・アカウントの作成は、以前にIndexing APIの呼び出しで紹介したことがあります。少し手順を変更するため、繰り返しにはなりますが、サービス・アカウントの作成も含めて作業手順を記述します。
作成したAPEXアプリケーションは以下のように動作します。
スタバのレシートから文字を抽出しています。文字や数値は、すべて抽出されていることが確認できます。
以下よりGoogle Cloud側の作業手順を記述します。
プロジェクトは作成済みとします。
Google側での準備
APIとサービスを開き、APIとサービスの有効化をクリックします。
キーのタイプとしてP12(PKCS#12形式)を選択し、作成を実行します。この鍵はPL/SQLのパッケージDBMS_CRYPTOで使用するため、PKCS#1またはPKCS#8形式に変換します。JSONが推奨となっていますが、opensslコマンドを使用するため扱いやすいP12を選択しています。
作成をクリックします。
秘密鍵が手元のPCにダウンロードされます。opensslで形式変換する際に、秘密鍵のパスワードを聞かれるため、表示されている秘密鍵のパスワードをクリップボードにコピーし、保存しておきます。
閉じるをクリックします。
ダウンロードされたPKCS#12形式のファイルをPKCS#8形式に変換します。openssl pkcs12コマンドを使用します。
openssl pkcs12 -in ダウンロードされたファイル -nocerts -nodes -out 出力ファイル
Enter Import Password:には秘密鍵のパスワードを入力します。
以下の例では、秘密鍵をdocument-ai-api.keyというファイルに出力しています。
$ openssl pkcs12 -in <秘密鍵のファイル> -nocerts -nodes -out document-ai-api.key
Enter Import Password: 秘密鍵のパスワード
MAC verified OK
$
APEX側の準備
create or replace package util_google_api | |
as | |
C_TOKEN_URL constant varchar2(160) := | |
'https://oauth2.googleapis.com/token?grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer' || chr(38)|| 'assertion='; | |
/* | |
* Googleのプロジェクトに登録したサービス・アカウントのキーより | |
* JWTを生成する。 | |
*/ | |
function generate_jwt( | |
p_secret in varchar2 | |
,p_iss in varchar2 default null | |
,p_sub in varchar2 default null | |
,p_scope in varchar2 default null | |
,p_aud in varchar2 default null | |
,p_iat in timestamp default current_timestamp | |
,p_duration in number default 3600 -- 秒で指定する | |
) | |
return varchar2; | |
/* | |
* JWTを使ってBearerトークンを取得する。 | |
*/ | |
function get_token( | |
p_jwt in varchar2 | |
,p_credential_static_id in varchar2 default null | |
) | |
return varchar2; | |
end util_google_api; | |
/ | |
create or replace package body util_google_api | |
as | |
/* | |
* OracleのTIMESTAMP型のデータをUNIX時間に変換する。 | |
*/ | |
function unixtime(p_timestamp in timestamp) | |
return pls_integer | |
is | |
l_date date; | |
l_epoc number; | |
begin | |
l_date := sys_extract_utc(p_timestamp); | |
l_epoc := l_date - date'1970-01-01'; | |
return l_epoc * 24 * 60 * 60; | |
end unixtime; | |
/* BASE64のデコード */ | |
function from_base64(t in varchar2) return varchar2 is | |
begin | |
return utl_raw.cast_to_varchar2(utl_encode.base64_decode(utl_raw.cast_to_raw(t))); | |
end from_base64; | |
/* BASE64へのエンコード - RAWより */ | |
function to_base64_from_raw(t in raw) return varchar2 is | |
l_base64 varchar2(32767); | |
begin | |
l_base64 := utl_raw.cast_to_varchar2(utl_encode.base64_encode(t)); | |
l_base64 := replace(l_base64, chr(13)||chr(10), ''); | |
return l_base64; | |
end to_base64_from_raw; | |
/* BASE64へのエンコード - VARCHAR2 */ | |
function to_base64(t in varchar2) return varchar2 is | |
begin | |
return to_base64_from_raw(utl_raw.cast_to_raw(t)); | |
end to_base64; | |
/* 秘密鍵を一行にする。 */ | |
function convert_to_single_line( | |
p_string in varchar2 | |
) | |
return varchar2 | |
as | |
begin | |
return regexp_replace( | |
p_string | |
,'(-+((BEGIN|END) (RSA )?(PUBLIC|PRIVATE) KEY)-+\s?|\s)' | |
,'' | |
); | |
end convert_to_single_line; | |
/* JWTを生成する実装 */ | |
function generate_jwt( | |
p_secret in varchar2 | |
,p_iss in varchar2 | |
,p_sub in varchar2 | |
,p_scope in varchar2 | |
,p_aud in varchar2 | |
,p_iat in timestamp | |
,p_duration in number -- second | |
) | |
return varchar2 | |
as | |
l_iat pls_integer; | |
l_exp pls_integer; | |
l_header_json json_object_t; | |
l_header_str varchar2(32767); | |
l_header_base64 varchar2(32767); -- 1st part of JWT | |
l_payload_json json_object_t; | |
l_payload_str varchar2(32767); | |
l_payload_base64 varchar2(32767); -- 2nd part of JWT | |
l_data varchar2(32767); | |
l_hmac_raw raw(32767); | |
l_hmac varchar2(32767); -- 3rd part of JWT | |
l_jwt varchar2(32767); | |
begin | |
/* iatとexpとなる値を求める。 */ | |
l_iat := unixtime(p_iat); | |
l_exp := l_iat + p_duration; | |
/* ヘッダーを手作業で作成し、BASE64でエンコードする。 */ | |
l_header_json := json_object_t(); | |
l_header_json.put('alg','RS256'); | |
l_header_json.put('typ','JWT'); | |
l_header_str := l_header_json.to_string(); | |
l_header_base64 := to_base64(l_header_str); -- ヘッダー | |
/* ペイロードを手作業で作成し、BASE64でエンコードする。 */ | |
l_payload_json := json_object_t(); | |
if p_iss is not null then | |
l_payload_json.put('iss', p_iss); | |
end if; | |
if p_sub is not null then | |
l_payload_json.put('sub', p_sub); | |
end if; | |
if p_scope is not null then | |
l_payload_json.put('scope', p_scope); | |
end if; | |
if p_aud is not null then | |
l_payload_json.put('aud', p_aud); | |
end if; | |
l_payload_json.put('iat', l_iat); | |
l_payload_json.put('exp', l_exp); | |
l_payload_str := l_payload_json.to_string(); | |
l_payload_base64 := to_base64(l_payload_str); -- ペイロード | |
-- シグネチャを手作業で作成する。 | |
l_data := l_header_base64 || '.' || l_payload_base64; | |
l_hmac_raw := dbms_crypto.sign( | |
src => utl_i18n.string_to_raw(l_data,'AL32UTF8'), | |
prv_key => utl_i18n.string_to_raw(convert_to_single_line(p_secret),'AL32UTF8'), | |
pubkey_alg => DBMS_CRYPTO.KEY_TYPE_RSA, | |
sign_alg => DBMS_CRYPTO.SIGN_SHA256_RSA | |
); | |
l_hmac := to_base64_from_raw(l_hmac_raw); | |
l_hmac := trim(translate(l_hmac, '+/=', '-_ ')); -- HMAC | |
/* JSON Web Tokenを返す。 */ | |
l_jwt := l_header_base64 || '.' || l_payload_base64 || '.' || l_hmac; | |
return l_jwt; | |
end generate_jwt; | |
/* トークンを取得する実装 */ | |
function get_token( | |
p_jwt in varchar2 | |
,p_credential_static_id in varchar2 | |
) | |
return varchar2 | |
as | |
l_request_url varchar2(32767); | |
l_token_clob clob; | |
l_token_json json_object_t; | |
l_token varchar2(32767); | |
begin | |
l_request_url := C_TOKEN_URL || p_jwt; | |
apex_web_service.clear_request_headers; | |
apex_web_service.set_request_headers('Content-Length',0,p_reset => false); | |
l_token_clob := apex_web_service.make_rest_request( | |
p_url => l_request_url | |
,p_http_method => 'POST' | |
); | |
l_token_json := json_object_t(l_token_clob); | |
l_token := l_token_json.get_string('token_type') || ' ' || l_token_json.get_string('access_token'); | |
/* | |
* Web資格証明の静的IDが指定されている場合は、アップデートする。 | |
*/ | |
if p_credential_static_id is not null then | |
apex_credential.set_session_credentials( | |
p_credential_static_id => p_credential_static_id | |
,p_username => 'Authorization' | |
,p_password => l_token | |
); | |
end if; | |
return l_token; | |
end get_token; | |
end util_google_api; | |
/ |
set serveroutput on | |
declare | |
C_RSA_KEY constant varchar2(32767) := q'~ | |
-----BEGIN PRIVATE KEY----- | |
サービス・アカウントに作成した秘密鍵 | |
-----END PRIVATE KEY----- | |
~'; | |
C_ISS constant varchar2(400) := 'サービス・アカウントのメールアドレス'; | |
C_SCOPE constant varchar2(400) := 'https://www.googleapis.com/auth/cloud-platform'; | |
C_AUD constant varchar2(400) := 'https://oauth2.googleapis.com/token'; | |
C_CRED_ID constant varchar2(400) := 'GOOGLE_DOCUMENT_AI_API_TOKEN'; | |
C_WORKSPACE constant varchar2(400) := 'APEXのワークスペース'; | |
l_workspace_id number; | |
l_jwt varchar2(32767); | |
l_token varchar2(32767); | |
begin | |
l_jwt := util_google_api.generate_jwt( | |
p_secret => C_RSA_KEY | |
,p_iss => C_ISS | |
,p_scope => C_SCOPE | |
,p_aud => C_AUD | |
); | |
dbms_output.put_line(l_jwt); | |
l_token := util_google_api.get_token(l_jwt); | |
dbms_output.put_line(l_token); | |
l_workspace_id := apex_util.find_security_group_id( | |
p_workspace => C_WORKSPACE | |
); | |
apex_util.set_security_group_id( | |
p_security_group_id => l_workspace_id | |
); | |
apex_credential.set_persistent_credentials( | |
p_credential_static_id => C_CRED_ID | |
,p_username => 'Authorization' | |
,p_password => l_token | |
); | |
commit; | |
end; | |
/ |
APEXアプリケーションの作成
declare | |
l_blob blob; | |
l_clob clob; | |
l_mime_type varchar2(80); | |
l_request json_object_t; | |
l_raw_document json_object_t; | |
l_response clob; | |
l_response_json json_object_t; | |
l_document json_object_t; | |
begin | |
select blob_content, mime_type into l_blob, l_mime_type | |
from apex_application_temp_files | |
where name = :P1_IMAGE; | |
l_clob := apex_web_service.blob2clobbase64( | |
p_blob => l_blob | |
); | |
l_clob := replace(l_clob, chr(13)||chr(10), ''); | |
-- translateの要不要は確認していない。 | |
-- l_clob := trim(translate(l_clob, '+/=', '-_ ')); | |
l_raw_document := json_object_t(); | |
l_raw_document.put('mimeType', l_mime_type); | |
l_raw_document.put('content', l_clob); | |
l_request := json_object_t(); | |
l_request.put('rawDocument', l_raw_document); | |
apex_web_service.clear_request_headers; | |
apex_web_service.set_request_headers('Accept','application/json',p_reset => false); | |
apex_web_service.set_request_headers('Content-Type','application/json',p_reset => false); | |
l_response := apex_web_service.make_rest_request( | |
p_url => :G_PROCESS_URL | |
,p_http_method => 'POST' | |
,p_body => l_request.to_clob() | |
,p_credential_static_id => 'GOOGLE_DOCUMENT_AI_API_TOKEN' | |
); | |
:P1_RESPONSE := l_response; | |
l_response_json := json_object_t.parse(l_response); | |
l_document := l_response_json.get_object('document'); | |
:P1_TEXT := l_document.get_string('text'); | |
end; |