Perl Hackers Hub

第54回サーバレスでもPerl―Microsoft Azure Functionsで動かそう!(3)

(1)こちら⁠2)こちらから。

関数の実装

いよいよコードを実装します。ここでは作業環境にMacを利用する前提で説明します。

まず、関数のプロジェクトとなるフォルダを用意します。顔認識をさせるプロジェクトですので、名前をFaceDetectProjectとして作っておきましょう。

プロジェクトとなるフォルダを作っておく
$ mkdir FaceDetectProject

Face APIを利用するモジュールの導入

Functionsには、Face APIへ顔認識のリクエストを行うためのバインディングが用意されていません。そのため、CPANモジュールを使用します。Face API向けのモジュールとして、MetaCPANで拙作のNet::Azure::CognitiveService::Face(執筆時点ではv0.04)を公開していますので、これを利用します。

CPANモジュールの導入には、本連載でお馴染みのcpanmを使います。プロジェクトフォルダ直下でcpanmを使い、プロジェクトにモジュールを導入します。

CPANモジュールをプロジェクトに導入する
$ cpanm -n -L local Net::Azure::CognitiveServices::Face

関数名フォルダの作成

関数名フォルダの名称はそのまま関数名となりますので、わかりやすい名称がよいでしょう。今から作るのは顔認識の結果(年齢と性別)を返す関数ですので、InspectFaceという名称で関数名フォルダを作ります。

関数名フォルダを作る
$ mkdir InspectFace

ここから先は、InspectFaceフォルダ内での作業となります。

function.jsonの作成

トリガとバインディングの設定をするために、function.jsonを作成します。InspectFaceはHTTPリクエストを受けてHTTPレスポンスを返す関数ですので、HTTPトリガを設定します。内容は、先述したHTTPトリガのfunction.jsonと同じです。

run.shの作成

関数本体となるrun.shは次の内容です。

run.sh
perl task.pl > $res

Functionsから起動されたときにtask.plを起動し、環境変数resで指定されたファイルにレスポンスデータを出力させる役割を担います。

Perlプログラムの作成

関数本体から呼び出されるtask.plを作ります。

task.pl
use strict;
use warnings;
use lib "../local/lib/perl5";
use Net::Azure::CognitiveServices::Face;
use JSON 'encode_json';

# (1)長い名前なので変数に入れて短くする
my $cog = 'Net::Azure::CognitiveServices::Face';

# (2)環境変数から、Face APIの設定値を取得する
$cog->endpoint($ENV{FACEAPI_URL});
$cog->access_key($ENV{FACEAPI_KEY});

# (3)HTTPクエリパラメータimageを取得する
my $image_url = $ENV{REQ_QUERY_IMAGE};

# (4)デフォルトのレスポンスを定義する
my $res = {
    status => 400,
    headers => {'Content-Type' => 'application/json'},
    body => {
    message => 'parameter "image" is required',
        }
};

# (5)画像URLが指定されている場合のみ顔認識を試みる
if ($image_url) {

    # (6)Face APIクライアントを使って顔認識を行う
    my $faceapi = $cog->Face;
    my $result = $faceapi->detect(
        $image_url,
        returnFaceAttributes => ['age', 'gender']
    );

    # (7)認識した顔情報から年齢と性別を抽出する
    my @faces = ();
    if ($result->[0]) {
        @faces = map { $_->{faceAttributes} } @$result;
    }

    # (8)レスポンスデータを上書きする
    $res = {
        status => 200,
        headers => {
            'Content-Type' => 'application/json'
        },
        body => {
            faces => [@faces]
        }
    };
}

# (9)レスポンスを返す
print encode_json($res);

冒頭のuse文では、4行目でNet::Azure::CognitiveService::Faceモジュールを利用するので、3行目でライブラリパスの指定を行っています。名前が長いモジュールですので、(1)で変数$cogにモジュール名を代入し、表記を短縮しています。5行目ではJSONモジュールをuseしていますが、これはレスポンスをJSON形式にするためです。

(2)では、Net::Azure::CognitiveService::Faceに対し、Face APIのエンドポイントURLとFace APIのアクセスキーを渡しています。これらの値は、のちほど環境変数の設定を行うときに登場します。

(3)では、HTTPリクエストのクエリパラメータimageを取得しています。

(4)ではデフォルトのレスポンスを定義しています。もしクエリパラメータimageが指定されていない場合、(5)の条件分岐で顔認識を行うロジックに入らず、そのまま400エラーを返すためです。

(6)でFace APIに顔認識のリクエストを行い、レスポンスを受け取ります。顔が認識された場合は、(7)で認識した顔情報から年齢と性別を取り出し、@facesに格納します。顔が認識されなかった場合は何もしません。

(8)でレスポンスデータを正常系のものに上書きしています。このとき、bodyに@facesを配列リファレンスに展開します。

そして(9)でレスポンスデータをJSONに変換して、printで出力しています。run.shではtask.plの出力内容をそのまま環境変数resで指定されているファイルにリダイレクトしており、これでFunctionsにレスポンスを引き渡すことができています。

Functionsリソースへの環境変数の設定

task.pl(2)で、環境変数からFace APIのエンドポイントURLのFACEAPI_URLと、アクセスキーのFACEAPI_KEYを参照している箇所がありました。これらの環境変数をFunctionsリソースに設定します。

まず、次のコマンドでFace APIのエンドポイントURLを調べます。

Face APIのエンドポイントURLを調べる
$ az cognitiveservices account show \
  --resource-group face-detect \
  --name face-detect-test |
  grep endpoint
  "endpoint": "https://japaneast.api.cognitive.microsoft.com/face/v1.0",
  ↑実行結果。エンドポイントURLなのでメモしておく

--resource-groupにはリソースグループ名を、--nameにはCognitive Serviceアカウント名を指定します。

続いて、次のコマンドでFace APIのアクセスキーを調べます。--resource-group--nameの指定は先ほどと同じです。

Face APIのアクセスキーを調べる
$ az cognitiveservices account keys list \
  --resource-group face-detect \
  --name face-detect-test |
  grep key1
  "key1": "ba74XXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  ↑実行結果。アクセスキーなのでメモしておく

Face APIのエンドポイントURLとアクセスキーの両方がメモできたら、次のコマンドでFunctionsリソースに環境変数を設定します。

Face APIの各設定をFunctionsリソースの環境変数に設定する
$ az functionapp config appsettings set \
  --resource-group face-detect \
  --name face-detect-app1001 \
  --settings \
    FACEAPI_URL=https://japaneast.api.cognitive.microsoft.com/face/v1.0 \
    FACEAPI_KEY=ba74XXXXXXXXXXXXXXXXXXXXXXXXXXXX

Azureへのデプロイ

できあがったコードをAzureにデプロイします。

Functionsでは、GitHubやOneDriveなど、いくつかのデプロイ方法に対応しています。本稿では比較的容易なZIPファイルによるデプロイを紹介します。

まず、プロジェクトフォルダをZIP形式で圧縮します。プロジェクトフォルダ内で次のコマンドを実行してください。

プロジェクトフォルダをzipコマンドで圧縮
$ zip -r FaceDetectProject.zip ./

できあがったZIPファイルのFaceDetectProject.zipをデプロイします。

できあがったZIPファイルをデプロイ
$ az functionapp deployment source config-zip \
  --resource-group face-detect \
  --name face-detect-app1001 \
  --src FaceDetectProject.zip

--resource-groupにはリソースグループ名を、--nameにはリソース名を、--srcには先ほど作成したZIPファイルのパスを指定します。

これで、Perlで作られた関数をFunctionsリソースにデプロイできました。

ブラウザで動作確認

ブラウザでアクセスして動作を確認します。アクセス先URLは関数のエンドポイントURLで、Perlで表現すると次のルールで割り当てられます。

関数のエンドポイントURLの割り当てルールをPerlで表現
# Functionsリソース名。この例では"face-detect"
my $functionapp_name = "face-detect";

# 関数名。この例では"FaceDetect"
my $funcname = "FaceDetect";

# エンドポイントURL
my $endpoint_url = sprintf(
    'https://%s.azurewebsites.net/api/%s',
    $functionapp_name,
    $funcname
);

print $endpoint_url;

上記の実行結果、つまりこの関数のエンドポイントURLはhttps://face-detect.azurewebsites.net/api/FaceDetectです。

また、動作確認に利用できる顔画像のURLが必要となります。各自画像URLを用意してください。先ほどの関数のエンドポイントURLに顔画像URLをimageパラメータとして付加すると、https://face-detect.azurewebsites.net/api/FaceDetect?image=顔画像URLとなります。

ブラウザでこのURLにアクセスすると、次のような結果が返ってくることでしょう。

完成した関数のレスポンス
{
  "faces":[
    {"gender":"male","age":56},
    {"age":58,"gender":"male"}
  ]
}

これは、指定した画像から2名の顔を認識した結果です。性別と推測年齢を人物ごとに抽出できていることが確認できますね。

まとめ

本稿を通じて、Functionsを使い、サーバレス環境上でPerlを利用できることをご理解いただけたと思います。Functionsについてより詳しく知りたい人は、Microsoft Learnをご覧ください。

Perlの強みは、後方互換性の高さにあります。筆者が管理するシステムでは、Perlで書かれたビジネスロジックがFunctions上で稼働しています。古いロジックであるにもかかわらず、Functionsでそのまま動作したことは、筆者にとっても驚きでした。

さて、次回の執筆者は東邦之さんで、テーマは「Perlコードを速くする書き方」です。

WEB+DB PRESS

本誌最新号をチェック!
WEB+DB PRESS Vol.130

2022年8月24日発売
B5判/168ページ
定価1,628円
(本体1,480円+税10%)
ISBN978-4-297-13000-8

  • 特集1
    イミュータブルデータモデルで始める
    実践データモデリング

    業務の複雑さをシンプルに表現!
  • 特集2
    いまはじめるFlutter
    iOS/Android両対応アプリを開発してみよう
  • 特集3
    作って学ぶWeb3
    ブロックチェーン、スマートコントラクト、NFT

おすすめ記事

記事・ニュース一覧