TECH PLAY

セーフィー株式会社

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

221

はじめに セーフィー株式会社の第3開発部 AIVisionグループで画像認識AIの開発エンジニアをしている土井 慎也です。 今回は、Segment Anything Model(SAM)というセグメンテーションモデルで、テキストプロンプトを使用してみたいと思います。 はじめに Segment Anything Modelとは Grounded-SAMとは 実践 環境構築 モデルダウンロード 画像とテキストプロンプト、閾値を指定 実行 出力結果 まとめ 最後に Segment Anything Modelとは Meta社(旧Facebook)が2023年4月に発表 公式ページ: https://segment-anything.com/ 公式コード: https://github.com/facebookresearch/segment-anything ゼロショットセグメンテーションモデル 未知の物体に対して、追加学習を必要としない 1100万枚の画像と10億以上のマスクを使用して学習 プロンプトにセグメンテーションを行いたい物体への点や矩形を指定 論文ではテキストをプロンプトとして使用している例があるが、コードは公開されていない 論文ではテキストをプロンプトとして使用している例があるが、コードは公開されていない! 今回、テキストプロンプトを可能にするために、 Grounded-Segment-Anything(Grounded-SAM) を代わりに使用する Grounded-SAMとは Grounded-DINOというテキストプロンプトが利用可能なゼロショット物体検出モデルとSAMを組み合わせたもの Grounded-DINOとSAMの二つのモデルを使うため大きく二つの処理に分かれます。 Grounded-DINO 画像と検出したい物体のテキストプロンプトを入力 検出結果として矩形を出力 SAM 画像と検出結果の矩形をプロンプトとして入力 セグメンテーション結果を出力 実践 環境構築 リポジトリをクローンする git clone git@github.com:IDEA-Research/Grounded-Segment-Anything.git リポジトリをクローン後、 Grounded-SAM: Installation を参考に環境にあったインストールを実行 モデルダウンロード cd Grounded-Segment-Anything wget https://dl.fbaipublicfiles.com/segment_anything/sam_vit_h_4b8939.pth wget https://github.com/IDEA-Research/GroundingDINO/releases/download/v0.1.0-alpha/groundingdino_swint_ogc.pth 画像とテキストプロンプト、閾値を指定 grounded_sam_simple_demo.py#L31-L35 ... SOURCE_IMAGE_PATH = "./assets/demo2.jpg" CLASSES = [ "The running dog" ] BOX_THRESHOLD = 0.25 TEXT_THRESHOLD = 0.25 NMS_THRESHOLD = 0.8 ... テキストプロンプトはドット(.)区切りで入力することで複数種類の検出が可能 実行 cd Grounded-Segment-Anything python grounded_sam_simple_demo.py 出力結果 The running dog Grounded-DINOの出力 © 2020 IDEA, Inc .  Apache-2.0 license .  Grounded-DINO+SAMの出力 © 2020 IDEA, Inc .  Apache-2.0 license .  Horse. Clouds. Grasses. Sky. Hill Grounded-DINOの出力 © 2020 IDEA, Inc .  Apache-2.0 license .  Grounded-DINO+SAMの出力 © 2020 IDEA, Inc .  Apache-2.0 license .  まとめ 今回、Grounded-SAMを利用することで自由なテキストプロンプトでSAMを利用できることを紹介しました。 この技術を使用することで、新たなデータセットを作成する際には、ある程度アノテーションを自動的に作成できるようになり、モデル改善などのスピードを上げることができると思います。実際に、アノテーションツールなどに組み込まれていたりします。 また、SAMの精度も十分な精度が出ているように思えますが、下記のようにさらに高精度なセグメンテーションができる SAM-HQ という物も発表されています。これもGrounded-SAMで使用することができるので、気になる方はぜひ使ってみてください。 SAMとSAM-HQの比較 © 2020 IDEA, Inc .  Apache-2.0 license .  © 2020 IDEA, Inc .  Apache-2.0 license .  最後に セーフィーではエンジニアを積極的に募集しています。気になる方はこちらをご覧ください! https://safie.co.jp/teams/engineering/ カジュアル面談から受け付けておりますので、気軽に応募いただければと思います! 最後までお読みいただき、ありがとうございました。
アバター
こんにちは。サーバサイドエンジニアの村田 ( @naofumimurata ) です。 本記事では、セーフィーのシステムでカメラ映像の録画機能を担うアプリケーションのデプロイを改善した話を共有したいと思います。 セーフィーの録画・配信システム カメラサーバのデプロイの課題 デプロイの流れ 実行環境 問題 時間がかかる 作業負荷が高い メンテナンス性が悪い 結果どういう状態になったか 改善に向けた取り組み GitHub Actions + AWS CodeDeployの構成に 監視の強化 成果 デプロイ時間の短縮 作業負荷の軽減 デプロイ頻度の向上 まとめ セーフィーの録画・配信システム セーフィーはクラウド防犯カメラ・録画サービスを提供しています。 バックエンドのシステムとしては、まずカメラから映像を常時受け取りストレージに保存するアプリケーション(本記事では以降「カメラサーバ」と呼びます)が稼働しています。カメラサーバでは映像の保存の他にもカメラの情報管理や制御などを行なっています。ユーザはSafie Viewerというフロントアプリケーション(Web/モバイル)を利用し、カメラサーバに入ってきた映像を別の動画配信アプリケーションを介して閲覧することができます。 カメラサーバはJava、動画配信アプリケーションはGolangで実装されています。 インフラ環境は全てAWSを利用しており、カメラサーバはEC2インスタンス上で稼働しています。現在は 約20万台以上 のカメラが接続しており、カメラサーバだけで 約2,000台以上 のEC2インスタンスが稼働しています。 インフラ構成の特徴的な点として、負荷分散のために水平分割の構成を取っています。一定カメラ台数(約13000台程度)ごとにそれを処理するためのインフラ構成(ネットワーク、DB、redis、サーバ類)一式を構築しています。社内ではこの水平分割の単位を「ゾーン」と呼んでおり、本記事でもこの用語を使います。 カメラサーバのデプロイの課題 セーフィーのシステムにおいてカメラサーバは特に重要な役割を担っていますが、そのデプロイには大きな課題がありました。ここでは具体的なデプロイの流れとどういった課題があったかについて紹介します。 デプロイの流れ カメラサーバのデプロイではざっくり以下のことを行う必要があります。 アプリケーションのビルド AMIのビルド ゴールデンイメージを用意しスケーリングで利用 jarファイルの配布 redis, DB上の接続、管理情報を更新 カメラサーバに対して停止リクエストを送る カメラサーバが担当しているデバイスを他のカメラサーバに移動させる プロセス再起動 改善前の構成 単純にビルド、成果物の配布、プロセス再起動だけではないステップ(2、3、5番)があるのが特徴的かと思います。 まず、アプリケーションのビルドを行います。カメラサーバはJavaで実装されているのでjarファイルを生成します。次にAMIのビルドを行います。カメラサーバはゴールデンイメージ運用を行なっており、jarファイルを含むAMIを用意しスケーリングで利用しています。スケーリングはEC2 Auto Scalingではなく自前でスケーリングを行うアプリケーションが別に存在するという特殊な構成になっているのですが、ここでは詳しい紹介は割愛します。生成したjarファイルをインスタンスに配布した後、プロセス再起動の前に幾つかやることがあります。 まず、redisとDBのデータ更新を行います。カメラサーバへのカメラ割り当て状況や、カメラの接続状態・割り当て履歴といった情報をredis、DBで管理しており他のバックエンドアプリケーションからも利用されています。デプロイ時にはそのカメラサーバをサービス全体から外す必要があるため、正しい情報に更新を行います。 次にカメラサーバに対してRPCでシャットダウンの要求を送ります。プロセス再起動前にこれを行う目的はカメラの接続品質向上のためです。カメラサーバはシャットダウンの要求を受け取ると担当しているカメラに対して他のカメラサーバへの再接続を指示します。全てのカメラが別のカメラサーバに移動するのを待った後にプロセスを終了させています。 無事にシャットダウン要求が処理された後にプロセスを再起動し新しいバージョンのアプリケーションが起動します。 実行環境 上記の作業は全てスクリプト化されており、シェルスクリプトとAnsibleとPythonスクリプトを組み合わせて実現されていました。スクリプトの実行はデプロイ作業用のEC2インスタンス上で手動で実行する必要がありました。 問題 さて、ここまで紹介してきたカメラサーバのデプロイ構成ですが、サービスの成長に伴い様々な問題が現れてきました。 時間がかかる まず一番大きな問題は、デプロイにもの凄く時間がかかるということでした。実際にどのくらい時間がかかっていたかというと、全てのカメラサーバのデプロイを完了するのに最短で 30時間強 かかっていました。 これには幾つかの要因があるのですが、まず根本的なところとして2000台を超えるインスタンスがあり対象が多いということがありますが、それに加えてスクリプトの作りの問題で並列度が著しく低いということがありました。 前述の通り、セーフィーの録画配信周りのシステムは「ゾーン」と呼ばれる単位で構築されており、デプロイもゾーン単位で行う必要があるのですが、1ゾーンずつ逐次でしか実行できないようになっていました。1ゾーン内では複数インスタンス並列で実行されるのですが、サービスの成長と共にゾーンの数が多くなったため全体として見るとかなり並列度が低いという状態となっていました。 1ゾーンで約2、3時間程度かかっており、ゾーン数が15を超えたあたりからトータルで30時間を超えるようになっていました。 作業負荷が高い 次にデプロイ作業を行うエンジニアの負荷がとても高いという問題がありました。まず、デプロイが自動化されておらず手動でデプロイスクリプトを実行する必要がありました。くわえて、スクリプトも実行したら放置で済むというわけではなく頻繁に途中で失敗するようになっており、それへの対応のため作業者がつきっきりで画面を見ている必要がありました。 デプロイスクリプトが途中で失敗する原因としては、前述の通りデプロイ時に接続カメラを別のカメラサーバに移動させる処理があり、これが結構時間がかかる(なかなか移動してくれないカメラがいたりする)ためでした。実際のところここの待ち時間はカメラ依存であるため仕方がないのですが、デプロイスクリプトの待ち時間の判定がシビアで頻繁にタイムアウトし失敗判定になるという状態になっていました。 途中で失敗してしまった場合は、エンジニアが実際にカメラサーバのインスタンスに入ってログを確認し問題ないかをチェックし、問題なければ再開するということを行う必要があり、頻繁に失敗することが作業負荷を著しくあげていました。 メンテナンス性が悪い 最後はデプロイの実行環境およびツールに関することで、メンテナンス性が悪いという問題がありました。 前述の通り、デプロイスクリプトはシェルスクリプトとAnsibleとPythonスクリプトが組み合わせてできているのですが、かなり複雑怪奇になっており何が実行されているのかを把握するのが難しい状態になっていました。特にPythonスクリプトの部分に関しては別のアプリケーション実装の機能を部分的に呼び出すような特殊な構成になっており、挙動を変えるには別のアプリケーションの修正を行う必要があるという状態になっていました。 結果どういう状態になったか このような問題によって、以下のような現象が発生するようになりました リリーススケジュールが後ろ倒しになる カメラサーバの変更を回避するようになる まずデプロイにもの凄く時間がかかるので、カメラサーバに変更を加えた場合それによってプロダクトのリリーススケジュールが大きく後ろ倒しになることがありました。最短で約30時間強かかると書きましたが、これは休まず寝ずに作業した場合の話で、実際にはエンジニアが日中にスケジュールが空いている時間で作業することになるので、デプロイしたいと思ってから実際に完了するまでもっと時間がかかります。金曜リリースを避けつつ、セーフィーでは本番環境へのデプロイは複数人体制で行うようにしているため、スケジュールを調整したりすると実際には2、3週間から長い場合1ヶ月程度かかっていました。 そしてもの凄く作業負荷が高いので、みんなカメラサーバのデプロイをやりたくなくなりました。その結果カメラサーバへの変更を避けるようになり、本来ならカメラサーバにあるべき機能などを別のところで実装しだし、カメラサーバへの変更を回避するようになりました。 改善に向けた取り組み こういった問題を解決するために、カメラサーバのデプロイ改善に取り組むことにしました。有志のメンバーでワーキンググループを組成し現状の調査から解決まで取り組みました。 GitHub Actions + AWS CodeDeployの構成に デプロイの構成を見直し、GitHub ActionsとAWS CodeDeployを利用した構成へとまるっと作り替えを行いました。ツールの選定理由としては、GitHub ActionsとAWS CodeDeployどちらも既に利用実績があったことが大きなポイントでした。 これまで作業用インスタンスで行っていたアプリケーションのビルドをGitHub Actions上で自動で行い、成果物の配布とアプリの再起動処理はCodeDeployのスクリプトとしてカメラサーバインスタンス上で実行するような構成にしました。 また、併せてカメラサーバのEC2インスタンスをEC2 Auto Scaling Group (ASG)の管理下に入れるように変更しました。これによりASGとCode Deployの連携機能でASGで起動したインスタンスにはCodeDeployの最新のリビジョンが自動デプロイされるようになったため、ゴールデンイメージが不要となりAMIをビルドするステップを消すことができました。 アプリケーションの終了・再起動に関連する処理はこれまでPythonスクリプトで行っていましたが、別アプリケーションの機能に密結合な実装となっており、これをカメラサーバインスタンス上に配置して実行できるようにするのは後々のメンテナンス性を考えて避けたかったため、機能を切り出したものをGolangで新たに実装しました。Golangの場合、バイナリを配置するだけで動かせるので、アプリケーションの成果物と一緒に配布し実行させる構成にしました。 また、問題だったデプロイスクリプトの失敗判定などもGolang実装に移行する段階で修正し、無駄に失敗判定にならないようにしました。 改善後の構成 監視の強化 これまではデプロイ時の正常性の確認は、作業者がEC2インスタンスに入ってログなどを目視で確認するというかなり温かみのあるやり方で行っていたため、作業負荷が非常に高いという問題がありました。 デプロイ構成を一新するにあたりここについても改善するべく、CodeDeployのデプロイスクリプトでの正常性確認ステップに加えて、メトリクス監視の強化を行いました。 セーフィーではPrometheusによるメトリクス監視の仕組みが既にありインスタンスレベルでの監視は行われていましたが、今回はアプリケーションにPrometheus Exporterを新たに実装、正常性を確認するためのメトリクスを追加し、問題があればアラートが発報し作業者が都度ログを確認する作業などが不要となるようにしました。 成果 試行錯誤の末、約1年ほど前に新たなデプロイ構成に移行が完了しました。 その結果、以下のような成果が得られました。 デプロイ時間の短縮 一番の大きな問題だったデプロイにかかる時間ですが、これは大幅に短縮できました。 これまでは最短約30時間強かかっていましたが、改善後は最短で 3時間強 程度でできるようになりました。新しい構成ではゾーン毎に並列で実行できるようになったため、ゾーン数によらず同時に全ゾーンに対してデプロイを行えるようになったところが大きなポイントでした。 作業負荷の軽減 作業負荷の問題についても大きく改善できました。これまでは、作業者が手動でデプロイを行い画面につきっきりで作業を行う必要がありましたが、新しい構成ではGitHubのイベントをトリガーに自動でデプロイが実行されるようになり、デプロイ中も基本的に放置しておけば良くなりました。 デプロイ頻度の向上 デプロイの頻度も大きく改善しました。セーフィーでは検証環境と本番環境の2つの環境があるのですが、検証環境では 9倍 程度、本番環境では 3倍 程度向上しました。 まとめ 本記事では、セーフィーのシステムで特に重要な役割を担うカメラサーバのデプロイの問題を改善した事例を共有させていただきました。大きな問題で改善に向けて様々な苦労がありましたが、無事にやり切ることができ、実際に改善効果も得られてよかったかなと思います。デプロイ構成の移行にあたっては互換性の維持を重視し、なるべく実行する処理は変えずに改善することを意識していたため、そもそもの処理の内容自体にまだまだ改善の余地が残っていると思っており、その辺りの根本的な改善も継続してやっていきたいと考えています。 さいごに、セーフィーではエンジニアを募集しております。ご興味が出た際はぜひご応募いただけたらと思います! https://safie.co.jp/teams/engineering/ 最後までお読みいただき、ありがとうございました。
アバター
はじめに セーフィー株式会社のVPoEを務めている谷口元信です。 2022年4月からVPoEとしてエンジニアの組織形成と運営に取り組んでいます。 今回はVPoEに何が求められるかについて私なりの解釈を伝えた上で、取り組んだ事と今後についてをお話ししたいと思います。 はじめに なぜVPoEが必要なのか? なぜVPoEになったのか? 取り組んだ事 取り組めた事 2022年度 育成 オンボーディングの体制強化 組織 プロダクト単位での開発体制の改善 採用 採用のプロセス改善 2023年度 育成 オンボーディングの体制強化 評価 スキルマップの可視化 組織 プロダクト単位での開発体制の改善 採用 採用のプロセス改善 エンジニア組織の認知度向上 今後について まとめ なぜVPoEが必要なのか? VPoEとはVice President of Engineeringの略ですが、直訳すると技術の副責任者となります。 CTOが最高技術責任者なので、そのサポートをする立ち位置としてVPoEが存在すると私は考えています。 海外のIT企業では2000年代初頭からVPoE職が生まれたと聞いています。 インターネットの普及が1990年代から広がっていくに伴って、インターネットを通じたサービスを提供する企業も増えていきました。 そういった企業がビジネスを拡大していくにあたって、企業内のエンジニア社員数も増えていき組織の管理が既存のやり方だけでは難しくなっていきました。 そのような状況の中でエンジニア組織に直接責任を持つ立場としてCTOが全ての責任を背負っていましたが、CTOに求められる責務が大きくなってきたので責任範囲を分けていく事が企業で求められていった結果、VPoE職が必要となった訳です。 CTO :技術を使った意思決定においての経営責任 VPoE :技術を使った意思決定を遂行する組織への責任 と分ける事で、CTOは技術に対する経営責任に集中して業務を進める事が出来るようになり、技術選定や選定した技術を使ったプロダクトのビジネス展開における企業としての意思決定速度を上げる所に貢献しやすくなります。 VPoEはエンジニア組織への責任を担う事で組織内のパフォーマンス向上や組織間の連携向上に集中する事が出来、経営で意思決定された方針を元に組織の機動力向上に貢献しやすくなります。 私はどちらも大事な役割だと思いますし、CTOとVPoEが相互に理解しながら仕事を進めていく事が組織を運営する中で意識しなければならないポイントだと思っています。 なぜVPoEになったのか? 広義でのVPoE職の役割については書かせてもらいましたが、続いてセーフィーで私がVPoE職についた経緯をお話しします。 私が入社したのは2019年4月ですが、2021年11月までは概ね下記のような職能別組織となっていました。 エンジニア数は50名程で、各技術領域をグループでまとめながら開発をしていく体制となっていました。 そして、2021年12月から事業部制組織に移行しました。 ここで大きな課題が生まれました。 3つの事業部とカスタマーサービス本部に職能単位のエンジニアを配置しましたが、各事業部でビジネス展開したいプロダクトがある中で各事業に絞り込んで開発を進めるだけのマンパワーが足りなかったのです。 一般的なSaaS企業だとフロントエンド、バックエンドのフルスタックエンジニアが入れば事業部で求められるプロダクトに対して一気通貫でコミットする事が可能です。 ただ、セーフィーの場合はフロントエンド、バックエンドに加えてネットワークカメラを主としたデバイス領域も一気通貫で見れるフルスタックエンジニアが圧倒的に不足していました。 その結果、毎月のように事業部間でエンジニアのアサイン調整をして開発を進める事となったわけです。 本来であれば事業部制に移行する事でそれぞれの事業領域を深堀しながらニーズに合ったプロダクトを提供してビジネス拡大のスピードを上げられる想定でしたが、逆にスピードが下がる結果となりました。 組織拡大をしていく中での痛みだと捉えていますが、その痛みを解決するために自ら手上げをして2022年4月からエンジニア組織の改善に対して取り組みを始めました。 改善するプロセスとしては、今までの組織で課題になっていた事を現状整理しつつステップに分けて組織変更をする案を作って実施しました。 組織変更した結果のあるべき姿として2022年4月に考えた組織は以下となります。 プロダクトに対してはバーチャルな組織で横断して向き合いながら、各技術領域における生産性を向上するための組織として考えました。 これは短期的な計画で1年以内にはこの形にするために2022年内に3回の組織変更をしてハレーションが起きないように進めました。 合わせて事業部と連携して開発を進めるにあたっての会議体を再設計し、情報共有が滞る事なく進められるようにもしました。 関連する部署とも合意を取りながら経営陣に組織変更に対して納得してもらって進める事は結構な労力と時間が必要でしたが、無事に進める事が出来ました。 2022年11月には以下の組織に変更しました。 領域が近い技術領域を部としてまとめながら、グループで開発のアサインを調整しやすい形に変えました。 過去にやりやすかった組織でのやり方に近い形に一先ずは変えつつ、横串で組織改善をするためにエンジニアリングオフィスも新設しました。 なので、2022年11月がエンジニア組織の再出発と私の中では定義しています。 取り組んだ事 再出発した中でVPoEとして取り組むべき事を育成、評価、組織、採用のカテゴリに分けて言語化しました。 やるべき事はたくさんありますが、エンジニアリングオフィスのメンバーを少しずつ増やしながら優先順位をつけて取り組む事としました。 取り組めた事 年度ごとに取り組めた事を洗い出しながら書いていきます。 2022年度 主には組織変更に伴う調整に多くの時間を割きました。また、採用プロセスの改善で応募から最終面接に至るまでのプロセス全体を短くするためにエンジニアの最終面接を私に委譲してもらいつつ採用グループとの連携に力を入れました。 育成 オンボーディングの体制強化 各グループで代表者を選出してもらい、それぞれのグループで現在使っているオンボーディング資料を洗い出しながら、必要に応じて足りてない要素を強化するための資料を追加していけるようにしました。 2023年4月から新卒1期生を受け入れるにあたって、内定承諾者のインターン受け入れを行ってエンジニアとしての開発体験をしてもらう設計・運用をしました。 組織 プロダクト単位での開発体制の改善 事業部制から開発本部にエンジニア組織を移行するために関係部署、経営陣と合意を取りながら進めて組織変更を実施しました。移行するにあたって生産性を向上していくための会議体設計をしながら業務への影響を減らしながら進めました。 採用 採用のプロセス改善 最終面接をCEOから委譲してもらい、選考におけるプロセスを効率化しました。 採用担当とデイリーミーティングを設けながら、候補者毎でのスケジュール調整を最適化するようにしました。 2023年度 育成 オンボーディングの体制強化 2022年度に引き続き、内定承諾者のインターン受け入れを行いました。2023年度はエンジニアリングオフィスのメンバーに教育を委譲し、再現性のある体制作りに努めました。 udemyを活用して技術力向上に励む事が出来る仕組みを作り始め、希望者が自学出来るようにしました。 評価 スキルマップの可視化 テックリードやエンジニアリングマネージャの職位を定義しながら、CTOや部長と共に職位に対するランクの定義を作り開発本部内で誰でも確認出来るようにしました。 組織 プロダクト単位での開発体制の改善 エンジニアリングオフィスのメンバーの採用を行い、エンジニアリングオフィスで取り組める事を増やせるようにしました。 現在の開発体制の現状整理と改善に向けての中長期の開発本部の体制を再設計しました。 採用 採用のプロセス改善 継続して採用担当とデイリーミーティングを設けながら、エンジニアリングオフィスのグループリーダーにも入ってもらい更なるプロセスの効率化を行いました。 エンジニア組織の認知度向上 セーフィーで初となるアイディアソンの企画・運営を行い、学生の方々にセーフィーを知ってもらいつつアイディアを形にするプロセスを学ぶきっかけ作りを提供しました。 技育祭に登壇し、ネットワークカメラの将来について技術周りも含めてプレゼンテーションを行いました。結果として初めて知った企業として登壇企業の中で1位となりました。 振り返ってみると、VPoEになってから総じて採用に時間をかけている事を実感しています。 本当であればもっと育成にも時間を使いたいところではありますが、開発業務における課題にも一定の時間を使っているので年単位で取り組む業務にフォーカスして取り組んでいかなければならないという自省もあります。 今後について 2023年度までに採用に多くの時間を割いてきましたが、2024年度は特に育成にフォーカスした改善を行っていきたいと思っています。 今いるエンジニアの成長が積み重ねる事で組織の成長にもつながると私は思っています。 私自身は20数年ほど開発現場で色々な開発をしてきましたが、私が成長していくにあたって必要だった事は出来る限り言語化しながら成長に対して必要な事を各自が考えて取捨選択して成長していける組織になる事を望んでいます。 まとめ 各企業には事情の異なる課題があるのでVPoEには臨機応変な課題解決能力が必要だと思っています。 エンジニアとしてプログラムに向き合って課題解決していく経験をされていた方は組織に向き合って課題解決していく所に置き換えて考えるとVPoEという職種が理解しやすいかもしれません。 プログラム以上に不確定要素は多くありますが、VPoEという職種は今後ますます必要となりますので、興味がある方がいましたら私の体験も踏まえてお話をしますので、ぜひご連絡下さい。 最後まで読んでいただき、ありがとうございました。
アバター
こんにちは。Safieの画像認識チームでサブグループリーダーをしている柏木です。 今回は私が主担当としてAIアプリ開発に携わった、 エッジAI搭載屋外向けクラウド録画カメラ について紹介します。 プレスリリースは こちら にあります。興味のある方はご覧ください。 エッジAI搭載屋外向けクラウドカメラについて 本製品は人物検出を行うAIアプリが搭載されたAIネットワークカメラで、i-PRO(アイプロ)株式会社(以下i-PRO)と共同開発したものです。i-PRO株式会社はパナソニックの技術を引き継いで2019年に設立した会社で、ネットワークカメラやセキュリティシステムの開発と販売を行っています。 今回i-PROのカメラにセーフィー独自のファームウェアおよびアプリを搭載することで、特別仕様のカメラの開発を行いました。 エッジAI搭載屋外向けクラウドカメラについて アプリ開発上の違い 実装上の工夫 独自データセットによる学習 終わりに 開発中のカメラ こちらのカメラにはAI-App 人数カウント(旧名: Store People Detection Pack)が搭載されています。AI-App 人数カウントはSafie Oneで搭載されていたアプリであり、カメラに映る人物の検知や、UI上で設定したラインを通過する人数のカウントを行うことができます。技術的な詳細については 過去のブログ にまとまっています。本アプリに興味がある方はご覧ください。 アプリ開発上の違い i-PROのカメラにおけるAI-App 人数カウントの開発は、Safie Oneで元々搭載していたアプリを移植した形になります。カメラが変わったことによるアプリの一番大きな違いは、Object Detection(物体検出)です。以下がアプリのシステム全体の構成です。 SafieOneとi-PRO、双方ともコードの大部分はC++で書かれており、これらに違いはあまりありません。特にTrackingとEvent Publishmentはほとんどそのままのコードで動作しました。 しかし、2つの機種ではカメラに搭載しているAIチップが異なります。SafieOneではQualcomm製のチップを搭載しており、 SNPE (Snapdragon Neural Processing Engine) SDK というSDKを利用することでチップでの推論が可能となっていました。一方、i-PROではambarella社のチップを搭載しており、i-PROの提供している独自SDKを利用して開発をする必要がありました。 SNPEとi-PROのSDKで、機能面での違いはそこまで大きくないです。しかし細かい仕様の違いは結構ありました。具体的は以下の面で違いがありました。 モデル変換ツール SNPEは独自形式への変換と量子化をステップごとに実行可能 i-PROは独自形式への変換と量子化がセット モデル読み込み SNPEはモデルをポインタで読み込む i-PROはパスを指定し、ファイルを読み込む 前処理の組み込み SNPEは前処理があまり用意されていない i-PROは一部前処理を組み込み可能 実装上の工夫 今回のアプリ開発はAI-App 人数カウントの移植だったため、実装上は大部分がSafie Oneのアプリと同じものでした。そこで、アプリのコードはi-PRO用に新規作成することはせず、SafieOneと同じコードで必要な箇所だけ分岐することにしました。 こうすることで保守コストを抑えることができます。例えばTrackingアルゴリズムの改修をする際はSafie Oneとi-PROの双方で適用することができます。 AI-App 人数カウントは、Make時にカメラ機種に応じてビルドタイプを変更してビルドしております。そこで機種で共通のIDetectorという検出用のInterfaceを作成し、機種専用のYoloV5がそれを継承する実装としました(今回の検出器ではYoloV5を用いています)。機種ごとに読み込むYoloV5のコードが格納されているディレクトリを分けることで実現していまます。 こうすることで、機種ごとの違いを意識せずにDetectorを扱うことができます。 classDiagram IDetector <|.. YoloV5_SafieOne IDetector <|.. YoloV5_iPro class IDetector{ + void Detect() } class YoloV5_SafieOne{ - void preprocess + void Detect() } class YoloV5_iPro{ - void preprocess + void Detect() } 独自データセットによる学習 学習モデルは屋外向けに再学習しました。屋内をターゲットとしているSafieOneと、屋外を対象としているi-PROでは、同じアプリでも検出するシーンが異なります。SafieOneでは屋内に特化したオリジナルデータセットを作成して学習していました。以下の画像のようなものをイメージしていただければと思います。 屋内データセットのイメージ 一方で屋外では以下の画像のようなイメージです。そこで、屋外においても独自に新しく画像を収集し、データセットを作成しました。屋外では背景が屋内と比較して乱雑でシーンが多様であるため、検出がやや難しくなります。今回は、本カメラが設置されるであろうシーンをいくつか選定し、それらのシーンに注力したデータセットを作成・学習しました。 屋外データセットのイメージ( APhoto より) 屋内データセット、屋外データセットそれぞれに対して、屋内用モデル・屋外用モデルで評価を行いました。PR曲線などの評価指標では大きな改善となりました。以下に結果を載せておきます。 屋内データセットに対するPR曲線 屋外データセットに対するPR曲線 屋内データセットでは大きな劣化はなく、屋外データセットにおける精度向上が見込めました。 もちろん評価ではPR曲線のような物体検出の評価だけではなく、設定したラインやエリアを通過する人数のカウント精度や人検知精度など、アプリのEnd-to-Endな評価も行っております。 今後は実際に製品を使っていただいたお客様の声や、データをもとにさらなる精度改善を行う予定です。 終わりに 屋外向けAIカメラの開発について紹介しました。複数のカメラを扱っている、セーフィーならではのEdgeAIの開発の話をしたつもりです。これをきっかけに興味を持っていただければ嬉しいです。 セーフィーではエンジニアを積極的に募集しています。どのような職種があるのか気になる方は こちら をご覧ください。カジュアル面談から受け付けておりますので、気軽に応募いただければと思います。
アバター
こんにちは。セーフィーでセキュリティマネジメント業務をしている川部です。 今回はサービス障害ハンドリングについて記載したいと思います。 サービス障害とセキュリティ?と疑問に思った方もいるかもしれません。 セキュリティの基本的な考えにあるCIAの可用性の観点から、セキュリティマネジメント業務の一環としてサービス障害発生時の取りまとめを担当しています。 主な業務はエンジニアと協力し、事象の詳細確認や影響するお客様の洗い出し、対外的な対応など多義にわたります。 今回はサービス障害ハンドリングにおける工夫という観点でお話したいと思いますのでSaaS企業で障害対応される皆さんや障害対応を任された皆さんの一助になれば幸いです。 セーフィーを取り巻く環境 スピード感をもって対応するための施策 サービス品質の維持管理するための施策 セーフィーを取り巻く環境 セーフィーでは現時点(2023年6月)で20.7万台のクラウドカメラが日々お客様の元で稼働している状況です。業界のシェア約56%であるため、広範囲な障害が発生した際は非常に多くのお客様に影響があるサービスです。 また当社の製品は様々な業界でご利用いただいており、防犯用途等で使用いただいてるお客様も少なくありません。 そのため、障害によってはお客様の目的が達成できないばかりか、お客様のビジネスにクリティカルな影響を与えてしまう可能性もあり、障害発生時の現場の緊張感は非常に高くなります。 一方で同じ商品でもBtoB向け、BtoC向けの商流があり、それぞれ障害対応時の観点も異なります。 BtoC向けであれば弊社のサポートを通して、お客様をサポートできるように社内への展開する情報を平準化しつつ、スムーズに情報展開する必要があります。 BtoB向けであれば、弊社がOEMとなり、パートナー企業様を通してサービスを提供しているため、お客様への情報展開が直販に比べ遅くなります。 そのため情報伝達のスピードが非常に重要になりますし、お客様の混乱を回避するため、パートナー企業様と発信内容を統一する必要があったりと気にすべきことはたくさんあります。 そのような背景の中、障害をハンドリングするわけですが、今回はハンドリングを行う上での大きな工夫2点をお伝えします。 スピード感をもって対応するための施策 一つ目は「スピード感をもって対応するための施策」です。 セーフィーでは障害報告が社内で上がった後、その障害の重要度を判断し、対応のスピードや対外報告の範囲を決定します。 内容によってはホームページや自社アプリ上で情報を展開する必要がありますし、影響範囲が小さければ個別に対応するなど重要度に応じてその後の工程を変えています。 この重要度判断に基づき、各部門が対応に向けて動き出します。なのでいち早く重要度を決定することがその後工程をスムーズにつなげるコツになります。 ただし、重要度を判断するには発生した障害事象を理解しながら影響範囲等を見定める必要があります。 一般的に判断と聞くと定量的な指標に基づき、判断するのだろうと思われるかと思います。もちろんそれも間違ってないです。 ただし障害はもともと想定していないもの(想定できるものは上流工程で潰されている前提ですが・・・)なので、発生事象によっては定量的な数字がスピーディーに取得できないものや定量的な指標で影響を判断できない事象等も考えられます。 そのため弊社では重要度判断基準として定量的、定性的な指標それぞれを設けて運用することでスムーズに判断できるようにしています。 サービス品質の維持管理するための施策 2つ目の工夫は障害対応が終わった後の話です。 障害発生後、暫定的に障害の封じ込めを行い、恒久対策を施し、より高い品質維持できるように工夫しています。 そのため非常にベタではありますが、障害を履歴として管理しており、遡って確認することはもちろん、発生した障害箇所により恒久的な対策やその日程感などを管理しています。 また障害の重要度に応じて対策に対しても各部門やCTOや経営層の確認を必須とする仕組みを整え、運用することで効率的かつサービス品質向上にもつながるように管理しています。 サービス障害はネガティブな事象と捉えることもできますが、サービスをより良いものにできるきっかけでもあるので、そういった意識をもってハンドリングにあたっています。 以上セーフィーにおけるサービス障害ハンドリングの工夫でした。 同じように障害対応で旗振りをしている方や障害対応について気になっていた方など何かの参考になればと思います。
アバター
​データ分析基盤グループでデータエンジニアをしている平川です。 DataVaultに関する記事の第3回目となります。 第3回の記事は、DataVaultモデリングをしている際に困った状態の対処方法についてまとめていきます 第1回: DataVaultってなに?どんな特徴があるの? 第2回: automate_dvを使ってDataVaultモデリングの中心となるテーブルを作ってみてわかったこと  第3回: BusinessVault、発展的なSatelliteテーブルやキーがNullだった場合の対処方法についてなど ← 今回はここ   前回のおさらい はじめに BusinessVaultとは StatusTrackingSatellite, EffectivitySatelliteの紹介 Status Tracking Satelliteとは Status Tracking Satelliteのテーブル構造 Effectivity Satelliteとは Effectivity Satelliteの構造 StatusTrackingSatelliteとEffectivity Satelliteの使い分け ゴーストレコード、ヌルキーとゼロキー ゴーストレコードとは ゼロキー(≒ ヌルキー)とは これらの概念を取り入れるメリット まとめ 参考資料 前回のおさらい 前回は、DataVaultモデリングの実装を簡易化するパッケージであるautomate_dvの紹介をさせていただきました。 特にDataVaultモデリングの中心となるHub/Link/Satelliteの生成について紹介させていただきました。 はじめに 今回の記事では、以下3つのトピックについて紹介させていただきます。 Hub/Link/Satelliteを利用する際に出くわす課題への対処法の1つとしてBusinessVaultについて 通常のSatelliteに追加機能を持たせたStatusTrackingSatelliteとEffectivitySatelliteについて ソーステーブルのキーの欠損などに対応するゼロキーやゴーストレコードについて BusinessVaultとは BusinessVaultとはRawVaultのデータを使いやすく整理し、特定のビジネスルールや変換処理を適用したモデルです。 この層は、RawVault層とユーザーが直接アクセスする層の間に位置しています。 このモデルを作るメリット・デメリットはいくつかあります。 メリット ビジネスロジックやデータ変換ルールを集約することで、Rawデータをルールに従った形で整理できます。これにより、分析が容易になることが考えられます。 多数のJOINや複雑な条件を含むクエリを事前に処理することで、パフォーマンス面での向上が見込めます デメリット BusinessVaultを生成する際にしばしば非正規化をするのでデータが冗長になります 適用するビジネスルールが頻繁に変わる場合、管理が困難になる可能性があります StatusTrackingSatellite, EffectivitySatelliteの紹介 Status Tracking Satelliteとは StatusTrackingSatelliteは、CDCが作用しない場合のデータの状態を追跡・管理する際にとても役に立ちます。 データソースでCDCが作用しない場合、ステータスの追跡が重要となり、ステータスの追跡をしない場合、いつレコードが消えたのかをSatelliteで把握するのは非常に困難です。 CDCが作用しない場合にはこのテーブルは非常に有用ですが、StatusTrackingSatelliteから必要なデータを取得するのは通常のSatelliteと比べるとSQLが複雑になります。 Status Tracking Satelliteのテーブル構造 Satelliteのテーブル構造とほぼ一緒ですが、record_statusというカラムを追加します。このカラムは名前の通りですが、レコードの状態を表し以下の3つのステータスを持ちます。 挿入("I"): 新しく追加されるレコードはrecord_status = 'I'としてレコードが追加されます。 更新("U"): 既存のレコードの属性情報が変わった際は、record_status = 'U'のレコードが追加されます 削除("D"): Satelliteテーブルには存在して、データソースに存在しないレコードには、record_status = 'D'としてレコードが追加されます Effectivity Satelliteとは Effectivity Satelliteはビジネスキー間の開始日と終了日を保持することで、有効期間を追跡することが可能になります。 Effectivity Satelliteの構造 StatusTrackingSatellite同様に、Satelliteテーブルが持つ基本的なカラムを持ちます。 Effectivity Satelliteには、開始日と終了日が追加されます。(automate-dvのeff_satではEFFECTIVE_FROMも追加されるようです) それぞれのカラムには以下のような値を格納します。 - 開始日: ビジネスキー間の関係性が開始した日付。 - 終了日: ビジネスキー間の関係性が終了した日付。関係性が続いている場合は、9999-12-31のように未来日が格納される。 StatusTrackingSatelliteとEffectivity Satelliteの使い分け データソースからレコードが削除された場合も、終了日を取っていれば対応できますが、StatusTrackingSatelliteとEffectivitySatelliteの違いは、追跡する対象が異なる点にあります。 前者はデータ自体の時間変化を追跡しますが、後者はビジネスキー同士の関係の有効期間を追跡します。 期間に関するデータを保持したい際には、EffectivitySatelliteが有用であると言えます。 例えば、EffectivitySatelliteを活用してキャンペーン情報と製品情報の関係性を保持することにより、売上情報との組み合わせで特定のキャンペーンがどのような影響を与えていたかを期間を絞って分析することが可能になります。 Effectivity Satelliteは終了日を更新する必要があるため、頻繁に終了日に値が入る場合はその都度updateされることになります。 両者で共通しているのは、データを抽出する際のクエリが複雑になる点です。(期間の指定やrecord_statusの絞り込みが必要になります) ゴーストレコード、ヌルキーとゼロキー ゴーストレコードとは データ(レコード)が存在しない場合に利用される仮のレコードです。このレコードを追加することで、各種モデルを結合するときのクエリを単純化することが可能になります。 ゼロキー(≒ ヌルキー)とは ビジネスキーが不明瞭であったり、欠落している場合に用いられる概念です。このキーを導入することで、データの品質を理解する手助けをし、品質改善の対策をする際に用いることが可能になります。 また、ゼロキーの概念を取り入れていない場合もしくはautomate-dvを使用してハッシュ化している場合、ハッシュ化したキーの値がnullになります。 これらの概念を取り入れるメリット データボルトモデリングをしているデータレイヤーの整合性や一貫性を保つことができます。 特にキーに欠落が発生する場合はゼロキーの概念を取り入れるべきだと思います。 ゴーストレコードについては、一時的にレコードが生成されていないという状況(例えば不具合等でデータが遅れて連携されるなど)であれば導入しなくても良いかなと思います。 まとめ 今回の記事では、DataVaultモデリングをする際に特定の場面で有効である概念の紹介をさせていただきました。 今回紹介したSatelliteを派生させたものや、キーなどがゴーストレコード、ゼロキー等である場合の対処方法は登場頻度は少ないかもしれないですが、データソースの状態や構造を直接的に変更することは難しいので、そのような時にこれらの概念を適用することでDataVaultモデリングの整合性を保ったままモデリングをすることが可能になります。 参考資料 The Data Vault Guru: a pragmatic guide on building a data vault automate_dvドキュメントページ
アバター
サーバーサイドエンジニアの三村です。 弊社では2024年の初めから国外へサービス展開をする準備として、一年ほど前からシステムの国際化対応を行ってきました。 この準備には、サービスの多言語対応や日本標準時以外のタイムゾーンでサービスが利用できるようにする改修などが含まれますが、サーバーチームでは特に後者に苦労しました。 そこでこの改修で得たPythonでタイムゾーンを扱う際の知見の一部を、このブログ記事にまとめます。 タイムゾーンについて タイムゾーンの基準 UTCオフセットだけでタイムゾーンを扱う問題点 サマータイム 特定タイムゾーンでのUTCオフセット変更 IANAのTime Zone Database (余談) Time Zone Databaseから読み取る世界の歴史 Pythonでの実装の注意点 pytzをdatetimeオブジェクトのコンストラクタに渡す pytzオブジェクトをdatetimeのコンストラクタに渡してもうまく動かない理由 zoneinfoファイルの「1901年問題」 strftime(%s)でのエポック秒変換 まとめ タイムゾーンについて Pythonでの実装の話に入る前に、まずはタイムゾーンの概念自体について説明します。 タイムゾーンの基準 世界中の各地域は異なる標準時間を用いていて、地域間に時差があることは言うまでもないと思います。 そしてこれらの異なる時間帯の基準となる時刻が存在することも有名かと思います。 こちらの基準時刻は、イギリスのグリニッジ天文台における時間を基に定められていると、昔学校で習った方も多いと思います。 経度0度の地点にある同天文台を基準時間とする考えは、GMT (Greenwich Mean Time)と呼ばれるものであり、現在こちらはUTC (Coordinated Universal Time)という概念に取って代わられています。 慣習的に同一のものと扱われることもある両者ですが、厳密には概念的、算出方法的な違いがあります。 GMTはグリニッジ天文台での地方平均時を表します。つまりGMTは特定地域での天体観測を基に算出される、ロンドンあたりを指す一つのタイムゾーンです。 比べてUTCは、天体観測だけではなく原子時計に基づく計算などから算出される基準時で、これ自体はどこかの地域のタイムゾーンではないです。地球上の地域の標準時を、この基準時との差分で表現するために存在しています。 UTCオフセットだけでタイムゾーンを扱う問題点 UTCのような基準となる時間の概念が存在すれば、それとのオフセット(基準との差異)のみを用いてシステムで世界中の時間を取り扱えそうだと一見思えます。しかし、実際はそんな甘くはないです。以下に、特定地域の人間の用いる時間の概念が単一のUTCオフセットでは表せないことの例を挙げます。 サマータイム 一部のタイムゾーンでは、一年を通して同じUTCオフセットの時間を用いているわけではなく、日照時間が長い時期にはサマータイムとして時間を早めることが行われています。 加えて、サマータイムの開始・終了時期も毎年一定とは限らず、頻繁に変更される地域もあります。 (例:1998年のブラジルでは、 サマータイムの開始日をローマ教皇の来訪時期とずらすため一日遅らせました。 ) 特定タイムゾーンでのUTCオフセット変更 歴史を遡れば、地球上の多くの地点で採用している標準時間の変更が行われてきました。 (例:サモアでは、 2011年にタイムゾーンをUTC-11からUTC+13に変更しました。 ) 後述する通り、このような標準時間の変更を経験した地域には、日本も含まれます。 IANAのTime Zone Database 上述の通り、人々が生活で用いている時間の概念は、季節的や歴史的な理由から単純なUTCオフセット一つでは表現しきれない場合があります。 そこで、このような一般的に使われるタイムゾーンを、季節的な変化や歴史的な経緯も包括してまとめているのが、Time Zone Databaseです。 www.iana.org Asia/Tokyo などのよく見かけるタイムゾーンの表記は、IANAによって編纂されているこちらのデータベースによって規定されています。 こちらのデータベースの情報は、プログラムからはいろいろな環境で利用できます。Unix likeなシステムであれば /usr/share/zoneinfo/ にバイナリ形式で入っているほか、Pythonの実行環境から参照したい場合は tzdataというモジュール を用いることでOSによらず利用できます。 こちらの中身を確認する一番簡単な方法は、上記のIANAのリンクを開き、データベースのファイルをダウンロードし解凍し開くことです。試しにニューヨークのタイムゾーン情報を開いてみると、以下のようになっています。 # Monthly Notices of the Royal Astronomical Society 44, 4 (1884-02-08), 208 # says that New York City Hall time was 3 minutes 58.4 seconds fast of # Eastern time (i.e., -4:56:01.6) just before the 1883 switch. # Rule NAME FROM TO - IN ON AT SAVE LETTER Rule NYC 1920 only - Mar lastSun 2 : 00 1 : 00 D Rule NYC 1920 only - Oct lastSun 2 : 00 0 S Rule NYC 1921 1966 - Apr lastSun 2 : 00 1 : 00 D Rule NYC 1921 1954 - Sep lastSun 2 : 00 0 S Rule NYC 1955 1966 - Oct lastSun 2 : 00 0 S # Zone NAME STDOFF RULES FORMAT [UNTIL] #STDOFF -4:56:01.6 Zone America/New_York - 4 : 56 : 02 - LMT 1883 Nov 18 17 :00u - 5 : 00 US E%sT 1920 - 5 : 00 NYC E%sT 1942 - 5 : 00 US E%sT 1946 - 5 : 00 NYC E%sT 1967 - 5 : 00 US E%sT 最初にコメントで、1883年の標準時間の変更までは東海岸時間より3分58.4秒早かったとする資料の紹介がされています。 その後過去のサマータイムの実施の歴史や、コメントで書かれていた1883年以前の時間の現在との時差についてが記述されています。 (余談) Time Zone Databaseから読み取る世界の歴史 上記のニューヨークの例の通り、Time Zone Databaseは単なる標準時の変化等を考慮した時刻変換のルールが記載されているのみでなく、豊富なコメントもあり読み物としても面白いです。日本の例を見てみます。 # From Paul Eggert (1995-03-06): # Today's _Asahi Evening News_ (page 4) reports that Japan had # daylight saving between 1948 and 1951, but "the system was discontinued # because the public believed it would lead to longer working hours." # From Mayumi Negishi in the 2005-08-10 Japan Times: # http://www.japantimes.co.jp/cgi-bin/getarticle.pl5?nn20050810f2.htm # Occupation authorities imposed daylight-saving time on Japan on # [1948-05-01].... But lack of prior debate and the execution of # daylight-saving time just three days after the bill was passed generated # deep hatred of the concept.... The Diet unceremoniously passed a bill to # dump the unpopular system in October 1951, less than a month after the San # Francisco Peace Treaty was signed. (A government poll in 1951 showed 53% # of the Japanese wanted to scrap daylight-saving time, as opposed to 30% who # wanted to keep it.) # -- 中略 -- # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule Japan 1948 only - May Sat>= 1 24 : 00 1 : 00 D Rule Japan 1948 1951 - Sep Sat>= 8 25 : 00 0 S Rule Japan 1949 only - Apr Sat>= 1 24 : 00 1 : 00 D Rule Japan 1950 1951 - May Sat>= 1 24 : 00 1 : 00 D # Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Asia/Tokyo 9 : 18 : 59 - LMT 1887 Dec 31 15 :00u 9 : 00 Japan J%sT コメント及び Rule の項には、日本が過去に実施していたサマータイムの情報が載っています。終戦直後日本ではGHQの指導によりタイムゾーンを導入していたようですが、人々はサマータイムによって労働時間を伸ばされるのではないかと不安になり大変不人気であったため、サンフランシスコ平和条約締結後日本が主権を回復すると、すぐにサマータイムは廃止されたそうです。 ちなみに Zone の項目にある通り、日本は1888年の正月より前には今より 18分59秒 早い標準時間を用いていました(東京の地方平均時)。 今までプログラムで日本時間を扱うコードを書いた際に、タイムゾーン取り扱いの不備で意図した時刻よりも 18分59秒 ずれた時刻となるバグを起こすなどしてこの時間を見たこともあるかと思いますが、この時間はここからきています(詳しい例は後述します)。 Pythonでの実装の注意点 やっとPythonの話に入ります。 Pythonでtimezone awareな形でdatetimeオブジェクトを取り扱う際に陥りそうな誤りを、いくつか挙げてみます。 pytzをdatetimeオブジェクトのコンストラクタに渡す Pythonにおけるタイムゾーン関連のよくある間違いでは一番有名な話かもしれませんが、 pytz 型のタイムゾーンをdatetimeオブジェクトのコンストラクタに渡すと、大抵の場合意図しないdatetimeオブジェクトが作成されます。 >>> import datetime >>> import pytz >>> datetime.datetime( 2023 , 8 , 25 , 12 , 0 , 0 , tzinfo=pytz.timezone( 'Asia/Tokyo' )).utcoffset() datetime.timedelta(seconds= 33540 ) # ↑9時間19分 上記のようなコードを書いた場合、実装者は大抵の場合UTCオフセットが9時間ちょうどのタイムゾーンのオブジェクトの生成を期待していると思いますが、実際には9時間19分となっています。 pytzはdatetimeオブジェクトのコンストラクタに渡されるような利用方法は想定されておらず(詳しくは後述)、このような利用方法ができないことが ドキュメントに記載されています。 This library differs from the documented Python API for tzinfo implementations; if you want to create local wallclock times you need to use the localize() method documented in this document. 上記引用にある通り、pytzでdatetimeオブジェクトにタイムゾーン情報を付与したい場合は、 localize() を用いるのが正解です。 >>> pytz.timezone( 'Asia/Tokyo' ).localize(datetime.datetime( 2023 , 8 , 25 , 12 , 0 , 0 )).utcoffset() datetime.timedelta(seconds= 32400 ) # ↑9時間ちょうど ちなみに、pytzをdatetimeオブジェクトのコンストラクタに渡す際のバグは上に例示しましたが、これはコンストラクタだけでなく replace() の引数として渡しても同様の事象は起こります。 >>> import datetime >>> import pytz # まずはtimezone unawareなdatetimeオブジェクトを作成 >>> d = datetime.datetime( 2023 , 8 , 25 , 12 , 0 , 0 ) >>> d datetime.datetime( 2023 , 8 , 25 , 12 , 0 ) # これにreplace()でtimezone情報をつけると、やはり19分ずれる >>> d.replace(tzinfo=pytz.timezone( 'Asia/Tokyo' )).utcoffset() datetime.timedelta(seconds= 33540 ) # ↑9時間19分 こちらの理由は単純で、datetimeクラスの replace() は内部で新規にオブジェクトの生成を行っていて、その際にコンストラクタの引数に replace() の引数をそのまま渡しているためです。 github.com def replace (self, year= None , month= None , day= None , hour= None , minute= None , second= None , microsecond= None , tzinfo= True , *, fold= None ): """Return a new datetime with new values for the specified fields.""" if year is None : year = self.year if month is None : month = self.month if day is None : day = self.day if hour is None : hour = self.hour if minute is None : minute = self.minute if second is None : second = self.second if microsecond is None : microsecond = self.microsecond if tzinfo is True : tzinfo = self.tzinfo if fold is None : fold = self.fold return type (self)(year, month, day, hour, minute, second, microsecond, tzinfo, fold=fold) 最終行で、新たにオブジェクトを作成しそのコンストラクタの中でtzinfoも渡しています。 pytzオブジェクトをdatetimeのコンストラクタに渡してもうまく動かない理由 pytzのこの挙動はPython界隈では結構有名ですが、なぜこのような挙動になっているのか説明しているブログ記事等は、(自分のなけなしのリサーチ能力だと)見当たりませんでした。そこで、少し自分で実装を調べてみました。 まず、Pythonのdatetimeのドキュメントには以下のような記述がありました。 docs.python.org For applications requiring aware objects, datetime and time objects have an optional time zone information attribute, tzinfo, that can be set to an instance of a subclass of the abstract tzinfo class. These tzinfo objects capture information about the offset from UTC time, the time zone name, and whether daylight saving time is in effect. Only one concrete tzinfo class, the timezone class, is supplied by the datetime module. The timezone class can represent simple timezones with fixed offsets from UTC, such as UTC itself or North American EST and EDT timezones. Supporting timezones at deeper levels of detail is up to the application. The rules for time adjustment across the world are more political than rational, change frequently, and there is no standard suitable for every application aside from UTC. Pythonの標準ライブラリであるdatetimeでは、タイムゾーン情報としては tzinfo という抽象クラス *1 と、その実装として timezone というクラスを用意していますが、こちらの実装はUTCオフセットが固定である前提となっています。UTCオフセットが歴史的経緯などで変わってくるような、実際の時間の概念に近いものが使いたければ、同ライブラリを利用するアプリケーション側で tzinfo をよしなに実装してどうにかしてね、という意図のようです。 こちらのtzinfoのクラスですが、実装の際にUTCオフセットをいい感じに対応するには utcoffset() というメソッドを上書きしてほしいそうです。 class tzinfo : """Abstract base class for time zone info classes. Subclasses must override the tzname(), utcoffset() and dst() methods. """ __slots__ = () def tzname (self, dt): "datetime -> string name of time zone." raise NotImplementedError ( "tzinfo subclass must override tzname()" ) def utcoffset (self, dt): "datetime -> timedelta, positive for east of UTC, negative for west of UTC" raise NotImplementedError ( "tzinfo subclass must override utcoffset()" ) def dst (self, dt): """datetime -> DST offset as timedelta, positive for east of UTC. Return 0 if DST not in effect. utcoffset() must include the DST offset. """ raise NotImplementedError ( "tzinfo subclass must override dst()" ) # 以下略 次に、datetimeオブジェクトのコンストラクタに渡しても正しく動く、 dateutil.tz の実装ではこの utcoffset() をどう実装しているのかみてみます。 github.com def utcoffset (self, dt): if dt is None : return None if not self._ttinfo_std: return ZERO return self._find_ttinfo(dt).delta # 中略 ↓こちらは上の関数から呼ばれている def _find_ttinfo (self, dt): idx = self._resolve_ambiguous_time(dt) return self._get_ttinfo(idx) # 中略 ↓こちらは上の関数から呼ばれている def _resolve_ambiguous_time (self, dt): idx = self._find_last_transition(dt) # If we have no transitions, return the index _fold = self._fold(dt) if idx is None or idx == 0 : return idx # If it's ambiguous and we're in a fold, shift to a different index. idx_offset = int ( not _fold and self.is_ambiguous(dt, idx)) return idx - idx_offset 上のコードでは、 utcoffset() が呼ばれた際には毎回、内部で標準時間の変遷の歴史を参照する関数を呼び出し、その結果を参考に採用すべきUTCとのオフセットを導き出しています。このことから、dateutil.tzでは utcoffset() が呼び出されると、その都度標準時間の遍歴を考慮してUTCオフセットの判定をしていることがわかります。 比べて、コンストラクタに渡すとうまく動かないpytzの方の実装を見てみます。 github.com def utcoffset (self, dt, is_dst= None ): # 関数のコメントは長いので略 if dt is None : return None elif dt.tzinfo is not self: dt = self.localize(dt, is_dst) return dt.tzinfo._utcoffset else : return self._utcoffset こちらは条件分岐によっては、dateutil.tzでの場合のようなUTCオフセットの決定のための複雑な計算はせずに、事前にクラスにセットされた _utcoffset の値を単純に返すのみになります。基本的にdatetimeオブジェクトが生成されてからは utcoffset() が呼ばれる際には、上記コードの条件分岐一番最後の else 節に入るようであったので、pytzはインスタンス作成時に設定されたUTCオフセットを返し続ける挙動になっていそうです。 上記の実装2例からは、dateutil.tzの方はUTCオフセットを参照された場合は毎回引数のdatetimeオブジェクトの日付を確認してUTCオフセットを決定していて、pytzの方は一度UTCオフセットの値が設定されたらそれを返し続ける、という違いが見て取れます。 datetimeオブジェクトのコンストラクタにタイムゾーンを渡すユースケースの場合、先に引数として渡すために作成されるタイムゾーンオブジェクトは作成予定のdatetimeオブジェクトを読み取れないので、どのような tzinfo の実装であれ一旦は正確にはUTCオフセットを決められない状態になります。dateutil.tzの方はその後改めてdatetimeオブジェクトの日付を確認してUTCオフセットを再計算するタイミングがあるのに対し、pytzの場合はそれがなさそうです。 このことから、pytzオブジェクトをdatetimeオブジェクトのコンストラクタに渡すと、UTCオフセットが不明な状態で記録された該当タイムゾーンでの最も古いUTCオフセットの情報を保持し続け *2 、上述のような挙動が起こると思われます。 zoneinfoファイルの「1901年問題」 上で、Time Zone Databseの情報はUnix likeな環境では /usr/share/zoneinfo 以下に同情報が入っていると書きました。 このファイルを用いてタイムゾーンを扱う際の意図しない挙動を紹介します。 一部の環境 *3 で以下のようなコードを実行すると、意図した時間よりも18分59秒ずれることになります。 >>> import datetime >>> import dateutil.tz # 年月日はなしに時間のみを指定してdatetimeオブジェクトを作成し、それにdateutil.tzでAsia/Tokyoのタイムゾーン情報を付与し、UTCオフセットを取得 >> datetime.datetime.strptime( '12:00' , '%H:%M' ).replace(tzinfo=dateutil.tz.gettz( 'Asia/Tokyo' )).utcoffset() datetime.timedelta(seconds= 33539 ) # UTCオフセットは9時間(32400秒)を期待しているのに、9時間18分59秒(33539秒)となった (上のコードはそもそも、日付のない時間のデータを扱うのにdatetime.timeではなく余計に情報量の多いdatetime.datetimeを用いているところが本質的な間違いですが、そこには目を瞑ってください。) こちらは、datetimeオブジェクトで時間のみを指定すると年月日は1900年1月1日となること、一部の環境でTime Zone Databaseの情報が載っているバイナリのファイルで日付データを32ビットまでしか扱えていないこと、の二つの要因が重なってこのような挙動になっています。 前者については、1900年1月1日時点では既に日本では今と同じ標準時間が採用されている状態であったため、こちら単体ではUTCオフセットは9時間18分59秒とはならないはずです。 後者については、32ビットで扱える時間の範囲を超えるとコンピュータが誤動作すると言われる「2038年問題」と同源です。32bitの秒数でエポック時間から負の方向に向かって表現できる限界は1901年の12月13日であり、datetimeオブジェクトに自動でつけられた1900年1月1日はこれよりも古いため、一部環境でUTCオフセットの計算がうまくいかなっているようです。 上記の事象が再現する環境で参照されているタイムゾーンの情報のファイルを、 RFC8536での仕様 をもとにバイナリを読んでみます。 $ hexdump -C /usr/share/zoneinfo/Asia/Tokyo 00000000 54 5a 69 66 32 00 00 00 00 00 00 00 00 00 00 00 |TZif2...........| 00000010 00 00 00 00 00 00 00 04 00 00 00 04 00 00 00 00 |................| 00000020 00 00 00 09 00 00 00 04 00 00 00 0c 80 00 00 00 |................| 00000030 d7 3e 02 70 d7 ed 59 f0 d8 f8 fa 70 d9 cd 3b f0 |.>.p..Y....p..;.| 00000040 db 07 00 f0 db ad 1d f0 dc e6 e2 f0 dd 8c ff f0 |................| 00000050 03 01 02 01 02 01 02 01 02 00 00 83 03 00 00 00 |................| 00000060 00 8c a0 01 04 00 00 7e 90 00 08 00 00 7e 90 00 |.......~.....~..| 00000070 08 4c 4d 54 00 4a 44 54 00 4a 53 54 00 00 00 00 |.LMT.JDT.JST....| 00000080 01 00 00 00 01 54 5a 69 66 32 00 00 00 00 00 00 |.....TZif2......| 00000090 00 00 00 00 00 00 00 00 00 00 00 00 04 00 00 00 |................| 000000a0 04 00 00 00 00 00 00 00 0a 00 00 00 04 00 00 00 |................| 000000b0 0c f8 00 00 00 00 00 00 00 ff ff ff ff 65 c2 a4 |.............e..| 000000c0 70 ff ff ff ff d7 3e 02 70 ff ff ff ff d7 ed 59 |p.....>.p......Y| 000000d0 f0 ff ff ff ff d8 f8 fa 70 ff ff ff ff d9 cd 3b |........p......;| 000000e0 f0 ff ff ff ff db 07 00 f0 ff ff ff ff db ad 1d |................| 000000f0 f0 ff ff ff ff dc e6 e2 f0 ff ff ff ff dd 8c ff |................| 00000100 f0 00 03 01 02 01 02 01 02 01 02 00 00 83 03 00 |................| 00000110 00 00 00 8c a0 01 04 00 00 7e 90 00 08 00 00 7e |.........~.....~| 00000120 90 00 08 4c 4d 54 00 4a 44 54 00 4a 53 54 00 00 |...LMT.JDT.JST..| 00000130 00 00 01 00 00 00 01 0a 4a 53 54 2d 39 0a |........JST-9.| 0000013e 下記画像に説明を書いた通り、本ファイルにはUTCオフセットが今よりも18分59秒ずれていた時期のルールが記載されていますが、そちらが採用されていた最後の時間にあたる部分に、符号付き32bitのintergerの最小値である 80 00 00 00 (-2147483648)が入っています。こちらをUNIX timeとして読み取ると1901年12月13日中の時間になるため、それ以前の時間はこのファイルを参照する限りUTCオフセットが9時間18分59秒と扱われてしまいます。(実際にこのUTCオフセットの標準時間が利用されていたのは1887年末までなので、およそ14年分の日本時間は正しく変換できないことになります。) 逆に、この現象が起きない筆者のMac上の同じファイルを見てみると、この符号付き32bit整数ではUNIX timeとして扱えない時期のデータは、省略されていることがわかりました。 % hexdump -C /usr/share/zoneinfo/Asia/Tokyo 00000000 54 5a 69 66 32 00 00 00 00 00 00 00 00 00 00 00 |TZif2...........| 00000010 00 00 00 00 00 00 00 03 00 00 00 03 00 00 00 00 |................| 00000020 00 00 00 08 00 00 00 03 00 00 00 08 d7 3e 02 70 |.............>.p| 00000030 d7 ed 59 f0 d8 f8 fa 70 d9 cd 3b f0 db 07 00 f0 |..Y....p..;.....| 00000040 db ad 1d f0 dc e6 e2 f0 dd 8c ff f0 00 01 00 01 |................| 00000050 00 01 00 01 00 00 8c a0 01 00 00 00 7e 90 00 04 |............~...| 00000060 00 00 7e 90 00 04 4a 44 54 00 4a 53 54 00 00 00 |..~...JDT.JST...| 00000070 01 00 00 01 54 5a 69 66 32 00 00 00 00 00 00 00 |....TZif2.......| 00000080 00 00 00 00 00 00 00 00 00 00 00 04 00 00 00 04 |................| 00000090 00 00 00 00 00 00 00 09 00 00 00 04 00 00 00 0c |................| 000000a0 ff ff ff ff 65 c2 a4 70 ff ff ff ff d7 3e 02 70 |....e..p.....>.p| 000000b0 ff ff ff ff d7 ed 59 f0 ff ff ff ff d8 f8 fa 70 |......Y........p| 000000c0 ff ff ff ff d9 cd 3b f0 ff ff ff ff db 07 00 f0 |......;.........| 000000d0 ff ff ff ff db ad 1d f0 ff ff ff ff dc e6 e2 f0 |................| 000000e0 ff ff ff ff dd 8c ff f0 03 01 02 01 02 01 02 01 |................| 000000f0 02 00 00 83 03 00 00 00 00 8c a0 01 04 00 00 7e |...............~| 00000100 90 00 08 00 00 7e 90 00 08 4c 4d 54 00 4a 44 54 |.....~...LMT.JDT| 00000110 00 4a 53 54 00 00 00 00 01 00 00 00 01 0a 4a 53 |.JST..........JS| 00000120 54 2d 39 0a |T-9.| 00000124 データの個数が問題の起こる環境と比べ一つ減っていることと、9時間18分59秒のUTCオフセットを表す 83 03 (09:18:59を秒数にした33540の16進数表記)という値が(時間の変換ルールの項目からは)見当たらないことがわかります。 これによって1888年1月1日から1901年12月13日までの日本の日付を正しく扱えるようになりますが、逆に1887年末以前のデータは実際の当時の標準時とはずれた時間で取り扱われます。 # 1887年末以前の標準時がzoneinfoファイルに記録されていない、筆者のMacで実行 >>> datetime.datetime( 1887 , 1 , 1 , 0 , 0 , 0 , tzinfo=dateutil.tz.gettz( "Asia/Tokyo" )).utcoffset() datetime.timedelta(seconds= 32400 ) # ↑実際は33540(9時間18分59秒)であるべきが、32400(9時間)となってしまっている 何らかの理由で1887年以前の日本の日付データを扱いたい場合は、こちらの挙動に気をつける必要がありそうです。 strftime(%s)でのエポック秒変換 datetimeオブジェクトをエポック秒に変換したい場合に、 strftime(%s) を用いることでこれが実現できると書いてある記事がちらほら存在します。 しかしこちらは Pythonのdatetimeのドキュメント ではサポートされているとは一切書いておらず、非推奨です。 # 日本時間で動く環境です >>> import time >>> time.tzname ( 'JST' , 'JST' ) >>> import datetime >>> import dateutil.tz # 現在時刻を取得した後、それにニューヨークのタイムゾーンを付与します >>> d = datetime.datetime.now().astimezone(dateutil.tz.gettz( 'America/New_York' )) >>> d datetime.datetime( 2024 , 2 , 8 , 1 , 41 , 57 , 844204 , tzinfo=tzfile( '/usr/share/zoneinfo/America/New_York' )) # strftime(%s)を用いてエポック秒に変換 >>> epoch_s = d.strftime( "%s" ) >>> epoch_s '1707324117' # これをdatetimeに変換し再びニューヨークのタイムゾーンをつけると、もとより14時間ずれていることがわかります >>> datetime.datetime.fromtimestamp( int (epoch_s)).astimezone(dateutil.tz.gettz( 'America/New_York' )) datetime.datetime( 2024 , 2 , 7 , 11 , 41 , 57 , tzinfo=tzfile( '/usr/share/zoneinfo/America/New_York' )) # こちらはエポック秒変換の際に、ニューヨークでの時間としてではなく日本時間として変換されたためです 上記のコード例の通り、 strftime(%s) だと変換元のdatetimeオブジェクトがtimezone awareであったとしても、変換時にはそのタイムゾーン情報ではなく、実行環境のタイムゾーン情報を参照してしまいます。 (日本時間で動く環境で、日本時間の時刻データのみを扱っていると気づかずにこのような実装が紛れてしまうかもしれませんが、どちらか一方が日本時間以外となるとこちらは不具合を起こします。) こちらdatetimeオブジェクトをエポック秒に変換したい場合は、 timestamp() を使うのが正解です。 まとめ コンピュータで扱う場合のタイムゾーンの概念は、単なる地域間の時差の寄せ集めではなく、歴史的変遷など時間軸の情報も含んだ複雑なものです (Pythonやタイムゾーンとかに限った話ではないですが)ちゃんとドキュメント読みましょう *1 : 厳密にいうとこれは抽象クラスとは呼ばないかもしれないですが、実際の実装はなく利用側での関数のoverrideを期待しているクラスという意味で、雑にこの語を使ってます *2 : pytzのオブジェクトがコンストラクタ内でとりあえず最も古い時期のUTCオフセットを参照する部分の実装はこちら: https://github.com/stub42/pytz/blob/fb43f957c5149e750c3be3cfc72b22ad94db4886/src/pytz/tzinfo.py#L189 *3 : ubuntu:focal-20230412のDockerイメージをもとにしたコンテナでapt-get install tzdataをした際に発生するのを確認しました
アバター
はじめに セーフィー株式会社 の AI Vision グループでテックリードを務めます橋本貴博です。 セーフィーではネットワークカメラ上で動作するエッジアプリケーションの開発にC++を利用しています。公開されている推論モデルはPythonで実装されていることが多いため、C++への移植が必要です。 この記事では、GitHub Copilot を使って 推論モデルの前後処理をPythonからC++に変換した結果を紹介したいと思います。 はじめに アルゴリズム 実験条件 前処理 後処理 むすび アルゴリズム mmsegmentation リポジトリで公開されている PP-MobileSeg のPython実装された推論コードを利用します。PP-MobileSeg は、2023年4月に発表された、モバイル向けセマンティックセグメンテーションのSOTAモデルです。論文は こちら から読めます。 引用元: PaddlePaddle/PaddleSeg (GitHub) / Apache License 2.0 実験条件 mmsegmentation をcloneし、リポジトリのルートを VSCode の Open Folder から開きます git clone git@github.com:open-mmlab/mmsegmentation.git 対象とするファイル(projects/pp_mobileseg/inference_onnx.py)のみをエディタで開きます GitHub Copilot Chat の New Session を作成して、過去のチャット履歴がコンテキストに入らないようにしています(実際に利用する際は何往復か対話をしたほうが良い結果が得られると思います) 前処理 推論コード inference_onnx.py の L39 - 45 をC++に変換してみます。入力された画像にリサイズと正規化を適用する一般的な前処理です。 # Resize resized_img = cv2.resize(img, input_size) # Normalize mean = np.array([ 123.575 , 116.28 , 103.53 ], dtype=np.float32) std = np.array([ 58.395 , 57.12 , 57.375 ], dtype=np.float32) resized_img = (resized_img - mean) / std コードを選択しCopilotのコンテキストに含めた状態でChatを使用します。「C++に書き直して」だけだとEigenライブラリも使ってしまったので、OpenCVライブラリだけを使うように指示します。プロンプトと回答は次の通りです。 t-hashimoto-safie: OpenCVだけを使って、C++に書き直して GitHub Copilot: 以下に、選択されたPythonコードをC++で書き直したものを示します。このコードはOpenCVライブラリを使用しています。 // Include necessary libraries #include <opencv2/opencv.hpp> #include <opencv2/core/core.hpp> // ... // Assuming 'img' is your input cv::Mat image cv::Mat resized_img; cv::Size input_size(your_width, your_height); // replace with your dimensions // Resize cv::resize(img, resized_img, input_size); // Normalize cv::Scalar mean(123.675, 116.28, 103.53); cv::Scalar std(58.395, 57.12, 57.375); resized_img = (resized_img - mean) / std; このコードは、Pythonコードと同じように画像をリサイズし、正規化します。ただし、C++のOpenCVでは、 cv::Scalar を使用して平均と標準偏差を定義します。また、リサイズのための新しいサイズは cv::Size オブジェクトとして定義されます。 正解です!以下のポイントが押さえられています。 OpenCV API は Python と C++ で微妙にインターフェースが異なることを理解している C++ の cv::resize メソッドはリサイズ後の画像の参照渡しする C++で変数の定義に必要な型を推測できている リサイズ後の画像を cv::Mat 型で事前に定義している np.array の代わりに cv::Scalar を使えばよい 後処理 次に、 inference_onnx.py の L108-L122 を書き直してもらいます。以下のコードは、ピクセルごとのラベル(int型)を示す seg_map を、ラベルに対応する色で入力画像 img に重畳し、ファイルに保存する関数です。セマンティックセグメンテーションで一般的に行われる可視化処理です。 def visualize (img: np.ndarray, seg_map: np.ndarray, filename: str = 'output.jpg' , opacity: float = 0.8 ) -> np.ndarray: assert 0.0 <= opacity <= 1.0 , 'opacity should be in range [0, 1]' palette = np.array(PALETTE) color_seg = np.zeros((seg_map.shape[ 0 ], seg_map.shape[ 1 ], 3 ), dtype=np.uint8) for label, color in enumerate (palette): color_seg[seg_map == label, :] = color # convert to BGR color_seg = color_seg[..., ::- 1 ] img = img * ( 1 - opacity) + color_seg * opacity cv2.imwrite(filename, img) GitHub Copilot へのプロンプトと回答は以下の通りです。 t-hashimoto-safie: OpenCVだけを使って、C++に書き直して GitHub Copilot: 以下に、PythonからC++への変換を行ったコードを示します。このコードは、OpenCVライブラリを使用しています。 #include <opencv2/opencv.hpp> #include <vector> #include <cassert> // Assuming PALETTE is a vector of cv::Vec3b (BGR colors) extern std::vector<cv::Vec3b> PALETTE; void visualize(cv::Mat& img, cv::Mat& seg_map, std::string filename = "output.jpg", float opacity = 0.8) { assert(0.0 <= opacity && opacity <= 1.0); // opacity should be in range [0, 1] cv::Mat color_seg = cv::Mat::zeros(seg_map.size(), CV_8UC3); for (int label = 0; label < PALETTE.size(); ++label) { color_seg.setTo(PALETTE[label], seg_map == label); } img = img * (1 - opacity) + color_seg * opacity; cv::imwrite(filename, img); } このC++コードは、Pythonコードと同じように動作します。各ラベルに対応する色をセグメンテーションマップに適用し、その結果を元の画像に重ねて表示します。その後、結果を指定されたファイル名で保存します。 こちらも正解です!気づいた点としては以下の通りです。 利用する C++ API の理解 ラベルと一致するピクセルに色を付ける処理には [Mat::setTo](https://docs.opencv.org/4.x/d3/d63/classcv_1_1Mat.html#a0440e2a164c0b0d8462fb1e487be9876) メソッドを使えばよい。 効率化のための提案 もともとの PythonコードRGB画像からBGRに変換をする部分が非効率だった。はじめからBGRのパレット std::vector<cv::Vec3b> PALETTE を用意したほうが良いと提案している。 むすび GItHub Copilot に「プログラミング言語」から「他のプログラミング言語」に変換するタスクを解かせてみました。APIリファレンスを調べながらコーディングしなくて済むので、作業時間が短縮できたと感じました。なんとなくは覚えているけど、正確な書き方を忘れたといった場合に効果があるのではないでしょうか。 セーフィーの開発部門では GitHub Copilot を導入しています。
アバター
はじめに リリースから3年以上経過しようやくモダンな開発環境に近づけていく活動ができるようになるくらい体制が整って来ました。 今回はAndroid版Safie Viewer for Mobileが2023年に行った改善活動の振り返りの話をしたいと思います。 はじめに ユニットテストの導入 ビジュアルリグレッションテストの導入 Jetpack Composeの導入 Detektの導入 Dependabotの導入 ビルドスクリプト周りの見直し 便利だったGithubActionsのAction 最後に ユニットテストの導入 改善活動の一歩としてユニットテストを導入しました。 ここまでの間テストがないという状況が続いており機能追加やライブラリのアップデートをした時手動でのテストが欠かせない状態でした。 今年からユニットテストを導入したので手動での検証作業が少し緩和されたという状態になりました。 カバレッジ率はまだ約12%というのが現実ですが、来年以降もカバレッジ率を高めていくような活動を続けていきます。 ビジュアルリグレッションテストの導入 ユニットテストに加え Roborazzi を使用したビジュアルリグレッションテスト(以下VRT)も合わせて導入しました。 Roborazziは nowinandroid や DoroidKaigi2023 でも採用されているライブラリで、JVM上でスクリーンショットを撮影し差分比較することのできるライブラリです。 Safie Viewer for Mobileでは サンプル を参考にGithubActionsのワークフローを作成し、プルリクエスト上でVRTを実行し差分比較を行えるような環境を用意しました。 プルリクエスト上で差分の確認ができる Safie Viewer for Mobileは多言語化対応をしており動作確認のため端末の言語設定を切り替えてから対象の画面を表示しレイアウトに問題がないかを確認しておりましたがRoborazzi導入後は自動で言語毎のスクリーンショットを撮って確認する事ができるので、かなりの作業時間の削減に繋がりました。 多言語化対応しているようなアプリであれば非常に強力なライブラリになるかと思います。 github.com Jetpack Composeの導入 今年からXMLからJetpack Composeにレイアウトの作りを置き換えるという事を始めました。 移行率は10%程度とまだ殆どがXMLでの構成となっておりますが、小さい画面から順にJetpack Composeに置き換えるといった活動をしています。 Jetpack Composeに置き換えて一番大きく感じた利点はテストの書きやすさにあると感じました。 これまで、Activity/FragmentのUI周りのテストを書くのは敷居が高いと感じ実機で手動確認で済ませる事が多かったのですがJetpack Composeであれば 表示のテスト 状態変化のテスト がかなり簡単に書けるようになったと思います。 developer.android.com Detektの導入 静的コード解析として、Detektを導入しました。 GitHubActionsで実行したDetektの結果を alaegin/Detekt-Action を使用して通知する仕組みで運用しています。 目的としては、これまで目視で確認していた「コーディングルールに則って書かれているかどうか」を自動で確認するためです。 その他にDetektの便利なところは、メソッド毎に複雑度の数値「CyclomaticComplexMethod」と「CognitiveComplexMethod」が測れることにあると思います。 今まで感覚的に「このメソッドなんか複雑に感じる」というのを数値で見る事ができるのでレビューの際に指摘しやすくなるといった効果があります。 detekt.dev Dependabotの導入 今まで手作業で確認していたライブラリのアップデートをDependabotを導入して自動化しました。 手作業で確認していた分の時間がかなり削減されたので開発者体験の向上に繋がりました。 docs.github.com ビルドスクリプト周りの見直し ビルドスクリプト周りの改善として以下の事を行いました Groovy から KTSに移行 Android Gradle Pluginを7系から8系にアップデート Version Catalogの導入 Androidは今後KTSでビルドスクリプトを書くのが標準なので、それに合わせてSafie Viewer for Mobileも全てKTSに置き換えました。 ついでにライブラリのバージョンもVersion Catalogで管理するようにしましたが今のところ大きな恩恵は受けておらず、これは将来的にマルチモジュール対応などを行った時に効果を発揮することになると思います。 developer.android.com developer.android.com 便利だったGithubActionsのAction r0adkll/upload-google-play GithubActonsからアプリのリリースファイルを作成しGooglePlayConsoleへ自動アップロードする為のActionです。 このActionを導入してアプリのリリース作業を半自動化する事ができました。 AlexSim93/pull-request-analytics-action PullRequestを解析し オープンしたプルリクエストの数 マージしたプルリクエストの数 プルリクエストを承認した回数 マージまでに掛かった時間 などのレポートを出力できるActionで、取り急ぎこれまでのプルリクエストの状況を確認したかった事があったので便利でした。 執筆時のバージョンは1.8.4ですが、頻繁に更新されているライブラリで日に日に新機能が追加されて行っているのが特徴的です。 github.com github.com 最後に 2023年は改善活動に力を入れた1年でした。 1年かけて様々なものを導入して来ましたが、まだまだ改善していきたい箇所が多く継続的に改善活動を続けていく必要があります。 日々の改善活動を通して、チームとして力を伸ばしていきたいと思います。 また、セーフィーではエンジニアの採用を積極的に行っております。もし興味が出てきた際はぜひご応募いただけたらと思います。 safie.co.jp
アバター
はじめに セーフィー株式会社 の AI Vision グループでテックリードを務めます橋本貴博です。セーフィーの一部のAIネットワークカメラは、Snapdragon Neural Processing Engine(SNPE)をランタイムに使ってエッジ推論を行っています。この記事では、SOTA セグメンテーションモデル PP-MobileSeg を SNPEで動かす方法を解説したいと思います。 はじめに PP-MobileSegとは? モデル変換 大まかな流れ PyTorch から ONNX への変換 ONNX モデルの修正 ONNX から DLC への変換 推論結果 むすび PP-MobileSegとは? PP-MobileSeg は、2023年4月に発表された、モバイル向けセマンティックセグメンテーションのSOTAモデルです。 ADE20Kデータセット で学習がなされており、既存モデルと比較して低レイテンシ、かつ、認識性能(mIoU)が高いことが分かります。論文は こちら (arXiv) から読めます。 引用元: PaddlePaddle/PaddleSeg (GitHub) / Apache License 2.0 モデル変換 大まかな流れ PP-MobileSegのPyTorchモデルをONNXモデルに変換します。ONNXモデルで移植性向上のためのノード編集を行なった後、ONNXモデルからSNPEのネイティブフォーマット(DLC形式)に変換します。 PyTorch から ONNX への変換 mmsegmentation リポジトリ v1.1.2 で 公開(2023年9月)されている PP-MobileSeg-Tiny の PyTorchモデルを利用します。はじめに、 Get started: Install and Run MMSeg を参考に環境構築を済ませておきます。 公式マニュアル に記載の手順にしたがって作業すれば、PyTorchモデルからONNXモデルに変換できます。 ONNX モデルの修正 ONNXモデルで使われている HardSigmoid オペレータ は、SNPEでサポートされないため、単純なオペレータの組み合わせに変換します。SNPEでサポートされるオペレータは 公式リファレンス から確認できます。 活性化関数に HardSigmoid が使われている。 https://netron.app/ で可視化。 まず、今回のノード編集で利用する ONNX の opset バージョンを確認しておきます。opset versionによってオペレータの定義が異なるため、入力モデルの opset バージョンに揃えることにします。以下のスクリプトはモデルを読み込み opset バージョンを表示します。 import onnx model = onnx.load(input_path) for opset in model.opset_import: print(f"Model opset version: {opset.version}") 今回使用するモデルの opset バージョンは 11 ということが分かります。 model opset version: 11 次に、HardSigmoid オペレータを変換します。HardSigmoid の定義は、入力を x 、出力を y として、 y = max ( 0 , min ( 1 , alpha * x + beta)) # alpha, beta はパラメータ なので、ONNXの Mul、Add、Clip オペレータ を用いて分解できます。 x1 = Mul(alpha, x) x2 = Add(x1, beta) y = Clip(x2, 0, 1) 以下のスクリプトは、Mul、Add、Clip を用いて、ONNXモデルのすべての HardSigmoid を消去します。13か所で HardSigmoid が使われていることが分かります。 from onnx import TensorProto, defs, helper count = 0 for pos, node in enumerate (model.graph.node): if node.op_type == "HardSigmoid" : count += 1 # Count HardSigmoid operator # Create Mul node and insert n0_name = node.name x0_name = node.input[ 0 ] alpha = node.attribute[ 0 ].f alpha_name = n0_name + "_alpha" alpha_tensor = helper.make_tensor(alpha_name, TensorProto.FLOAT, [ 1 ], [alpha]) y0_name = n0_name + "_mul" multiply_node = helper.make_node( "Mul" , [x0_name, alpha_name], [y0_name]) model.graph.node.insert(pos, multiply_node) # Create Add node and insert y1_name = n0_name + "_add" beta_name = n0_name + "_beta" beta_tensor = helper.make_tensor(beta_name, TensorProto.FLOAT, [ 1 ], [ 0.5 ]) add_node = helper.make_node( "Add" , [y0_name, beta_name], [y1_name]) model.graph.node.insert(pos + 1 , add_node) # Create Clip node and insert y2_name = node.output[ 0 ] min_name = n0_name + "_min" max_name = n0_name + "_max" min_tensor = helper.make_tensor(min_name, TensorProto.FLOAT, [ 1 ], [ 0.0 ]) max_tensor = helper.make_tensor(max_name, TensorProto.FLOAT, [ 1 ], [ 1.0 ]) clip_node = helper.make_node( "Clip" , [y1_name, min_name, max_name], [y2_name]) model.graph.node.insert(pos + 2 , clip_node) model.graph.node.remove(node) # Remove HardSigmoid node model.graph.initializer.extend([alpha_tensor, beta_tensor, min_tensor, max_tensor]) print (f "# hardsigmoid nodes: {count}" ) # Outputs 13 作成されたモデルを検証して保存します。 onnx.checker.check_model(model) # Validation onnx.save(model, output_path) # Save 変換後のONNXモデルを見ると、HardSigmoid オペレータが、Mul、Add、Clip オペレータに置き換わっていることが確認できます。 HardSigmoid が Mul/Add/Clip に変換されている。 https://netron.app/ で可視化。 ONNX から DLC への変換 SNPE SDKに含まれるツール を使って、ONNXモデルからDLCモデルに変換が可能です。モデル実行時の入力テンソルのサイズを固定します。 snpe-onnx-to-dlc --input_network model.onnx --input_dim "input" 1,3,512,512 推論結果 当社のAIネットワークカメラ(Safie One)にモデルをインストールし、推論を行います。クラウド経由でモデルをアップロードできます。 SafieOne 推論が動作する様子をWebアプリ(Safie Viewer)から確認します。ここでは人物クラスを水色で表示しています。人物の輪郭が抽出されていることが分かります。 人物が水色でセグメンテーションされている むすび セマンティックセグメンテーションのSOTAモデル PP-MobileSeg をAI ネットワークカメラで動かしました。ONNXモデルからHardSigmoid オペレータを除去し、DLCモデルに変換する方法を詳しく解説しました。最後に、AIネットワークカメラ上の推論結果をWebアプリから確認しました。 セーフィーではエッジAIを用いたプロダクトの開発を行っています!
アバター
Androidチームの品質改善の取り組みとして、今回はアプリサイズを計測した話をしたいと思います。 はじめに 動作環境 rulerとは rulerの導入方法 settings.gradleの編集 app/build.gradleの編集 analyzeタスクの実行 継続的に計測する ワークフロー 最後に はじめに 頻繁にアプリを更新しているとある日突然「アプリのサイズが肥大化している」と気が付く時があります。 新しいリソースファイルを追加した 新しいライブラリを導入した など原因は様々考えられますがこれがいつの時期から発生したのかを調べるのは一苦労です。 そうならない為にも今回は「ruler」を使用し継続的にアプリのサイズを監視できる仕組みを導入しました。 動作環境 Android Studio:Flamingo 2022.2.1 Patch 2 AGP:8.0.2 Gradle Version:8.0 ruler:1.4.0 rulerとは ruler とは、Spotifyが公開しているGradle pluginです。 アプリのサイズはもちろんのこと、各モジュールと依存関係がどれくらいアプリのサイズに影響を与えているかを レポートで出力することができるプラグインです。 rulerの導入方法 settings.gradleの編集 プロジェクト直下の「settings.gradle」を編集しrulerのプラグインを追加します。 pluginManagement { repositories { google() mavenCentral() gradlePluginPortal() } //追加 resolutionStrategy { eachPlugin { if (requested.id.id == "com.spotify.ruler") { useModule("com.spotify.ruler:ruler-gradle-plugin:1.4.0") } } } } dependencyResolutionManagement { //省略 } 参考: https://github.com/spotify/ruler/issues/85 app/build.gradleの編集 app/build.gradleにrulerの設定を追記します plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' id 'com.spotify.ruler' //追加 } android { //省略 } dependencies { //省略 } //追加 環境に応じて設定値は変更してください。 ruler { abi.set("arm64-v8a") locale.set("ja_JP") screenDensity.set(480) sdkVersion.set(33) } analyzeタスクの実行 ここまで上手くいくとGradleタスクに「analyze Bundle」のタスクが追加されています。 このタスクを実行するとアプリサイズを計測したHTMLとJSONのレポートが出力されます。 継続的に計測する いつアプリのサイズが肥大化したのか継続的に監視する為にrulerのタスクをGithub Actionsのワークフローに取り入れ 出力したJSONファイルを解析し内製のダッシュボードに記録ことで監視する仕組みを導入しました。 ワークフロー 以下実際に作成したものから抜粋したワークフローです。 Kotlin Script「sendAppSizeToDashboard.main.kts」でJSONを解析し結果を内製のダッシュボードに送信するという仕組みを作りました。 name: Output App Size Task on: push: paths: - 'app/**' branches: - main jobs: rulerJob: name: Exec ruler runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - name: Set up JDK 17 uses: actions/setup-java@v3 with: java-version: '17' distribution: 'temurin' cache: gradle - name: Make gradlew executable run: chmod +x ./gradlew - name: Run ruler run: ./gradlew analyzeStagingDebugBundle - name: Make scripts executable run: chmod -R +x ./.github/workflows/scripts #rulerの結果を内製ダッシュボードに送信するスクリプトを実行 - name: Send dashboard env: TZ: 'Asia/Tokyo' run: ./.github/workflows/scripts/sendAppSizeToDashboard.main.kts 計測したデータ 計測データを元に数値をグラフで可視化 最後に 今回の取り組みでは rulerを使用した継続的なアプリサイズの計測 という環境を作りました。 以下の記事によると「アプリのサイズはインストール率に影響している」との事なので、もし「アプリのサイズを計測したい」と思った際に本記事が少しでも参考になれば幸いです。 developer.android.com また、セーフィーではエンジニアの採用を積極的に行っております。もし興味が出てきた際はぜひご応募いただけたらと思います。 safie.co.jp
アバター
今回は直近のAndroidチームの品質改善の取り組みとして、ユニットテストを導入した件についてお話したいと思います。 はじめに 実際にやった事 ユニットテストを書く どこからユニットテストを書くか 技術スタック JUnit4 + Mockito-Kotlin + Truthを使用したテストコード カバレッジ率の可視化 Jacocoの設定 Jacocoの出力 CIで自動でテストが実行される環境 導入したアクション ワークフロー 最後に はじめに Android版Safie Viewer for Mobileは開発が始まって3年が経過しようとしています。 3年間、継ぎ足しを重ねて作成してきたコードはもうじき5万行を超える大きさになりました。 Androidチームは直近までプロジェクトのメンバーが少なく、機能を実装してリリースする事で精一杯になっていました。 なんとかリリースまで辿り着けているものの、目の前には日に日に複雑になっていくコードがあるというのが現実でコードのリファクタリングまで手が回らずメンテナンスする事ができない状況に陥っていました。 この状況はAndroidチームとしても今後の課題となって行く事が目に見えていたため リファクタリングの前段階として、既存コードが設計通りに動くかの保証を目的としたユニットテストを導入する事にしました。 実際にやった事 主な取り組みとして以下の3点を行いました ユニットテストを書く カバレッジ率の可視化 CIで自動でテストが実行される環境構築 ユニットテストを書く どこからユニットテストを書くか Android版Safie Viewer for Mobileのアーキテクチャは 公式 が推奨しているような UI Layer Domain Layer Data Layer の3層に分け、責務を分離する事を心がけ作られています。 幸いにもこのルールが守られている事もあってか、レイヤ間が疎結合にはなっておりユニットテストが書ける土壌にはありました。 アーキテクチャの図(公式より引用) しかしながら、土壌はあったもののAndroidチームとしてこれまでユニットテストを書くという文化が無かったので いきなりActivity/FragmentやViewModelなどの画面に近い箇所のテストコードを書くのはハードルが高いという事もあり 画面から遠く機能としても孤立しているData Layerからテストを書き始め、徐々に上のレイヤーに向かっていくという方針にしました。 技術スタック 今回、ユニットテストを書くために導入した技術スタックは主に以下になります。 JUnit4 Androidのプロジェクト作成時に既に導入されているテストフレームワーク Robolectric Android依存部分のテストを行うためのライブラリ Mockito-Kotlin モックライブラリ Mockito をKotlinでも使いやすくしたライブラリ Truth アサーションライブラリ Jacoco テストカバー率の測定 JUnit4 + Mockito-Kotlin + Truthを使用したテストコード 記述例として、下記のようなAndroidに依存しないData Layerのクラスを用意してテストコードを書きます。 package com.safie.test.data import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.withContext interface EmployeeService { /**従業員IDの最大値を取得*/ fun getMaxId(): Int } class EmployeeRepository( private val service: EmployeeService, private val dispatcher: CoroutineDispatcher ) { /** * IDの最大値にプラス1した、新しい従業員IDを取得 * @return 従業員ID */ suspend fun getNewId() = withContext(dispatcher) { val maxId = service.getMaxId() return@withContext maxId + 1 } } 上記のようなクラスに対しては次のようなテストコードを書くことができます。 import com.google.common.truth.Truth import com.safie.test.data.EmployeeRepository import com.safie.test.data.EmployeeService import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.Test import org.mockito.kotlin.mock import org.mockito.kotlin.whenever class EmployeeRepositoryTest { @Test fun testGetNewId() = runTest { val mockEmployeeService: EmployeeService = mock() val dispatcher = StandardTestDispatcher(testScheduler) val employeeRepository = EmployeeRepository(mockEmployeeService, dispatcher) //getMaxId()を実行した時、10を返すように設定 val mockMaxId = 10 whenever(mockEmployeeService.getMaxId()).thenReturn(mockMaxId) //maxIdにプラス1した値が返却される事を検証 val id = employeeRepository.getNewId() Truth.assertThat(id).isEqualTo(mockMaxId + 1) } } カバレッジ率の可視化 ユニットテストを書き続けるというモチベーションを維持するのはとても大変です。 そのモチベーションを維持する取り組みとして テストコードによってどの行が実行されたか 各クラスのカバレッジ率は何%か の計測結果をレポートとして出力するためにJacocoの導入をしました。 Jacocoの設定 以下のQiitaの記事を参考にJacocoの設定を行いました。 Android開発のテストカバー率取得にはこのツールを使い分けると良いという話 #Android - Qiita apply plugin: 'jacoco' jacoco { toolVersion = "0.8.9" } android.applicationVariants.all { variant -> def variantName = variant.name.capitalize() //ex. ProdDebug def realVariantName = variant.name //ex. prodDebug if (variant.buildType.name != "debug") { return } task("jacoco${variantName}TestReport", type: JacocoReport) { dependsOn "test${variantName}UnitTest" group = "testing" description = "Generate Jacoco coverage reports for ${realVariantName}" reports { xml.required = false html.required = true } //無視するファイル(excludes)の設定を行います def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', 'android/**/*.*', 'androidx/**/*.*', '**/Lambda$*.class', '**/Lambda.class', '**/*Lambda.class', '**/*Lambda*.class', '**/*Lambda*.*', '**/*Builder.*', '**/*Activity*.class', '**/*Fragment*.class' ] def javaDebugTree = fileTree(dir: "${buildDir}/intermediates/javac/${realVariantName}/compile${variantName}JavaWithJavac/classes", excludes: fileFilter) def kotlinDebugTree = fileTree(dir: "${buildDir}/tmp/kotlin-classes/${realVariantName}", excludes: fileFilter) def mainSrc = "${project.projectDir}/src/main/java" getSourceDirectories().setFrom(files([mainSrc])) //Java, Kotlin混在ファイル対応 getClassDirectories().setFrom(files([javaDebugTree, kotlinDebugTree])) getExecutionData().setFrom(fileTree(dir: project.projectDir, includes: [ '**/*.exec', //JUnit Test Result '**/*.ec']) //Espresso Test Result ) } } Jacocoの出力 設定したJacocoのタスクを実行することで以下のようなHTMLのレポートが出力され テストコードによってどの行が実行されたか 各クラスのカバレッジ率は何%か を確認をすることができます。 CIで自動でテストが実行される環境 Android版Safie Viewer for MobileではCIにGithub Actionsを採用しております。 PullRequestのイベントをトリガーにテストを自動で実行し、テストの実行結果と現在のカバレッジ率を表示するGithub Actionsのワークフローを用意しました。 導入したアクション ユニットテストの結果とカバレッジ率を表示する為に導入したアクションは以下になります。 action-junit-report Junitで出力したテスト結果のレポートを表示するワークフロー jacoco-reporter Jacocoで出力されたXMLをインプットに結果を表示するワークフロー ワークフロー 今回の取り組みで実際に用意したワークフローは以下の通りです。 name: PullRequest Tasks on: pull_request: paths: - 'app/**' branches: - main jobs: UnitTestJob: name: Android Staging Unit Test runs-on: ubuntu-latest steps: - name: checkout uses: actions/checkout@v3 - name: set up JDK 17 uses: actions/setup-java@v3 with: java-version: '17' distribution: 'temurin' cache: gradle - name: Make gradlew executable run: chmod +x ./gradlew - name: run UnitTest StagingDebug run: ./gradlew jacocoStagingDebugTestReport - name: JaCoCo Code Coverage Report id: jacoco_reporter uses: PavanMudigonda/jacoco-reporter@v4.8 with: coverage_results_path: ${{ github.workspace }}/app/build/reports/jacoco/jacocoStagingDebugTestReport/jacocoStagingDebugTestReport.xml coverage_report_name: Coverage coverage_report_title: JaCoCo github_token: ${{ secrets.GITHUB_TOKEN }} skip_check_run: false minimum_coverage: 40 fail_below_threshold: false publish_only_summary: false - name: Add Coverage Job Summary run: echo "${{ steps.jacoco_reporter.outputs.coverageSummary }}" >> $GITHUB_STEP_SUMMARY - name: Publish Test Report uses: mikepenz/action-junit-report@v3 if: cancelled() != true with: report_paths: '**/build/test-results/*/TEST-*.xml' このワークフローが実行されるとPullRequest上でテストの結果や現在のカバレッジ率を確認することができるようになります。 ワークフローの実行結果 jacoco-reporterの結果 最後に 今回行った取り組みは ユニットテストを書く カバレッジ率の可視化 CIで自動でテストが実行される環境構築 だけの最低限の環境を作っただけにすぎず、これだけではチームにユニットテストを書いていく文化が広がっていきません。 しかしながら、ユニットテストを作る環境を導入した事は開発チームにとって重要な足がかりとなります。 今後は、チーム全体でテストの重要性を認識しつつユニットテストを書く事が当たり前の文化を作っていく取り組みをしていきます。 また、セーフィーではエンジニアの採用を積極的に行っております。もし興味が出てきた際はぜひご応募いただけたらと思います。 safie.co.jp
アバター
こちらは Safie Engineers' Blog! Advent Calendar の18日目の記事です。 こんにちは。セーフィー株式会社でバックエンドエンジニアをしている河津です。 私たちの会社は2023年10月27-28日に、PyCon APAC 2023への出展を行いました。この記事では、出展までの準備や当日の様子について紹介したいと思います! PyConとは 用意したデザインアイテム 当日の様子 出展デモ Safie One Safie Pocket2 Plus Safie Connect 撤収と振り返り PyConとは プログラミング言語「Python」を中心としたボランティアによる非営利の年次カンファレンスです。 セーフィーは前回開催された PyCon JP 2022 から出展していますが、2023年はなんと10年ぶりの国際カンファレンスとなる PyCon APAC 2023 が日本で開催されることになりました。 2023-apac.pycon.jp PyCon APACの運営チームは、アジア太平洋地域における国または地域が主体となり、現在では、シンガポール、マレーシア、インドネシア、フィリピン、タイ、韓国、香港、ベトナム、日本、台湾、インド、バングラデシュが毎年交代して開催されるそうです。 世界中のPythonエンジニアにセーフィーの存在を認知いただくチャンス!ということで出展準備を進めていきました。 用意したデザインアイテム 前回出展時の反省点の一つに、ブースに来ていただいた方々に会社のことを知っていただくためにお見せする資料などの用意が不十分だったというものがありました。 商品説明などは販促用パンフレットなどを使うことができますが、来場するエンジニアの方々が興味ありそうなシステム構成や技術スタック、どのあたりにPythonが使われているの?などの技術的な質問に対して、口頭でしか回答できないというもどかしさがありました。 そのため今回は、システム構成や技術スタックなどを説明するためのA1サイズのパネルやパンフレットを準備しました。見やすい内容となるようにデザイナーの方々にデザインいただきました。 A1パネル パンフレット めちゃくちゃ素敵なデザインとなりました!このパネルとポスターがあればセーフィーすごいと思ってもらえるはず。 今回は国際カンファレンスなので海外のエンジニアの方々も多く来場すると予想し、英語バージョンのものも用意しました。 これで準備完了です。 当日の様子 展示前日の10/26日に荷物を搬入しブース設営しました。設営担当やブース担当などは社内のたくさんのエンジニアメンバーにご協力いただきまして、事前に対応シフトを組んで臨みました。 設営完了。いい感じです! あとは展示会場には出展企業が自由にメッセージを残せるボードがあるのですが、こちらに採用募集ポスターを掲載しておきます。 せっかくなのでイラストも描きました。パンフレットやブースにもフクロウ要素が入っているので、フクロウの会社と覚えてもらえればいいなという思いも込めております。 当日は国際カンファレンスなだけあり、海外からの来場者が多い印象でした。ある程度は予測していましたが、特に初日(10/27)は英語で話をされる方が日本人よりも多い印象でした。 ブースでサービス説明や技術要素の説明などの対応を行います。 出展デモ ブースに来場いただく方にセーフィーのことを知っていただくには、動いているプロダクト・サービスを触っていただくのが一番良いと考えまして、いくつかのカメラをブースに設置しました。 Safie One エッジAIアプリが入った屋内用のカメラ。 safie.jp 三脚を持参し少し高いところから撮影し、ブースに来場いただいた方々の人数をカウントする試みをしてみました。 実際に2日間でカウントされた結果がこちらです。人数は伏せていますが、時間帯によって来場者数の推移が見て取れます。初日の14時ごろが一番来場者が多いようですね。(ちなみに初日は平日でした) この試みはブースに来ていただいた方々にも一定ウケまして、セーフィーが提供しているプロダクトについて、映像の視聴だけでなくデータを使ったソリューションという観点で、一番イメージいただけたものになったのではないかと思います。 Safie Pocket2 Plus 胸ポケットにつけるなどして持ち運びが容易なウェアラブルカメラ。 safie.jp 会場の様子が遠隔でもわかるということを示すために、展示中たまにこちらのカメラを胸につけて、他社様のブースなどに行くなどしていました。 他社様のブースで話を聞いてる際に、「その胸につけてるものはなんですか?」と聞かれることが多く、そのままセーフィーの紹介とブースへの誘致を行うことができたので、話の接点を持つのにも一役買ったカメラでした。 Safie Connect HDMI出力された映像をセーフィーのクラウドにアップロードしどこでも映像を見れるようにできるルーター。 safie.jp 展示にはドローンごと持参し、ドローンの映像を遠隔で視聴するデモを行いました。ドローンを展示することで来場した方々にも覚えてもらいやすくなったのではないかと思います。 撤収と振り返り 社内のいろんな方のご協力のおかげで2日間のブース出展を無事に終えることができました。最後に荷物を梱包し直して撤収します。お疲れ様でした! その後の社内での振り返りとしては、 デモの内容が前回出展時よりもウケが良かったように感じた パネルを用意したため説明がしやすかった ブース担当者は椅子に座らず立つオペレーションだったため、来場した人に声をかけやすかった。結果ブースが賑やかだったと言われた などが良かった点として挙がりました。 一方で、 デモ用のディスプレイはもっと目立つように大きいものを準備すれば良かった 英語による配布物をもっと充実させておけば良かった もう少し企画っぽい出し物もあれば認知に貢献したかもしれない(他ブースでやられていたくじ引きやクイズなど) などの反省点なども挙がったため、次回出展する際には検討できるようにしたいなと思っております。 セーフィーでは継続的にエンジニアへ向けた認知拡大活動を行なっており、各種イベントへの出展・登壇や、このテックブログへの記事執筆もその活動の中の一つです。 今後も継続的な発信を行なっていきますので、よろしくお願いいたします。 セーフィーではプロダクト開発に関わる職種を積極採用中となりますため、興味を持たれた方はぜひ会社概要や募集職種なども見てみてくださいませ! speakerdeck.com open.talentio.com
アバター
メリー・クリスマス、セーフィーCTOの森本です。 こちらは Safie Engineers' Blog! Advent Calendar の25日目のエントリーです。 時間の経つのは早いもので、当社も少し前まで数十人でバタバタやっていたように感じていますが、それが今では400人を超え、いよいよ創業10年目に突入しました。 まだまだやりたいこと、やらなければならない事が山積みでそのために更に大きく成長していく必要も感じていますが、現在の姿も過去からの積み重ねですので、今後を考える上でもまずは年末のこのタイミングで今までの歴史を振り返ってみたいと思います。 はじめに 暗黒時代 エンジニア数〜10名 2014年〜2016年 2017年〜2018年 闇からの夜明け エンジニア数〜30名 2018年〜2020年 未来への飛躍 エンジニア数30〜100名 2021年、2022年 2023年 今後に向けて まとめ 最後に はじめに 上記は創業してから今日までのエンジニア人員数、対応カメラの台数、主要プロダクトを表した図です。 カメラ台数は2015年のサービス開始から2019年あたりまで中々伸びず苦しい時期を過ごしましたが、2019年頃からカメラ台数が一気に増加、 2021年〜2022年に一時的に成長が鈍化し、2023年から再度成長軌道に乗っています。 エンジニア人員数はと言うと、上記に合わせたわけではありませんが、同様に2019年ごろまで極めて少ない体制でやっており、少しづつ増加したものの大きな伸びはなく、2022年頃から大きく増員しています。 尚、全体に渡ってコンスタントにプロダクトを出せていますが、チャレンジングなプロダクトが出せるようになったのは2018年からという感じになっています。 engineers.safie.link 暗黒時代  エンジニア数〜10名 2015年5月に満を持してサービスを開始しましたが、中々カメラが売れずビジネス面で苦戦、且つプロダクトの品質もイマイチな状況でとにかくもがき苦しんだ時期でした。 本当に二度と戻りたくないです。 開発体制としてはエンジニア数名〜十名程度なので組織という概念も無く全員で日々のバグ修正や改善、新機能開発を同時に進めていました。 必然的にリポジトリはアプリケーション、バックエンド/インフラ、デバイスには分離しているものの、それぞれが単一のリポジトリで構成されていました。 当時はスピード感という観点でこの状態が最もやりやすかったのは事実です。後々この構成がボディーブローのように効いてきますが。 新機能開発も完全にエンジニアドリブンで、ほぼエンジニアだけでプロダクト開発を進めていました。 上記の通りの品質でしたが、とにかく出すことを優先して活動していました。 2014年〜2016年 創業メンバー2名(プラス業務委託2名程度)で全領域を回していました。 全システムや仕様が頭には入っていたのでテストの仕組みも薄い感じで、簡単に正常系と異常系をテストしてリリースしていました。(今だと考えられないです) 2017年〜2018年 ようやくエンジニアが増え始め、各領域に担当者を立てる事が出来ました。 またバックエンド/インフラは複数人体制(と言っても2名だが)が取れるようになりました。 闇からの夜明け  エンジニア数〜30名 2018年〜2020年 プロダクト、サービス品質の安定化、機能拡充、新商品の提供により急激にカメラの販売台数が伸び始めました。 エンジニア数の増加、プロジェクト数の増加に伴い、問題が発生するプロジェクトも散見されるような状態となり、エンジニアで全てを回していく体制から脱却し、PM/PdM専任者の配置を行いプロジェクトの安定化を図りました。 エンジニアも全員で保守運用や基本サービスの拡充と新プロダクト、プロダクトの開発を同時に行う体制から、分離して開発に当たる体制へ移行していきたいと組織変更を行いました。 エンジニアの総数が圧倒的に少なかったので、結果的に職能で分かれるような形になってしまいリポジトリは今までの構成をそのままキープして進めていました。 残念ながらサービス終了となってしまったものもありますが、様々なプロダクトを上記体制で世に送り出してきましたが、よくやれたなと、驚き、反省とともに振り返っています。 SafieVisitors(サービス終了済み) SafieEntrance(サービス終了済み) 未来への飛躍  エンジニア数30〜100名 2021年、2022年 事業成長とともに2021年の9月にIPOを行いました。 ビジネスが順調に成長し、様々な業界の顧客課題の深堀りを進める中、業界毎に課題が異なる為画一的な対応では厳しく、業界ごとに対応を分けていく必要があるということを痛感しました。 元々会社規模が拡大する中で、職能型組織では今後のスケールに耐えられないのではという思いもあり、2022年BU制度を採用し、業界軸に沿った組織体制に変更を行いました。 各BUにはPM/PdM、エンジニアも配置し、それぞれが単独で顧客とする業界向けのプロダクト開発を進めて行けるようにとの狙いがありました。 同時に認証基盤、デバイス管理基盤、録画配信基盤など共通部分はPUとして横串で配置することにより、業界ごとのプロダクト開発を進めつつ、基盤部分の強化を並列で進めたいという想いもありました。 例によってエンジニア数は十分ではありませんでしたが、採用の強化で対応して行くという事も考えていました。 残念ながら以下ような課題があり、組織として再検討が必要な状態となってしまいました。 採用が思ったとおりに進まずエンジニア数が不足したままとなってしまいました。(以下の理由により比較的経験のあるメンバーしか採用出来なかった事も原因だと感じています。) 組織や担当プロダクトを分割したものの、プロダクトの根幹にあるシステムやリポジトリが適切に分離されておらず、結局複数の組織が同じリポジトリを扱う状態が変わっていませんでした。 複雑なシステムが影響して新規参入メンバーのオンボーディングやキャッチアップに多大な時間がかかってしまう状態となっていました。 システム間の連携の複雑さに起因する細かい障害も度々発生してしていました。(大きな障害に繋がらなかったのは流石と思っています。) BU制への移行を通じて、今まで成長の影に隠れていた各種問題が一気に噴出したと感じました。上記の状態を招いてしまった事を反省しつつ、同時に今後の更なるスケールを実現するためにはこれらの課題解決が必須であるとも強く感じました。 2023年 サービス、事業は上記のような組織の急拡大や変更に伴う課題を抱え踊り場を迎えつつも伸長していました。 しかし組織としての課題を放置するわけにも行かず、開発組織は一旦2021年以前の状態に戻すことにしました。 勿論当時と比べエンジニア数そのものが増加していますし、業務システムやAIアプリケーション開発、データ分析を受け持つエンジニアもいましたので、規模そのものが大分大きくなっていました。 また、ただ戻す事はせず組織課題を解決し、更なる成長を目指すためにエンジニアリングオフィスを設置しました。 エンジニアリングオフィスにはEM陣にも兼務してもらい、以下の取組を進めて行きました。 採用、育成強化(イベントなど認知向上活動も) オンボーディングコンテンツ整備 エンジニアスキルテーブル、キャリアパスの整備 その他組織課題全般 qiita.com また、同時にプロダクト開発に加えシステムの強化、改善も明示的にロードマップに上げ取り組みを進めました。 本記事執筆中の現在、上記の取り組みにより開発組織は安定を取り戻し再度アクセルを踏める状態に推移出来てきたと感じています。 今後に向けて 2022年の踊り場を脱却し、事業は再成長軌道に乗って来たと実感していますが、将来の更なるスケールを実現するためには、BU制移行で露呈した課題の解決が必須だと感じています。 現状のシステムは、結局過去のシステム、リポジトリ構成を多少の修正をしつつ維持してしまっている所が多く、巨大、複雑すぎるために学習効率は元より開発効率や信頼性にも影響を与えてしまっています。また単一で分割に適していない為、組織を分割したとしても結果的に一つのシステムを複数の組織で同時に扱う事となり効率化が進みません。 これを抜本的に解決しなければ、職能型組織からの脱却や開発組織の効率化、拡大など将来の大きなスケールが実現できません。 システムの刷新を行い巨大なシステムを分割し、それぞれの依存度をできる限り小さくすることにより、分割した組織でもそれぞれに対応したシステムを集中して扱う事が出来、開発効率の改善が実現出来ると考えています。 2024年は上記の システムの刷新 対応を進め、確実に一つ以上の実績を出すことを目標に進めて行きます!! システム刷新については以下の記事でも触れられています。 engineers.safie.link まとめ こうやって整理し直してみると、色々な事があったなと感じるのと前もって分割可能なシステムを想定して作っておけばよかったと感じるところもあります。 勿論当時は一刻も早くプロダクトを世に送り出しビジネスとして成立する状態を作る事が最優先だったので、それを同時に行う事は簡単ではありませんでした。 会社には成長段階でそれぞれのステージが存在し、それぞれに適した状況があるというのも理解しており、最初から全てを見越して準備しておくことが今の成長に繋がったのかという疑問もあります。 いずれにせよ大きな理想は描きつつ、失敗も繰り返しながら着実には進化してきていると感じています。 ただ、眼前には今後に向けて超えなければならない システムの刷新 という大きな壁が立ちはだかっています。 最後に システムの刷新 は難易度が非常に高い取り組みですが、今後の成長に向けて我々の意志は決まっています。 一緒に取り組んでくれる人を絶賛募集していますので、興味が有る方はご応募よろしくお願いします!!!! https://safie.co.jp/teams/
アバター
はじめに セーフィー株式会社でバックエンド開発のテックリードをしております鈴木敦志と申します。セーフィー株式会社は創業から9年経過し、サービスの拡大と開発者の増加に伴う開発生産性の問題に直面しています。この問題の解消のため、職能横断型チームの再編成とコードベースの分割によるマイクロサービス化を進めています。 セーフィーのサービス・チーム構成 セーフィーのクラウドサービスはカメラ管理および映像閲覧のほか、業務システム、AI応用サービス、開発者向け機能および入退館管理システムなどで構成されており、サーバー/Webの開発者のほかモバイル/組み込み/QAエンジニアなど様々な職種が関わっています。チームは職能別に分かれており、チームごとに複数のプロジェクト (開発案件) に参加します。 開発チーム構成 開発組織の規模が増えるにつれ、様々な問題が明らかになってきました。 機能追加・改修にはたいていWeb・サーバーなど複数職種が関わってくるため、開発に伴い複数チーム間の調整が頻繁に発生します。各チームは複数プロジェクトを並列で掛け持ちすることになり、開発リードタイムの増加と認知的負荷の増加に繋がっていました。 特にバックエンド開発において、モノリシックなサーバーコンポーネントを複数チームで共有しているためコード変更やデプロイ作業の競合が頻繁に発生していました。こういった状況に対処するためにコードベースの分割が進められていましたが、各コンポーネントのオーナーシップが不明確であるためにいわゆる分散モノリスの状態になり改善には繋がりませんでした。 これらの問題を抜本的に解消するため、職能横断型チームの再編成とマイクロサービス化に取り組んでいます。 マイクロサービス マイクロサービス とは、個別にデプロイ可能なサービスの集合体としてシステムを構成することで多人数による並列開発を効率化する手法です。今回は下記方針でマイクロサービス化およびチーム再編成を進めていきます。 マイクロサービス境界を決定: マイクロサービスの導入にはどのようにサービス境界を定めるかが重要になりますが、今回はビジネス機能により分割し、必要以上に細かく分割しないようにしています チームおよびサービス境界の分割 チームの再編成: 各チーム内で開発業務が遂行できるよう企画・QA含め機能を集約し機能横断チームを編成します コンポーネントの分割と内部APIの整備: ソフトウェアコンポーネントの機能がマイクロサービス境界をまたいでいる場合、コードベース・デプロイ単位・DBを分割します。境界をまたいで機能提供を行う場合、gRPC等で内部APIを整備します。 モジュラーモノリスの検討 マイクロサービスに関連する手法としてコードベースおよびデプロイ単位は単一のままモジュールのオーナーシップを明確化するモジュラーモノリスも知られており、マイクロサービス移行の前段階として、あるいはマイクロサービスの複雑さを回避しつつ大規模開発を行うために用いられます。弊社においてはモジュール間参照の規律を保つのが難しいと判断したため、マイクロサービスを選択しました。 既存コードベースのマイクロサービス化 既存コードベースのうち特にバックエンドのソフトウェアはAPIサーバー、デバイス接続サーバー、タスクキュー、バッチサーバー等多数のサーバーで構成され、いわゆるモノリスあるいは分散モノリスとなっています。これらのソフトウェアの機能を保ったままマイクロサービス化を進めるため、以下の手順を取ります。 既存コードベースのうちマイクロサービス境界をまたぐものをチームごとにフォーク 自チームの担当範囲外の実装範囲外を削除またはプロキシに置き換え DBへの接続権限を変更し担当範囲外のテーブルへの書き込みを禁止 マイクロサービス境界をまたぐ機能提供をAPIプロキシまたはDBの参照からgRPC等の内部APIに置き換える DBサーバーを分割 例: カメラ管理と契約・決済情報の分離 場合によっては単純なAPI分割だけではサービスの分割ができず、再設計が必要な箇所があります。 例として、セーフィーではカメラごとに顧客とSaaS契約が締結され、契約プランにより使用できる機能が異なります。契約プランはフロントエンドチームの内製する管理ツールにより操作され、また社内の業務システム (Salesforce, Zuora等) と同期されています。チームをまたいだ複雑なデータの同期が行われ、またどこの情報が元データなのかがわかりづらくなっていました。 現在サーバー内では契約情報とそれにより有効化される機能が単一のRDBテーブルで表現されていますがこれを分離し、カメラの機能有効化API/ライフサイクル管理APIを業務システムチームに提供する形で連携することで、契約情報の一元管理を実現します。 まとめ セーフィーにおけるサービス規模・開発組織規模の増大に伴う開発生産性の問題に対応するため、既存システムのマイクロサービス移行を進めています。 現在セーフィー株式会社ではソフトウェアエンジニアを採用中です。マイクロサービスアーキテクチャや開発生産性の向上についてご興味のある方はぜひともご応募ください。 https://safie.co.jp/teams/
アバター
この記事は Safie Engineers' Blog! Advent Calendar 23日目の記事です。 企画本部のマネジメントをしております植松です。 本テックブログももうすぐ丸4年を迎えます(祝!)。テックブログのごく初期に プロダクト(商品)開発の歴史 を投稿してからもSafieは様々なプロダクトを世に出してきました。 そこで、本記事ではこの4年間でリリースしてきたプロダクト群の一部紹介と、今後の展望について書いてみたいと思います。 プロダクト年表 (2020~) Safie API / Safie Developers Safie Pocket2 / Pocket2 Plus Safie One / SPDP 今後の展望 Safie API / Safie Developers 2020年迄はエンドユーザーやパートナーの方向けの製品を作っていましたが、開発者向けの初のプロダクトとして2021年2月に Safie API をリリースしました。 これにより、お客様の既存の業務ツールに我々の映像を組み込んでいただいたり、画像解析をお客様側で行っていただく、など、お客様側の使い方の幅が格段に広がりました。 元々の企画の始まりはパートナー様向けの案件があり、それは画像取得しViewerへのイベント登録をパートナー様側で画像解析して登録する、というものでしたが、我々のサービスとしては汎用化を見据え同時にストリーミングAPIを準備しました。かなりタイトなスケジュールでしたがなんとかリリースにこぎつける事ができました。このパートナー様向け機能については継続的に多くの台数使っていただいて売上高も順調に伸ばしており、今後のAPI利用の足がかりにもなったと思っています。 当初はβ版として限られたユーザにのみ使っていただいてましたが、今年頭から晴れて Developer(Safie API v2) として生まれ変わり、登録したユーザは誰でも使っていただけるようになっています。このブログでは APIの使い方 や、 APIの便利なユースケース も紹介していますので、そちらもぜひご覧いただければと思います。 Safie Developers Topページ Safie Pocket2 / Pocket2 Plus ウェアラブルカメラPocketシリーズは2020年にリリースしたPocket2 , 2023年にリリースした Pocket2 Plus と着実な進化を遂げていますので、その歴史について簡単に記したいと思います。 2020年一番の出来事は何と言っても新型コロナウイルスでした。弊社のサービスもコロナウイルス対策最前線の病院などで活用され、その事例は英国のBBCニュースでも取り上げられました。 そこに遠隔業務の切り札として満を持して2020年7月に登場したのが「 Safie Pocket2 」です。 初代Pocketは、所謂MVP(Minimum Value Product)と呼ばれる仮説検証のため、市販のウェアラブルカメラのソフトウェアをカスタマイズしたものです。静止画スナップショット機能やWebRTCによるリアルタイム双方向音声通話の実現といった新開発機能を盛り込んだ、新しいサービスでした。 私が入社した頃には既に初代Pocketは開発が終わっていましたので、伝え聞くところにはなりますが、カメラ本体に加えて無線LANルーターやモバイルバッテリーが別途必要で、ご利用いただいたユーザー様からもやはり、重い、持ち運びしにくい、というフィードバックを数多くいただいていたようです。そこで、エンジニアが、インターネットで見つけた世界中のウェアラブルカメラメーカー(英語では "body worn camera" と呼ばれます)に手当たり次第問い合わせをし、国内外のこれといった展示会を巡った末に実現したのが「Safie Pocket2」でした。 当初は3月に製造して4月販売開始の予定でした。新型コロナウィルスの脅威が日に日に高まり、外出自粛やリモート勤務が行われる中で、Pocket2をご活用いただけるはず、だったのですが、なんとPocket2の工場まで閉鎖されてしまったのです。メーカーさんが何とか頑張って工場を再開したのに部材も人も揃わない。組立が完成しても、物流が混乱しているので発送したカメラが届かない。お待ちいただいていた皆様にようやくお届けすることができたのは、3カ月遅れの7月になってしまいました。予定よりも遅れてしまいましたが、ユーザー様にはご満足いただき、さらにはテレビ東京 WBSで取り上げていただくなど、社会的にも注目される商品となっていきました。 ここで歩みをとめたわけではなく、使っていただいている中で様々な品質課題や改善要望が上がってきておりそれらを解消すべく、今年の5月に新たに Pocket2 Plus をリリース。ウェアラブルならではの「手ぶれ補正」や、音声品質改善のための「スピーカー通話」、ウェアラブルといいながらも実際には固定して長時間利用されている方も多いため「モバイル給電」などの機能を搭載。より痒いところに手が届く、使い勝手の良さを追求したプロダクトとなっています。来年は海外にも羽ばたく予定です。 Safie One / SPDP 弊社CEOの佐渡島が創業当時から何度も語っていた「かしこくなるカメラ」の実現。Safie初のエッジAIカメラである Safie One は2022年9月にリリースされましたが、このリリースも苦難の連続でした。 CC2 / CC2Lという主力プロダクトの製造会社に開発依頼し、商品コンセプト、ハードウェアデザインについてデザイナーの方とコンセプト固めをして何度もやり取りを行い、試行錯誤を行いながらデザインを決めていきました。その後試作機が届いたのですが、SoCから発生する熱問題(AIを定常的に使っていると熱暴走して停止してしまう)・カチカチ音問題(カメラ内部で音が定常的に鳴る)・IrLED反射問題(レンズの下部に反射して映像にゴーストが発生してしまう)... などなど、大きな課題から小さな課題まで膨大な件数を、1つ1つ潰していきながらプロダクトリリース。 当初の構想から足掛け2年。やっとの思いでリリースした後も安定して量産にこぎつけるまでに様々な苦労がありましたが、現在はぽん置き出来る手頃なAIカメラとしてセーフィーの主力商品となっています。 当然ながら、エッジAIカメラというからには同時並行でAIアプリも開発が必要です。 SPDP という主に店舗で使っていただくための立ち入り検知・通過人数カウント・立ち入りカウントがパッケージされたAIアプリを同時リリースしているのですが、これも途中で1度UI部分の仕様変更に伴った作り直しが入り、今の形となりました。もちろんViewerからスムーズに使える使い勝手の良さは他のアプリと同じ思想で作られています。 vimeo.com 今後の展望 まだまだ書きたいプロダクトは色々あるのですが、長くなってしまうので改めての機会とさせていただき、ここでは今後の展望を記したいと思います。 セーフィーでは「映像から未来をつくる」をビジョンに掲げ、ユーザーの課題を解決するプロダクトを創業当初から作り続けて来ました。今後は、 パートナーであるカメラメーカーをユースケースに合わせて増やしていき、カメラ、特にエッジAIカメラのラインアップや、カメラを使える環境(ネットワーク機器、SIMなど)の提供 よりユーザーの課題に寄り添ったソリューションの提供 映像プラットフォーマーとして技術をさらに活用してもらう方法をAPIやSDKで提供 といったあたりを中心に、セーフィーを使ってくださる方を増やし、セーフィーの映像を使って課題解決出来ることをどんどん増やしていきたいと考えています。 最後に、セーフィーでは新しいプロダクト、サービス、ソリューションを一緒につくっていただけるエンジニアやプロダクトマネージャーを大募集しています。面白そうだな、と思った方はカジュアルに話してみたい、で結構ですので是非コンタクトいただけると幸いです! open.talentio.com
アバター
この記事は Safie Engineers' Blog! Advent Calendar 22日目の記事です。 セーフィー株式会社でソフトウェアエンジニアをしている大場です。 2022年10月に子供が生まれました。あれから1年という月日が経ち、育児の大変さを実感してきました。ソフトウェアエンジニアという職種はPC1台とネット環境さえあれば場所を選ばないため、コロナ禍もあり、リモートワークを中心として育児と仕事の両立を図りました。 本稿では育児の中で培ってきたあやし方や寝かしつけ方の回顧録を多少のエンジニア視点を交えてまとめておきます。尚、子供によって合う合わないはありますので、必勝の攻略法は(おそらく)存在しないことをご承知おきください。 爆誕から生後3ヶ月ぐらいまで 〜6ヶ月ぐらいまで 〜9ヶ月ぐらいまで 〜12ヶ月まで 1年を振り返って 爆誕から生後3ヶ月ぐらいまで このフェーズでは如何に寝かしつけることができるか、が重要になってきます。と言うのも、生後間もない赤ちゃんは我々のように寝るという概念を知らないため、眠たくなると不機嫌になります。そして、不機嫌になることで泣いて苦しさを訴えてきます。そしてさらに眠れなくなり、不機嫌に... を繰り返す言わばデッドロックの状態に陥ります。 この状態を打破するには泣き疲れさせるか、睡眠導入ツールを活用するかになるわけですが、泣き疲れさせる場合は30分ほどで寝てくれる場合もありますが、大抵は寝てくれないので睡眠導入ツールが必須になります。 とりわけ、我が子はコリック(黄昏泣き)が酷く、生後3週間ほどから毎日のように夕暮れ時からギャン泣きしました。そこで、何か解法はないかと模索したわけです。 当時活用したツールは抱っこしながら音楽を流すことです。曲はネット界隈で話題となっている反町隆史のPOIZONです。なぜこの曲が有効なのか定かではないですが、一説によると低い音程で一定のリズムを刻んでいることが体内の音に近いということらしい。 反町隆史を交えた実際の検証記事はこちら www.oricon.co.jp 曲の長さは4分ほどですが、我が家の場合は5サイクルほど(約20分)流すと大抵は寝てくれました。寝てくれない時は追加で数サイクル流します。そして、ベビーベッドで横に寝かせた後は耳元で音量を少しずつ下げながら流します。そして次第に深い眠りに入ったら成功です。 ですが、背中スイッチ(横になった瞬間起きてしまうこと)が発動すると振り出しに戻ってしまうため、無事に寝てくれた時は奇跡と思い、一切の物音や足音をたてない忍びの心得が身につきました。 しかし、寝ている時でもモロー反射(驚いた時に手足を広げる原始反射の一つ)はランダムで発動し、これに自ら驚いて起きてしまうというどうやっても回避不能なイベントが存在します。1年を通してみてもこの時の寝かしつけが最も困難であったと思います。 〜6ヶ月ぐらいまで この頃は多少の寝返りはしつつ、テレビをなんとなく観ることができるようになりました。Prime Videoで適当な子供向けの動画を流すわけですが、ある時、特定の動画に対して笑みを浮かべていることに気付きました。 その動画のタイトルは「 卵で色を学ぶ 」で、内容は目玉のついた卵に色をつけて、「ハーイ」と叫びながら画面に迫ってくるものですが、この瞬間が好きだったようです。赤ちゃんは(おそらく)複数のオブジェクトを同時にトラッキングすることができないため、画面一杯で単一のキャラクターを映すようなシンプルなシーンの方が興味を引きやすい傾向にあるようです。 www.amazon.co.jp 一方、寝る方はというと、コリックによるギャン泣きの回数は減っていきました。しかしながら、体重増加に伴い、連日の抱っこで腰痛を発症しており、立った状態で寝かしつけることが困難になってきました。様々なツールを試行しましたが、最終的に行き着いたのは「バランスボール」でした(教えてくれた同僚の方ありがとう)。 寝かしつけの基本動作は上下に揺らすことなのですが、バランスボールがあると、座った状態で上下に大きく揺らすことができるため、腰の負担も少なく割と安定して寝かしつけを行うことができました。ただし、バランスボールから急激に立ち上がると、感覚が狂いバランスを崩してしまい、その瞬間起きてしまう恐れがあるため、体を上下に揺らしながら少しずつ立ち上がるという小技を編み出し、シームレスな移行を実現しました。 ちなみに、私が購入した製品はこちらです。実際にスポーツジムで使われており、空気もほとんど抜けないため、寝かしつけ以外でも利用価値が十分にあります。サイズですが、空気をフルで入れることはなく、空気の量で高さの調整が効くので割と大きめを買った方が良いです。 https://www.amazon.co.jp/gp/product/B00DU46YSA/ 尚、この頃までベビーベッドで寝かせていたのですが、ある日を境にベビーベッドで寝ることが一切できなくなりました。色々試行した結果、床にマットを敷いて一緒に寝たらいけそうだということで、それから床で寝るようになりました。 〜9ヶ月ぐらいまで この頃になると、つかまり立ちとハイハイができるようになりました。我が子はハイハイの速度が異常に速く、Macで作業をしているとキーボードを目掛けて脱兎の如く突進してくるようになりました。記憶力もついてきて、それまでは布団を被っていれば気付かれなかったのですが、布団をはいでくるようになり、手の届く所での作業が困難になってきました。 そんな時は例のごとくテレビで動画を観せるのですが、さすがに卵は観飽きたのか集中力が続かなくなりました。代わりに興味を示したのがPrime Videoの「 モラン 」です。ウサギのモランとヒヨコのピウピウのアニメなのですが、これが大ヒットし、映してる間はテレビにかじりつき、卵に代わる鉄板のコンテンツになりました。こうした番組を一つは見つけておくと、いざという時に大変助かります。 ちなみにですが、モランは大人も楽しめる内容で、私はグッズを買いにいくほどのファンになりました。 www.creativeyoko.co.jp www.amazon.co.jp 寝かしつけの方ですが、バランスボールは子供の成長に伴い、ボールの上で立ってしまうようになり、座った状態で寝かしつけることが困難になってきました。ですが、自分の寝床を何となく理解できるようになり、ツールを使わなくても自分で頑張って寝ようと試行してくれるようになりました。 〜12ヶ月まで この頃になると成長が著しく、歩き始めたと思ったら、いつの間にか走り出すようになっていました。疲れて床に横たわっていると、平然と踏み潰して乗り越えていきます。子供にとっては単なる障害物と認識されているのでしょう。寝かしつけに活用していたバランスボールはいつしか転がして遊ぶようになっていました。 テレビはモランが相変わらずのお気に入りですが、モランは独自のモラン語を話すため、このままでは日本語でも英語でもなく、モラン語を話す子に育つのではと危惧し、他のコンテンツも併用して観せるようにしました。 テーブルの上でMacを開いているとソファによじ登って攻め込んでくるため、最終的にキッチンカウンターで立った状態で作業していました。そうしていると、子供が脚にまとわりついてきて、奥さんと隠れんぼで遊びだします。 寝かしつけは走り回って疲れさせた後は、電気を消せば自分の力で寝てくれるようになりました。寝るまでは一緒に横になって添い寝してあげるのですが、寝返りをする際に裏拳とソバットが飛んでくるので、ガードは崩さないよう注意が必要です。 1年を振り返って 振り返ってみると、この一年は仕事も含め壮絶であり、長いようであっと言う間でした。育児は自分が想像していたよりもずっと難解であり、本稿では語られていないお風呂、ミルク・離乳食、おむつ替えなども含め、日々時間に追われていました。また、これまで通用していたことが次の日から全く通用しなくなる、ということも日常茶飯事でした。そのため、子供の成長に伴い、生活サイクルを適応させていくことが常に求められました。 仕事との両立面では、子供が誕生してからは子供を中心とした生活に激変しました。それまでは、好きな時に開発することができましたが、それは不可能になりました。特に生後間もない頃は育児で1日が終わってしまう、という日も多々ありました。 そのため、限られた時間を如何に有効活用し、効果を最大限に発揮するか、という意識が強く芽生えるようになりました。また、何より、家族の支えにより、仕事に集中できる時間を捻出できるようになったことで、育児と仕事を両立しながら、次第に以前と変わらぬパフォーマンスを発揮できるようになった点も大きいです。 これらの経験を通じて感じたことは、 育児と仕事を分業するのはあまり効率的ではなく、大切なのは互いに助け合う意識を持ち、互いの負荷を減らすことを常に意識する、それが結果的に高いパフォーマンスの維持に繋がるということです。 思考の面では、エンジニアは物事を論理的に考え、必ずそこに解があると信じて止まない生き物ですが、子供の成長を通じて、イレギュラーケースに対してはそういうものなんだな、と思えるようになり、これまで自分が持ち合わせていなかった考え方ができるようになりました。 総括すると、育児を通して、自分自身もまだまだ未熟であると再認識することができ、今年1年は人生の中でエンジニアスキルとは別に、人として大きく成長できた1年になったかなと思います。 ※ご紹介した番組はPrime Video以外にYoutube等でも配信されていますので、お好きな配信サイトをご利用ください。
アバター
この記事は Safie Engineers' Blog! Advent Calendar 2023 の21日目の記事です。 はじめに セーフィー株式会社で画像認識AIの開発エンジニアをしている木村(駿)です。主に、エッジAI搭載カメラで動作する人やモノを検出するアプリケーションの開発を行っています。 このAIカメラを使って、弊社では指定したエリアへの人の侵入を検知するサービスや混雑度合いを数値化するために滞留人数をカウントするサービスなどを提供しています。 これらのサービスが、必要な基準や要件を満たしているかどうかを確認するため、QA (Quarity Assureance) 評価を実施しています。具体的には、AIの検出性能や追跡性能のみを評価するのではなく、実際にユーザーが見る値(侵入検知のフラグやカウント数など)が正しく機能しているかを評価します。 このQA評価をするには評価用データ、つまりアノテーションされたデータが必要になりますが、動画のアノテーションデータの作成は非常に大変です。仮にFPS30の10分の動画に対してアノテーションデータを作ろうとすると、1フレームごとに5人映っていれば 30fps×600sec×5=90000個の矩形を手作業でつける必要があります。バリエーションを作ろうと思うと複数の動画のアノテーションデータが必要で、さらに大変です。 そこで、CGで映像を作成し、自動でアノテーションデータを作成できないかと考えました。 今回は、ゲームエンジンであるUnityを使ったCG映像の作成方法と、CGの人物に対して人検知を行った結果および簡易的なQA評価の結果をご紹介します。 はじめに 利用したツール Unity Perception Package CG映像の作成方法 背景を設定 移動経路と動作を設定 人検知の確認 QA評価 まとめ 利用したツール Unity CG映像の作成にはUnityを利用しました。Unityは米国のUnity Technologiesが提供するゲームエンジンで、ゲームを簡単に作るための支援をしてくれる統合開発環境(IDE)です。3Dや2Dのグラフィックスを扱いやすくし、ゲームだけではなく映像の作成にも活用されています。 使用したUnityのエディタバージョン:2021.3.29f1(LTS) Perception Package CGの人物に自動でアノテーションを付与するため、Unityが提供するPerception Packageを利用しました。 Perception Packageは、画像認識モデルの訓練や検証に利用するための大規模なデータセットを生成するためのツールです。以下の動画ように、物体検出タスク向けのBounding Box 2D、セグメンテーションタスク向けのSemantic Segmentation や Instance Segmentation、姿勢推定タスク向けのKeypoint など様々なアノテーションデータを作成できます。今回は人検出なので、Bounding Box 2Dを利用します。 使用したバージョン:1.0.0 CG人物の生成 Perception Packageでは人物の生成が可能です。上記でアノテーションされている人物もアセットではなく生成された人物で、[ 性別、年齢、人種、身長、体型、服装 ] のパラメータをランダムに振って生成することができます。 生成した人物は、通常のUnityのアセットと同じようにC#Scriptやアニメーションなどのコンポーネントを付けることができるため、作成したい映像に合わせて動作させることができます。 CG映像の作成方法 次に、CG映像の作成方法を説明します。ここでは、弊社のAIカメラの機能であるエリア内に滞留している人数をカウントする機能のQA評価をするため、例としてスーパーのレジ待ちで並ぶお客さんを想定したCG映像を作成します。 背景を設定 まず、背景画像を設定します。今回は簡易的に静止画像を背景として、その前景に人を動かす仕様にしました。Unity上の設定方法としては、Camera Object の子オブジェクトとしてPlane Object を作成し、Plane Objectに背景画像を貼り付けます。そして、Camera Object と Plane Object の間で人を動かすことで、画像の前景として人が動く映像を作ります。 今回はこちらのスーパーのレジ付近の画像を背景に設定しました。人を左から右へ歩かせ、☆を先頭にして赤で囲んだ範囲に並ばせようと思います。 移動経路と動作を設定 人物の移動経路や動作を制御するアルゴリズムをC# で実装しました。 ① 画面左側に人オブジェクトを生成し、上図の☆の位置を目標地点として左から右へ歩かせます。 以下のスクリプトで人オブジェクトを現在位置(transform.position)から目標地点(targetPosition)に向けたベクトル (moveDirection)方向へ移動させます。targetPositionはUnityのUIで入力できるよう、public 変数にしています。 public Vector3 targetPosition; public float moveSpeed; Vector3 moveDirection = targetPosition - transform.position; Vector3 deltaMovement = moveDirection.normalized * moveSpeed * Time.deltaTime; transform.position += deltaMovement; ② 目標地点に到達もしくは前に別の人がいたら立ち止まらせます。 以下のスクリプトで前方に人がいるかを判定します。前方のみの当たり判定を得るため、ColliderではなくRaycastを使用しました。 Ray ray = new Ray(transform.position, transform.forward); RaycastHit hit; hasFrontObject = false ; if (Physics.Raycast(ray, out hit, 0.5f )) { hasFrontObject = true ; } 目標地点に到達もしくは前にオブジェクトがあれば停止し、そうでなければ前進させます。 if ( ! hasReachedTarget) { Vector3 moveDirection = targetPosition - transform.position; // 目標地点に到達もしくは前にオブジェクトがあれば停止 if (moveDirection.magnitude > 0.5f && ! hasFrontObject) if (moveDirection.magnitude <= 0.5f || hasFrontObject) { // 停止 transform.position = transform.position; hasReachedTarget = true ; } else { // 前進 Vector3 deltaMovement = vec.normalized * moveSpeed * Time.deltaTime; transform.position += deltaMovement; } } ③ 列に並んだ人オブジェクトは、しばらく時間が経過したら右を向いて再び歩きだしフレームアウトさせます。 if ( ! hasReachedTarget) { ~~~~ // 手順2の処理 ~~~~ } else { // 目標地点に達したので、一定時間が経つまで待ちます。 stayDuration += Time.deltaTime; if (stayDuration < stayTime) { // 一定時間経つまでその場で停止 Vector3 moveDirection = targetPosition - transform.position; } else { // 一定時間が経つと右に体の向きを変えて、再び前進 if ( ! hasRotated) { Vector3 exitDirection = transform.right; Quaternion rotation = Quaternion.LookRotation(exitDirection); transform.rotation = rotation; hasRotated = true ; } Vector3 forwardDirection = transform.forward; walkForward(forwardDirection); } } 以上のアルゴリズムを用いて作成した映像がこちらです。描画されているバウンディングボックスは自動作成されたアノテーションです。 期待通り列を形成してレジを待っている感じの動画が作られています。 人が宙に浮いている感は否めないですが、人検出モデルの性能を評価する分には問題なさそうです。 最後に、QA評価を行う前に、このCGの人物をAIが人として認識してくれるか確かめてみます。 人検知の確認 作成した映像に対してYolov5sで推論したところ、ちゃんと人として検出してくれました! この生成と動作を繰り返す映像を作成すれば、レジに並んだ人数を正しくカウントできているかのQA評価ができそうです。 QA評価 上記の作成方法をベースにレジに並ぶ人数を変動させて30分ほどの映像を作成しました。この映像を用いて、カウントが正しくできているか評価します。カウントは人検出したBBOXの中心が10秒以上エリア内に留まった場合に行い、カウント数は1分ごとに集計しました。 以下のグラフが結果で、横軸は時間、縦軸が1分あたりのAIによるカウント数とGTです。GTはアノテーションデータを用いてカウントした数です。 30分の映像の中で集計間隔ごとの誤カウント数が3、未カウント数が2と、かなり正確にカウントできていることが分かります。 22分あたりの誤カウント+2の原因を映像から探ってみると、背景と同系色の服を着た人物の検出が途絶え、同じ人物を複数回カウントしていることが原因でした。実際のQA評価では背景やカメラアングル、人数などのパラメータを変えてバリエーションのあるデータセットで評価しているので、誤カウントや未カウントを起こしやすい環境や条件などをさらに詳しく分析できました。 まとめ Unity Perception Packageを使って作成したCG映像でQA評価ができることが分かりました。 物体検出タスク以外にも、セグメンテーションや骨格推定などのタスクにも利用できますので、ぜひ使ってみてください。
アバター
この記事は Safie Engineers' Blog! Advent Calendar 20日目の記事です 自己紹介と本日のテーマ はじめまして、セーフィー株式会社でSafieの映像サービスを支えるSaaSプラットフォーム プロダクト群のPdMをマネジメントしているマネージャーの光田です。 本日はPdM組織のマネジメントについて、発生した課題と解決のために取り組んでみた施策についてお話しさせていただきます。 自己紹介と本日のテーマ 前提 発生した問題 どうやって解決したか まとめ 前提 PdMの業務は会社によって大きく変わりますが、SafieのPdMの業務分掌は以下となります。 市場調査、企画提案、収益試算 要求定義、要件定義 KPI設計、プロダクト改善 主にWhy/Whatの整理と関係各部署との調整がPdMの仕事、How/Whenの整理と実現がPMの仕事となります。ほとんどのプロダクトでPMMロールをおいていないので、PMMの仕事もPdMの役割に入ってきます。 また、私がマネジメントしている組織はPdMのみ6名で、 映像を閲覧するSafie Viewer カメラの権限などを統合管理するSafie Manager の2つのユーザー向けプロダクトと、これらの情報を制御するプラットフォームシステム(録画日数の制御や、カメラの稼働状況監視、録画プランやオプションなどの管理)のプロダクトマネジメントを実施しています。 発生した問題 PdMのみの組織をマネジメントするようになって3ヶ月程度が経過したところ、1on1などを通じていくつかの課題がメンバーから共有されるようになりました。 PdMのカバー範囲が広すぎる Safieは一般的なSaaSサービスと異なり、カメラやルータなどのハードウェアも扱っています。PdMはビジネス、ハードウェア、ソフトウェア、在庫・物流オペレーションも把握した上でプロダクト仕様について意思決定しなければならず、PdMとして活躍できるようになるまでの学習コストがとても高い状態でした。 個人事業主の集まりのようになってしまう PdMは各々が担当しているプロダクトの成果を最大にするために、プロダクトチームのマネジメントに注力します。するとPdM同士がコミュニケーションをとる機会が減ってしまい、組織としての連帯感が生まれない状況が発生しておりました。 PdM自身が休めない プロダクトに対する意思決定が迅速にできるよう1プロダクト1PdM体制にしていました。体調不良などで予定外の長期休暇を取るとプロダクト進捗が遅れてしまうため、多少無理をしてでも業務を実施せざるを得ないケースがありました。また、PdMの退職でノウハウが引き継がれず、過去の経緯がわからず右往左往するケースがありました。 PdM自身で全て抱えて解決しなければならず、孤独を抱えて業務をこなしていることが明らかとなり、対策を講じることにしました。 どうやって解決したか さまざまな手段をメンバーとともに検討し試してみましたが、最終的に有効な解決策は 『全てのプロダクトにおいてPdM2名体制でプロダクトマネジメントを行う』 ということでした。 PdMは幅広い知識や経験が必要になりますが、それぞれの得意分野があることが多いです。 営業職や事業開発職からPdMになったメンバーはビジネスが、ソフトウェアエンジニアやPMであればソフトウェア開発にキャリアの軸を持ってPdMになっています。 SafieのPdMもそれぞれの得意分野があり、逆にそれ以外の部分については入社後にキャッチアップが必要でした。 PdM2名体制を標準にすることで、ビジネス / ハードウェア / ソフトウェア / オペレーションについて得意分野が異なるメンバを組み合わせてアサインし、相互に教え合う体制を作ることができ、全てに対して知見がなくともPdMとしてプロダクトマネジメントに取り組むことができるようになりました。 また、2名でプロダクトを見ることでお互いの連帯感が高まり、PdMが休めない状況も解決しました。とても単純なこの施策ですが、1名体制だった時よりも安定感が増しプロダクト改善も以前より進むようになりました。 まとめ 現在は上記の施策に加え、組織の連帯感を高めPdMとしての成長を加速する目的で、週次でPdMに関するオンライン講座を受講し、学習内容をサマリーして輪番で発表する会を運営しています。 講座の内容に関連する悩みをメンバーに共有したり、自分だったらこうする、などのノウハウを共有することで、お互いを高めあういいサイクルが出来上がっています。 これからも色々な施策を試しながら、チームの成長に繋げていけたらと考えています。 もし同じ悩みをお持ちのPdM組織のマネージャーがいらっしゃいましたら、参考にしていただけると嬉しいです。 最後まで読んでいただきありがとうございました。
アバター
この記事は Safie Engineers' Blog! Advent Calendar 19日目の記事です こんにちは。2023年3月にセーフィーにジョインした入社一年目のQCDグループの森重です。 入社してからはカメラの対応機種拡大やマイページ、モバイルアプリと様々な領域・プロダクトにQAとして携わっていて、テスト設計や手動テストの実行だけでなく自動テストにも取り組んでいます。 この記事ではQCDグループで取り組んでいる自動化、特にMagicPodに焦点を当てて、QCDグループがMagicPodでどんなテストを自動化しているかをお話できればと思います。 MagicPodについて 自動テストの目的 自動テストのスコープ 新機能:ヘルススコア メリット/課題 最後に MagicPodについて そもそもMagicPodとはなんぞや?という方もいると思うので、簡単にツールについて説明します。 MagicPodは有償のテスト自動化ツールでWeb・モバイルに対応したE2Eテスト自動化ツールとなっています。 他の自動テストツールと比べて優れていると思うのは、自動テストの実行回数に制限がない点、メンテナンス性を確保し易い点です。 実行回数に制限がないので、毎日自動テストを実行したいプロダクトに向いているツールと言えます。 自動テストの目的 次にQCDグループで自動化に取り組んでいる目的ですが、これはテスト実行回数増加と広範囲化でバグの早期検出とバグ検出件数増加のためです。 MagicPodに決まったテストを毎日実行させることでエラーを早期検出し、今までリスクベースで切り捨てていたテストもやってもらうことで、カバーできる範囲を増やすようにしています。 自動テストのスコープ 対象のテストは、ずばりリグレッションテストになります。 テスト対象の変更が少なくメンテナンス性の確保が比較的容易なことが大きな理由です。逆にE2Eテストで自動化しないほうが良いのは頻繁に変更が入る新規機能だったり、変更が入りやすい画面を対象としているテストケースです。 実際の画面をお見せするとこんな感じです。失敗しているテストケースもありますが、定期実行して毎日結果をモニタリングしています。 メンテナンスはチームで毎日持ち回りで担当していて、日々やっているという感じです。 新機能:ヘルススコア 少し話はそれてしまいますが、先日のMagicPodのメジャーバージョンアップで追加されたアナリティクスで自動テストのヘルススコアを算出してくれます。参考までにマイページのヘルススコアを紹介しておきます。 マイページ マイページではヘルススコア89です。このプロジェクトはQCDグループの中では後発で自動化を勧めており、パイロットプロジェクトでの運用経験からメンテナンス性を重視したテストケース作成をしています。 特にMagicPodの機能の中でも共有ステップ・変数は様々なテストケースの中の手順を一つの共有ステップで集約することで、テストケースのメンテナンス性・可読性が狙えるため積極的に活用しています。 メリット/課題 一通りテスト対象や運用については書き終わりましたのでMagicPodを自動テストに取り入れたことで感じているメリットと使い続ける上での課題についてもここに書いておきます。 MagicPodを導入したことで感じたメリット 早期バグ検出に役立つ テスト環境を広げたことによるバグ検出ができた メリットですが、こちらはMagicPodを導入した当初の狙い通り、バグ検出関連の恩恵が大きいです。 MagicPodを使い続ける上での課題 テストケースの作成 テストケースの属人化 テストケースの管理・メンテナンス 共有テストケースの活用不足、メンテ工数増 自動テストの実行結果をTesrailに自動でインプットできていない テスト分析 データ分析用の基盤の立ち上げができていないのでスピード感を持った分析が困難 教育 教育資料がなく、ジョインされた方も自己流で学ぶ必要がある 一方課題ですが、MagicPod利用上のルールやそのデータを活用できていないことが多く挙げられています。今後の改善活動として取り組んでいく予定です。 最後に 以上、QCDグループで使っているMagicPodについて紹介させていただきました。 当初狙っていた効果は一定得られているものの、まだまだ課題が残っていますので、継続して改善が必要そうです。 MagicPodにとどまらずテスト自動化について今後もテックブログで発信していきますので、よろしくお願いします。
アバター