0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【kintone】物体検出モデルを kintone アプリと連携するための環境構築

Last updated at Posted at 2023-10-31

はじめに

前回、前々回で、建築資材の物体検出を YOLOv8 を利用して実装する方法を書きました。

弊社では業務アプリとしてサイボウズ社の kintone を使用していますので、kintone アプリとの連携を実装してみたいと思います。

シナリオの概要としては以下のようになります。
① kintone アプリに対象となる画像 (パイプ/クランプ等の資材) をアップロードする。
② アップロードされた画像を、作成した物体検出モデルで推論し、結果としてバウンディングボックス付き画像と検知した個数を kintone アプリに返して該当レコードを更新する。

この kintone と連携する環境として外部に仮想マシンを構築するのが本記事の目的となります。

1. 環境の構成

kintone との連携環境として GCE 上に仮想マシンを構築します。
環境のイメージとしては以下のようになります。

2. 仮想マシンの構築

2.1 VM インスタンス作成

GCE で仮想マシンを作成します。既定値から変更した内容は以下の通りです。

  • リージョン:asia-northeast1 (東京)
  • ゾーン:asia-northeast1-a
  • マシンタイプ:n2-standard-2 (2 vCPU、1 コア、8 GB メモリ)
  • ブートディスク:サイズ:20 GB (既定値 10 GB) / イメージ:Ubuntu 22.04 LTS

マシンスペックですが、
・コストの関係で GPU は未使用
・ディスクサイズは 10 GB ではいずれ不足するためとりあえず倍の 20 GB
・イメージは使い慣れた Ubuntu 22.04
としました。

2.2 外部 IPv4 アドレスの予約

kintone からの通信を受け取るため固定の外部 IP アドレスとします。
作成した VM インスタンスを編集画面で開き、"ネットワーク インターフェース" を展開し外部 IPv4 アドレスを静的に予約します。
image.png

3 インスタンスへ接続

3.1 SSH による接続準備

作成した VM インスタンスに対し、コンソール (ブラウザ) から SSH 接続します。
image.png

以下のコマンドでキーペアを作成します。

ssh-keygen -t ed25519 -f ./key/id_ed25519

実行結果例です。パスフレーズも設定します。
秘密鍵 (id_ed25519) と公開鍵 (id_ed25519.pub) が作成されます。
image.png

秘密鍵はローカルにダウンロードしておきます。ブラウザの SSH 画面の右上にある「ファイルをダウンロード」から、秘密鍵のフルパスを指定してダウンロードします。
image.png

一方、公開鍵は VM インスタンスに設定します。
インスタンスの編集画面で "SSH 認証鍵" セクションで、公開鍵 (id_ed25519.pub) の中身をコピペします。
image.png

3.2 SSH による接続

いったん TeraTerm から SSH 接続確認してみます。
設定したパスフレーズと秘密鍵を使用して接続出来れば OK です。
image.png

ターミナルソフトを使用してもよいのですが、通常の業務では Windows 上で VSCode を使用してコーディングしますので、VSCode から接続して使用ます。
VSCode の拡張機能の "Remote - SSH" をインストールします。

image.png

接続の設定ファイルはホームディレクトリ配下の「.ssh」ディレクトリ配下の "config" を作成して編集します。
(ディレクトリ、ファイルが存在しない場合は手動で作成します)
image.png

"config" ファイルに接続情報を記載します。

Host kintone
    HostName xxx.xxx.xxx.xxx
    Port 22
    User USER_NAME
    IdentityFile "PATH\TO\Private_Key_File"

VSCode を起動し、"リモート エクスプローラー" に設定ファイルに記載したホストがリストされ VM インスタンスに接続出来れば OK です。
image.png

以上で作業の準備が整いました。

4. 環境の準備

作業に入る前に、各種環境の設定を実施します。

① ルートのパスワード設定

sudo passwd root

② パッケージ情報の更新

sudo apt update

③ pip のインストール

sudo apt install python3-pip

④ タイムゾーン・言語を変更

sudo apt install language-pack-ja-base language-pack-ja ibus-mozc
sudo timedatectl set-timezone Asia/Tokyo
sudo localectl set-locale LANG=ja_JP.UTF-8

5. 通信環境の構築

kintone と本環境の間の https 通信環境を用意します。
python を使用できる軽量な Web フレームワークである Flask を使用します。
Web サーバーには nginx。その間のインターフェースとして uWSGI を使用します。
以下のイメージです。

image.png

5.1 各種インストール

必要なモジュールをインストールします。

sudo apt install -y nginx
sudo pip3 install uwsgi
pip3 install flask

5.2 nginx の構成

先に、VM インスタンスのファイアウォールを変更しておきます。
外部からの http / https アクセスを有効化します。
image.png

nginx の設定ファイルを作成します。

sudo vi /etc/nginx/conf.d/flask.conf
server {
    server_name xxx.xxx.co.jp;  # 予約した外部 IP アドレスに解決されるインターネットホスト名を DNS で設定しておきます。
    location / {                # ルートのリクエストをキャッチ
        root /PATH/TO/flask;    # 探索先ディレクトリ
        index index.html;
        proxy_pass http://127.0.0.1:5000; # リクエストの転送先
        proxy_redirect default; # レスポンスの Location ヘッダーを更新
    }

    access_log /PATH/TO/flask-access.log;
    error_log /PATH/TO/flask-error.log; 
}

nginx を再起動します。

sudo systemctl restart nginx

5.3 Flask の構成

先に設定した flask.conf の flask ディレクトリ配下の構造を以下とします。

flask
├── templates
│   └── index.html
└── home.py

index.html を適当に用意します。

<html>
    <head>
        <meta charset="utf-8" />
    </head>
    <body>
        <h1>Hello World!</h1>
    </body>
</html>

home.py の内容です。

from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def home():
    return render_template('index.html')

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0', port=5000)

実行して外部から接続確認します。

$ python3 ./run.py 
 * Serving Flask app 'run'
 * Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:5000
 * Running on http://xxx.xxx.xxx.xxx:5000
Press CTRL+C to quit
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 291-073-651
127.0.0.1 - - [12/Oct/2023 01:11:18] "GET / HTTP/1.0" 200 -

外部から http 接続して問題ないことを確認します。
image.png

kintone から Webhook を受け取るためには https 通信が必要となるため、無料 SSL である Let's Encrypt を導入します。
まずモジュールをインストールします。

sudo apt install certbot python3-certbot-nginx

証明書を発行します。質問に Y で答えます。

sudo certbot --nginx -d xxxx.xxxx.co.jp

問題なく証明書が発行されました。

Deploying certificate
Successfully deployed certificate for xxx.xxx.co.jp to /etc/nginx/conf.d/flask.conf
Congratulations! You have successfully enabled HTTPS on https://xxx.xxx.co.jp

改めて設定ファイルを確認すると、色々と設定が追加されていることが見て取れます。

server {
    server_name xxx.xxx.co.jp;
    location / {
        root /PATH/TO/flask;
        index index.html;
        proxy_pass http://127.0.0.1:5000;
        proxy_redirect default;
    }

    access_log /PATH/TO/flask-access.log;
    error_log /PATH/TO/flask-error.log; 


    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/xxx.xxx.co.jp/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/xxx.xxx.co.jp/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}

server {
    if ($host = xxx.xxx.co.jp) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    server_name xxx.xxx.co.jp;
    listen 80;
    return 404; # managed by Certbot


}

証明書の更新を検証します。

sudo certbot renew --dry-run

こちらも問題ないことを確認します。

Congratulations, all simulated renewals succeeded: 
  /etc/letsencrypt/live/xxx.xxx.co.jp/fullchain.pem (success)

再度 home.py を実行し、今度は https で接続できることを確認します。
image.png

Let's Encrypt で発行された証明書の有効期間は 3 ヵ月です。
定期的に更新するように cron を設定します。
(実際に更新されるのは有効期限が 30 日を切ってからにはなります。)

PATH=/usr/sbin:/usr/bin:/bin
XX XX XX * * /usr/bin/certbot renew --post-hook "systemctl restart nginx" >> /tmp/certbot.log 2>&1

5.4 WSGI の構成

uWSGI を設定します。
home.ini を作成します。

flask
├── templates
│   └── index.html
├── home.py
└── home.ini
[uwsgi]

# uWSGI 実行時にアプリケーションのルートディレクトリに移動
chdir = /PATH/TO/flask

# uWSGI アプリケーションとして実行する Pythonフ ァイルのパス
wsgi-file = /PATH/TO/flask/home.py

# home.py 内で定義されているアプリケーションオブジェクトの名前
callable = app

# UNIX ソケットファイルを使用して nginx と通信
socket = /tmp/home.sock

# UNIX ソケットファイルのパーミッション
chmod-socket = 666

# uWSGI をマスターモードで実行
master = true

# uWSGIのログの保存先
logto = /PATH/TO/flask/home.log

# uWSGI が起動時に生成するプロセス ID を保存
pidfile = /PATH/TO/flask/home.pid

先ほど作成した flask.conf を変更し uWSGI と連携します。
ルートへのアクセスは UNIX ソケットを介して uWSGI に転送され、uWSGI はこのソケットを使ってアプリケーションと通信します。
nginx は再起動します。

    location / {
        # root /PATH/TO/flask;
        # index index.html;
        # proxy_pass http://127.0.0.1:5000;
        # proxy_redirect default;
        include uwsgi_params;
        uwsgi_pass unix:///tmp/home.sock;
    }

uwsgi 経由でバックグランドで実行します。
先ほどの index.html の内容が表示されることを確認します。

uwsgi --ini /PATH/TO/flask/home.ini &

停止する場合は以下のコマンドを実行します。

uwsgi --stop /PATH/TO/flask/home.pid

上記コマンドを cron に登録しておくことでインスタンスの起動時に実行されますが、systemd を使用してサービスとして管理します。

$ sudo vi /etc/systemd/system/home.service
[Unit]
Description = uWSGI
After = syslog.target

[Service]
ExecStart = /usr/local/bin/uwsgi --ini /PATH/TO/flask/home.ini

User=xxxx
Group=xxx
RuntimeDirectory=uwsgi
Restart=always
KillSignal=SIGQUIT
Type=notify
StandardError=syslog
NotifyAccess=all

[Install]
WantedBy=multi-user.target

設定ファイルを作成したら起動して動作確認します。

$ sudo systemctl start home.service

自動起動する場合のコマンドです。

$ sudo systemctl enable home.service

とりあえず、若干長くなりましたが環境の準備は整いました。

6. kintone 連携

当初の目的は、kintone アプリとの連携でした。
ここまで来ればほぼ出来たようなものです。

6.1 kintone アプリの設定

連携するアプリの設定画面にある "Webhook" を設定します。
image.png

Webhook 先を追加します。
実際に本番で使用する場合は、サービス毎にサブディレクトリを切ることになると思います。
("https://xxx.co.jp/api/service1" 等)

image.png

6.2 kintone からの Webhook を受け取る python コード

最もシンプルに記載すると、以下のようなコードで "record_data" 変数に kintone のレコードが JSON で取得できます。

from flask import Flask, request

app = Flask(__name__)


@app.route('/', methods=['POST'])
def test():
    record_data = request.json['record']

    return 'OK'


if __name__ == '__main__':
    app.run()

あとは取得できたレコードのデータを使用して好きなように処理すれば OK です。
今回のシナリオでは以下のような処理を実装しました。

① アップロードされた資材の画像の filekey を取得
② filekey から実際の画像をローカルにダウンロード
③ 作成した物体検出モデルを使用して画像を推論
④ 推論結果の矩形が描画された画像を kintone の一時領域にアップロードして filekey を取得
⑤ 上記画像の filekey とバウンディングボックス数を、該当レコードのフィールドに対して更新

7. まとめ

GCE インスタンスでの推論は GPU ではなく CPU で実行しましたが、推論にかかる時間としては 1~2 秒くらいでした。
リアルタイム性を求めていませんので業務利用には耐えうると判断しました。

Ultralytics のサイトには
Export to ONNX or OpenVINO for up to 3x CPU speedup.
という記載もありますので、そのうち試してみたいと思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?