IoT Connect Gatewayを使ってみた 番外編 第2回 ~インターンシップでStorage転送機能を使って開発してみた~ - NTT Communications Engineers' Blog

IoT Connect Gatewayを使ってみた 番外編 第2回 ~インターンシップでStorage転送機能を使って開発してみた~

目次

はじめに

こんにちは。この度、NTTコミュニケーションズの職業体験型インターンシップのエンジニアコースに参加させていただきました小林と申します。大学では情報科学を専攻しています。 趣味は折り紙・ゲーム・アニメなどで、特に折り紙は小さい頃から親しんできました。サークル活動としてはロボコンサークルに所属し、NHK学生ロボコンへの出場を目指してロボット製作をしています。

今回はNTTコミュニケーションズのインターンシップで、IoT Connect Gatewayというサービスを開発しているチームに参加いたしました。 インターンシップではIoT Connect Gatewayの検討/開発中の新規機能を使い、サービスの提案・開発をする機会をいただいたので、その体験談をまとめます。よろしくお願いいたします。

この記事を作成するにあたって、夏のインターン生の方の記事を参考にさせていただきました。

IoT Connect Gatewayを使ってみた 番外編 ~インターンシップでリリース前の機能を使って開発してみた~

インターンシップ参加にあたって

私は大学の授業で情報科学を学んでいるのですが、まだ2年ということもあって、学んでいることがどのように仕事の役に立つのかというイメージがもてませんでした。 そこで現場で本当に必要とされている能力は何かを実感できれば、大学での学びをより有意義なものにできるのではと考え、インターンシップに応募しました。 またサークルでのロボット製作活動を通して、得意分野や性格がそれぞれ異なる集団のなかでいかに自身の能力を発揮するか、円滑なコミュニケーションができる環境をどのようにつくるかについて考える機会があったため、チームでの開発にも興味がありました。

体験内容

2週間で体験したインターンシップの内容をまとめます。

1. IoT Connect GatewayとStorage転送機能の理解

1.1 IoT Connect Gatewayの概要

まず、本チームの開発サービスであるIoT Connect Gateway (ICGW)について教わりました。

図1 IoT Connect Gatewayの概要図

ICGWは以下の2つの機能を主軸としています。

  • プロトコル変換機能
    • IoTデバイスが送信するデータを暗号化
  • クラウドアダプタ機能
    • 接続情報や鍵の交換を代理で実行

これらの機能により、デバイス側の負荷や暗号化設定の労力を増やすことなくセキュアな通信ができるうえ、クラウド側のインターフェースの仕様変更があった場合にも、ユーザによるデバイス側の操作が不要になるということを教わりました。

1.2 Storage転送機能の概要

次に、ICGWの検討/開発中の新規機能であるStorage転送機能についても教えていただきました。

図2 Storage転送機能の概要図

Storage転送機能はIoTデバイスからクラウドストレージへのデータを転送する機能として開発され、以下の4つの特徴を備えています。

  • セキュアプロトコル変換
  • HTTPメソッドでの基本的なオペレーション
  • URLのパスを利用した柔軟なオブジェクト操作
  • カスタムメタデータの付与

これらの特徴により、IoTデバイスからのセキュアなファイルのアップロードが容易に実現できます。 なかでも、私が注目したのはカスタムメタデータの付与ができることです。 カスタムメタデータとは、ICGWが保持しているIMSIやIMEI、デバイス名、日時といった情報のことを指します。例えば、この機能を用いてアップロードするファイル名に日時を含めることで、予期せぬ上書きを防ぐとともに送信時間を把握しやすくなります。 実際に私が提案したサービスにもこの機能を使用しました。

詳しくは5. IoT Connect Gatewayの設定6. Amazon Web Services上での画像認識をご覧ください。

2. 新サービスの提案

インターン中では、"ICGWの「Storage転送機能」を利用した新サービスを考える"という課題に挑戦させていただくことになりました。

以下が、私の提案したサービス概要となります。

図3 提案したサービスの概要

今回考案した遠隔監視システムは大きく3つの特徴があります。 1つ目は、需要の高いシステムであるということです。昨今のCovid-19の流行や労働者不足により、少人数で広範囲をリアルタイムで監視できるシステムはあらゆる分野で求められていると考えられます。 2つ目は、通信量を少なく抑えられるような設計になっていることです。動体を検出したときの画像のみを送信する仕組みとなっているため、そのまま動画を送るより圧倒的に少ない通信量で運用できます。 3つ目は、他の用途に応用しやすいことです。動体検出を利用しているため監視だけでなく、交通量・歩行量調査や生態系の調査といった研究の分野での使用も想定できます。

3. 開発環境

システムの開発環境は以下の通りです。

  • 機材、端末
    • MacBookPro(13-inch, M1, RAM 16GB)
    • RaspberryPi Zero W V1.1
    • RaspberryPi Camera Rev 1.3
    • 無線LANルータ(NEC Aterm MP02LN CW)
  • 利用サービス
    • IoT Connect Gateway Dashboard
    • Amazon Simple Storage Service (S3)
    • Amazon Web Services Lambda
    • Amazon Rekognition
    • Amazon CloudWatch

4. カメラの開発

RaspberryPiとRaspberryPi Cameraを用いて、動体検知のためのカメラを開発しました。

図4 実際に使用したRaspberryPiとRaspberryPi Camera

4.1 動体検出および画像抽出

RaspberryPiにインストールしたMotionというソフトウェアを使って、監視対象をモニタリングし、動体検出と画像の抽出をしました。

参考:https://www.handsonplus.com/electronic-works/how-to-use-motion-with-raspi/

今回は被写体として私の弟に協力してもらいました。

図5 モニタリング中の映像と動体検出した画像

4.2 画像データの送信

続いて、Pythonを使って各画像ファイルのデータを送信します。

参考:https://www.handsonplus.com/electronic-works/how-to-use-motion-with-raspi/

実際に使用したPythonのコードは以下の通りです。

import os
import time
from glob import glob


# 画像送信
def send_img(attachments):
    # ファイルを送信
    for attachment in attachments:
        os.system("curl -v -X PUT -H 'Content-Type: image/jpeg' --data-binary @" \
                  + attachment \
                  + " http://***:8081/s3")

# メイン関数
def main():
    while(True):
        # 画像ファイルを探す
        attachments = sorted(glob('./*.jpg'))

        # 添付ファイル確認
        if len(attachments) >= 1:

            # 画像送信
            send_img(attachments)
            time.sleep(10)

            # 送信後ファイル削除
            os.system('sudo rm *.jpg')

        time.sleep(10)

main()

jpg形式の各画像ファイルを読み込み、それぞれに対してcurlコマンドを実行することで送信の処理を行います。 IoTデバイスのストレージを圧迫しないように、送信し終わったファイルは削除します。

5. IoT Connect Gatewayの設定

次に、AWS S3にデータを送信するためのStorage転送機能の設定をICGWのコンソール画面から行いました。

以下が実際に投入した設定になります。

図6 Storage転送機能の設定

今回はFilepathに"$YYYY"や"$kk"というプレースホルダーを設定しています。これによりデータが送信された際に、ICGWの機能によってその時の日時データを埋め込めるようになっています。 この機能を活用することで、動体を検出した時間が明記されるだけでなく、ICGW側で一元的に日時データを管理ができます。

6. Amazon Web Services上での画像認識

次に、AWS上で画像認識処理をできるように設定しました。 今回はS3・Lambda・Rekognition・CloudWatchの4つの機能を連携させました。

6.1 S3上のファイル

S3とは、AWSのクラウドストレージサービスです。

図7 S3上のファイル

まずIoTデバイスからICGW経由でS3に画像ファイルが送られてきます。送信されたファイル名は、5. IoT Connect Gatewayの設定で設定したように送信日時となっていることが確認できます。

6.2 LambdaとRekognitionによる画像認識処理

S3に送られた画像は、LambdaとRekognitionによって画像認識処理されます。 Lambdaとは、サーバーレスでプログラムを実行できるサービスです。

図8 Lambdaでの処理

今回使用したLambdaは、トリガーをS3の送信先のディレクトリに設定しているので、画像が送られてくるとLambda上の関数が自動で実行されるようになっています。 関数の処理の流れは上記のフローチャートの通りです。 Lambdaでは送られてきた画像に対して、Rekognitionを用いて画像を解析した後に結果を出力します。

参考:https://acro-engineer.hatenablog.com/entry/2018/12/12/120000

以下が、実際に使用したLambda上の関数のソースコードです。

import os
import boto3

s3 = boto3.resource('s3')

def lambda_handler(event, content):
    # S3にアップされた画像の情報を取得する。
    bucket_name = event['Records'][0]['s3']['bucket']['name']
    object_key = event['Records'][0]['s3']['object']['key']
    file_name = os.path.basename(object_key)

    print("bucket_name=" + bucket_name)
    print("object_key=" + object_key)
    print("file_name=" + file_name)

    # 画像をRekognitionで解析する。
    rekognition = boto3.client('rekognition')
    response = rekognition.detect_labels(Image={
        'S3Object': {
            'Bucket': bucket_name,
            'Name': object_key
        }
    })

    print(response)

6.3 CloudWatchでの画像認識結果の確認

画像認識の結果はCloudWatchで確認できます。

図9 CloudWatch上の画像認識結果

結果はログとして表示されるため少しわかりにくいですが、ラベル・確信度・対象物の頂点座標などが出力されます。 上記のログは一つの画像に対する画像認識結果です。 赤枠で囲まれた所には、"ラベルがPersonでその確信度が約97.4%"であるオブジェクトを認識できた旨が書かれています。 つまり、動体の正体として人間の存在を認識したことを意味します。

7. 画像認識結果の可視化

画像認識結果がログのままではわかりにくかったので、可視化してみました。

図10 画像認識結果の可視化

本来はLambda上で可視化処理まで行おうと考えていましたが、時間が足りなかったため、手元の検証端末にインストールしたOpenCVを用いました。

参考:https://acro-engineer.hatenablog.com/entry/2018/12/12/120000

以下に、可視化処理で使用したプログラムのソースコードを示します。

from urllib import response
import cv2

# 加工するファイルの名前を代入
filename = '2022-23-123902.jpg'

# CloudWatchでの画像認識結果を代入
response = {'Labels': [{'Name': 'Clothing', 'Confidence': 99.97942352294922, 'Instances': [], 'Parents': []}, {'Name': 'Apparel', 'Confidence': 99.97942352294922, 'Instances': [], 'Parents': []}, {'Name': 'Suit', 'Confidence': 99.97110748291016, 'Instances': [], 'Parents': [{'Name': 'Overcoat'}, {'Name': 'Coat'}, {'Name': 'Clothing'}]}, {'Name': 'Overcoat', 'Confidence': 99.97110748291016, 'Instances': [{'BoundingBox': {'Width': 0.47841206192970276, 'Height': 0.8942648768424988, 'Left': 0.18658344447612762, 'Top': 0.05319703370332718}, 'Confidence': 81.37818908691406}], 'Parents': [{'Name': 'Coat'}, {'Name': 'Clothing'}]}, {'Name': 'Coat', 'Confidence': 99.97110748291016, 'Instances': [], 'Parents': [{'Name': 'Clothing'}]}, {'Name': 'Person', 'Confidence': 98.30304718017578, 'Instances': [{'BoundingBox': {'Width': 0.42601126432418823, 'Height': 0.9695423245429993, 'Left': 0.21858000755310059, 'Top': 0.01187931653112173}, 'Confidence': 79.02320861816406}], 'Parents': []}, {'Name': 'Human', 'Confidence': 98.30304718017578, 'Instances': [], 'Parents': []}, {'Name': 'Man', 'Confidence': 96.02997589111328, 'Instances': [], 'Parents': [{'Name': 'Person'}]}, {'Name': 'Standing', 'Confidence': 79.08413696289062, 'Instances': [], 'Parents': [{'Name': 'Person'}]}, {'Name': 'Face', 'Confidence': 64.02348327636719, 'Instances': [], 'Parents': [{'Name': 'Person'}]}, {'Name': 'Tuxedo', 'Confidence': 56.30610656738281, 'Instances': [], 'Parents': [{'Name': 'Suit'}, {'Name': 'Overcoat'}, {'Name': 'Coat'}, {'Name': 'Clothing'}]}], 'LabelModelVersion': '2.0', 'ResponseMetadata': {'RequestId': '***', 'HTTPStatusCode': 200, 'HTTPHeaders': {'x-amzn-requestid': '***', 'content-type': 'application/x-amz-json-1.1', 'content-length': '1427', 'date': 'Wed, 23 Feb 2022 12:39:04 GMT'}, 'RetryAttempts': 0}}

# CV2で画像ファイルを読み込む。
np_image = cv2.imread(filename)
height, width = np_image.shape[:2]

# ラベルの中から人と思われるものを探して四角で囲う。
for label in response['Labels']:
    if label['Name'] not in ['People', 'Person', 'Human']:
        continue

    for person in label['Instances']:
        box = person['BoundingBox']
        x = round(width * box['Left'])
        y = round(height * box['Top'])
        w = round(width * box['Width'])
        h = round(height * box['Height'])
        cv2.rectangle(np_image, (x, y), (x + w, y + h), (0, 0, 255), 3)
        cv2.putText(np_image, label['Name'], (x, y - 9),
                    cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 3)

cv2.imwrite(filename + '_result.jpeg', np_image)

CloudWatchの出力ログから人間を表すラベルの頂点座標を抜き出し、その情報を基に作成したバウンディングボックスを元画像に貼りつけるという流れの処理を行いました。

インターンシップを終えた感想

最後に、本インターンシップを通して感じたことをまとめます。

1. 自力で調べる力の重要性を再確認できた

RaspberryPiやICGW、AWS、Macは私にとって触れたことすらないものであり、2週間という短い期間でそれらすべてを扱うためには、自分で自発的に調べることがとても重要だと改めて実感しました。 また、特にIT業界は短いスパンで新しい技術が登場する変化の目まぐるしい業界であることからも、自力で調べる力は常に意識して伸ばしていきたいと思いました。

2. 大学での学びに対するモチベーションが得られた

インターンシップに参加するまでは、大学で学んだことがどのように仕事に活かされるのかイメージできませんでした。 特にサークルでのロボット製作は、"あくまでサークル活動でお遊びである"という意識があり、自分がしてきた活動に自信を持てないことがありました。 しかし、実際に業務内容を経験させていただいたことで、大学での自分の活動が確かに仕事に結びつくと実感でき自信につながったとともに、これからの大学での学びに対する絶大なモチベーションを獲得できたと感じています。

3. チーム開発における"良い雰囲気"を実感できた

インターンシップに参加することが決まったとき、技術面・経験面でも未熟な大学2年の自分が、社員の方々にご迷惑をかけずにしっかり業務をこなせるのか非常に心配でした。しかし、チームメンバーの方々は緊張して硬くなった私をアットホームな雰囲気で暖かく迎えてくださったおかげで、楽しく仕事ができました。 特に、私が自力ではどうしても解決できない問題に直面した際、気兼ねなく質問できるようにしてくださいました。 ICGW開発チームの方々からは、技術だけでなく、チーム開発をするうえで大切なチーム内の雰囲気がどのようなものであるかを身をもって学ばせていただきました。 2週間という短い間でしたが大変お世話になりました。ありがとうございました!

トレーナーからのコメント

小林さんのトレーナーを務めたICGWチームの岩田です。

今回のインターンシップでは、ICGWのStorage転送機能を使って新規サービス提案に取り組んでいただきました。RaspberryPiやICGW、AWSといった小林さんにとってあまり馴染みのない技術領域が多かったかもしれないですが、その中でも自分なりのウィルを持って取り組む姿勢は印象的でした。また、大学2年生ながらもすでに大学での勉学の意義や将来を見据えており、素晴らしい姿勢だと感心しました。今後もその姿勢を大切に、学業やロボコンに打ち込んでいただけたらと思います。このインターンで得られた経験を今後の小林さんの大学生活に役立ててもらえると我々も嬉しいです!

2週間お疲れ様でした!

© NTT Communications Corporation 2014