TECH PLAY

セーフィー株式会社

セーフィー株式会社 の技術ブログ

221

こんにちは、セーフィー株式会社の23卒、新卒エンジニア職入社予定の伊東です。 この記事は Safie Engineers' Blog! Advent Calendar 10日目の記事です。 私たち新卒一期生は入社前に5ヶ月間インターンシップを行ってきました。 このインターンシップの振り返りをしつつ皆さんにセーフィーという会社の教育環境や会社の雰囲気をお伝えしようと思います。 自己紹介 伊東 スキルセット セーフィーを選んだ理由 インターンで期待していたこと 土田 スキルセット セーフィーを選んだ理由 1.会社と事業のビジョンに共感できる 2.エンジニアとして成長できる環境 なぜインターンをしようと思ったか インターンで取り組んだこと Udemy講座 チーム開発 1. 仕様書作成(半月) 難しかった点 心がけた点 画面遷移図 ER図 2.開発環境構築(2週間) フロントエンド バックエンド ぶつかった壁 心がけたこと 3. 実装(2ヶ月) ぶつかった壁 4. 結合テストとデプロイ(1週間) 結合テスト デプロイ 完成したアプリケーション 1. ログイン画面 2. ユーザー一覧表示画面 3. ユーザー情報詳細表示画面 4. ユーザー情報変更画面 インターンで学んだこと 伊東 土田 まず、今回のテックブログは2人で書いたので、それぞれ自己紹介させてください。 自己紹介 伊東 改めまして、23卒、エンジニア職入社予定の伊東です。私は、大学では水産学部といういわゆる情報系ではない学部からエンジニアを目指してこの業界に入ってきました。 スキルセット python暦2年(濃縮すると1年ぐらい) 他社の長期インターンでバックエンド(FastAPI)を使用し、約7ヶ月働いていた 競技プログラミング(AtCoder)は茶色になったばかり 大学の研究でfortran90という超レガシー言語を3年ほど使用している(早くやめたい) セーフィーを選んだ理由 就活シーズン、昨今の意識高い系youtube、(ニュースピックス、 日経テレ東大学等)の動画を見すぎていつの間にかベンチャー思考になっていました。その中で、勢いのある会社を探していたところ、就活していた2021年12月当時に上場したばかりのセーフィーを見つけ、選考に進んだところ内定をいただけたのでそのまま承諾しました。 インターンで期待していたこと フロント、インフラを実際に触り、web開発がどのように成り立っているのか知りたいと思いました。 土田 こんにちは、もう一人のブログの筆者で23卒の新卒エンジニア職入社予定の土田です。 私は大学は工学部で、現在は大学院でロボットアームとAIについての研究に取り組んでいます。また、研究室は立ち上げ一期目のタイミングに入り、環境構築のところから卒業研究を実装するところまでをやり遂げる経験をしてきました。 スキルセット ロボットの研究をC++でゼロから実装(2年) AIをpythonで実装(1年) 趣味の範囲ででC#を用いたwindowsデスクトップアプリ開発 (1か月) セーフィーを選んだ理由 私はセーフィーに対して二つの点で魅力を感じました。 1.会社と事業のビジョンに共感できる セーフィーのビジョンである「映像から未来を作る」を聞いて私はテクノロジーが社会に本格実装する未来を想像し、自分もその未来を作る一員になりたいと強く思いました。 2.エンジニアとして成長できる環境 セーフィーの技術領域はWebのフロントエンド、サーバーに加えてAIエンジニア、組み込みエンジニアの領域もあり、エンジニアとしてのキャリアの幅の広さに大変魅力を感じました。また自ら積極的に開発事業の立ち上げもできる期待もあり、成長環境としても最高だと思いました。 なぜインターンをしようと思ったか 一流のエンジニアから実際の開発現場で活かせる知識を身につけたい これまで開発の知識をほとんど独学で身につけていましたが、知識の偏りを感じていたため、本場の環境でバランスよく矯正したいと思いました。 チーム開発を経験したい これまでは研究や趣味での個人開発しか経験がなかったのでチーム開発の中で自分がどう役に立てるかを見出したいと思いました。 インターンで取り組んだこと セーフィーの新卒エンジニア向けインターンは以下のような流れで行われました。 Udemy講座(1ヶ月半) チーム開発(3ヶ月) Udemy講座 上長の方に選んでいただいた、今後エンジニアとして知っておいて欲しい技術として、SQLやドキュメント作成の方法フロント、バックエンド、インフラの基礎を受講しました。実際に手を動かしながら、今後エンジニアとして必要な知識を大量にインプットしました。 チーム開発 チーム開発は、フルリモートで、来年から同期になる23卒エンジニア内定者の4名で行いました。開発が始まって最初に要件が伝えられました。 ユーザーとデバイスの紐付けを管理するためのwebサイトが欲しい ユーザー、デバイスはそれぞれ、グループに所属しているのでその紐付けもして欲しい この要件以外には特に制約はなく、どんなフレームワークを用いるかなどはチームで自由に決めることができました。 開発の流れは大まかに以下のようになります。 仕様書作成 開発環境構築 実装 テストとデプロイ ここから一つ一つチーム開発の過程を述べていきたいと思います。 1. 仕様書作成(半月) 作成した仕様書は以下の4つです。 画面遷移図(画面とその遷移を現した図) クラス図(バックエンドの設計) ER図(データベース設計) シーケンス図(バックとフロントの関係をみる図) 難しかった点 仕様書の作成を誰も経験したことがなくどんな内容を書くべきか分からなかった 心がけた点 仕様書作成の目的を「メンバー間の認識のずれを解消するため」とした とりあえずできる範囲で形にして上長から早めにフィードバックを得て何度も修正した 画面遷移図 ER図 2.開発環境構築(2週間) フロントエンドとバックエンドでGitHubのリポジトリを分けて環境構築を行いました。 フロントエンド Vue.jsを使用 Vue-cli環境でyarnを用いてパッケージ管理 Dockerファイルの作成 リンターとフォーマッターを用いるためにEslintとprettierを設定 ユニットテストのためにJestの環境設定 バックエンド APIはFastAPI, データベースはMySQLを使用 poetryを用いたパッケージ管理 Dockerファイルの作成 リンター・フォーマッターはflake8, mypy, black, isortなどを併用 ユニットテストはpytestを用いて実施 ぶつかった壁 Eslint、 prettier、JestとVue-cliとの接続 FastAPIとMySQLのDockerコンテナ間の接続 心がけたこと ネットの記事をそのまま応用するだけではうまくいかなかったので、ローカルでひたすら実験を繰り返しながら解決策を探る チーム間で丁寧にレビューし合い、参考になる記事をみんなで見つけてアイデアを出し合う 3. 実装(2ヶ月) 実装にあたって、Githubを用いた開発を行うこととしたため、始めに開発の流れをチーム内で共有しました(どのブランチにマージするかやissueの書き方等)。 また、インターンでの学びを最大化するためメンバーみんながフロントとバックエンドの実装の両方に携われるように役割分担しました。しかし、いざ開発を進めてみると予定工数から2ヶ月も超過することに、、、 ぶつかった壁 1.フォルダ構成、クラス名、関数名、変数名において個人の色がでてしまい、その都度、ルール決めをする必要があった(工数が超過した大きな要因となりました) 対策: 命名規則をpython, Vue.jsのそれぞれで調べ、根拠をもって統一 2.詰まった時に誰に聞けば良いかわからない 対策: 普段から開発以外の中でもコミュニケーションを取ってお互いの強い分野を把握 3.インターン終了の期限が迫っている 対策:チーム内でやらないことを話し合うようにし、必須ではない工程(例: ユニットテスト)を削った 4. 結合テストとデプロイ(1週間) 実装で大幅に工数が超過し、インターンの残り期間一週間というところまで追い込まれていました。そこで効率を重視するため結合テストを三人、デプロイ一人で役割を完全に分けてスピード重視の体制をとりました。 結合テスト テストケースを作成する(全部で150ケースほどになりました) テストケースを一つ一つ実行しバグがあれば新たにイシューを作成 イシューをもとにコードの修正 ぶつかった壁: テストケースについて開発者目線ばかりの視点になっていて、ユーザー目線で考えられていなかった 対策: テストケースを考えるうえで先に大まかな観点を決め、それがユーザー目線になっているかを徹底的にフィードバックを受けてそのあと詳細にテストケースを作成 デプロイ AWSのEC2インスタンスをフロントとバックエンド両方に構築し、EC2内でDockerコンテナを作成してその内部でフロントはnginx、 バックエンドはuvicornによってプログラムを動作させました。 ぶつかった壁: ここ数年でサードパーティークッキーの制限が厳しくなった影響で、デプロイの際にブラウザにクッキーが保存されなかった 対応: もともとhttpドメインを使用していたが、ssl証明書をAWSから取得しhttpsドメインを使用することでセキュリティ要件に対応できるようにした クッキーの設定をhttps専用に設定を変更する 完成したアプリケーション 開発の終了日ギリギリまでかかりましたが、何とか要件を満たしたアプリケーションを作成することができました。以下が完成したアプリケーションの概要になります。 1. ログイン画面 メールアドレスとパスワードで認証を行う画面です。 2. ユーザー一覧表示画面 データベース内に存在するユーザーの一覧を表示します。 3. ユーザー情報詳細表示画面 マウスで選択したユーザーアカウントの詳細情報を表示します。また、所属(ユーザーグループ)や紐づいているデバイスも表示します。 4. ユーザー情報変更画面 ユーザーのアカウント情報の更新や新規アカウントの作成を行う画面です。 インターンで学んだこと 伊東 このインターンでは当初私が求めていた以上のことを学ぶことができたと思っています。 1つ目は、0から開発を行う経験です。これは、今回のインターンで最も大きな学びになりました。要件から初めてシステムを作成することは、すでに存在するシステムをアップグレードするのとは別の知識や技術がいるということを知ることができました。 2つ目はチームで開発する際のノウハウです。今回のチームは全員同期であることから、フラットに質問、会話をすることができましたが、時には意見が対立することがありました。そのような中でも開発を進めていくためには、コーディング技術以外のコミュニケーションが大事であるということを再確認させられました。 土田 私は今回のインターンをやってよかった点が三つあります。 一つ目は開発現場で活かせる知識をバランス良く得られたことです。UdemyやデイリーMTGでの上長への質問によって、体系的なところから実際に現場で使われる範囲までバランスよく身につけられました。 二つ目はチーム開発をスムーズに進めるための知見を実践を通して得られたことです。納期に間に合う開発にするために工程を細かく分割しそれぞれ工数の予定を立てることが大切だと分かりました。また、実際どれほど工数を消化するのにかかったかを振り返ったうえで自分たちの工数の見積もりの甘さも実感したので、今後より改善していきたいと思います。 三つめは開発に没頭するとユーザー視点を忘れがちになることに気づけたことです。特に仕様書作成やテストケースを考える際に自分がユーザー目線になれていないことを実感しました。開発のことで頭がいっぱいのときでもこの機能やユーザーにとって本当に必要か?という問いを自分で立てていけるエンジニアになっていかねばと強く思いました。
アバター
こんにちは、セーフィー株式会社 サーバサイドエンジニアの河津です。 この記事は Safie Engineers' Blog! Advent Calendar 6日目の記事、また enebular Advent Calendar 23日目の記事です。 約1年前に、弊社から提供しているSafie APIとNode-REDを連携して、ローコードな仕組みで会議室の空き状況を見る試みを記事にさせていただきました。 engineers.safie.link 今回はその続編ということで、この1年でSafie APIに追加された機能を用いて、部屋に入退室された方の時間と体温を把握するような試みをしてみようと思います。特に体温を取得できるというのは、ご時世的にもかなり需要があるのではないでしょうか。 Safie APIやNode-REDについての説明も上記記事内でさせていただいています。この記事内では説明を省略しますが、もし良ければ上記記事を読んでいただけると、この記事もわかりやすくなるかもしれません! 今回作るもの Safie Entrance2とは フローの解説 injectノード functionノード(Safie APIへ送るリクエストパラメータ作成) http requestノード functionノード(Safie APIからレスポンスパラメータを受け取り加工する) slackノード フロー実行 まとめ 今回作るもの その日に部屋に入退室があったログを取得し、ログに含まれる入退室時間と体温情報を定期的にSlackに送るようなものを作ってみます。 入退室のログを確認するには、弊社から提供しているサービス「Safie Entrance2」を使用します。 Safie Entrance2とは Safie Entrance2は、顔認証端末を用いて自動解錠を行うことができる、クラウド型入退室管理サービスです。 顔認証なのでICカードなどと違い紛失やなりすましのリスクがないのと、データはクラウド管理されるためどこでも入退室履歴を確認することができます。 safie.link 入退室者の情報は、顔認証端末で解錠を行う際にサーバに溜まっていきます。溜まっていくデータは主に入退室した「日時」と、顔画像から画像処理にて割り出した「体温」が保存されます。 この情報を、Safie APIを用いて取得することができるようになりました。下記の「入退場一覧取得API」を使用します。 openapi.safie.link それでは、情報の取得ができるようNode-REDのフローを作っていこうと思います。 フローの解説 Node-REDで下記のようなフローを作ってみました。前回と同じように、今回もクラウドのNode-RED環境であるenebularというサービスを使いました。 www.enebular.com それぞれのノードの役割について解説していきます。 injectノード injectノードは手動でノードを始動させたり、一定間隔で実行させることができるノードです。 「inject実行」というボタンでフローを開始できるほか、実行間隔や繰り返しのあり・なしも設定することができます。 ノード同士で値を受け渡しする際は、 msg.payload というJavaScriptオブジェクトの中に値を作ってやりとりをしますが、今回はこのinjectノードから次のノードに値を受け渡すようなことはしないので、 msg.payload はデフォルト設定であるタイムスタンプを送るようになっています。 functionノード(Safie APIへ送るリクエストパラメータ作成) functionノードはノード間で受け渡しを行う msg.payload を加工することができるノードで、JavaScriptを用いて内部処理を記載することができます。 入退場取得APIはいくつかの条件で検索をかけることができますが、今回は特定の端末に紐づく入退場データを取得するために、 terminal_id を指定してデータを取得することにします。 http requestノード その名の通りHTTP Requestを行えるノードです。ここでは実際に入退場一覧取得APIが実行できるように、エンドポイントやアクセストークン情報などを入力していきます。 ※アクセストークンの取得方法については、こちらの記事に記載があります。 engineers.safie.link functionノード(Safie APIからレスポンスパラメータを受け取り加工する) 入退場取得一覧APIからは下記のようなレスポンスが返ってきます。 { total : 617 , offset : 0 , count : 20 , has_next: true , list : [ { detection_id: xxx , timestamp : " 2022-11-17T11:28:28+00:00 ", terminal : {} , // 割愛 result : false , person : null , temperature : 36.1 } ] } timestamp が入退場の日時であり、 temperature が体温の値です。 このレスポンス情報をSlackに送信するために加工を行います。 var list = msg.payload.list var response = "" list.forEach( function (obj, index) { var timestamp = obj.timestamp var temperature = obj.temperature response += "入退場時間:" + String (timestamp) + " 体温:" + String (temperature) + " \n " } ) msg.payload = response return msg; やってることとしては、入退場日時と体温情報をメッセージとともに1行の文字列にして、取得できるオブジェクト分改行しているだけです。 slackノード Slackに送信を行うことができるノードです。 SlackのWebhook URLと投稿したいチャンネル名を指定するだけで、 msg.payload の内容をSlack投稿してくれる便利ノードです。 Webhook URLの取得方法はこちらの記事を参考にさせていただきました。 qiita.com フロー実行 作成したフローを実行してみます。injectノードの「inject実行」をクリックすると・・・ このようにSlack送信できました! あとはinjectノードを一定間隔で実行するような形で設定すれば、「定期的に入退場データをSlackに送信してくれる仕組み」の完成です! まとめ 前回の記事を書く際にも感じていましたが、ローコードにシステムを作れるNode-REDとSafie APIは、かなり相性良いなと感じています。 この記事を書くためにNode-REDを色々と動かしていましたが、フローの組み始めからSlack投稿が完了するまで大体1時間かからないくらいで作成できました。(前回記事の知見を多数流用できたというのも大きいですが) プロトタイプ的に物作りをする上では便利ですね。 Safie APIは継続的に機能追加が行われていくため、しばらくしたらまた試してみたいなと思います!ここまでお読みいただきありがとうございました!
アバター
Safie Engineers’ Blog! Advent Calendar 12月5日を担当します。yokと申します。 Safie の本社には、「テックステージ」と呼ぶショールームスペースがあります。 (一般公開しているものではなく、打ち合わせなどでご来社いただいた方へのサービス紹介が主です。) テックステージ スマートプラグ スマートリモコン ここでは、Safie が取り扱うカメラの数々、最新機種 Safie One やそのAI 機能( Store People Detection Pack )、顔認証による入退管理( Safie Entrance2 )、 POS レジ連携 など、さまざまなサービスを体験できるようになっています。 タイミングによっては、検証を兼ねてリリース前の機能が展示されていることもあります。 Webサービスを展示しているため、多数のPCやディスプレイが稼働しています。 その他、市販のTVを8台繋いだ、手作り感のあるディスプレイウォールもあります。 これらのディスプレイをどう管理するか。以前のオフィスはモニターの台数が少なかったこともあり、つけっぱなしでもあまり気にならなかったのですが、これだけの台数になると、使わない時は消す必要があります。 オフィスのショールームに求められる要件としては できるだけ手間をかけず、自動的に起動・終了して欲しい 土日、祝日など営業していない日は使用しない(節電!) これらを満たすような電源管理が求められます。 あの手この手を試しましたが、、、最終的に「スマートホーム化」することにしました。 スマートプラグ コンセントをOn/Off することで、家電を無理矢理IoT 化するもの。自宅ではホットカーペットに愛用してます。 今回は SwitchBot 社の製品を使用しました。 https://www.switchbot.jp/products/switchbot-plug 公式のAPI で簡単にON/OFF 制御ができます。 https://github.com/OpenWonderLabs/SwitchBotAPI スマートリモコン インターネット経由で操作できるリモコン。こちらは M5Stack ATOM で自作しました。 TV のリモコン制御で困るのが、ON と OFF に同じリモコンコードが割り当てられているということです。つまり、現在ON なのか、OFF なのかわからないと、間違った制御をしてしまう可能性があります。 ネットの情報を調べて「何回か押す」というハックに辿り着きました。 参考: https://besma9.net/smart-remocon-tv-light/ メーカーや機種によって動作が異なるようで、今回制御したいTV は2回だとうまくいかず、「3回連続でON すると、ON状態でもOFF状態でも、必ず ONになる」ということがわかりました。 全部スマートプラグでもよかったのですが、PCとの相性で、「主電源を落とすとマルチディスプレイの設定が消える」箇所があり、一部リモコンでのON/OFF制御をおこなっています。 自作リモコン 3画面のそれぞれに確実に届くよう、IR信号を分配してTV の裏から操作するようにしています。 遠隔操作は Slackbot として実装し、専用のチャンネルで ("IR_ON"、"IR_OFF") といったメッセージを送るとON, OFF できるようにしました。 これらの制御をスクリプト化し、定期実行することで、要件を満たす制御が完成しました。 平日か土日祝かの判定も、スクリプト内で行っています。 ショールームのオープン当初は、出社が早いCEO や、遅くまでいるCFO が、手動でオン・オフしていたということもあって、自動化は課題でした。また、スマートプラグは消費電力が見えることもあって、あらためて節電意識が高まりました。 大画面TV の省電力はやばい。
アバター
こんにちは、セーフィーでバックエンドのエンジニアをしております神田です。 今回は、バックエンドのAPIを開発する際に使用しているフレームワークについてお話ししていこうと思います! API開発で使用しているフレームワークの紹介 Tornado FastAPI FastAPIを使用するまでの流れ FastAPIの何がよくて採用したのか 速い ドキュメントが自動生成される 実際に使ってみて感じること ドキュメント自動生成が本当に助かる 簡単にデータの堅牢性が保たれる まとめ API開発で使用しているフレームワークの紹介 まず初めに、現在使用しているフレームワークについて紹介します。 Tornado 公式リンク セーフィーのサービスができた当初(2014年)から使われています。 複数あるフレームワークの中から、APIのみで使用する前提で パフォーマンス 開発のしやすさ デバッグのしやすさ の観点から、当時最も条件を満たしているために採用されたそうです。 FastAPI 公式リンク FastAPI自体は2018年にリリースされ、近年人気を集めているフレームワークです。 セーフィーでは2020年以降に新たに作成されたAPIサーバーで使用されています。導入の理由はこれからお話しします! FastAPIを使用するまでの流れ もともとはtornadoですべてのAPIを開発していましたが、完全に新規のコンポーネントを開発することになり、せっかく新しく作るのであればtornadoにこだわる必要もないとのことで、その当時評判になりつつあったFastAPIをお試しで採用してみようという話になりました。 これが2020年ごろの話で、創業当初にはなかったFastAPIを導入するきっかけになりました。 FastAPIの何がよくて採用したのか 実際にFastAPIのどのようなところが魅力的で、導入に至ったのかをお話ししたいと思います。 速い 名前からもわかるように、FastAPIは速いことが特徴の1つで、公式ドキュメントにも「NodeJS や Go 並みのとても高いパフォーマンス 」と書かれています。 参考 Web Framework Benchmarks で確認すると、2020年時点ではFastAPIはかなり速いフレームワークであることがわかります。 Web Framework Benchmarksでベンチマークが示されているフレームワークの中で、FastAPIは全体4位、tornadoは29位でした。 表1. 1リクエストあたり20クエリでの1秒あたりのレスポンス(2022年時点) フレームワーク 1リクエストあたり20クエリでの1秒あたりのレスポンス FastAPI 12,991 Tornado 1,354 ドキュメントが自動生成される FastAPIでは作成したAPIのドキュメントが自動で生成されます。 ドキュメントを別途作成する手間がなくなるので、 ドキュメントの作成忘れ ドキュメント作成の手間 がなくなります。 実際に使ってみて感じること 現在、当初からあるコンポーネントはtornado、新規のコンポーネントはFastAPIで開発しているので、実際に開発していて両者にどのような違いがあり、FastAPIにどのような良さがあるかを2点お話ししようと思います。 ドキュメント自動生成が本当に助かる FastAPIの採用理由でも触れましたが、ドキュメントが自動生成されることで得られるメリットは想像以上でした。 tornadoでAPIを開発する場合、 仕様書のPRを作成 APIを実装するリポジトリでAPIを実装しPRを作成 APIに修正が入った場合も2つのリポジトリでPRを作成 という作業が必要になっています。 リポジトリが分かれている上、両者が連動されていないため、とくに、緊急に軽微な修正を行った際にドキュメントの修正漏れが発生してしまいます。 そうすると、APIを組み込むフロントエンドとモバイルの方が誤ったドキュメントを見ながら組み込んでしまい、うまく動作しないなどということが発生しかねません。。 FastAPIではこのような問題を全て手間なく解決してくれました! 簡単にデータの堅牢性が保たれる FastAPIはpydanticがベースになっていて、リクエスト・レスポンスのデータ型を簡単に定義することができます。 下記のようなAPIを考えます。(例のためなので内容に意味はありません) FastAPI @ router.get ( "/api/hoge" ) async def get_hoge ( id : Optional[ int ] = Query( max title= "id" ), name: str = Query(max_length= 200 , title= "名前" ) ): id, nameをリクエストパラメータにもつgetのAPIです。 idは数値で任意パラメータ、 nameは文字列で必須パラメータであることがわかります。これをtornadoで書くと、 Tornado class HogeHandler (self): def async_get (self): id = self.get_argument( "id" , None ) if not instance( id , int ): raise Exception () name = self.get_argument( "name" , None ) if name is None : raise Exception () if not instance(name, str ): raise Exception () if len (name) > 200 : raise Exception () 条件分岐をたくさん書かなければなりません。。 このように、FastAPIではデフォルトで簡単にバリデーションの記述ができます。また、FastAPIの方がどのようなバリデーションをしているのか一目で理解できる上、自前で用意する必要がないため、データの堅牢性が保たれ、予想外のエラー発生を防いでくれます! さらに、pydanticのBaseModelを使用すれば、ネストしたデータのバリデーションもできますし、レスポンスの型定義をすることで必要なデータを返却し忘れることもなくなります。 まとめ セーフィーでは、使い慣れた技術や手法を常に選ぶのではなく、その都度最適な技術を選定して開発を行っています。実際、FastAPIを導入することでたくさんの恩恵を受け作業効率も上がりました。 技術は日々進化するので、新しい技術情報にアンテナをはってより良いプロダクト・開発環境を作っていきたいな〜と思います!
アバター
サーバサイドエンジニアの松木 ( @tatsuma_matsuki ) です。Safie解析プラットフォームやSafie APIの開発を主に担当しています。 この記事は Safie Engineers' Blog! Advent Calendar 4日目の記事です。 解析プラットフォームでは、学習済みモデルおよびランタイムをSafieクラウド上に登録することで、Safieサービスで録画したカメラの映像・画像に対して、任意の推論処理を実行し、その実行結果をイベントとして保存することができます。 まだサービス公開はされていませんが、まずは社内エンジニアで利活用の推進したいという思いから、Safie解析プラットフォームを利用したアプリケーションの開発を実践しています。 私はサーバサイドのエンジニアで、機械学習の分野には明るくないのですが、今回解析プラットフォーム上でのアプリ構築を一から自力で開発可能にするために、学習済みモデルの作成と推論処理の実装を初めてやってみました! 結果として、なかなか高い精度を出すまではまだ少し時間がかかりそうですが、その作業・開発工程をこの記事で共有したいと思います。 開発するアプリケーション 画像や動画の収集は簡単! 学習(モデル作成) データの分割 データの拡張 ベースモデルの読み込み 学習の実行 推論処理の実行 精度の評価 AIモデル作成は簡単ではなかった 開発するアプリケーション まずは適当に思いついたものを開発してみよう!ということで、オフィスにいる弊社のCTOがカメラに写ったら検知して、通知(メール・Slack)を出すようにすることを目標としました。 弊社の五反田オフィスは、4F、5F、8Fの3フロアあり、分析対象として使えそうなカメラが10台弱ほど設置されています。様々なアングルや大きさで人が写りこみますが、全てのカメラで汎用的に使用できるアプリケーション(モデル)を作成できればベストです。 画像や動画の収集は簡単! Safie社内で学習用の画像を集めるのはとても簡単です! オフィス内にカメラが既に常時稼働しており、過去の映像も全て確認できるため、Safie Viewerからポチポチやるだけで画像がどんどん集まります。 Safie Viewerのストリーミング画面を開いて、右上のカメラのアイコンをクリックするとその時に写っている画像のスナップショットが取得できます。 実質1~2時間ほどで、様々なカメラから合計400枚ほどの学習用画像が集まりました。 学習(モデル作成) 今回開発するアプリケーションの場合、入力画像を「CTOが映っている」「CTOが写っていない」の二つに分類するモデルを作成する必要があります。 Efficientnetという有名な分類系のモデルをベースモデルとしてファインチューニングします。 arxiv.org また、以下のKaggleのnotebookを参考にして、学習処理部分を実装しました。 www.kaggle.com 以下では、その処理内容をざっくりと順を追って説明します。 データの分割 まず、 scikit-learn の train_test_split 関数を使って、集めた画像を用途ごとに分割します。 今回は、70%の画像を学習データ、15%の画像を評価データ、残りの15%をテスト用データとしてランダムに分割しました。 filepaths = [] labels = [] # `label_file_path`で指定したパスには、<画像ファイルのパス>,<ラベル> が列挙された # CSVファイルが置かれています。 with open (label_file_path) as f: reader = csv.reader(f) for row in reader: filepaths.append(row[ 0 ]) labels.append(row[ 1 ]) f_series = pd.Series(filepaths, name= "filepaths" ) l_series = pd.Series(labels, name= "labels" ) df = pd.concat([f_series, l_series], axis= 1 ) train_df, tmp_df = train_test_split( df, train_size= 0.7 , shuffle= True , random_state= 123 , stratify=df[ "labels" ] ) valid_df, test_df = train_test_split( tmp_df, train_size= 0.5 , shuffle= True , random_state= 123 , stratify=tmp_df[ "labels" ], ) データの拡張 深層学習ライブラリである Keras の ImageDataGenerator を使ってデータを拡張します。 gen = ImageDataGenerator( horizontal_flip= True , rotation_range= 20 , width_shift_range= 0.2 , height_shift_range= 0.2 , zoom_range= 0.2 , ) aug_img_count = 0 required = 100 # 作成する拡張画像の枚数 aug_gen = gen.flow_from_dataframe( df, x_col= "filepaths" , y_col= None , target_size=img_size, class_mode= None , batch_size= 1 , shuffle= False , save_to_dir=target_dir, # `target_dir`で指定したディレクトリに作成されます。 save_prefix= "aug-" , color_mode= "rgb" , save_format= "jpg" , ) while aug_img_count < required: images = next (aug_gen) aug_img_count += len (images) ベースモデルの読み込み 出力層付近を除いたモデルを読み込み( include_top=False )、2クラスの分類結果を出力するように置き換えます。今回はEfficientnet B3のモデルを利用しています。 base_model.trainable = True にするとファインチューニング、 False にすると転移学習となります。 base_model = tf.keras.applications.efficientnet.EfficientNetB3( include_top= False , weights= "imagenet" , input_shape=img_shape, pooling= "max" ) base_model.trainable = True x = base_model.output x = Dropout(rate= 0.4 , seed= 1 )(x) output = Dense(class_count, activation= "softmax" )(x) model = Model(inputs=base_model.input, outputs=output) model.compile( Adamax(learning_rate=lr), loss= "categorical_crossentropy" , metrics=[ "accuracy" ] ) 学習の実行 引数に、学習用データ( train_gen )と検証用データ( valid_gen )を指定して model.fit() を実行すると学習がスタートします。 model.fit( x=train_gen, epochs=epochs, verbose= 1 , callbacks=callbacks, validation_data=valid_gen, validation_steps= None , shuffle= False , initial_epoch= 0 , ) model.save(model_save_path) コンソールの出力を見ていると、 accuracy や val_accuracy の値が良くなっていくのが分かります。とりあえず、学習はうまく進んでいるようです。 Epoch 1/40 20/20 [==============================] - ETA: 0s - loss: 8.0606 - accuracy: 0.7800 validation loss of 9.0880 is 0.0000 % below lowest loss, saving weights from epoch 1 as best weights 20/20 [==============================] - 76s 3s/step - loss: 8.0606 - accuracy: 0.7800 - val_loss: 9.0880 - val_accuracy: 0.7000 Epoch 2/40 20/20 [==============================] - ETA: 0s - loss: 6.8788 - accuracy: 0.9050 validation loss of 7.7559 is 14.6582 % below lowest loss, saving weights from epoch 2 as best weights 20/20 [==============================] - 34s 2s/step - loss: 6.8788 - accuracy: 0.9050 - val_loss: 7.7559 - val_accuracy: 0.7000 Epoch 3/40 20/20 [==============================] - ETA: 0s - loss: 6.2107 - accuracy: 0.9225 validation loss of 6.1968 is 20.1025 % below lowest loss, saving weights from epoch 3 as best weights 20/20 [==============================] - 34s 2s/step - loss: 6.2107 - accuracy: 0.9225 - val_loss: 6.1968 - val_accuracy: 0.9167 Epoch 4/40 20/20 [==============================] - ETA: 0s - loss: 5.6444 - accuracy: 0.9350 validation loss of 5.6057 is 9.5382 % below lowest loss, saving weights from epoch 4 as best weights 20/20 [==============================] - 35s 2s/step - loss: 5.6444 - accuracy: 0.9350 - val_loss: 5.6057 - val_accuracy: 0.9500 Epoch 5/40 20/20 [==============================] - ETA: 0s - loss: 5.1150 - accuracy: 0.9500 validation loss of 4.9568 is 11.5753 % below lowest loss, saving weights from epoch 5 as best weights 20/20 [==============================] - 37s 2s/step - loss: 5.1150 - accuracy: 0.9500 - val_loss: 4.9568 - val_accuracy: 0.9833 Epoch 6/40 20/20 [==============================] - ETA: 0s - loss: 4.6689 - accuracy: 0.9675 validation loss of 4.5310 is 8.5913 % below lowest loss, saving weights from epoch 6 as best weights 20/20 [==============================] - 34s 2s/step - loss: 4.6689 - accuracy: 0.9675 - val_loss: 4.5310 - val_accuracy: 0.9833 Epoch 7/40 20/20 [==============================] - ETA: 0s - loss: 4.2913 - accuracy: 0.9900 validation loss of 4.2057 is 7.1782 % below lowest loss, saving weights from epoch 7 as best weights 20/20 [==============================] - 34s 2s/step - loss: 4.2913 - accuracy: 0.9900 - val_loss: 4.2057 - val_accuracy: 0.9500 Epoch 8/40 20/20 [==============================] - ETA: 0s - loss: 3.9923 - accuracy: 0.9750 validation loss of 3.9645 is 5.7369 % below lowest loss, saving weights from epoch 8 as best weights 20/20 [==============================] - 34s 2s/step - loss: 3.9923 - accuracy: 0.9750 - val_loss: 3.9645 - val_accuracy: 0.9500 Epoch 9/40 20/20 [==============================] - ETA: 0s - loss: 3.7193 - accuracy: 0.9725 validation loss of 3.6385 is 8.2214 % below lowest loss, saving weights from epoch 9 as best weights ... 推論処理の実行 テスト用のデータを model.predict() の引数に渡すことで推論を行うことができます。 推論の結果は、 [0.00226656, 0.99773353] というように、その画像がそれぞれのクラスである確率のようなものを表しているため、数値が最も大きいクラスを分類結果として使います。 image_generator = ImageDataGenerator().flow_from_dataframe( test_df, x_col= "filepaths" , y_col= "labels" , target_size=image_size, class_mode= "categorical" , color_mode= "rgb" , shuffle= False , batch_size=batch_size, ) results = [] predicts = model.predict(image_generator, verbose= 1 ) for i, p in enumerate (predicts): file = image_generator.filenames[i] label_index = image_generator.labels[i] predicted_index = np.argmax(p) results.append(( file , predicted_index, label_index, p)) print (results) 精度の評価 最初に用意した15%のテスト用画像(61枚)に対して評価してみた結果が以下の通りです。 label precision recall f1-score support 0 1.0000 0.8571 0.9231 21 1 0.9302 1.0000 0.9639 40 さくっとやった割に簡単に精度が出て、かなり驚きました。 AIモデル作成は簡単ではなかった しかし! 別途、別の人が用意したテスト用の画像に対して推論を実行して評価すると、precision 0.5, recall 0.8!!という残念な結果でした。ほとんどランダムで0, 1選択したのと変わらない!! 全くの未知の画像に対して精度を出すことの難しさがわかりました。。 おそらく私が集めた学習用データセット全体に対してかなり偏りがあるのではないかという考察をしているのですが、実際のアプリケーションとして使えるレベルにするにはもう少し試行錯誤が必要そうです。 精度を出すためにはもう少し勉強が必要そうですが、分類系の学習済みモデルの作成~推論処理の実装までを一通り経験することができました! Safieのサービスを利用すると画像収集の部分はかなり楽なので、その点は大きなメリットがありそうです。が、まだまだ手間であることには変わりないので、ラベル付きのCSVのような形である程度まとまった単位で画像ダウンロードできるようになるとなお良さそうです。この辺りはフロントチームと連携して利便性を向上させていきたいと思います。 推論の精度を上げて、無事にアプリケーションが完成しましたらまたこの記事で結果を共有したいと思います!
アバター
この記事は Safie Engineers’ Blog! Advent Calendar 2日目の記事です。 セーフィー株式会社テックリードの鈴木敦志です。 セーフィーでは開発者の積極採用を進めており、エンジニア組織の人数が2年間で約35名から約75名にまで成長しました。 開発者の人数増加に伴うチーム内のコラボレーションの問題に対応するため、アジャイル開発手法の一つであるスクラム開発をサーバー/インフラチーム内で導入し2年間ほど運用し、一定の成果を得られましたので経緯や実際の施策、結果などについて共有させていただきます。 スクラム導入前の課題 チーム内のコラボレーションが希薄 業務知識の属人化 スクラム導入 チームの分割 プロダクトバックログ スクラムイベントの実施 スクラム導入により解決された課題 チーム内コラボレーションの推進および知識移転の推進 解決されていない課題 機能横断型チーム プロダクトオーナー 今後の方針 さいごに スクラム導入前の課題 スクラム導入前の開発組織はおおよそ職能別のチームで構成されていました。 開発案件はプロジェクト単位に分割され、各プロジェクトごとに職能別チームから開発者を0~2名程度ずつ割り当てプロジェクトチームとしていました。 各チームメンバーはそれぞれ1~数個のプロジェクトに並列で参加することになります。 スクラム導入前のチーム構成 チーム内のコラボレーションが希薄 各開発者は職能別チームの中ではそれぞれ参加しているプロジェクトが異なり、プロジェクトチームの中では他メンバーと職能が異なるため必然的に単独で取り組むことになるため、チームメンバーとの協調作業が難しい状況でした。 例えば設計の相談やコードレビューの際も、相談を受ける側はプロジェクトの細かい要件や経緯を把握しているわけではないためどうしても形式的なものにならざるをえません。 業務知識の属人化 特定個人が特定のプロダクトやコードを触り続けることになるため、他のメンバーに知識が共有されづらくなります。 改修案件や質問対応、障害対応が集中しやすくなり、また転属や退職などでの混乱が発生しやすくなります。 スクラム導入 こういった状況を改善するため、サーバー/インフラ開発者内でスクラムの導入を行いました。 スクラムの導入には機能横断型チームの編成や開発案件の意思決定者としてのプロダクトオーナー制度の導入など組織構造の変更が必要になります。 スクラムの組織構成 今回は全社的な導入でなくあくまでサーバー/インフラ開発者の間でのボトムアップ的な導入であるため、こうした組織変更を伴う施策を行うことはできませんでした。 スクラムガイドでも否定的に言及されているスクラムの一部的な導入にとどまってしまい、いくつかのメリットを受けることができませんが、それらを抜きにしてもチーム内でのコラボレーション促進の効果を得られると思い導入を進めました。 実際に導入されたチーム構成 チームの分割 導入時点でサーバー/インフラチーム合計で11人のメンバーがおり、チームを2チームに分割しました (現在は4チーム)。 それぞれのチーム内の開発者一名が兼務でプロダクトオーナーとしてプロダクトバックログの管理およびステークホルダーとの協議を行います。 (このプロダクトオーナーは実際にプロジェクト/プロダクトに十分な権限を持つわけでなく、バックログの管理と各プロジェクトの会議体に出席し要件を整理する役割です) プロダクトバックログ プロダクトバックログおよびスプリントバックログはWrikeにより管理されています。 Wrikeはさほどスクラムに向いている感じではありませんが、前者共通のプロジェクト管理で使われていたためツールを合わせました。 スクラムイベントの実施 スプリントを2週間 (チームによっては1週間) とし下記スプリントイベントを実施しています。 スプリントプランニング (1回/スプリント) スプリントで行う開発の計画を行います デイリースクラム (毎日) 一日の作業の調整を行います スプリントレトロスペクティブ (1回/スプリント) KPT法で開発進捗やチームの状況について振り返りを行います バックログリファインメント (1回/スプリント) プロダクトバックログアイテムの詳細化を行います スクラム導入により解決された課題 チーム内コラボレーションの推進および知識移転の推進 開発者をプロジェクトに割り当てる習慣をやめ、スプリントプランニングおよびバックログリファインメントの場で開発項目を全員で議論し詳細化することで、チームの全員が各開発項目の実装を実装を進めることができるようになりました。 各人が別々のプロジェクトに割り当てられていた頃とくらべ、特定の技術や業務知識がある人とペアプロをしたり、各人の休暇や割り込み作業などにあわせてタスクを融通しあうなど、チーム内での協力と知識の移転を促進することができました。 チーム内でプランニングポーカーによるストーリーポイントの見積もりを行っており、見積り結果の値はまだ活用できていないものの、全員で見積りを行う行為自体が開発項目への理解を深めるのに役に立っていたように思います。 解決されていない課題 今回導入したスクラムは既存チーム内で完結するもののみで組織的なスクラム導入には至っていないため、スクラムにより解決が期待される課題の一部は残ったままです。 機能横断型チーム スクラムチームは機能横断型であり、必要な機能をすべて備えていることを求められます。 一方で現在の組織は職能別のチーム構成となっておりひとつの機能を実装するにはサーバーチーム、フロントエンドチーム、デバイスチームなど複数チーム間での情報共有や優先度や調整が必要になり、都度ロスが発生します。 プロダクトオーナー スクラムにおいてプロダクトオーナーはチームが開発するプロダクトに権限と責任をもつロールです。 現在の組織体制では開発案件の権限はプロジェクトマネージャーが持っており、スクラムのプロダクトオーナーに当てはめることはできません。 今回のスクラム導入の範囲ではチームの開発者のうち1名を便宜上プロダクトオーナーとし、各プロジェクトのミーティングへの出席やステークホルダーとの折衝およびプロダクトバックログの管理を行っています。 プロダクトに関する意思決定者がチームの近くにおらず判断に時間がかかりますし、開発業務と平行して複数プロジェクトのミーティングに出席するため非常に負荷が高くなります。 今後の方針 開発チームにスクラムを導入し、チーム内コラボレーションの促進および業務知識の属人化防止に一定の成果を得ることができました。 しかしボトムアップ的に始まったためスクラムのフレームワークすべてを導入できているわけではなく、チーム内でのスクラムイベントの実施などの一部のみにとどまっています。 スクラムのメリットを享受して組織課題を解決していくためには組織全体として対応していく必要があり、今後はスクラムの推進のための施策を進めていきます。 社内でのスクラムの普及 機能横断チームの編成 プロダクトオーナー制度の導入 専任スクラムマスター採用 各チームリーダーの認定スクラムマスター資格取得 さいごに スクラムにおいて一部の施策だけの導入は推奨されていませんが、とはいえ現場からボトムアップででもスクラム導入を進めていきたい場面もあると思います。 本件事例がそういった方々の役に立てば幸いです。 現在セーフィー株式会社ではソフトウェアエンジニアを積極採用中です。 興味のある方はぜひ下記ページよりご応募ください。 https://safie.co.jp/teams/
アバター
セーフィーCTOの森本です。 この記事は Safie Engineers' Blog! Advent Calendar 1日目の記事です 2020年2月より開始したセーフィーのテックブログですが、一時期運営が危機的な状況に陥ったこともありました。 しかし、有志からなる運営チームのガンバリにより2021年9月以降着実に更新を継続して行ってくれており、様々な会社さんからテックブログ継続の難しさを伺っている中、非常に喜ばしく感じている今日此頃となっています。 そんな中、運営チームが2022年の年末に向けてアドベントカレンダーをやろうと熱く提案してくれました。 月イチの更新すらままならなかった当初を知っているだけに、ホントに出来るのかという気持ちもありましたが、当社カルチャーでも「迷ったときはやってみる」と謳っていますのでこれはやるしか無いなと言うことでGOを掛けた次第です。 今回はそのトップバッターという事で、当社で今年初めから注力し開発している項目の一つである解析プラットフォームについて紹介致します。 セーフィーとは セーフィー解析プラットフォームについて 解析プラットフォームの今後 最後に セーフィーとは 既にテックブログで何度も紹介済みかとは思いますが、セーフィーはクラウド録画サービス「Safie(セーフィー)」を運営しています。 safie.jp カメラにより撮影した大量のデータをクラウド上に保存するとともに、必要に応じてユーザーへLive動画、過去動画を配信したり、動きや異常音が発生した場合にその旨を通知するような機能も提供しています。 インターネットに繋ぐだけで簡単に利用できますが、使いやすさやセキュリティを意識した作りとなっており、広くお客様にご利用頂いています。 現在17万台を超えるカメラを運用しており、20PBに迫るデータが当社システム上に蓄積されています。 Safieサービス概要 また、カメラから取得したデータを解析し、様々な現場の課題解決につなげるようなサービスも既に複数提供しています。 Safie AI People Count safie.jp Safie OneとStore People Detection Pack safie.jp engineers.safie.link engineers.safie.link このようなサービスを実現する上では、旧来の手法やAIを用いた画像解析技術の活用が必須となります。 最近はデバイス側で簡易的なAI処理が駆動できるような、所謂エッジAIカメラやエッジAIデバイスが販売されていますが、解析する内容によって必要なコンピューティングリソースも大きく異なり、ものによってデバイス側で実行出来ない事も多々あります。 また、エッジAIカメラはまだまだ普及している訳ではないので、AI機能を搭載していないカメラが大多数を占める状況となっていますが、当社ではデバイス、クラウド環境共に自分たちで開発、管理していますので、これらの画像解析をどちらでも適した環境で実現できるところに大きな強みがあります。 セーフィー解析プラットフォームについて 先程ご紹介したように、当社では複数の画像データ解析をベースとしたサービスを提供しています。 また、当社データを取得し画像解析を行うような連携サービスを提供しているパートナーさんも存在しています。 当社録画サービスをご利用中のお客様からご相談を頂く事も頻繁にあり、明確に画像データ解析のニーズの高まりを感じています。 一方で、現状では上記サービスそれぞれの開発に数ヶ月以上かかっている状況にあります。 例えば動画から特定の物体を検出するようなサービスを考えた場合にも、解析部分の開発だけでなく、データ入力IFの設計開発、解析エンジン駆動部分の設計開発、開発結果の保存部分、取得部分の設計開発、ものによっては結果を表示するUIの設計開発と多岐にわたる項目の開発が必要となります。 解析系のサービスはまだまだこれから市場開拓をしていく必要がある中で、もっと簡単にソリューション開発を実現出来なければ、大きく市場を広げて行く事は難しいと感じています。 上記の課題を解決するために、セーフィーでは解析プラットフォームの開発を進めています。 解析プラットフォームは主として以下の機能を提供します。 解析プラットフォームイメージ ①開発したAI等に基づく解析エンジンをセーフィーのクラウドシステムに登録 ②必要に応じ、セーフィークラウドシステム、セーフィー対応カメラに展開し解析を実行 ③解析結果をセーフィークラウドシステム上のデータベースに保存 ④解析結果をセーフィーAPI経由で取得し利用可能 現在システムの1stステップがほぼ完成しており、最初の解析サービスの開発を進めている状況となっています。 こちらの仕組みにより、自社、他社問わず解析エンジンだけ開発すればすぐに新しい解析サービスの提供が開始出来るようになると期待しています。 但し、画像解析系のサービスを広げていく上では他にも課題が存在しています。 解析系サービスでは精度が非常に重要な要素を占めますが、画像解析はカメラの設置された環境により精度が大きく影響を受けるという問題があります。 カメラの設置する画角や、設置環境の明るさ、設置環境に検知対象以外にどのようなものが映り込むかなど、様々な事柄により期待する精度が実現できない事があります。 これにより、設置コストやサポートコストが上がったりするだけでなく、結果的にお客様の期待値を裏切るような形になってしまうこともあり得ると考えており、解析サービスを広げていく上で合わせて解決が必要な問題と捉えています。 この問題を解決するために、セーフィーでは個別学習の仕組みの整備を検討しています。 個別再学習機能イメージ ⑤特定の環境下のカメラのデータを元に自動(目標)で個別学習を実行し、問題のある環境下でも精度の担保が出来るAIモデルを生成する この仕組みを合わせて整備する事により、設置コスト、サポートコストを抑える事が出来るだけでなくお客様の期待にきちんと向き合う事ができ、結果的に解析系サービスを広げていく事が本当の意味で実現出来ると期待しています。 解析プラットフォームの今後 上記にてご紹介した解析プラットフォームですが、現在も開発を継続しており、目下以下を目標に対応を進めています。 解析プラットフォームベースのソリューションを確実にリリースする パートナーさんにも解析プラットフォームベースのソリューション開発とリリースまで実化して頂く また、合わせて以下の項目も進めています。 ※解析プラットフォームそのものには含まないですが 多様なアプリケーション開発が簡単に行えるようセーフィーAPIやSDKの整備を進める AIモデル開発そのものがセーフィークラウドシステム内で完結して簡単に行えるような環境を整備する AIモデル開発まで一気通貫にサポート 解析プラットフォームにより、単一環境で容易に画像解析ベースのソリューションが構築できるようになるだけでなく、実運用時の環境要因による精度劣化を低減する事が出来るようになる事が期待できます。 ここまでやることにより本当の意味で解析サービスの利用が広がっていくのではと考えています。 最後に 上記解析プラットフォームの開発を更に進めると共に、2023年は上記を用いた解析サービスによる業界DXを確実に実現していきたいと考えています。 当社としては今後画像解析システムとの連携を更に強化していくべく開発業務に取り組んでいますが、まだまだエンジニアさんが足りていない状況です。ご興味がある方はお気軽にご連絡頂けますと幸いです。
アバター
こんにちは、セーフィー株式会社でサーバサイドのエンジニアをしている河津です。同時に、このSafie Engineers' Blog!の運営も行っています。 この度セーフィー株式会社 初の アドベントカレンダーを実施しようと思っており、告知も兼ねた記事を投稿させていただきます! 基本情報 アドベントカレンダーとは? 想定参加者は? 掲載媒体は? 執筆テーマは? 最後に 基本情報 セーフィー株式会社のアドベントカレンダーはこちらになります。 qiita.com なんと25枠全て埋まっており、初開催にしてかなりの盛り上がりを見せております! アドベントカレンダーとは? 元々の意味としては、クリスマスまでの日数をカウントダウンするために使われていたカレンダーで、12月1日からはじまり、25個ある「窓」を毎日1つずつ開けて中に入っている小さなお菓子やプレゼントを楽しむものになります。 このカレンダーに倣って、何か一つのテーマに対して技術記事を12/1から12/25までいろんな人が毎日投稿する、という風習がエンジニア界隈には存在しています。 そんな風習に、弊社も乗っかってみようという取り組みになります! 想定参加者は? セーフィー株式会社に勤める、プロダクト開発に関わる方々を対象にしています。 セーフィーには様々な職種のエンジニアが在籍しており、広い領域の技術話が集まってくるのではないかと(個人的に)期待しています! エンジニアの職種については、以前投稿したこちらの記事もご覧になってみてください。 engineers.safie.link また、「プロダクト開発に関わる方々」という言い方はエンジニアに限定していないというところもミソでして、デザイナー職種の方々、PdMの方々などからも広く記事を募集しております。 掲載媒体は? このブログ「Safie Engineers' Blog!」以外に、「Qiita」、「Zenn」、「note」での執筆もOKとしております。 なのでアドベントカレンダー期間中は、このブログ以外のところでもセーフィーの技術記事が上がっていくことになりますので、興味を持っていただける方は是非ともカレンダーをご確認ください。 執筆テーマは? 自由 としています。 元々このブログに掲載する場合でもそこまでテーマの縛りを設けているわけではありませんが、掲載媒体をSafie Engineers' Blog!にとどめない体制から、さらにフリーダムな記事が生まれるのではないかと思っています。 しっかりした技術話はもちろん、ライトめなものやポエム的なものまで歓迎としているので、ぜひ自由度の高い記事をお楽しみください。 最後に 是非ともセーフィー株式会社のアドベントカレンダーを、このブログ共々よろしくお願いいたします。 アドベントカレンダー終了時には、この記事に追記する形で振り返りの内容を掲載する予定ですのでお楽しみに! qiita.com
アバター
こんにちは!セーフィーでサーバーサイドエンジニアをしている神田です。 今回は2022/10/14、10/15に行われたPyCon JP 2022に参加した時のお話をしようと思います! 以下のアジェンダに沿って進めていきます! PyConはどのようなイベントか 参加企業の特徴 どんな人がイベントに参加していたか ブース運営の様子と感想 セッションに参加してみた感想 まとめ PyConはどのようなイベントか ざっくり言うと、Pythonユーザーが集まり、交流できるイベントです! さまざまな企業がブースを出していて、各プロダクトのどのような部分にPythonが使われているかなどお話を伺うことができます。 また、セッションではPythonのライブラリの活用方法やPythonを活かした簡単なプロダクトを作る、などといったお話を聞くことができます。 pycon2022 参加企業の特徴 ほぼ全ての企業ブースを回りましたが、メインでPythonを使用している企業もあれば、第二言語としてPythonを使用している企業もありました。活用方法もさまざまで、バックエンドの言語としてPythonを採用している企業もあれば、機械学習やデータ分析の部分でのみPythonを使用している企業もありました。 さまざまな企業ブースでお話を伺って、Pythonはさまざまな分野のエンジニアが選定する言語なのだな〜と改めて感じました。 今回は全部で22ブースありました。ブースでは企業の方とお話しするだけではなく、プロダクトを見せてもらったり、かわいく便利なノベルティをもらえたり、くじ引きやアンケートなども実施していてとても楽しかったです! どんな人がイベントに参加していたか Pythonユーザーの交流の場なので、Pythonが好き!という方がやはり多かったです。参加する前までは仕事でPythonを使っている人が大半だろうと思っていたのですが、案外仕事では使っておらず趣味で触っている程度という方もそれなりにいらっしゃいました。学習が難しくないPythonだからこそ、さまざまな人に興味を持ってもらえるイベントなのですね! また、中には学生も何人かいました。就活のためにPythonがどういうプロダクトで活用されているのか見にきたそうです。なんて意識の高い。。 ブース運営の様子と感想 セーフィーのブースはこんな感じでした! SafieOne と Pocket2 を展示して、Safie Viewerのマルチビューアーをモニターに映していました。 SafieOneについては下記の記事でも詳細をご確認いただけます! エッジAIカメラ「SafieOne」のアプリ「Store People Detection Pack」とは エッジAIカメラ「SafieOne」の画像認識 カメラのようなハードウェアを展示しているブースは少なく、これはなんだろう?と興味を持ってくれた方が多く立ち寄ってくださいました! エンジニア紹介資料 に沿って、セーフィーのクラウド録画がどのような現場で活用されているかを説明させていただきました。クラウド録画=防犯目的 のみと考えている方が非常に多かったのですが、実際には防犯以外の側面でも活用いただいていることを伝えられたのがよかったと思っています! お昼やセッションの合間の休憩時間には、ブース担当2人では足りないほどのにぎわいでした! ノベルティも好評で、特にステッカーはとても人気でした! セッションに参加してみた感想 2日目はブース担当ではなかったので、ほぼ1日セッションを聞いていました! 参加したセッションは以下です。 退屈なことはSlack botにやらせよう(ver.2) https://slides.takanory.net/slides/20221015pyconjp/#/ PyCon運営をする中で、運営のユーザーから何度も同じ質問に答える手間を無くそう!ということをテーマにSlackBotの簡単な作り方を学べるセッションでした。ユーモアあふれる話し方で聞き手をひきつけ、とても楽しく学ぶことができました! Pythonではじめる地理空間情報 geamapとleafmapを使用して簡単に地理情報を表示する方法について学べるセッションでした。もともと地理空間に関心があったので、ぜひ自分でも試してみようと思いました! データに関する堅牢性と可読性を向上させるpydanticとpanderaの活用方法の提案 データ分析においてデータのスキーマをpydanticとpanderaを用いて使いやすくする、といった内容のセッションでした。pydanticは業務でも使っていたのですが、QAセッションの中でさまざまなPythonユーザーのpydanticへの意見を聞くことができて、とても良い機会でした! はじめての量子コンピューター 量子コンピュータの基礎的な部分を学べるセッションでした。基礎とはいっても内容が難しかったのですが、非常に難しい計算をすばやく解くことができるということだけは理解できました。 どのセッションも匿名で質問を投稿することができ、質問したい人が多い項目から登壇者に答えていただくシステムでした。毎回質問が山ほど投げられており(わたしもたくさん投げていました)、非常に盛り上がっていました! セッションに参加したことで、今まで知らなかったPythonの活用方法を知ることができたのはもちろんのこと、イベントで代表に選ばれて登壇している方を直接目にしたり、多くの人が真剣にセッションを聞いている姿を見て、自分ももっと頑張りたいと思えたことがとてもよかったです! まとめ ブースの参加もセッションの参加も本当に楽しく、非常に充実した2日間を過ごすことができました。コロナウィルスの影響でここ数年はオフラインのイベントに参加できていませんでしたが、来場者の方や他の企業参加者の方と直接コミュニケーションをとることで得られるものも大きいな〜と感じました。 そしてなにより、PyConはPythonユーザーのためのイベントなので、初対面の人でもとりあえずPythonの話ができるため、人と話すのが苦手な自分でもたくさんの人と楽しくお話しできたことが本当に嬉しく、来年も絶対に参加したいなと思っています! また、さまざまな人から自社のプロダクトに興味を持っていただき、いいね!と言ってもらえることで日々の業務にやりがいを感じました!次回もたくさんの人と交流したいので、Pythonユーザーの皆さんはぜひPyConに参加してみてください!
アバター
サーバサイドエンジニアの松木 ( @tatsuma_matsuki ) です。 Safieでは、FastAPIを利用していくつかのサービスを開発しています。Safieのサービスの性質上、APIサーバで画像ファイルなどのオブジェクトを扱うことが多いです。 大きいサイズのオブジェクトをクライアントにダウンロードさせるAPIなどでは、FastAPIの StreamingResponse を使うのが便利ですが、このStreamingResponseの使い方を扱った良いリファレンスがネット上であまり見つからなかったので、この記事で実際にコードなどを示しながら実装例を共有していきたいと思います! StreamingResponse S3からオブジェクトをダウンロードして返す 同期・非同期イテレータでのダウンロード時間比較 他サービスAPIからオブジェクトをダウンロードして返す 単体テストの実装 まとめ StreamingResponse FastAPIのStreamingResponseは、HTTPのbodyとなるコンテンツをいくつかのチャンクに分割してストリームで返すために利用されるレスポンスクラスです。まずは、このStreamingResponseクラスのコンストラクタ引数についてまとめてみます。詳細は以下のリンクを参照ください。 https://github.com/encode/starlette/blob/0.21.0/starlette/responses.py#L224 引数 型 説明 content Iterator or AsyncIterable メインとなるオブジェクトのデータ(必須) status_code int ステータスコードを指定(デフォルトは200、省略可) headers Mapping レスポンスヘッダーを指定(省略可) media_type str 指定した値は、Content-Typeヘッダーの値として利用されます。headersでもContent-Typeを指定した場合は、headersの値が優先されます。(省略可) background BackgroundTask BackgroundTask を指定(省略可) メインとなるオブジェクトのデータはcontentの引数に渡します。content引数は、イテレータ、非同期イテレータ(もしくは、ジェネレータ、非同期ジェネレータ)を指定できますので、この仕様に合わせてcontentを渡してあげる必要があります。 S3からオブジェクトをダウンロードして返す オブジェクトを管理するオブジェクトストレージのサービスとしてまず思い浮かぶのはおそらくS3でしょう!APIサーバ上でS3からオブジェクトをダウンロードして、そのオブジェクトをクライアントに返すというような処理に、StreamingResponseが利用できます。 S3からオブジェクトをダウンロードして、それをStreamingResponseで返す処理は、例えば以下のような実装になります。S3からのオブジェクト取得には、 aiobotocore を利用しています。aiobotocoreは、botocoreとaiohttpによる 非同期の クライアント実装となっています。 StreamingResponseは、非同期イテレータと通常の同期イテレータの両方が使用可能なので、非同期ではないbotocoreを利用した場合とのダウンロード時間を比較した結果も後で紹介します。 ... async def s3_client ( settings: Settings = fastapi.Depends(get_settings), ) -> AsyncGenerator[aiobotocore.client.AioBaseClient, None ]: session = aiobotocore.session.AioSession() async with session.create_client( "s3" , endpoint_url=settings.aws_endpoint_url, aws_access_key_id=settings.aws_access_key_id, aws_secret_access_key=settings.aws_secret_access_key, verify=settings.aws_tls_verify, ) as client: yield client def s3_client_sync ( settings: Settings = fastapi.Depends(get_settings), ) -> botocore.client.BaseClient: session = botocore.session.Session() return session.create_client( "s3" , endpoint_url=settings.aws_endpoint_url, aws_access_key_id=settings.aws_access_key_id, aws_secret_access_key=settings.aws_secret_access_key, verify=settings.aws_tls_verify, ) @ router.get ( "/tasks/{task_id}/image" , summary= "Download Image" , response_class=StreamingResponse, ) async def get_image ( task_id: int = Path(..., title= "Task ID" ), settings: Settings = fastapi.Depends(get_settings), s3_client: aiobotocore.client.AioBaseClient = fastapi.Depends(s3_client), # s3_client_sync: botocore.client.BaseClient = fastapi.Depends(s3_client_sync), ): s3_bucket = settings.s3_image_bucket s3_key = f "test/{task_id}/image.jpg" try : image = await s3_client.get_object(Bucket=s3_bucket, Key=s3_key) # image = s3_client_sync.get_object(Bucket=s3_bucket, Key=s3_key) except Exception : raise HTTPException(status_code= 500 , detail= "Failed to get the image" ) return StreamingResponse( content=image[ "Body" ], # 非同期イテレータ media_type= "image/jpeg" , headers={ "content-length" : str (image[ "ContentLength" ]), "etag" : image[ "ETag" ]}, ) botocoreのget_object()のレスポンスはDict型で、"Body"キーの値(image["Body"])にオブジェクトのデータが返ってきます。 image["Body"]の型は aiobotocore.response.StreamingBody ですが、これは非同期イテレータとなっており、StreamingResponseのcontentにそのまま渡すことができます。 同期・非同期イテレータでのダウンロード時間比較 StreamingResponseは、通常の同期イテレータと非同期イテレータの両方に対応していますが、それぞれを利用した場合にどの程度オブジェクトのダウンロード時間が変わるのかを少し見てみたいと思います。 S3(今回はlocalstackを利用)上に10MBのオブジェクトをアップロードして、それを上記で実装したAPIでダウンロードします。 まずは、通常の同期イテレータ(botocore)を用いた場合の結果です。 $ time curl -sv http://localhost:30080/tasks/1/image -o image.jpg * Trying 127.0.0.1:30080... * TCP_NODELAY set * Connected to localhost (127.0.0.1) port 30080 (#0) > GET /tasks/1/image HTTP/1.1 > Host: localhost:30080 > User-Agent: curl/7.68.0 > Accept: */* > * Mark bundle as not supporting multiuse < HTTP/1.1 200 OK < date: Wed, 28 Sep 2022 04:26:28 GMT < server: uvicorn < content-length: 10485760 < etag: "46f40f95feefb6ab49b2b3679b5ec0a2" < content-type: image/jpeg < { [1024 bytes data] * Connection #0 to host localhost left intact real 0m19.873s user 0m0.115s sys 0m0.300s 約20秒 ほどかかりました。次に、非同期イテレータ(aiobotocore)を用いた場合です。 $ time curl -sv http://localhost:30080/tasks/1/image -o image.jpg * Trying 127.0.0.1:30080... * TCP_NODELAY set * Connected to localhost (127.0.0.1) port 30080 (#0) > GET /tasks/1/image HTTP/1.1 > Host: localhost:30080 > User-Agent: curl/7.68.0 > Accept: */* > * Mark bundle as not supporting multiuse < HTTP/1.1 200 OK < date: Wed, 28 Sep 2022 04:28:05 GMT < server: uvicorn < content-length: 10485760 < etag: "46f40f95feefb6ab49b2b3679b5ec0a2" < content-type: image/jpeg < { [3072 bytes data] * Connection #0 to host localhost left intact real 0m3.825s user 0m0.039s sys 0m0.198s こちらは 4秒弱 という結果でした!非同期イテレータをcontentに指定することでダウンロード時間がかなり早くなっています。 個人的には思ったよりも違いが出たので、API実装する上では、StreamingResponseに非同期のイテレータを渡すことが性能面でかなり重要になってきそうです。 他サービスAPIからオブジェクトをダウンロードして返す S3以外の実装例も紹介したいと思います。複数のサービス連携を実装する中では別サービスのAPIからオブジェクトをダウンロードして、それをStreamingResponseで返すようなシーンもありますので、その実装例も書いてみます。 ... async def get_exit_stack () -> AsyncGenerator[contextlib.AsyncExitStack, None ]: async with contextlib.AsyncExitStack() as stack: yield stack async def get_aiohttp_session () -> AsyncGenerator[aiohttp.ClientSession, None ]: async with aiohttp.ClientSession() as session: yield session @ router.get ( "/tasks/{task_id}/bytes/{size}" , summary= "Download bytes" , response_class=StreamingResponse, ) async def get_bytes ( task_id: int = Path(..., title= "Task ID" ), size: int = Path(..., title= "The number of Bytes to download" ), exit_stack: contextlib.AsyncExitStack = Depends(get_exit_stack), session: aiohttp.ClientSession = Depends(get_aiohttp_session), ): res = await exit_stack.enter_async_context( session.get(url=f "http://httpbin.org/stream-bytes/{size}" ) ) if res.status != 200 : raise HTTPException(status_code= 500 , detail= "Failed to download bytes" ) return StreamingResponse( content=res.content.iter_chunked( 1 * 1024 ), media_type=res.headers[ "content-type" ], ) HTTPクライアントの 非同期実装 である aiohttp を用いて、他サービスのAPIからオブジェクトを非同期でダウンロードする実装にしています。他サービスとして、今回は httpbin.org を利用しています。 aiohttp.ClientSessionでgetした際のレスポンス(ClientResponse)のcontentは、 aiohttp.streams.StreamReader 型となっています。これ自体非同期イテレータとして扱えるため、そのままStreamingResponseのcontentに渡してもOKですが、 iter_chunked(size: int) のメソッドを使って各イテレーションで扱うデータサイズを指定することもできます。 ちなみに、aiohttp.ClientSessionのレスポンスは、コンテキストマネージャーとして実装されており、そのままwith分を書くとwithブロックを抜けるタイミングでクローズされてしまい、contentをすべて読み込めないため、 contextlib.AsyncExitStack を用いて、レスポンスがクローズされるタイミングを遅らせています。 単体テストの実装 最後にmockを用いた単体テストの例も書いてみます。aiohttp.ClientSessionのメソッドをmockすることで他サービスからのResponseをmockしてテストを実装します。 ... class MockByteGenerator : async def iter_chunked (self, size: int ) -> AsyncGenerator[ bytes , None ]: for _ in range ( 5 ): yield b "x" * size class MockStreamResponse : def __init__ (self, content: Any, status: int , headers: Dict[ str , str ]): self.content = content self.status = status self.headers = headers async def __aenter__ (self): return self async def __aexit__ (self, exc_type, exc, tb): pass @ pytest.mark.asyncio async def test_get_bytes (test_client): with mock.patch( "aiohttp.ClientSession.get" ) as mock_get: mock_get.return_value = MockStreamResponse( content=MockByteGenerator(), status= 200 , headers={ "content-type" : "application/octet-stream" }, ) res = await test_client.get(f "/tasks/1/bytes/1" ) assert res.status_code == 200 assert res.content == b "x" * 1024 * 5 aiohttpのClientResponseをmockするclassであるMockStreamResponseは、ClientResponseと同じくコンテキストマネージャーとして実装する必要があり、 __aenter__ や __aexit__ のメソッドを追加しています。また、インスタンス変数として、contentを用意して、iter_chunked()のメソッドが非同期ジェネレータを返すように実装しています。 まとめ FastAPIでStreamingResponseを使って、オブジェクトをダウンロードするAPIの実装例を二つ書いてみました!StreamingResponseは、同期イテレータと非同期イテレータの両方をサポートしていますが、 非同期イテレータを渡してあげることで応答時間を改善することが可能です。S3クライアント、HTTPクライアント共に非同期の実装がすでに存在するので、それらを利用するだけで比較的容易に実装できるかと思います。これから、FastAPIを用いてAPI等を開発される方の参考になりますと幸いです!
アバター
こんにちは、フロントエンドエンジニアの沖です。 セーフィーには2022年1月にジョインし映像閲覧WebアプリであるSafie Viewer(以下Viewer)の開発を担当しています。 今回は、Viewerで利用しているフレームワークであるAngularのバージョンアップを9月末のリリース時に行ったのでその話をしたいと思います。 Viewerについて Viewerのアップデートサイクル 今回のアップデートによる変更点 Standalone Components Typed Forms 地味な変更点 アップデート作業について 今後試してみたい新機能 Standalone Components runInContext(Angular 14.1系) まとめ Viewerについて セーフィーのクラウド録画サービスは、カメラで撮影した映像をクライアントアプリケーションを用いて閲覧することが出来ます。クライアントアプリケーションはいくつかありますがその一つがViewerです。ViewerはWebブラウザで閲覧可能なWebアプリケーションとして提供されています。 機能としては、普通に映像が閲覧できる基本機能の他に複数のカメラの映像を同時に閲覧できるマルチビューアーだったりユーザーが独自の画面を作成し、閲覧できるダッシュボードだったり様々な機能があります。 このViewerでは下記の技術が導入されています。 言語:typescript フレームワーク:Angular CSS:Tailwind CSS Code Formatter:prettier Lint:eslint この度Viewerで利用しているフレームワークであるAngularを13から14へアップデートしました。 Viewerのアップデートサイクル Angularは約半年ごとにメジャーリリースが行われるので、セーフィーでは原則として新しいバージョンがリリースされてから1~2か月を目途にバージョンアップを行います。 Angular 13の時のIEサポート終了などインパクトが大きい変更がある場合にはリリーススケジュールが前後する可能性がありますが、出来るだけはやくバージョンアップできるようにしています。 マイナーバージョンについても随時Viewerのリリースサイクルに合わせてバージョンアップしています。 古いバージョンを使い続けることで安定性を担保するという考え方もありますが、最新バージョンに追従することで積極的に新しい機能を利用していくという面もセーフィーでは重要視しています。 今回のアップデートによる変更点 Angular 14の目玉と言ったらなんといってもStandalone Components(Standalone APIs)とTyped formsですね。 Standalone Components Standalone Componentsはmoduleを仲介しなくても直接呼び出せるcomponentで、今までmodule内で定義されていた暗黙的な依存がなくなるということで注目されている新機能です。 今まではNgModuleを定義してその中にcomponentと一緒に使うモジュール群をimportsに入れて定義しなければいけませんでしたが、NgModuleを介さずに直接componentを呼び出すことが出来るようになりました。 @Component({ selector: ‘sf-test’, template: ‘<div>standalone test</div>’, standalone: true // ここをtrueにすることでstandaloneとして扱うことが可能 }) export class TestComponent {} standaloneコンポーネントは直接呼び出せる一方で依存しているmoduleのimportを自身で行う必要があるため全てをstandaloneにするというより要所要所で利用していくのがいい印象です。 Typed Forms Typed formsはReactive Formsのvalueの型を指定できる機能です。 初期値を入れてFormControlを作成、もしくは型を指定してFormControlを定義しておくとそのControlのsetValueを呼ぶ際の型チェックやそのControlが属しているFormGroupのvalueChangesで型をつけることが出来ます。 const a = new FormControl(‘test’); a.setValue(true); // こういった場合にエラーとなってくれます。 地味な変更点 angular.jsonでのdefault projectが廃止になりました。 これにより今後ng buildやng serveの際に必ずprojectを指定する必要があります。 アップデート作業について 今回のアップデートは、あまり破壊的変更はなく新機能の追加がメインだったのでng updateで問題なくアップデートすることが出来ました。 default projectの部分は多くの人が作業が必要になるかと思われますが、それも大きな修正は必要ないため今回のアップデートは比較的気軽に出来るのではないかと思います。 今後試してみたい新機能 Standalone Components セーフィーのアプリケーションの性質上、ユーザーによって同じカメラを所有していたとしても利用できる機能が異なる場合があります。 現状もそのような場合にはLazyLoadingを利用して機能が必要のないユーザーに余計なモジュールをロードさせないような仕組みがあるのですが、その一部をStandalone Componentsに置き換えることで見通しのいいコードにできるのではないかと考えています。Developer previewが明けたら本格的に導入を考えたいと思います。 runInContext(Angular 14.1系) こちらは14.1の機能ですが、EnvironmentInjectorにrunInContextというメソッドが追加されました。この機能はrunInContextの中ではinjectメソッドを使うことによりEnvironmentInjectorでinject可能なサービスを取り出すことが出来るというものです。 今までは、DIで取り出すサービスを用いた処理は、InjectableなクラスやComponentなどAngularの管理下のクラス内で記述する必要がありましたが、これを用いることでサービスを用いたビジネスロジックもfunctionとして外に持たせることが可能となります。 const test = function() { const testService = inject(TestService); // testはただのfunctionですがこの中でinject出来ます。 } envirionmentInjector.runInContext(() => { test(); }); ビジネスロジックを外に出せるとテストの観点でも再利用の観点でもメリットが大きいので是非こちらは今後採用していこうと思っています。 まとめ 今回のAngularアップデートでは破壊的変更はあまりないものの新しい魅力的な機能が多数追加されているので、積極的にそれらを取り入れて使いやすく拡張性の高いアプリケーションにしていきたいと思っています。 フレームワークアップデートは場合によってはコストがかかる場合もありますが、保守性の向上やアプリケーションの最適化のためにも素早い追従が重要であると考えています。
アバター
こんにちは。セーフィー株式会社 モバイルエンジニアの渡部です。 今回は、モバイル開発のデザイン作成で活用しているツール「Figma」についてお話ししたいと思います。 Figmaとは 基本的な構成 Figma導入までのいきさつ Figmaで解決できたこと Figma vs XD XD Figma 最後に Figmaとは UIデザインやワイヤーフレーム作成のためのアプリケーションです。 デスクトップ版も用意されていますが、メインはWebアプリケーションとして提供されています。 動作が軽快で、リアルタイムでの共同編集が簡単に行えることが強みのツールです。 料金プランは現在3つ用意されており、安価なプランの順に、Starterプラン(無料)、Professionalプラン、Organizationプランがあります。 料金が上がるにつれ機能制限が無くなり、また扱える機能もより組織に向けた高度なものが増えていきます。 無料のStarterプランは作成できるファイル数に限りがありますが、編集権限を上限数無くユーザーに付与できます。 基本的な構成 画面サンプル キャンバス上にフレームを置き、フレーム上にボタンやテキスト等のパーツを追加していきます。上の画像だと、iPhone13をイメージした画面が2つキャンバス上に並んでいますが、これらがフレームです。 フレームやパーツの追加はツールバーから行います。繰り返し使うパーツはコンポーネント化(テンプレートのような機能。コンポーネント本体を編集すると、その複製も一括で編集される)もできます。 プロパティパネルでは、オブジェクトのスタイルを編集する他に、画像ファイルへの書き出しやプロトタイプ(モックのアニメーション)が作成できます。 また、ファイルに書き出すことなくその場で、選択中のオブジェクトのコード表記も確認できます。 Figma導入までのいきさつ モバイルチームでは従来、Adobe XDを用いてアプリデザインを行なっていました。 XDもUI/UXデザインツールとしては定番のアプリケーションです。 ツールを実際に活用するのはライセンスを割り当てられたデザイナーのみで、エンジニアはオンラインでの共有機能を使用して成果物の確認を行っていました。 しかし、XDを活用するにつれデザイナーとエンジニア双方から様々な要求と、それに伴う課題が挙がるようになりました。 成果物を手軽に共有したい XDの共有機能は、共有したいデータについてURLを発行することでオンライン上で閲覧が可能になるもので、データ作成者のみがURLを発行できます。 現状のチームの運用では、作成者と更新者が必ずしも一致しないので、URLをどこかに控えたり、発行を都度依頼する必要がありました。 また、データを更新すると、URLを据え置きにするか再発行するか選択できるはずなのですが、稀にその連携がうまくいかない場合が…… ファイル内のアートボード(アプリ画面)が増えても動作は軽いままがいい アプリ内デザインを一つのファイルに集約すると、それをXDで開く時点では問題無いのですが、共有URLで閲覧する際の動作が重くなります。 共有URLでの動作を軽くするために、ファイルを複数に分けていましたが、更新や管理が煩雑になりがちでした。 デザインデータとセットでコメントやメモを残したい 閲覧者もオンライン上のデータにコメントを残すことができますが、URLが削除されるとコメントも消える仕様でした。 完了したプロジェクトの共有URLは削除されることもあり、コメントを見返すことができないケースもありました。 軽微な修正をエンジニアも行えるように編集権限を与えたい 人数ごとにAdobeアカウントと利用料が必要となり、費用を考慮すると全員への付与は厳しいです。 これらを解決する代わりとなるツールを検討することになりました。 上記に加えて、検討の際に条件として上がったのは、 動作が軽快であること プロトタイプが作成できること お試しで運用がしやすいこと(無料プラン等が提供されている) などです。 結果、以上を満たして操作性や費用面を加味した上でFigmaに乗り換えることとなりました。 Figmaで解決できたこと Figmaに乗り換えてみて、先述した要求はかなり改善されました。 成果物を手軽に共有 原則としてブラウザ版を使用し、チーム全員に閲覧権限を付与することで、常に最新状態のデザインを確認できるようになりました。 例えばオンラインでのミーティングでも、各自がファイルを開きつつリアルタイムで変更点を話し合ったり編集することが可能になりました。 ファイル内のアートボード(アプリ画面)が増えても動作は軽いまま Figmaに移行するにあたって、XDで作成していたデータを1つのファイルにまとめました。下の画像のように、キャンバス内に全てのフレームを集約しています。 実際のファイルは更にフレームが増えていますが、読み込みも動作も軽快なままです。 ブラウザ上のファイル デザインデータとセットでコメントやメモを残す コメント機能も備わっており、デザイン決定までの経緯などをメモとして残せます。キャンバスを縮小してもコメントの存在を示すマーカーが表示され続けるので、どの部分にコメントが集中しているか視覚的に把握できます。 slackとの連携も可能で、自分へのメンションにも気付きやすいです。 編集権限に上限が無い 現在、モバイルチームではStarterプランを利用しています。 冒頭で書いた通りStarterプランは無料かつ編集権限に上限数が無いので、費用とアカウント数を気にせずエンジニアにも権限を付与できています。 実装過程で発生したデザイン上の軽微な修正や変更をエンジニアが行うことで、わざわざデザイナーに依頼してその作業待ちをすることが無くなり、全体の効率が上がりました。 また、当初は予想していなかった副次的に良かった点もありました。 採用・異動でチームの入れ替わりがあっても、仕様の共有が簡単になった 以前よりも成果物の共有と管理が楽になったことで、新しいメンバーにとっても仕様が把握しやすく、プロジェクトに参入しやすくなりました。 エンジニアのデザイン仕様に向ける関心が高くなった 編集を通じてエンジニアが直接的にデザインに関わる機会が増えたためか、当事者意識が高まった気がします。デザイナーとエンジニア双方が集まって意見を交わすことが増え、UIの検討が活発になりました。 エクスポート機能など、デザイン以外で便利な機能も多い 書き出しを介さないコード変換機能や画像出力機能など、デザイナーだけでなくエンジニアにとっても助かる機能が多々あります。 1ファイルが巨大になりがちな分、その一部分だけを扱う操作にも優れています。 Figma vs XD モバイルチームでは結果的にFigmaの特徴がうまく噛み合い活用できていますが、それまで活用していたXdにも利点はありました。XDのメリット、またどのようなケースであればそれを活かせるのかをFigmaと比較して考えてみました。 XD 編集権限はライセンス保持者のみに与え、閲覧者には簡易機能のみを許可できる 編集者と閲覧者で業務を明確に切り分けたい場合に有効。 成果物の共有ページ(共有URL)の機能がシンプル 操作が分かりやすいので、閲覧者が相当数いるチームでも活用しやすい。 他のAdobe製品と共通の操作性 チームが元々Adobe製品に馴染んでいる場合は導入しやすい。 プランによってはAdobe製品を横断して使用可能(CreativeCloud) UI/UX制作の他にも幅広くデザイン業務を行いたいケースでは便利。 Figma ユーザーに与えられる編集権限の上限数がない デザイナーに限らずメンバーが成果物に手を加えたい場合に有効。 コード変換機能やクリップボードへの直接書き出し機能など、ちょっとした機能が多数あり チームに開発者が多い場合、特に実装で役に立つ。 作成できるファイル数に限りがあるが無料プランあり 安価で運用したい少人数のチームにハマる。 そして、少し前になりますが、2022年9月16日にAdobeによるFigma買収が発表されました。驚きましたね… 個人的には、XDとFigmaは同じデザインツールといえどターゲットとするユーザーが異なると思っていたところだったので、両者の利点を生かしたままサービスが継続してくれればいいなと願っています。 最後に FigmaとXDの双方を使用してみてそれぞれの強みがあると実感しましたので、今後もツール選定の際は個々の特徴と自チームの目的が噛み合うか加味して決めていきたいなと思いました。 Figmaの特性である「共同編集に優れている」点は、実際使ってみることでデザインやUIに関わることの敷居を下げてくれ、非デザイナーがデザインに寄せる関心を高めることにも繋がっているなと、こうして振り返って思いました。 Figmaの理念は「すべての人がデザインを利用できるようにする」とのことで、まさしくそれを体感できたなと感じた次第です。 ここまで読んでいただきありがとうございました。
アバター
はじめに セーフィーで画像認識エンジニアをしている橋本です。本記事では、先日発売されたエッジAIカメラ SafieOne で利用可能なサービス 「Store People Detection Pack」 で開発した 画像認識システムとアルゴリズム について紹介したいと思います。 はじめに システムの概要 物体検出 学習時の工夫 量子化の工夫 トラッキング イベント発行と送信 まとめ 参考URL 「Store People Detection Pack」とは、小売業界の課題を解決するために作られた人物検出AIを使ったサービスです。 別記事 でも解説されていますので、是非合わせてご覧ください。 本サービスを利用することで、Webマーケティングでは当たり前に可視化できているコンバージョンにつながる顧客の行動を、リアルな店舗の映像から明らかでき、 小売業界での映像データの利活用が進む のではないかと期待しています。 Webサイトでは、どのようにページを遷移して商品を探し最終的な購入に至ったのかを把握できるため、「クリック率(逆に離脱率)がどのくらいなのか」といった情報から分析を行い、最適化が行いやすくなっています。一方で、リアルの店舗では、レジの売り上げ(POS)データで最終結果は分かるものの、購入に至るまでの経緯はブラックボックスになっています。 例えば、「今日は『かつ丼弁当』を食べよう」と思い立ってスーパーマーケットのお弁当コーナーに行き購入に至った目的買いのお客様と、店舗で様々なお弁当を検討して「かつ丼弁当」を購入したお客様の違いをPOSデータから見分けることはできません。本サービスを使えば 「かつ丼弁当」の前で立ち止まった人数を可視化でき、さらに録画映像を振り返って要因分析を行うことができます 。 システムの概要 本サービスの技術的な特徴は エッジAIを使った画像認識 を行っていることです。エッジAIというのは画像をアップロードしてサーバー上で推論するのではなく、カメラ毎に搭載されたプロセッサで推論するAIのことを指しています。一般的に、エッジデバイスには AIアクセラレータ が搭載されており、ニューラルネットワークを効率的に計算することができます。SafieOneの場合はQualcomm製のDSPが搭載されており、 SNPE(Snapdragon Neural Processing Engine)SDK を使ってネットワークの実行部分を実装しています。 本サービスがエッジAIを採用している理由としては、 低コストなリアルタイム処理が得意 デバイスごとに異なる賢さのAIをインストールできる(個別AI) の2点が挙げられます。 リアルタイム処理については、カメラの映像をデバイス内で直接処理できるのでアップロードが不要でレイテンシが少なく、専用アクセラレータを用いるためサーバー上の汎用ハードウェア(CPU、GPU)と比べてコストパフォーマンスに優れています。 また、個別AIについては、まだお見せできないのですが、 特定のユーザ・業種に対して専用のAIを簡単に作成してカメラ毎に異なる賢さを搭載できる ように開発を進めています。サーバー推論の場合はある程度のユーザ数で推論モデルと計算資源を共有することになるため、低コストな個別対応が難しいといった課題があるのですが、当社は自社でエッジAIカメラを販売しているため対応が行いやすくなっています。 システム全体の構成は上記のようになっています。SafieOneにはオプションを付けることで様々なアプリ(AI-App)がインストールできるようになっています。 PeopleDetection はエッジAIの本体でニューラルネットを使った画像認識を行うアプリです。 SafieEdgeAppFramework はSafieOneにインストールされるアプリのライフサイクル管理や、カメラ画像をアプリに送信する役割を持っています。 SafieClient はカメラハードウェアの制御、映像や検出結果(イベント)をサーバに送信するのが主な役割です。SafieEdgeAppFrameworkやSafieClientについては、 別記事 で解説されていますので是非合わせてご覧ください。 本サービスではエッジ側で画像認識結果をある程度集計して、ユーザが興味を持つ有益な事象(イベント)だけをサーバーに送って保存する仕組みになっています。ユーザーがSafieViewerを操作して検出結果の時系列グラフを見たいといったリクエストを送った場合には、サーバーに保存されているイベントを使って描画するという処理の流れになります。 エッジAI本体のPeopleDetectionは上のようなパイプラインで処理を行っています。物体検出( Object Detection )、追跡( Tracking )、イベント発行( Event Publishment )については次節以降で詳細を説明します。 物体検出 ライセンス: CC BY-SA 4.0 画像認識とは画像に含まれる意味や構造を抽出して、テーブルデータとして利活用できる状態にするプロセスのことを言います。画像認識のタスクはいろいろありますが、本サービスでは 物体検出 を利用しています。物体検出とは、 オブジェクトのカテゴリと位置と大きさを検出するタスク のことを言います。物体検出(object detection)は受け取った画像から、検出結果(raw detections)を計算して、次の追跡(tracking)処理に渡します。 SafieEdgeAppFrameworkにはビデオフレーム購読用のインターフェースが定められており、これをアプリ開発者が実装すると、当該メソッドが1 sec / 10 fps = 100ms間隔でcall backされて、アプリがカメラから画像を取得できる仕組みになっています。推論間隔が空いてしまう場合に、人物の動きが大きくなってトラッキングが不安定になるため、パイプライン全体のレイテンシの平均値と標準偏差を計測して、100msに収まるように ネットワークの選定とチューニング を行っています。 当社では、MobileNetV2-SSD、Yolov5、YoloXなどのアルゴリズム調査とチューニングを行い最も性能の良いネットワークと学習結果を採用しています。物体検出のアルゴリズムの進歩は早く、最近はPicoDet、Yolov7など新しいネットワークが登場していますので随時アップデートをしていきたいと考えています( ので エンジニアを鋭意募集 しております! )。 学習時の工夫 右画像のライセンス: CC BY-SA 3.0 画像認識の学習では、 推論環境と学習時で画像の分布を合わせることが重要 です。当社のカメラ(左)は天井に設置することが多く俯角が大きいことや、水平114° 垂直60°の広角レンズを使っているため、人物の見え方がCOCOのような汎用的なデータセット(右)とはかなり異なってきます。そこで、COCOをベースとしながら、 当社のドメインに合わせた画像を約21,000枚追加 したほか、画像回転のデータ拡張を強めにかけることで、パースによる人物の傾きに対応するようにしています。 量子化の工夫 エッジ実装では高速な演算と引き換えに、量子化誤差によって推論精度が低下することがあります。CPUでは32ビット浮動小数、GPUでhalf precisionを使った場合は16ビット浮動小数が利用できるため、丸め誤差を気にする必要はありませんが、SafieOneのDSPで 8ビット固定小数を利用した場合、256 段階の表現しかできないため考慮が必要 になります。量子化誤差を少なくするためのポイントは、 各レイヤーの出力の値域を絞る 出力の分散ごとにtensorを分ける ことです。 上の図はYolox-nanoの1・2番目の畳み込みレイヤーをnetronで可視化したものです。デフォルトのactivationは SiLU (左)となっていますが、上界がない(unboundedな)ため大きな値が出力される可能性があります。そうした場合に8bitで量子化するとステップ幅が大きすぎるためおおきな丸め誤差が発生し細かな諧調が失われる可能性があります。そこで、学習時に ReLU6 (右)など適当な閾値でクリップするようなactivationを使って値域を限定することで、学習時の性能(mAP)は下がりますが、8ビット変換時の量子化誤差を低減できました。 また、フレームワークに依存しますがchannel単位の量子化に対応していない場合(tensor単位の量子化しか対応していない場合)は注意が必要です。畳み込みの出力するtensorの分散がchannel毎に異なっていた場合、分散の大きなchannelにつられて量子化幅が決定され、分散の小さなchannelの情報が消えてしまいます。これを解決するには、分布が似ているチャネルごとにconvolutionの出力を分ける必要があります。 Yolox-nanoに416×416の画像を入力した場合に、一番大きな特徴量マップ(52×52)には、4チャネル(x, y, w, h)を出力する回帰用のヘッダ(左)、objectness(0~1)を出力する分類用のヘッダ(真ん中)、11クラスのスコア(0~1)を出力する分類用のヘッダ(右)が接続されています。回帰用のヘッダは値域が限定されないため、分類用のヘッダと同じtensorに纏めないほうが良いとされています。このネットワークでは、畳み込みは異なる経路で行っているため問題ないのですが、その後で1つのtensorにconcatする部分に注意する必要があります。1つのtensorにすることで量子化が再度行われ、丸め誤差が発生してしまうため、concatの前の出力をCPUの32ビット浮動小数点を使って計算することで精度の悪化を防ぐことができました。 物体検出では一般的に最終レイヤーで回帰と分類を行い、その後、デコード処理としてAddやMultiply、出力をまとめる目的でConcatenateが使われることが多いです。このような変換処理の度にtensor全体で再度量子化が行われ、丸め誤差が繰り返し発生してしまうため、 後処理の量子化実行には注意しておくと良い と思います。 トラッキング トラッキング処理は物体検出の結果(raw detections)を受け取って、人物IDのついた追跡結果(tracklets)を出力します。主な機能として、 同一人物に同じIDを割り当てる 検出漏れがあった場合に位置を推定して補間を行う を持っています。PeopleDetectionでは、軽量かつ性能も良いため SORTアルゴリズム を使っています。処理の流れは以下の通りです。 カルマンフィルタ を使って前フレームのtracklet(人物の位置と速度)から現フレームのtrackletを推定する 推定したtrackletとraw detectionを ハンガリアン法で二部マッチング する イベント発行と送信 イベント発行(Event Publishment)処理では、追跡処理で出力したtrackletsを受け取り、ユーザが興味を持つ有益な事象が発生した場合に、イベントとしてサーバに送信します。サーバーに送付するイベントは以下の3種類を定義しています。 設定した領域が滞留人数と時間の条件をみたした時に送信される PeopleDetectionEvent 設定した領域から人物が退出した時に送信される AreaLeaveEvent 設定した線分を人物が交差した時に送信される LineCrossEvent 上記のイベントがそれぞれ、 立ち入り検知 立ち入りカウント 通過人数カウント のサービスに利用されます。 PeopleDetectionEventは「 設定した領域で、N人以上、M秒以上の滞留が発生した場合 」にサーバーに送信されるイベントです。人物毎に滞留時間を計算することで、一時的にエリアに入った通行人を除くことができます。サンプル動画では3人以上10秒以上の条件を設定していますが、3人がエリアに入っただけでは条件をみたさず、最後に入った人物の滞留時間 t ≥ 10のときに条件が成立したと判定しているのが分かると思います。 このイベントが発生した時には、ユーザに通知を送る、Viewerのタイムラインにフラグとサムネイルを表示するといった処理が行われます。例えば、 レジ待ち行列が長くなったタイミングで通知を送ってレジ応援を呼ぶ といったオペレーションや、 映像の振り返りを効率化する といった活用方法を想定しています。 AreaLeaveEventは「 設定した領域から人物が退出したとき 」にサーバーに送信されるイベントで、イベントが発生した時刻のタイムスタンプと、人物がその領域に滞在した秒数を含んでいます。このデータをサーバで集計することで時間帯ごとにエリアに滞在していた人数を計算して、時系列グラフを表示できるようになっています。さらに、「N人以上、M秒以上」のクエリで絞り込むことで立ち止まりの人数だけを可視化することもできるため、 通行人と立ち止まりの比率を使ったファネル分析 といったご活用をしていただいています。 LineCrossEventは「 設定した線分を人物が交差したとき 」にサーバーに送信されるイベントで、イベントが発生した時刻のタイムスタンプと、線分を交差した方向を含んでいます。サンプル動画ではOut方向に2人、In方向に1人をカウントしています。 線分の前後でうろつく人物を過大にカウントするのを防止するため、線分を跨いだら同一人物に対して一定のクールダウン期間を設けるなど、フィールドテストで得られた知見から細かな改良を行ってきました。このデータもサーバーで集計されて時間帯ごとの入退場人数を計算して、時系列グラフを表示するのに使われます。 店舗入り口や通路に設置して人流の解析に使用する など、様々な用途でご活用をいただいています。 まとめ 本記事では、SafieOneで利用可能なサービス「Store People Detection Pack」で開発した画像認識システムやアルゴリズムについて紹介しました。当社のクラウドには、 15万台以上のカメラが常時接続されており、画像認識サービスを開発するのに恵まれた環境 だと思います。本記事で紹介したサービスの他にも、多様な映像サービスの考案と開発を進めていますので、 是非エントリーしてみてください 。 エントリーページ 参考URL かしこくなるAIカメラ Safie One(セーフィー ワン) SHIBUYA109渋谷店においてAIカメラ「Safie One」の実証実験を開始 セーフィー、ベルクと共同で「Safie One」を活用した実証実験を実施 セーフィー、エッジAIカメラ「Safie One」来店人数や混雑具合を可視化 | IT Leaders
アバター
こんにちは。セーフィー株式会社 バックエンドエンジニアの村田 ( @naofumimurata )です。 セーフィーには2020年9月に入社して今年の9月で2年が経ちました。入社後はインフラグループに配属となり、既存インフラの改善や新規サービスのインフラ設計/構築などサービスのインフラを支える仕事を担当していました。実は1年ほど前から自分の希望でチームを移動し、現在はサーバサイドの開発業務をメインで行っています。 今回はこれまでを振り返ってみて、サーバサイド開発に転向する上で大変だったことや良かったことなどを書いてみようと思います。 自己紹介 インフラグループでの仕事 スクラム開発の導入とサーバサイド開発へのチャレンジ なぜ転向したのか 実際やってみて 大変だったこと 良かったこと さいごに 自己紹介 まず、私自身の経歴について簡単にご紹介したいと思います。 大学院 CS専攻 を卒業 Web系企業に入社し SRE (Site Reliability Engineer) として2年半ほど勤務 セーフィーに入社 元々、低レイヤーの技術領域に関心があり、大学院ではデータベース管理システムの高性能化を研究していました。2018年に大学院を修士で卒業し、Web系企業に入社しました。入社後は SRE (Site Reliability Engineer) チームに配属され、サービスのインフラを管理する仕事を担当していました。2年半ほど経った頃に転職で今のセーフィーへと入社し現在に至ります。 インフラグループでの仕事 2020年9月にセーフィーに入社した後はインフラグループに配属となりました。インフラグループはセーフィーのクラウド録画サービスを支える全てのインフラの管理/改善などを行うチームで、主に以下の業務を行っています。 100万台規模のクライアントが接続するクラウド録画サービスを安定して提供できるアーキテクチャの設計・開発 サーバコストを抑えるための各種最適化 サービス・システムの監視やチューニング等の運用 参考: https://open.talentio.com/r/1/c/safie/pages/54192 セーフィーではインフラ環境としてAWSを利用しているのですが、自分が入社した当時はEC2インスタンスで稼働するアプリケーションのECS移行をまさに始めようとしている時期でした。入社直後はまずはその仕事を任せられることになり、いきなり重要な仕事を任せてもらえて嬉しかったことを覚えています。その後も既存インフラの改善から新規サービスのインフラ設計/構築、CI/CDの改善などいろんな仕事をやらせていただき、知識/技術を深めていくことができました。 スクラム開発の導入とサーバサイド開発へのチャレンジ インフラグループで1年ほど経った後、転機が訪れます。サーバ/インフラグループを中心に開発手法としてスクラムを導入することになり、チームの再編成が行われることになりました。これまでは人に対してプロジェクトを割り振るという開発スタイルだったために属人化や責任が集中してしまうといった問題があり、それを改善するべく認定スクラムマスターの資格を持ったメンバーを中心に導入することになりました。 参考: https://article.safie.link/safietimes/voice/2388 スクラムの導入にあたりチームが再編成されることになったのですが、その時の上司との1on1で、サーバサイド開発に挑戦したいという意向を伝えたところ(以前からそういう気持ちがあることを伝えてはいました)、いいタイミングだしサーバサイドの開発を行うチームに移るのが良いんじゃない?というお話しをいただき、はれてサーバサイドの開発を行うチームに異動することになりました。 なぜ転向したのか そもそもなんでサーバサイド開発をやりたかったの?という部分ですが、大きく以下の2つの動機がありました。 自分のキャリアに対する見識を広めたい 開発スキルを身につけ、セーフィーのシステム全体の理解度を高めたい まず1つめの動機についてですが、自分のエンジニアとしてのキャリアを考えるにあたって選択肢をなるべく増やせると良いかなと考えています。自分は今年でエンジニア歴5年目になりますが、これまでインフラの仕事しかしてきませんでした。元々、低レイヤーの技術領域が好きだったので、仕事内容に不満はなくそれなりに適性はあるのかなと思う一方で、ほんとにこの道が自分には合ってるのか?と悩む時もありました。色々悩んだ結果、そもそも他の技術領域をやったことないのに適性を判断するのは難しいので、色々やってみるのが良いんじゃないかという結論になりました。人生の時間は有限なので全ての領域にチャレンジするのは難しいですが、なるべく遠回りしていければ良いかなと思っています。 2つめの動機は単純で、開発を通してアプリケーション部分の理解を深めたいという思いです。これまでインフラチームでの仕事を進めるなかで、インフラ部分の知識や理解はついてきたのですが、各アプリケーションの具体的な動作やどう連携しているかについてはざっくりとしか把握できておらず、問題の調査がある段階までいくと難しくなったり、インフラに留まらない一歩踏み込んだ改善などができずにいました。開発業務を行わなくてもこれらの知識を深めることも可能だとは思いますが、やはり実際に自分で開発することで、より実感として得られるものがあるかなと考えました。 実際やってみて さて、実際にサーバサイドの開発業務を行うことになりましたが、いきなり全ての仕事をがらっと変えるというのではなく、ちょっとずつ開発の仕事をとっていくスタイルで進めていきました。徐々に開発業務の割合を増やしていき、現在はだいたい9割が開発業務という状態になっています。 ここでは、特に最初の頃を振り返ってみて大変だったこと、そして良かったことについて書いてみようと思います。 大変だったこと あたりまえですが、最初は技術面でキャッチアップすることが多くなかなか大変でした(今も苦労していますが)。これまで、TerraformやAnsibleといったいわゆるIaCのコードについては日常的に書いていましたが、アプリケーションのコードを書くという経験があまりなかったため、まず言語やフレームワークの使い方といった基本的なところから習得する必要がありました。 セーフィーのサーバサイドで稼働するアプリケーションには映像閲覧用のWeb/モバイルアプリケーション向けのAPIを提供するものや、カメラ映像を録画/配信を行うものなど様々なものがあります。前者ではPythonが使われており、後者ではJavaやGoなどが使われています。実際にサーバサイドの開発業務を始めるにあたり、まずは前者のPythonによるWeb APIの開発を行うこととなったため、まずはPythonの書籍を読みつつSQLやWeb APIの設計に関する書籍などを読んでいきました。具体的には、技術評論社の『Python 実践入門』、オライリー社の『Web API: The Good Parts』、『SQLアンチパターン』などです。カメラ映像の録画/配信を行う部分についても、少しずつ触れてきてはいますが、映像配信周りはかなり専門知識が求められる部分で難しく、その辺りのキャッチアップが自分の今後の課題です。 また、技術そのもの以外だと各アプリケーションのパッケージの構成やテストの書き方などについても最初キャッチアップするのに苦労したのを覚えています。このあたりは、歴史的な経緯でこうなってるみたいな部分もあり、背景を知るチームメンバーに教えてもらいつつ徐々に知識を付けていきました。 良かったこと まず、会社/チームからのサポートを得られたことが一番良かったことでした。未経験の技術領域にチャレンジするにあたって、自分のやりたい!という気持ちとは裏腹にチームとして受け入れてもらえるのか?という点が一つ懸念されることだと思いますが、その点が全く問題なかったのは良いことでした。短期的にみると、どうしてもパフォーマンスが下がってしまう可能性がありましたが、それよりも難しい課題に対してチャレンジすることを評価すると背中を押してもらえたので、会社からの評価が下がるといった不安なく仕事を進めることができました(実際、評価はむしろ上がりました)。 また、実際に開発業務をやるようになって、各アプリケーションの具体的な動作や連携方法などが以前よりもわかるようになり、システム全体の理解が深まったのも良かったです。これはそもそもの動機として挙げていた内の一つで、まだまだ把握できていないところは依然多いのですが、着実に達成されつつある実感があります。 さいごに 今回は、インフラ職からサーバサイド開発に転向している話をご紹介させていただきました。セーフィーに入社してまだ2年ですが、振り返ってみるといろいろ自由にやらせてもらっているなと感じました。まだ転向してから日が浅く、ちょっと道半ばな振り返りでしたが、同じようにキャリアについて悩んでいる方の参考になれば幸いです。ここまで読んでいただき、ありがとうございました。
アバター
こんにちは「Store People Detection Pack」のPdM(プロダクトマネージャー)の谷野です。 8/4にセーフィー初となるエッジAIカメラ、「 Safie One 」の製品発表を行いました! safie.co.jp 「SafieOne」では、エッジAIを利用する為の設定や結果を確認することができるブラウザアプリケーション「 AI-App(あいあっぷ) 」を使うことで、「映像×AIによる課題解決」ができる賢くなるカメラを目指しています。 その「AI-App」の第一弾として、「 Store People Detection Pack 」(AI-Appを使用できるようにするための、カメラと紐づくオプション)が小売り・飲食等向けに販売されます。 今回は、「AI-App」が目指すもの、そして「Store People Detection Pack」のコンセプトや機能に関して、PdM視点でご紹介させていただきます! 店舗におけるAIの役割 「Store People Detection Pack」のコンセプトとリリースまでの道のり Store People Detection Packの3つの機能 ①立ち入り検知 ②通過人数カウント ③立ち入りカウント 「AI-App」の今後 さいごに 店舗におけるAIの役割 セーフィーでは、「映像から未来をつくる」というビジョンのもと、防犯利用にとどまらずカメラが進化していく為のロードマップを5つのステップで提唱しています。「SafieOne」では、ステップ4の「映像×AIによる課題解決」を目指しています! 飲食や小売りの店舗様では、セーフィーのカメラを防犯用途はもちろんのこと、店舗運営のサポート、業務改善等にご活用いただいています。例えば、チェーン店等ではコンサルタントや本部の方が実際の店舗に訪問する+映像を確認することで、効率的かつ確度高く店舗の状態を把握したり、コンサルティングすることで、PDCAサイクルを早めることができます。 この映像を確認するきっかけとなるのが、下記のような場合です。 防犯:何かトラブルが発生した際に過去の映像を確認をする 業務改善:店舗のデータから気になる日時の映像を確認する 店舗でAIを使うと、リアルタイムにトラブルを発見出来たり、膨大な映像から知りたい映像にフラグが立てられ映像を探す時間が短縮されたり、検知結果の該当映像を確認することで新たな顧客の行動が発見されたり、店舗の既存システムでは取得できないデータを可視化したりすることができます。 店舗にとってAIは、店舗運営や課題解決に必要不可欠なものを見える化してくれるものだと思っています。 「Store People Detection Pack」のコンセプトとリリースまでの道のり 「Store People Detection Pack」は、誰もが手軽に店舗の課題解決につなげることができる機能とUI、価格であることをコンセプトとしています。 小売り・飲食のお客様から課題として多く声をいただいていたのが、主に下記3つです。「Store People Detection Pack」ではエッジAIの「人検出」を使い、それぞれを解決できる機能を盛り込んでいます。エッジAIは、カメラ側で人の検知を行いその情報を都度カメラからサーバーに送信します。そのため、利用者はリアルタイムに情報を受け取ることができます。 レジ混雑による機会損失をなくしたい →レジが混雑した場合に、検知を行い通知を受け取れる 棚レイアウトの効果検証手段がない →棚や商品の前に立ち止まった人数をカウントしグラフ化する 人員配置が適正かどうかわからない →店舗入口での入店、退店の人数をカウントしグラフ化する また、課題と共に多く声を頂いていたのが、価格です。複数のお客様から「課題を解決できるかもしれないAIカメラの提案を他社から受けたが、価格が非常に高く導入を見送った」と話しを伺い、「Store People Detection Pack」ではAIを手軽に使える価格での提供に努めました。 機能が形になってきた段階でフィールドテストを実施し、「店舗の課題が解決できる機能が盛り込まれているか」、「簡単に設定ができ、手軽に結果が取得できるか」等の確認を行いました。実は、初めてお客様に使ってもらった時、お客様だけでは、設定を完了することができませんでした。そのフィードバックを受け、現在は、設定時の遷移の仕方を改善することで、簡単に設定が完了できるようになっています。また、グラフも、知りたい情報が得られないとフィードバックがあり、グラフで表示される期間や集計単位がPOSデータと照らし合わせられる等、運用に合うようにるように改善を行っています。 また、「設定したエリアの中に立ち止まった人数をグラフ化する」という1つの側面においても、設計の試行錯誤がありました。設計にAIの画像処理側では、どの検知情報をどのタイミング(エリアに入ったorエリアを出たタイミングなど)でカメラからサーバーに送るか、サーバーでは時間をかけずにどう集計するか、フロントではどのように滞留時間でグラフを更新するかなど。 ここに記載の内容は本当に一例ですが、約7か月に渡るフィールドテストの期間を経て、機能、UI共に改善を繰り返し、リリースに至りました。 Store People Detection Packの3つの機能 SafieOneでは、エッジAIにて人を検知しています。「Store People Detection Pack」では上述した課題に応えるため、人検知を活用した以下3つの機能が盛り込まれています。 ①立ち入り検知 概要 :設定した条件に応じて、エリアに対して立ち入った人を検知し、通知する ユースケース :レジ混雑時に通知する、ディスプレイに立ち止まった人がいた場合に検知する、立ち入ってほしくないエリアに人が立ち入った場合に通知する ②通過人数カウント 概要 :設定したラインに対して、IN、OUTの人数をカウントしグラフ化する ユースケース :POSデータからの来店数ではなく、実際の入店数(日別や時間帯 別)から、人員配置の適正化をする ③立ち入りカウント 概要 :設定したエリアに対して、入った人数をカウントしグラフ化する 滞留時間での絞り込みが可能 ユースケース :特定の棚や商品前の立ち止まり人数で比較検証を行う ※画面は開発中のものです。仕様は予告なく変更される場合があります。 「AI-App」の今後 「Store People Detection Pack」では小売り・飲食のお客様から多く声をいただいた課題を解決できる機能を盛り込んでいますが、例えば、単に小売りと言っても業態はさまざまです。スーパーマーケット、百貨店、コンビニエンスストア、家電量販店、ドラックストア、ホームセンター、アパレル店など。抱えている課題も1つではありません。 今後、「AI-App」では、お客様の課題に応じたアプリケーションを準備していくことで、賢くなるカメラを目指しています。 さいごに 読んでいただきありがとうございました! 今回はPdM視点でAIアプリケーションに関してご紹介させていただきました。店舗の〇〇な課題を解決する為に作られたんだと伝わっていたら嬉しいです。 ご興味のある方やもっと技術的な内容が知りたいという方は、是非、お気軽にご連絡下さい。AI-Appは、引き続き、店舗の課題を解決できるアプリケーションを続々と!?作っていきますので、またこちらのブログにてお会いできる日を楽しみにしています。
アバター
こんにちは。セーフィー株式会社 エンジニア 谷口、江守です。 今回、セーフィーで初めての 社内ハッカソン を開催しました! 所属部門内に閉じた形ではありますが、事業成長とともに組織が変わっていく中で、組織活性化の手段として、また単純にハッカソンをやりたい!という個人的な思いを掛け合わせて実現させました。 その過程と実際に開催した内容をご紹介したいと思います。 ハッカソンをやりたいと思った理由 実施の承認を得る どのような形で開催するか 社内ハッカソン実施要項 目的 期間 参加者、チーム分け、作るもの 進め方 成果発表 その他 チーム分けと企画内容 途中経過 発表会 最後に ハッカソンをやりたいと思った理由 最近の組織変更で、所属部門に営業-企画-エンジニアといった複数の職能が集まる部門となり、同じ目標を共有するメンバーとして 一体感を醸成したい 思いがありました。 また、弊社は技術領域が広く、様々な職種のエンジニアがいます。 (詳細はこちら→ セーフィーにはどんな種類のエンジニアがいるの?職種別にエンジニアを紹介! - Safie Engineers' Blog! ) 私自身はフロントエンドですが、組織変更で組み込みやAIなどのエンジニアとも同じチームになったことで、それらについても知りたいと思うようになりました。 ただ、全く知識がない中でいきなり業務となるとハードルが高いので、まずは軽く触ってみる程度で知ることができる手段を考えたときに、ハッカソンを思いつきました。 実施の承認を得る これらの思いを上長との1 on 1で会話する中で快く賛同して頂き、社内ハッカソン開催に向けて準備することになりました。 どのような形で開催するか 私自身の経験としては、外部のハッカソンイベントに2回参加したことがあります。 しかし、いずれもエンジニアのみの参加で数日間で企画〜デモまで行うもので、大体の流れは把握していましたが、社内ハッカソンは初めてでした。 そこで社内ハッカソンを行った他社のブログをいくつか参考にさせて頂きつつ、実施要項を決めました。 決めるにあたって意識したことは以下です。 スモールスタートでやってみようということで、 所属部門内での開催 とすること。 営業や企画も参加しやすいように、普段の業務でこんなのできないかな?といった アイデア出しだけでもOK とすること。 デモをゴールとせず、競争要素も排除し、 コミュニケーション活性化 を目的とすること。 エンジニアは、一人で進めてしまうといったことがないように(目的であるコミュニケーション活性化につながらないため)、 担当技術領域以外にチャレンジ すること。 初めての開催でいきなり数日間業務を止めるのは難しそうなので、 薄く長く開催する こと。 忙しくて時間がとれなかった...がなるべく発生しないように、業務時間内に、 業務の一環として行う こと。 業務として行うので目標/評価に連動できるような開催期間とし、 目標に入れる こと。 モチベーションの維持のため挙手制とし、 モチベーションの高いメンバーのみで行う こと。 活動費用は会社負担 とすること。 社内ハッカソン実施要項 営業、企画、エンジニアでやりたい人が集まってアイデア出して何か作ってみよう アイデア出しだけでも参加OK、もちろん経験がないけど作ってみたいもOK 目的 営業含めたプチ案件や便利ツールを開発することによるコミュニケーション活性化 自分の保有していない技術領域に関して知見を得る場 期間 3ヶ月 参加者、チーム分け、作るもの 挙手制、参加者でやりたいことを持ち寄りチーム分け 進め方 例えば、チームごとに隔週2時間、スケジュールを先に抑えてその時間内で行う、など、業務時間内に、かつ忙しくて時間がとれなかった...がなるべく発生しないような仕組みにする 成果発表 所属部門内で発表会(デモ)を行う 順位付けなど競う要素はなし その他 必要な機材費等の費用は会社負担で購入 エンジニアは担当技術領域以外の開発にチャレンジすること チーム分けと企画内容 募集をかけ、参加者が確定したところで、まずはテーマ等を設定せず各人がやりたいことを持ち寄ってもらいました。 いくつか同じような内容があったのでマージしつつ、どのテーマに取り組みたいか記名してもらい、営業-企画-エンジニアのバランスをとりつつ、以下4チームになりました。 ラズパイとセンサーを組み合わせて、イベント検知とセーフィー映像の連携 Hey Siri, Safie Pocket2 でスナップショット撮って 宇宙 x Safie GO - 衛星通信対応SIMでの、どこでも繋がるGO センサー等連携ハブとしてのカメラ 営業、企画が参加してくれたことにより、顧客ニーズから自社サービスとしてこんなことできたらいいよね、といった 具体的でかつ色々な技術領域にチャンレジ できるとてもよい企画内容が出来上がりました。 途中経過 ハッカソン実施要項の「エンジニアは担当技術領域以外の開発にチャレンジすること」が意外によく効いていて、例えばフロントエンドエンジニアがラズパイとセンサーの組み合わせに取り組んだとき、そもそも、ラズパイとセンサーってどうやって繋げるの?ブレッドボードってなに?A/Dコンバーター??抵抗って...大きさは適当でOK??など、デバイス部分が全く分からず、ソフトウェアだけの、なんて狭い世界で生きてきたんだ...と打ちのめされていました。 ラズパイとセンサーの配線 接続した感圧センサー、センサー小さすぎ!こんな接続でよいのか謎 配線を終えようやくソフトウェアまで辿り着き、Pythonでロジック調整中 発表会 広めの会議室をとり、ハッカソン参加者と一部の出社していたメンバーは会議室で、他はオンラインで、といった形で発表会を実施しました。 発表にあたって決めたことは、1チームあたりの発表時間と発表順だけで、どのように見せるかや発表用の資料を作成するか、などは各チームに任せました。 デバイスをどう接続したのかなど、オンライン参加のメンバーにも伝わるように、事前に同じ会議室でリハーサルをしたり、意図が伝わるようにスライドを作成したりなど、自チームにとって今までの活動と成果が一番伝わる形を考え準備してくれました。 ハッカソンあるあるかもしれないですが、発表本番のデモで動かないといったことがままある中で、無事デモも見せることができ、大成功だったと思います。 発表内容を一部ご紹介します。 ラズパイとセンサーを組み合わせて、イベント検知とセーフィー映像の連携 荷受け場に、感圧センサー+ラズパイとSafieカメラを設置 荷物の重量変化を感圧センサー+ラズパイで検知する 「荷物が置かれた」「持って行った」をラズパイ上のPythonで判定し、Safie Viewerにイベントフラグを立て、Slack通知する Hey Siri, Safie Pocket2でスナップショット撮って 最後に ハッカソンが初めてのメンバーもいて、エンジニア以外の職種でも参加しやすい形や、参加は挙手制、デモすることを目的にしない、など、 とにかくゆるく、ただ何かしら発表はできるように 、を意識して運営しました。 一部のチームはデモまで辿り着けなかったのですが、今回の目的はそこではなくコミュニケーション活性化なので、一定程度達成できたのかなと思います。 やりたいことを快く賛同して後押ししてくれた上長、挙手制でしたが盛り上げるために目的を理解し参加してくれたメンバー、発表会に来てくれた事業部のみんな、何かの取り組みを皆で応援できることは素晴らしいことだと思います。 社内ハッカソンや何か社内での取り組みを考えられている方の参考になれば幸いです。 ありがとうございました!
アバター
こんにちは。セーフィー株式会社 バックエンドエンジニアの河津です。 セーフィーにはクラウドカメラやユーザーアカウントを一括管理できる統合環境である「 Safie Manager 」というサービスがあり、主にエンタープライズのお客様にご活用いただいています。 safie.link 7/7(木)には SSO(シングルサインオン)の機能をリリース しました! 今回はそのリリースされたばかりの新機能「シングルサインオン(以下SSO)」と、それを実現させる仕組み「SAML認証」について解説させていただきます。 SSOについて 1. 利便性が上がる 2. セキュリティリスクが下がる 3. IT部門での管理が容易になる SAML認証に関わる用語解説 SAML認証について 1: ユーザーがアプリケーションにアクセス 2: SAML認証要求を生成 3,4: IdPへのリダイレクト 5: SAML認証要求を受け取り、認証を試みる 6,7,8: ユーザー認証 9: SAML認証応答を作成 10: SPへの302 HTTP POST Binding 11,12,13: SAML認証応答を検証し、ユーザーログインを許可 14: ログイン成功 Safie ManagerとSSO まとめ SSOについて SSOとは、1度システム利用開始のユーザー認証 (ログイン) を行うと、複数のシステムを利用開始する際に、都度認証を行う必要がない仕組みのことです。 基本的に一つのWebサービスで使われるID・パスワードなどの認証情報はそれぞれ独立しており、Aのサービスを使用するにはAの認証情報、Bのサービスを使用するにはBの認証情報が必要です。 SSOを使用すると、この各サービスで分かれてしまう認証情報を統一して使用することができるようになります。 例えばAzure Active Directoryのような認証基盤を利用している場合、その認証基盤とWebサービスを連携させることで、認証基盤の方にログインするだけで様々なWebサービスを使うことができるようになります。 SSOのメリットは大きく3つあるかと思っています。 1. 利便性が上がる 認証基盤にログインさえしていれば、各WebサービスでIDやパスワードなどの情報を入力する必要がなくなるため、Webサービスの利便性が上がります。 2. セキュリティリスクが下がる 本当はあるべきではないですが、各WebサービスでIDやパスワードを使い回してしまうユーザーは一定数いるのではないかと思います。ただ、同じID・パスワードを各Webサービスで使い回すと、その分だけ流出する可能性を増やすことになります。 認証基盤を経由するSSOであれば、流出する可能性のあるサービスは認証基盤のみに絞られることになります。 この場合、認証基盤のID/Passwordが漏れた場合は全てのサービスにアクセス可能なリスクが残るものの、認証基盤には2要素認証機能やクライアント証明書の検証機能が含まれていることが多いため、それらの機能を併用する前提であればSSOを使用する方がセキュリティリスクが下がるのではないかと思います。 3. IT部門での管理が容易になる 各Webサービスの認証情報が認証基盤に集約されると、アカウントの停止やパスワードの変更などを行う際は認証基盤を操作するのみでよくなるため、とても楽になります。 SAML認証に関わる用語解説 SSOを実現するための認証手法はいくつかありますが、セーフィーから提供しているSafie ManagerではSAML認証をサポートしております。 これからSAML認証の解説をさせていただきたいと思っていますが、その前に解説に必要となる用語の説明をさせていただきます。 IdP(Identity Provider) : 認証情報を提供する側のシステムです。上の説明で出てきた「認証基盤」がこちらになります。 SP(Service Provider) : 認証情報を利用する側のシステムです。 Safie ManagerではSSO機能をサポートしていますが、本記事が公開された段階では認証基盤についてAzure Active Directoryを推奨とさせていただいております。上の用語に当てはめると、Safie ManagerはAzure Active Directoryを認証基盤として使用しSAML認証を行うことになるので、この場合はIdP=Azure Active Directory、SP=Safie Manager となります。 以降ではIdP・SPという用語を用いて解説をさせていただきます。 SAML認証について ここから本格的にSAML認証の話をしていきますが、SAML認証はそのフローの複雑さから初見で理解するのはかなり難しい処理だと(個人的に)思っています。 この記事を一度読むだけですと、もしかすると不明点だらけになってしまう恐れがあるため、2度・3度ほど読み返していただくことをおすすめします。 まずSAMLとは、Security Assertion Markup Languageの略称です。インターネットドメイン間でユーザー認証を行なうためのXML(マークアップ言語)をベースにした標準規格のことを指すのですが、「インターネットドメイン間でユーザー認証を行う」という点がまさしくSSOでやりたいことになります。SSOでは、IdPとSPという異なるドメイン同士で認証を確立させる必要があるためです。 SAML認証ではどのようなシーケンスで処理が行われるのでしょうか?シーケンス図を起こしてみました。 様々な処理が入り乱れていますね。 一つ一つ処理の内容を取り上げていきたいと思いますが、その前にSAML認証の事前準備の話をさせてください。 シーケンスを見ていただければ分かるとおり、SPからIdPにリダイレクトしたり、IdPからSPに戻ってきたりしているようです。このリダイレクト先は、事前にわかっていないとリダイレクトすることができません。SAML認証の事前準備として、SP・IdPそれぞれの認証エンドポイント情報を登録しあう必要があります。 また後に触れますが、IdPから送られてきた情報が正しいものであるのか検証するフローがあり、IdPより発行した公開鍵をSPに登録することも必要となります。それらの事前準備を経ることで、SAML認証が可能となります。 それでは、割り振った番号それぞれで行われている処理について解説します。 1: ユーザーがアプリケーションにアクセス ここでは、SAML認証を行うためのログインボタン押下などのアクションをイメージしていただけたらと思います。 2: SAML認証要求を生成 ユーザーがアプリケーションにアクセスした際に最初に行われるのは、SP側で SAML認証要求 が生成される処理です。 SAML認証要求 とはSPからIdPに「今からこのユーザーでSAML認証したいので許可をください」とIdPに要求するためのデータです。 フォーマットとしてはbase64エンコードされたXML形式のデータとなります。 < md : EntityDescriptor xmlns : md = "urn:oasis:names:tc:SAML:2.0:metadata" validUntil = "2022-02-10T08:51:19Z" cacheDuration = "PT604800S" entityID = "{SPのEntityID}" > < md : SPSSODescriptor AuthnRequestsSigned = "false" WantAssertionsSigned = "false" protocolSupportEnumeration = "urn:oasis:names:tc:SAML:2.0:protocol" > < md : SingleLogoutService Binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location = "{IdPの認証エンドポイント}" /> < md : NameIDFormat> urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified </ md : NameIDFormat> < md : AssertionConsumerService Binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location = "{SPの認証エンドポイント}" index = "1" /> </ md : SPSSODescriptor> </ md : EntityDescriptor> 上記の中には、SSOを行いたいSPに対する固有のIDや、IdP側の情報などが含まれており、この情報をIdPに渡します。IdPではこれらの情報が想定する情報かどうかを検証することになります。 SAML認証要求をIdPに渡すやり方は単純で、下記のようにIdPの認証エンドポイントに対して「 SAMLRequest 」というキー名のクエリパラメータをつけてリダイレクトすればOKです。 https://{idp_host}/{path}?SAMLRequest=xxxxxxxxxxxxx 3,4: IdPへのリダイレクト 上記にて発行されたURLにリダイレクトします。リダイレクトすると、次はIdP側での処理が始まります。 5: SAML認証要求を受け取り、認証を試みる IdP側では最初にSAML認証要求を受け取り、想定しているSPからのリクエストかどうかを検証します。 検証といっても、ここではXML内のEntityIDなどを突合するくらいの内容になります。 その後もしIdPにログインしていなければログイン画面を表示し、ユーザーにIdPへのログインを促します。 6,7,8: ユーザー認証 もしIdPにログインしていなければ、このタイミングでログインを行います。 ID・パスワードを入力しつつ、IdPによっては2要素認証が導入されていたり、クライアント証明書の有無によるIdPへのアクセス制御などの機能もあるかと思います。 9: SAML認証応答を作成 IdPにて認証が済んでいることが確認できると、IdPでは SAML認証応答 を生成します。 SAML認証応答 とは、IdPからSPに「IdP側の認証はOKなので、その証明を送るよ」といった形で、SP側で認証情報として取り扱える情報です。 こちらもSAML認証要求と同様、base64エンコードされたXML形式のデータです。 < samlp : Response ID = "{SessionIndex}" Version = "2.0" IssueInstant = "2022-07-14T08:50:33Z" Destination = "{SPの認証エンドポイント}" Consent = "urn:oasis:names:tc:SAML:2.0:consent:unspecified" InResponseTo = "xxx" xmlns : samlp = "urn:oasis:names:tc:SAML:2.0:protocol" > <Issuer xmlns = "urn:oasis:names:tc:SAML:2.0:assertion" > {Issuer} </Issuer> < samlp : Status> < samlp : StatusCode Value = "urn:oasis:names:tc:SAML:2.0:status:Success" /> </ samlp : Status> <Assertion xmlns = "urn:oasis:names:tc:SAML:2.0:assertion" ID = "{SessionIndex}" IssueInstant = "2022-07-14T08:50:33Z" Version = "2.0" > <Issuer> {Issuer} </Issuer> < ds : Signature xmlns : ds = "http://www.w3.org/2000/09/xmldsig#" > {署名の内容} </ ds : Signature> <Subject> <NameID Format = "urn:oasis:names:tc:SAML:1.1:nameid-format:xxx" > {NameID} </NameID> <SubjectConfirmation Method = "urn:oasis:names:tc:SAML:2.0:cm:bearer" > <SubjectConfirmationData InResponseTo = "xxx" NotOnOrAfter = "2022-07-14T08:53:33Z" Recipient = "{SPの認証エンドポイント}" ></SubjectConfirmationData> </SubjectConfirmation> </Subject> <Conditions NotBefore = "2022-07-14T08:50:28Z" NotOnOrAfter = "2022-07-14T09:50:33Z" > <AudienceRestriction> <Audience> {SPのEntityID} </Audience> </AudienceRestriction> </Conditions> <AttributeStatement> {IdPから取得できる属性情報} </AttributeStatement> <AuthnStatement AuthnInstant = "2022-07-14T08:50:33Z" SessionIndex = "{SessionIndex}" > <AuthnContext> <AuthnContextClassRef> urn:federation:authentication:windows </AuthnContextClassRef> </AuthnContext> </AuthnStatement> </Assertion> </ samlp : Response> 10: SPへの302 HTTP POST Binding IdPからSPにSAML応答の情報を渡すには、SP側で用意している認証エンドポイントに対しHTTP POST Bindingし、そのリクエストボディに SAMLResponse というキー名で指定し送信します。 ちなみにこの時HTTP POST BindingとなるかはSPから送るSAML認証要求の内容によって決定されます。 # この辺り <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location= Safie Managerは執筆時点ではHTTP POST Bindingのみ対応しています。 11,12,13: SAML認証応答を検証し、ユーザーログインを許可 SPには上記フォーマットのSAML認証応答が送られ、メールアドレスやユーザー名などの属性情報も含まれていますが、これらの情報をそのまま使用するわけにはいきません。 不正に変更されたSAML認証応答が送られてくる場合も考えられるためです。 SAML認証応答の中には署名の情報があるのですが、この署名情報を検証することで意図したIdPからの認証応答かどうかを確かめます。 検証するには事前にIdPよりダウンロードできる公開鍵が必要になりますので、この公開鍵をSPにあらかじめ登録しておくことが必要です。 14: ログイン成功 検証が成功すると晴れてSAML認証成功となります! この後は、SAML認証応答から取得できる情報を元に、SPにログインするための処理へと続いていくこととなります。 元々あるSPの認証基盤に合わせてSAML認証を組み込んでいくことになり、各SPによってIdPから取得しなければいけない属性情報に違いが出てくるため、SPごとに実装内容は分かれてくるところになるかと思います。 Safie ManagerとSSO SAML認証を行うには事前にSP・IdPそれぞれに認証エンドポイントや公開鍵などを登録し合うことが必要と書きましたが、私たちが提供しているサービス「Safie Manager」ではどのような画面になっているのでしょうか。 見てみたいと思います。 ほとんど黒で塗りつぶしてしまっていてすみません。 「IdPへの登録情報」という欄に書かれている情報を、IdPに登録するための情報として表示してあります。 逆に、IdP側の情報をSafieManagerに登録する欄として「IdP情報」という項目を設けてありますので、「ログインページのURL」「識別子のURL」「証明書」を登録することで、SafieManager(SP)側の準備は完了です。 今度はIdP側の情報を登録する箇所を見てみましょう。 ここでは例としてAzure Active Directoryを取り扱います。 基本的なSAML構成という箇所にSPの情報を入力し、 {アプリ名}のセットアップ(ここではsafie-manager-sso-testという名前)にSP側に登録する情報が乗っており、証明書もダウンロードできます。 まとめ 今回はSAML認証についての解説をさせていただきました。 SAML認証はかなり複雑な内容で、実装する際にはまず概念の理解に時間がかかってしまうため、この記事が理解の助けになれば幸いです。 サービスにSSO機能を組み込む際は、SAML認証を経た後、SP側のユーザーデータに対して新規登録や認証を行う処理につながってきます。その辺りはSPの既存の認証基盤の仕様によって実装の勘所が分かれる箇所だと思っています。その辺りにも試行錯誤ポイントがあるため、SSO機能の実装を予定されている方は、基本設計・詳細設計の時間を長めに取られることをおすすめします。 ここまで読んでいただきありがとうございました!
アバター
プロダクト基盤開発グループデバイスコアチーム、 組み込みエンジニア の楊(よう)です。 セーフィーのプロダクトというと、「 カメラ 」というイメージを持つ方も多いかもしれません。 そんなカメラを始めとしたデバイスの開発は、組み込みエンジニアが担当しています。 今回は 組み込みエンジニアの業務内容やカメラ開発の中身 をご紹介したいと思います。 セーフィー組み込みエンジニアの業務 Safieカメラ・デバイスの構成 Safie Client SDKの開発 カメラ・デバイスの開発 自社カメラの開発 他社市販カメラのSafie化開発 Safie Client SDKを外部へ提供 Safieエッジアプリやそのプラットフォームの開発 最後に セーフィー組み込みエンジニアの業務 以前、「 商品開発の歴史 」の記事でもご紹介したように、セーフィーでは創業以来さまざまなカメラとデバイスを開発してきました。 デバイス開発をメインで担当する組み込みエンジニアの業務・開発内容は、採用サイトにも記載があるように、主にこちらの6つになります。 カメラ・デバイス開発 Safie Client SDKの開発 エッジアプリプラットフォームの開発 CI/CD環境や開発環境の整備と改善 カメラ設定用各種ツール開発 カメラユーザーの問い合わせ・障害対応 今回は、上記のうち「 カメラ・デバイス開発 」、「 Safie Client SDKの開発 」、「 エッジアプリプラットフォームの開発 」にフォーカスし、Safieカメラの構成と併せて説明します。 Safieカメラ・デバイスの構成 セーフィーでは SafieClient という実行ファイルを各カメラ/デバイス向けにビルドして搭載しています。 当初、SafieClientはカメラの制御/映像の送信を担うだけでしたが、現在ではSafieエッジアプリを管理する機能も実装され、Safieプラットフォームの一部としての機能を日々進化させています。 SafieClientは大まかに下記三つのモジュールによって構成されています。 Safie Client SDK :サーバへの映像/音声の送信、サーバからの命令処理などSafieClientのコア機能がここに集約されています。 Camera I/F :各カメラ/デバイスの固有実装への対応を担います。映像/音声データを取得してSafie Client SDKに渡したり、Safie Client SDKからの命令に応じてカメラ/デバイスの制御を行ったりします。 SafieエッジアプリやEdgeAppFramework :Safieエッジアプリがセーフィー対応のカメラ/デバイスに配布可能なエッジアプリになります。EdgeAppFrameworkがSafieエッジアプリの動作管理を担っており、必要な一連の開発環境も含まれています。現在開発中のAIカメラ用のエッジアプリもこちらの仕組みを利用して開発しています。(厳密にはSafieClientとは別のプログラムですが、SafieClientによって制御される付随したモジュールになります。) Safie Client SDKの開発 Safie Client SDK の基本的な役割は、Safieクラウドとの接続管理と各カメラ/デバイス全体の管理です。そして、通信状態が悪くても映像を視聴できるように、また通信状態が復帰した時に映像をリカバーできるように色々工夫しています。また、Safie Client SDKはSafieが対応する全てのカメラに共通で使われているので、どのカメラでも動くよう汎用性のある設計・実装にする必要があります。 Safie Client SDKの機能を項目としてまとめると以下の通りです。 詳細の説明は割愛しますが、イメージをつかんでいただければ幸いです。 ・デバイスのクラウド接続時の自動認証 ・Safieクラウドとの安全・安定な接続・再接続 ・カメラの制御、カメラの状態管理 ・カメラの動画ストリームやイベント、通知のアップロード ・通信状態が悪い時のビットレート調整 ・通信切断状態の映像のバックアップと後のリカバリ ・LogやDebug情報のアップロード ・SafieClientまたは各カメラ/デバイスのシステムFW更新 ・WebRTCのシグナリング ・Safieエッジアプリの管理 また、カメラの数やユースケースの増加と共に、以下のような機能追加・改善を検討しています。 ・よりスマートなビットレート調整 ・通信状態によらず録画欠損の無いバックアップと後のリカバリ ・設置環境やニーズに合わせたハイブリッド映像配信(デバイス配信/サーバー配信) ・プラットフォーム化に伴うセキュリティ対策 ・Androidなど汎用プラットフォームへの対応 そして、今まではプロダクトを市場に出すことを優先してきたため、技術負債が溜まっています。 今後の市場拡大を考慮すると、より品質を担保するためにも負債を解消しなければなりません。 現在は、CI/CD環境の整備やエミュレーション環境の構築などを通して、負債の解消を目指しています。 カメラ・デバイスの開発 Safie Client SDKがSafieの共通機能をまとめて対応しているので、新規カメラ・デバイスの開発・対応が基本 Camera I/F でカメラ固有機能の開発になります。基本的に以下3つのパターンに分類されます。 自社製カメラの開発 他社市販カメラのSafie化開発 Safie Client SDKを外部へ提供 自社カメラの開発 自社製の代表的なカメラと言えば、 CC-2シリーズ 、 Safie Pocket2 、現在開発中のエッジAIカメラになります。 自社製と言ってもセーフィーでは、ハードウェアも含めて完全に社内で開発する訳ではなく、カメラ製品開発能力があるハードウェアベンダーとの協力開発になります。ハードウェアおよびOSなどのシステム周りの開発と製造を先方にしてもらって、セーフィーがその上位にあたるカメラ全体の制御や通信部分の開発を行うのが一般的です。 ただ、製品の開発元として全体を見通した工程管理を行う必要があるので、場合によってドライバレベルのデバッグや試作もハードウェアベンダーと一緒にやる必要があります。また、新しいハードウェアベンダーにもセーフィーにもノウハウがない技術の開発をする際は、我々組み込みエンジニアが先陣を切って勉強するところから始めなければなりません。これからは新しいカメラを開発する機会も増えてくるので、5G、手ぶれ補正、位置推測など技術的な挑戦をする機会がたくさんあります。 また、これからは事業の拡大とともに、理想的な製品や機能の開発、より安くて高い品質を保つためにも、当社の組み込みエンジニアがソフトウェア開発だけではなく、ハードウェアを設計する技術や生産管理のノウハウも積極的に貯めて守備範囲を拡げていく必要があります。 他社市販カメラのSafie化開発 「 商品開発の歴史 」の記事でもご紹介したように、セーフィーではユーザーぞれぞれの利用用途、設置環境のご要望に応えるため、またより多くの販路やパートナーを巻き込むため、自社製のカメラだけではなくトップメーカーであるAxis Communications社やVivotek社を始めとした、他社製市販のセキュリティカメラのSafie対応を行っています。 他社市販カメラはRTSPやONVIFといった映像を取得したり制御したりするために規格化されたインターフェースを持っていることが多く、さらにAxis Communications社のACAPやVivotek社のVADPのようなアプリケーションプラットフォーム機能を持っているカメラもあります。他社市販カメラのSafie対応は基本はRTSP /ONVIFあるいはそのカメラ特有のアプリケーションプラットフォーム機能を使うCameraI/F開発になります。 そして、各種他社市販カメラは基本的にはLinuxベースのデバイスになりますが、ToolchainやSDKがそれぞれ異なっているので、ビルド環境の整備や再現が大変です。今はDockerでカメラToolchainを管理するなど色々工夫をしています。 Safie化している他社市販カメラは、AXIS社とVivotek社のカメラだけで200機種以上、機能や仕様も様々です。多くの機種に対応することによって、Safieがカバーできる利用用途や設置環境はかなり増えますが、その分サポート対応が煩雑になります。 Safie Client SDKを外部へ提供 Safie対応のカメラはすべて自社で開発したわけではありません。条件次第で、積極的に Safie Client SDK を開発パートナーへ提供して共同開発をしています。当然、技術的なサポートは組み込みエンジニアから行います。 Safie対応カメラを増やしていく為には、様々な開発パートナーにより広くカメラ開発を行って頂くのが理想的ですが、今はまだSafie Client SDKが使いにくいなどの課題があるので、偶に密なコミュニケションしないと開発が進まないケースや自社で開発した方が早いケースがあります。なので、これからはSafie Client SDKをより使いやすくし、今後いずれオープンソース化などしてより誰でも使えるようにしていきたいと思います。 Safieエッジアプリやそのプラットフォームの開発 昨今ではカメラ(エッジ側)で処理できることが増えています。そのため多くのカメラをSafie化したとしても、機能追加できるのが自社のエンジニアだけになってしまうのはとてももったいないと考えています。 そのため、セーフィーではテックパートナーと協創できるプラットフォームの実現を目指しており、その一環として SafieEdgeAppFramework を開発。現在はエッジAIカメラ用のAIエッジアプリやWebRTC音声通話機能の開発環境として使っています。 SafieEdgeAppFrameworkは、SafieデバイスにデプロイできるSafieエッジアプリを動かす・開発するためのフレームワークになります。Safieエッジアプリを開発するためのツールとしては、以下の一連機能を提供しています。 ・アプリの自動生成 ・アプリのビルド・構築 ・単体テスト環境 ・実機デバッグ環境 ・アプリの登録・デプロイ そして、Safieエッジアプリの実装に関しては、以下の機能・ライブラリーを用意。カメラから映像などの情報を取得して解析をしたり SafieクラウドとRPC通信で連携するエッジアプリを作ることができます (現状Safieエッジアプリの実装は C++ だけサポートしています)。 ・アプリのリモート更新 ・アプリのリソース(推論モデルなど)のリモート更新 ・Safieクラウドからのアプリ設定 ・RPC通信(Safieクラウドから、他のSafieエッジアプリから、WebRTCなどのP2P通信から) ・イベントをSafieクラウドに送信・記録 ・カメラの映像・イベントを購読 ・カメラ制御・Overlay設定など ・3rd Party Libs(TensorflowLite、OpenCV、Dlibなど)、デバイス固有のSDK(SNPEなど) Safieエッジアプリの構成概要とSafieクラウド/カメラとの動作概要の全体像については、下図になります。 基本アプリのライフサイクルコールバックI/Fを実装し、上記機能・ライブラリーを使ってアプリの機能を実装すればアプリが出来上がるイメージです。 現状SafieEdgeAppFrameworkは社内のみの利用で、まだまだブラッシュアップ中です。完成度が上がり対応するカメラなどの条件が揃い次第、テックパートナーに公開していきたいと考えています。 ちなみに、SafieエッジアプリはWebRTC経由や Safie API 経由でも通信できる構想(妄想?)があります。例えば、顔検出のSafieエッジアプリを作って、Web側で下記のようなjavascriptコードで顔の検出通知をsubscribeしたり、検出したい顔の特徴をSafieエッジアプリに設定したりすることもできるかもしれません。 そうなると、Safie開発アカウントを持っていれば、Safieカメラと連携するフルスタック開発ができるので、面白いかもしれません。 // この顔見つけたら教えてね camera. edgeapp ( "FaceDetector" ) . subscribe (“FaceDetectedEvent”, (event) => { console. log ( "people in:" , event.face_name) }) camera. edgeapp ( "FaceDetector" ) . request (“StartFaceDetected”, face_data) 最後に ここまでで、多少理想語りも含めたセーフィーの組み込みエンジニアの業務・開発内容を紹介をしてみましたが、イメージは少し湧きましたでしょうか。 組み込みエンジニアの全ての努力は、Safieのカメラ・デバイスや開発環境を、より安全・安定でより手軽にインフラのように、顧客や共に開発するパートナーの身近に届けるためです。 そして、理想を語りながら、顧客ニーズや現実を見て調整し、確実に泥臭い現実課題を解決していくのがセーフィーの組み込みエンジニアの姿になると思います。 現在、一緒に働く組み込みエンジニアを募集しています。セーフィーのビジョンや組み込み、エンジニアの開発内容に共感・興味を持って頂けるようであれば、是非ともご連絡ください! 募集職種一覧/セーフィー株式会社
アバター
初めまして。セーフィーの伊原と申します。 今年で社会人3年目で、セーフィーには2021年11月に入社いたしました。 現在はビジネスユニット1にてフロントエンド開発を主に担当しています。 ビジネスユニット1は主に飲食・小売業のDX実現を目的とした部署で、多店舗経営者向けのクラウドカメラ管理アプリケーションや、店舗内の人を検知するAIを搭載したクラウドカメラの開発を行っています。 今回は私が所属しているフロントエンドチームにて実施しているスクラム開発についてご紹介いたします。 私にとってスクラム開発はセーフィーが初めての経験で、当初は慣れない事も多かったですがとても良い取り組みだと感じています。 本記事では初めにスクラムの簡単な説明をした後、開発現場で実践している開発フローについてご紹介いたします。その後、私が考えるスクラムの良さについて所感を述べます。 スクラム開発の概要 Safieのフロントエンドの開発フロー スクラム開発のメリット 最後に スクラム開発の概要 まず初めに、スクラム開発を知らない方に向けて簡単にスクラム開発の説明をいたします。 スクラム開発とは、スクラムと呼ばれるフレームワークを活用したソフトウェア開発のことです。チーム毎にスプリントと呼ばれる一定の期間を指定し、次の図の流れを繰り返すことでプロダクトのゴールを目指していきます。 ①バックログリファインメント プロダクトに必要なものを洗い出し、プロダクトバックログと呼ぶ一覧にします。新規機能の追加、機能改修など様々なものをまとめるため、抽象的な内容も含みます。 これはスプリント初めに行うのみでなく、適宜開催することもあります。 ②スプリントプランニング プロダクトバックログの内容をタスクレベルまで落とし込み、1スプリントの間に実施するタスク一覧に抽出します。ここで抽出されたものをスプリントバックログと呼びます。 選定の際には、チーム全員でタスクの作業量を見積もり、スプリント内で終わる現実的な量を抽出します。 ③スプリント期間 各メンバーにタスクを割り振り、開発を進めます。 タスクは最初に全て割り振ってしまうのではなく、メンバーが自主的にスプリントバックログから取得します。タスクが完了次第、新しいタスクを取っていく形です。 期間内はタスクの遂行を円滑にするためにデイリースクラムと呼ばれる集まりを毎日実施します。 スプリントの終了まで③を繰り返します。 ④スプリントレビュー 開発期間の終了時に、以下の2点を評価します。 期間内で完了したタスク量の算出:チームの生産性の計算 良かった点・改善点の洗い出し 評価の内容を踏まえ、再び②に戻ってサイクルを回します。 一連のスプリントを繰り返すことで徐々にプロダクトと開発フローを発展・洗練させていくのがスクラム開発です。 Safieのフロントエンドの開発フロー 私のチームにて実際に取り組んでいる開発フローを前述した図の①〜④に当てはめてご紹介します。 チームでは1スプリントを2週間として設定しています。 ①バックログリファインメント スプリントの初めに1度実施しており、本イベント後の同日に②のスプリントプランニングも実施しています。 ここでは開発チームメンバーで集まり、プロダクトに必要な機能を一覧化します。 タスクの進捗管理にはWrikeというツールを使っており、タスクを次の段階に分け、ボードで管理しています。 Backlog: プロダクトバックログ ToDo: スプリントバックログ Doing: 遂行中 Dev Review: レビュー中 QA Review: QAチーム確認中 Done: 完了したタスク Closed: スプリント中に完了したタスクの一覧 それぞれのリストにあるタスクは移動させられるようになっており、各自が自分の持っているタスクを段階に応じて移動させます。Doneまで移動させたらタスクは完了となります。 DoneからClosedへの移動は後述するデイリースクラムにて実施します。 ②スプリントプランニング 前回のスプリントにて完了したタスク量を目安に、スプリントバックログを作成します。 必要があれば、機能をタスクの粒度まで落とし込みます。例えばユーザ登録機能が必要な場合、「ユーザ登録」という機能単位ではなく、「画面開発」・「API連携」という単位に分割するような形です。 ここでは機能の優先度のみでなく、企画やサーバサイドの進捗状況に合わせて柔軟にタスクを選定するようにしています。 スプリントバックログの作成後、積んだタスクの見積もりをチーム全員で行います。見積もりはストーリーポイントで数値付けをしています。 ストーリーポイントとは相対見積の指標で、「このタスクだったらどれぐらいかかりそうか」の規模感をこれまでのタスクから考慮して設定します。ここで重要なのは、何人日・人月といった絶対値で見積もりをしないことです。 ストーリーポイントの決定にはプランニングポーカーという方法を採用しており、これは各メンバーの見積もった数値を一斉に開示するやり方です。 見積もりの数値はフィボナッチ数列から選択します。開示した際にメンバー間で見積もりが一致しなかった際には、互いに想定した理由を伝え合って議論します。ここで重要なのは、見積もり内容にメンバー全員が納得する事です。 見積もった結果として想定のタスク量との差が大きかった場合には、プロダクトバックログを再度見直してタスク量を調整します。 ③スプリント期間 スプリント期間内は毎朝15分程度のデイリースクラムを実施し、以下の3点をチームで共有しています。 前日にやったこと 本日やること 困っていること 報告の際にはWrikeのボードを画面共有し、各自が持っているタスクの総量を全員が把握できるように進めます。「本日やること」の報告の際に新しいタスクをスプリントバックログから取っていくのですが、この際にもタスク内容を宣言しておく事で、今後どういったタスクに取り組む予定かをチーム全員が知っているようにします。 また、WrikeにてDoneとなっているタスクを確認し、本当に完了で問題ないかをチームメンバー全員で確認するようにしています。これによって、本人が気付かないタスク漏れ等を防止しています。 ④スプリントレビュー スプリント中に完了したチケットを整理し、チームのベロシティを算出します。 また、以下4点に関してスプリント区間内の振り返りを行い、次のスプリントの改善を目指します。 Good: 良かった点 Problem: 課題点 Try: 改善策 Keep: 次回以降も続ける試み スクラム開発のメリット ここで、私がスクラム開発を実際に体験して良かったことを3つに分けて説明いたします。 1. 属人化の軽減 1つ目は、属人化(ノウハウが共有されない状態)を防ぐことができる点です。 スクラムではチームメンバー間でやっていることが共有されるため、各メンバーのタスクの全容が透明化されます。Aさんでないと/Bさんでないと分からない、というような事がなくなると共に、メンバー間での質問やアドバイス等が行いやすい環境が促進されます。 スクラム開発における最大のメリットはこの点であると私は考えています。 設計方針や実装方針の良し悪しを開発者間で積極的に話し合う事ができ、技術者として成長できる環境が整っていると感じました。 2. メンバーの負荷状況の可視化 「属人化の軽減」の項で透明化について述べましたが、透明化によってチーム内の負荷がかかっているポイントが明確になること、それが2つ目の良い点です。 デイリースクラムで困りごとを共有する機会を作ることで、課題に個人ではなくチームとして取り組む流れを自然に作り出す事ができます。また、手が空いている人が他の人のタスクを巻き取るなどの融通も効きやすいです。 3. 見積もり能力の向上 全てのタスクに見積もりが行われるため、実際に業務を遂行する中で予想より短い・多かったという事が必ず発生します。そこで「なぜ想定と異なったのか?」を考えて共有することで、チーム全体でタスクに対する見積もりの感覚が向上していきます。 ストーリーポイントを付ける際に他の方の意見を聞く機会も多く、見識が広まりやすいのも利点です。 また、チームがどの程度のタスク量をこなすことができるかも明確になるため、開発スケジュールに無理が無いかを早めに判断することができます。 最後に セーフィーでスクラム開発を経験してみて、開発を健全に進める上でチームに必要な要素を自然に促してくれるような、良い開発体制であると感じています。 各メンバーの自主性は求められますが、自主的に動く事の重要性はかなり大きいので、それも含めて成長する必要があると思っています。 セーフィーは厳格な上下関係が無い事もスクラム開発との相性がよく、開発として社内のコミュニケーションを取りやすく、開発していてとても楽しいです。 本記事で少しでもセーフィーの開発フローの良さがお伝えできていれば幸いです。 読んで頂きありがとうございました🙇‍♂️ 本記事の執筆に当たり、次のスクラムの解説記事を参考にさせていただきました。 詳細を理解する上で非常に勉強になる内容でした。 qiita.com
アバター
こんにちは! セーフィー株式会社 業務システム部でエンジニアをしている大林と申します。 業務システム部とは、その名の通り 業務系システムの開発・運用 を行なっている部署です。 以前の 職種別エンジニア紹介の記事 における「業務システムエンジニア」が所属しています。 ということで今回は、セーフィーのサービスそのものについてのお話ではなく、セーフィーのサービスを支える業務システムのお話です。 業務システムの全体像 業務フローに沿って紹介 1. 商談作成 2. 出荷 3. 売上作成 4. 請求書発行 DataSpider Cloud これから 業務システムの全体像 セーフィーはSaaSビジネスですが、 単純なサブスクリプション契約だけではなく、カメラや付属品などの「モノ」も取り扱う ことが業務システムにおける特徴です。 今回の記事では、セーフィーのカメラ・サービスをお客様に使っていただくまで・使っていただいている間、どのように業務システムが関わっているかの全体像についてご紹介します。 簡単にまとめた全体像が以下の図です。 セーフィーでは、2019年に、クラウド型のSFA(営業管理)・CRM(顧客管理)のプラットフォームであるSalesforceを導入して以降、現在は Salesforceを中心として各種システムと連携 しています。 数字は以下で説明する業務フローの流れと対応しています 業務フローに沿って紹介 ここからは実際にセーフィー社内のオペレーションがどのような流れで行われているかを、業務フローに沿ってご紹介します。 実際はマーケティング活動にもSalesforce等のシステムが使われているのですが、そちらはマーケティング部が主に管轄しているので今回の記事では割愛します。 以下が業務フローの大きな流れです。ここではカメラの販売商流の例でご紹介します。 Salesforceの商談を作成し注文情報を入力する 商談情報を元にカメラを出荷・キッティングする 出荷(もしくは工事)完了後に売上データを作成する 売上データを元に請求書を発行する 1つずつもう少し詳細に見ていきましょう。 1. 商談作成 お客様がセーフィーのカメラを購入するには、 ECサイト からの直接購入や、お問い合わせフォームからご連絡いただくなどしてセーフィーの営業担当者とやりとりの上でご購入いただくなど複数の経路がありますが、 注文情報はすべてSalesforceの商談で管理 されています。 ECサイトから購入する場合は、見積情報や受注情報がSalesforceの商談に自動連携されるようになっており、その連携部分はEAIツールであるDataSpider Cloud(詳しくは後述)を用いています。 営業担当者を通して購入する場合は、まず営業担当者がSalesforceで商談を作成します。そこから必要に応じて見積書を作成し、受注の運びとなった際には申込フォームを発行します。そしてお客様が申込フォームに入力し提出いただいた内容が、商談に自動で反映されるようになっています。 この段階で、 カメラの機種と録画プランの組み合わせや、カメラにひもづくオーナーアカウント、納品希望日・送付先などが管理 されます。 カメラとプランやオプションの組み合わせを登録する画面 2. 出荷 作成した商談情報を元に、出荷に必要な情報を作成します。 Salesforceのレポート機能を用いて、納品希望日やカメラの機種・台数・送付先などを倉庫での出荷担当者と連携しています。 出荷した後には、倉庫の出荷担当者が、出荷したカメラのシリアルナンバーをSalesforceに連携し、 どのカメラにどのプランやオプションが紐づいているか(キッティング情報)を管理 します。 カメラのキッティング情報の一例 この段階で、 Salesforceに格納されたキッティング情報をセーフィーのPlatformサーバーにもAPI連携 します。ここでいうPlatformサーバーとは、セーフィーのサービスやユーザー情報を管理しているサーバーです。 この連携を行うことで、お客様の元にカメラが届いた時点でスムーズに利用開始できるようにしています。 また、当日出荷した商談について、出荷のお知らせメールをお客様へ自動配信したりもしています。 Salesforce画面のボタンからキッティング情報を連携します (AgencyTool=Platformサーバーを利用した社内アプリケーションです) 3. 売上作成 出荷(工事を伴う商談の場合は工事)が完了すると、商談をクローズします。 この段階で、出荷や工事完了日に合わせた課金日が確定し、商談に設定された期間分の売上データが作成されます。 ここで作成された売上データを元に、経理などの経営管理部門が会社としての売上管理をしています。 ちなみに、現在ECサイトから購入した場合はクレジットカード決済が可能です。クレジットカードの決済情報は、月末時点の決済データを元にDataSpider Cloudを介してSalesforceに売上データを作成しています。 4. 請求書発行 毎月やお客様に合わせた請求書発行のタイミングで、売上データを元に請求データを作成します。 ここで作成された請求データを、セーフィーで利用している 請求管理ロボ というシステムに連携 し、請求書発行をします。 その後は請求管理ロボ上で入金消込をしたり、最終的に会計システムへ連携して会計処理をしたりします。 請求管理ロボ for Salesforceを利用しており、Salesforceで対象データを取得して連携します ここまでがカメラの販売商流のざっくりとした一連の流れの紹介でした。 今回紹介した内容以外にも、レンタル商流では、2020年に 従来のスプレッドシート管理からSalesforceでのシステム管理に移行 し、カメラの在庫管理なども行っています。 システム化以前は、いくつものファイルやシートに分かれ数式が絡み合っていたため、スプレッドシートがとてつもなく重くなっていたり、作業が属人化したりしていました。システム化によってオペレーションフローが整い、急速に成長するレンタル商流の管理にも対応できるようになりました。 DataSpider Cloud 大筋とは外れますが、せっかくの機会なので、1や3で出てきたDataSpider Cloudについて、もう少しだけご紹介します。 DataSpider Cloudは、種類の異なるシステムやアプリケーション間でデータのやり取りを行う際に、通常はプログラム開発、もしくは手作業でデータの移行や入力作業を行う処理を、GUIベースで作成することができるデータ連携サービスです。 (参考: ヘルプページ ) セーフィーでは、ECサイトからの連携以外にも複数箇所で利用しています。 例えば、 Webサイトのお問い合わせフォームからSalesforceへ連携しデータ作成 Excel形式の申込書を取り込んで商談を作成 AWSのS3に格納されているクレジットカード決済による入金データ(CSVファイル)を月次で取得して加工処理 などなどです。 GUIでの操作となるので、上記のような処理をノーコードで実現することができます。 このようなシステム連携を実現することによって、今までExcelファイルなどからシステムへ一つ一つ転記していた作業がなくなったり、Excel等での属人化した加工処理がなくなったりするなど、 オペレーションを効率化して手作業によるミスもなくす ことができるようになりました。 DataSpider 画面サンプル これから ここまででSalesforceを中心とした現在の業務フローをご紹介しましたが、現在の運用にも「不」があります。 キャンペーン施策などのたびにプログラム修正が必要となりSE対応 契約変更や契約更新が手作業 便宜上未来の売上データも作成しているため扱うデータが必要以上に多くなる などです。 今年は業務システム部総動員で、契約・請求管理システムの導入に取り組んでいます。 現在はSalesforceが中心となり業務システムの全体を担っていますが、今後はSalesforceはSFA(営業管理)の機能に寄った形にし、契約管理や売上・請求管理は新システムに移行したいと考えています。 柔軟なプライシング戦略や請求の自動化など、上記で挙げた不の解消に向けてプロジェクトが進行中です。 セーフィーのビジネスが拡大するにつれ、管理するデータ(顧客・カメラ・請求など)は膨大になっていきます。 社員が効率よくオペレーションができ、そしてお客様の満足度向上につながるように、 セーフィーの業務システムはこれからも進化していきます!
アバター