TECH PLAY

アプトポッド

アプトポッド の技術ブログ

248

映像データでデータ分析したいみなさん、 こんにちは、システムアーキテクトの伊勢です。 特定用途で映像を解析するAIモデルを作るのは大変です。 本記事では、映像をそのまま解析するのではなく、 生成AIでフレームをテキストに要約し、CSVとして分析するアプローチを紹介します。 はじめに 映像のAI解析 コンセプト 映像フレームのテキスト要約 全体構成図 インストール クライアントライブラリ OpenAIパッケージ やってみた ローカル線の車窓映像 分析課題 プロンプト データ分析 雪国の市バス映像 分析課題 プロンプト データ分析 起動オプション サンプルプログラム説明 要約入力データ量・頻度削減 グリッド画像生成 JPEG圧縮率指定 キューサイズ上限指定 トークン数超過判定 OpenAI問い合わせ アップストリーム おわりに リンク はじめに 映像のAI解析 本ブログでは、事前学習したAIモデルでの物体検出をご紹介しました。 SDK入門④でYOLOで物体検出・矩形描画・人数カウントしています。 tech.aptpod.co.jp ただし、これらのAIモデルは事前学習による開発が必要です。 軽めのPoCなど利用シーンに応じて、本格的な専用モデル開発を伴わず、対象物の検出や文字読み取りが実現できると便利です。 コンセプト 本ブログでは、計測結果をChatGPTでデータ分析してきました。 tech.aptpod.co.jp tech.aptpod.co.jp 数値や文字列をCSVファイルでChatGPTに与え、検索や統計ができます。 しかし、映像はバイナリデータであるため、単純に同じ方法は使えません。 そこで、映像フレームをテキストデータ化してCSVでデータ分析してみます。 映像フレームのテキスト要約 映像フレームをAIモデルに要約・記述させ、テキストデータに変換します。 1 OpenAIの chat.completions APIに 映像フレームをBase64エンコードしてリクエストします。 qiita.com 生成AI活用で重要なのは、すべてを送るのではなく「何を送らないか」の設計です。 精度・安定性のため、映像フレームを間引き・集約してデータ量と頻度を抑えます。 2 # 工夫 効果 1 映像フレームを縮小 データ量低減 2 前フレームとの差分が一定以上のときだけ 頻度低減 3 4×4グリッド画像に時系列圧縮 頻度低減 4 JPEG圧縮 データ量低減 5 リクエスト待ちを2件までに制限 頻度低減 6 トークン数超過したらスキップ 頻度低減 これらは前処理が必要なのでintdash SDKを使って実装します。 3 全体構成図 intdash リアルタイムAPIからダウンストリームした映像フレームを グリッド状に並べた画像と要約結果をアップストリームします。 要約結果は、あとでCSVでダウンロードして計測全体の分析を行います。 全体構成図 インストール クライアントライブラリ REST API、リアルタイムAPIの使い方は SDK入門④ と同じです。 OpenAIパッケージ 追加でOpenAIライブラリをインストールします。 pip install openai やってみた ローカル線の車窓映像 分析課題 2024年11月、房総半島を走る久留里線の一部区間(久留里駅~上総亀山駅間)が廃止されると発表されました。 利用客の激減による不採算が理由だそうです。 www.sankei.com 実際に乗車してきました。 4 木更津駅を出発して、廃線予定の久留里駅〜終点上総亀山駅までを走る区間です。 5 計測と要約結果を可視化しました。 6 youtu.be プロンプト 画像を要約させるプロンプトを chat.completions APIに渡します。 あなたは列車のカメラ映像(4x4グリッドの時系列画像)を要約するAIです。 遠景を含む、人物・居住・移動・インフラ管理から人間生活の兆候を検出します。 出力は必ず以下のJSON形式で返してください。 { "person": 人物の数 int, "group": 集団の有無 無/有, "view": 視野 無/狭/中/広, "area": エリア種別 市街地/住宅地/農地/草地/山野 etc, "building": 住宅密度 無/疎/密, "road": 道路幅 無/細/太, "infra": 設備の整備度 無/荒/整, "text": 画像から読み取れる文字(グリッド左上の時刻は除く), "description": 見える風景の説明 } 有効なJSONのみを出力してください。 このようなJSONが返ってきます。 7 { " person ": 3 , " group ": " 無 ", " view ": " 広 ", " area ": " 市街地 ", " building ": " 疎 ", " road ": " 太 ", " infra ": " 整 ", " text ": " 28 ", " description ": " 市街地の道路沿いにある建物と車両、バイクが見える風景。周囲には草木が生えている。 " } リアルタイムで要約した結果をintdashにアップストリームします。 python ./lesson10/src/analyze_video.py --api_url https://example.intdash.jp --api_token < YOUR_API_TOKEN > --project_uuid < YOUR_PROJECT_UUID > --edge_uuid < YOUR_EDGE_UUID > --openai_key < YOUR_OPENA_KEY > プログラム起動 Data Visualizerでリアルタイム可視化 以下の項目が表示されています 入力データ:画面左上 GNSSデータ H.264映像 出力データ:画面中央・右側・下側 プレビュー画像:グリッドを埋めている途中の画像(JPEG) 要約対象画像:グリッドが埋まって要約対象になった画像(JPEG) 要約結果:JSON内の項目 データ分析 それでは、分析を行います。 課題 ローカル線の利用者数はそんなに少ないのか。 Data Visualizerでデータを選択してCSVファイルをダウンロードします。 intdash Motionアプリ GNSSデータ グリッド画像の要約結果 JSONの各項目 https://example.intdash.jp/vm2m/?playMode=storedData&startTime=2026-01-12T14:54:44.000%2B09:00&endTime=2026-01-12T16:05:04.000%2B09:00 CSVダウンロード CSVファイル ChatGPTにCSVファイルをアップロードして、基本情報を確認します。 ローカル線走行中のGNSSデータ、カメラ映像の要約結果のCSVデータをアップロードします。 GNSSデータ(緯度経度、速度、高度、方位)、要約結果(人物数、集団有無、視界の広さ、エリア種別、建物密度、道路広さ、インフラ整備度、文字起こし、説明)が含まれます。 課題:ローカル線の利用者数はそんなに少ないのか。 のためにデータ分析を行います。 以下を確認してください。 - 項目の一覧 - データ行数 - タイムスタンプ範囲 データの確認 データ項目を整えます。 データを整えてください。 - 見やすさのため、項目名の"GNSS_"と"@"以降を削除してください。 - 空の値が含まれます。GNSSデータについては直前の値を使用して穴埋め(forward fill)してください。 また、 - 分析結果のグラフは英語で、本文は日本語で表記してください。 データクレンジング まずはシンプルに聞いてみます。 検出した人数の最大値、集団を検出したか、 を教えてください。 人物・集団検出の結果 グラフや地図で可視化してみます。 人数が 0人 / 1–2人 / 3人以上 の時間割合 集団が 無 / 有 の時間割合 をそれぞれグラフにしてください。 人数、集団有無の割合グラフ GNSSの緯度経度データを、PythonのFoliumを使って以下をプロットしてください。 以下を可視化してください。 ・人物検出箇所・人数 ・集団検出箇所 人数・集団の地図プロット 検出されたのはいずれも木更津駅発の前半区間であり、廃線予定の久留里駅以南では人物・集団が見られません。 8 ただし、誤検出の箇所があり、精度は専用AIモデルに劣りそうです。 墓石を人間と間違えてそう 今度は地図上に、エリア種別を色分けしてプロットしてください。 エリア種別 赤 :市街地 オレンジ :住宅地 緑 :草地 濃緑 :農地 青 :山野 久留里駅以南ははっきりと山野が多いのがわかります。 9 同じく、各要約結果をプロットしました。 建物密度 赤 :密 濃緑 :疎 青 :無 道路幅 赤 :太 濃緑 :細 グレー :他 インフラ整備度 濃緑 :整 黒 :荒 グレー :無 文字起こしの精度も見てみます。 文字起こしできた文字列をリスト化してください。 文字起こしリスト 沿線に看板などがそれほど多くはないようです。 複数行の場合は改行コード付きです。 JPEGの画質を落としているため、"俵田"駅の誤字が目立ちます。 結論 1回の計測では久留里駅以南で"混雑"を確認できなかった。 一方で、沿線環境の特徴を読み解くのには有効な情報が得られた。 車窓から広がる自然の眺めは、思わず時間を忘れてしまう魅力がある。 雪国の市バス映像 プロンプトを差し替えて別のデータ分析を行います。 北海道 千歳市街〜新千歳空港までのバス映像です。 分析課題 2026年1月13日、寒波到来により、積雪による交通麻痺等が懸念されていました。 www.uhb.jp 実際に行って確認してきました。 10 千歳市街でバスに乗り込み、新千歳空港まで走ります。 11 計測と要約結果を可視化しました。 12 youtu.be プロンプト プロンプトをこのように差し替えます。 あなたは豪雪地帯の市バスのカメラ映像(4x4グリッドの時系列画像)を要約するAIです。 交通・積雪・除雪状態から生活の支障具合を検出します。 出力は必ず以下のJSON形式で返してください。 { "person": 人物の数 int, "vehicle": 車両の数 int, "view": 視野 無/狭/中/広, "area": エリア種別 市街地/住宅地/郊外/トンネル/空港 etc, "depth": 建物・樹木・車上の積雪 無/少/多, "sidewalk": 歩道の除雪 未/中/済, "pile": 路肩の堆雪 無/少/多, "text": 画像から読み取れる文字(グリッド左上の時刻は除く), "description": 見える風景の説明 } 有効なJSONのみを出力してください。 このようなJSONが返ってきます。 { " person ": 1 , " vehicle ": 1 , " view ": " 中 ", " area ": " 市街地 ", " depth ": " 多 ", " sidewalk ": " 中 ", " pile ": " 多 ", " text ": " 消火栓 ", " description ": " 雪が積もった市街地の道路に、歩道と車道があり、人の人物と1台の車が見える。 " } プロンプト設定ファイルを指定してプログラムを起動します。 python ./lesson10/src/analyze_video.py --api_url https://example.intdash.jp --api_token < YOUR_API_TOKEN > --project_uuid < YOUR_PROJECT_UUID > --edge_uuid < YOUR_EDGE_UUID > --openai_key < YOUR_OPENA_KEY > --prompt_path ./lesson10/config/snow.txt プロンプト差し替えで起動 プロンプト差し替えで可視化 データ分析 分析を行います。 課題 豪雪地帯の除雪は行き届いているか。 Data Visualizerでデータを選択してCSVファイルをダウンロードします。 CSVダウンロード CSVファイル ChatGPTにCSVファイルをアップロードして、基本情報を確認します。 豪雪地域の市バス走行中のGNSSデータ、カメラ映像の要約結果のCSVデータをアップロードします。 GNSSデータ(緯度経度、速度、高度、方位)、要約結果(人物数、車両数、視界の広さ、エリア種別、建物・樹木・車上の積雪状況、歩道の除雪状況、路肩の堆雪状況、文字起こし、説明)が含まれます。 課題:豪雪地帯の除雪は行き届いているか。 のためにデータ分析を行います。 以下を確認してください。 - 項目の一覧 - データ行数 - タイムスタンプ範囲 データの確認 データクレンジング 今回はいきなりグラフ化してみます。 歩道の除雪状況の割合 をグラフで表示してください。 歩道の除雪状況グラフ 文字化けしていますが、左から 未 / 中 / 済 の順で並んでいます。 済 と判断されたのは40%以下、 未 は50%超です。 ただし、除雪後も完全に歩道が見えないケースでは画像で白く見えるため、判断の精度は高くなさそうです。 除雪状況が判断が難しそうなケース もう少し詳しく見てみましょう。 GNSSの緯度経度データを、PythonのFoliumを使って以下をプロットしてください。 以下を可視化してください。 ・歩道の除雪状況 歩道の除雪状況のプロット 赤 :未 オレンジ :中 濃緑 :済 グレー :他 市街や交差点、空港構内はおおむね除雪率が高そうです。 他の項目も可視化してみましょう。 今度は地図上に、エリア種別を色分けしてプロットしてください。 市街地/住宅地/郊外/トンネル/空港 etc で色分けしてください。 エリア種別 赤 :市街地 青 :住宅地 濃緑 :郊外 紫 :トンネル オレンジ :空港 ピンク :他 エリア種別ごとの除雪率を確認してみます。 エリア別の除雪状況 未/中/済 を積み上げグラフ化してください。 グラフ内の文言は英語に訳して表示してください。 エリア別除雪状況 空港と判断されたエリアは除雪率が80%超、市街地は部分的も含めると70%弱です。 郊外の除雪率は20%に留まっており、歩行者が多いエリアを優先して除雪されているのがわかります。 トンネルはそもそも積雪がありませんが、除雪も 未 と判断されています。 トンネル内は 積雪:無 かつ 除雪:未 他の観点でも見てみましょう。 路肩の堆雪と視界の広さを組み合わせて、 路肩の堆雪により、視界を確保できないと思われるポイントをプロットしてください。 視界確保リスクポイント Data Visualizerで見ると確かに該当時刻は大きな堆雪が映っており、歩道の状況がほとんど見えません。 13 該当時刻の映像 結論 除雪は限られたリソースで極めて効率的に行われていることが伺える。 大動脈である空港は"最優先"で守られている。 起動オプション --api_url required :サーバーURL --api_token required :APIトークン --project_uuid :プロジェクトUUID(省略時は Global Project) --edge_uuid required :ダウンストリームエッジUUID --dst_edge_uuid :アップストリームエッジUUID --openai_key required :OpenAI アクセスキー --prompt_path :システムプロンプトファイルパス サンプルプログラム説明 複雑に見えますが、 SDK入門④ と同じく各処理をシンプルな非同期ステージに分けて並列起しています。 クラスアーキテクチャ 要約入力データ量・頻度削減 グリッド画像生成 フレームを縮小します。 img = np.frombuffer(frame, dtype=np.uint8).reshape((self._in_h, self._in_w, 3 )) tile_img = cv2.resize( img, (self._tile_w, self._tile_h), interpolation=cv2.INTER_AREA ) 前フレームと現フレームの差異が閾値未満なら、スキップします。 ヒストグラム化して比較していますが、場合によってはもっと厳密に判定する方がいいかもしれません。 if self._prev_tile_hist is not None : d = cv2.compareHist(self._prev_tile_hist, cur_h, cv2.HISTCMP_BHATTACHARYYA) if d < self._diff_threshold: return None , False self._prev_tile_hist = cur_h 4x4グリッドに配置します。 for i, t in enumerate (self._tiles[: self._grid_size]): c = i % self._cols x0 = c * self._tile_w r = i // self._cols y0 = r * self._tile_h grid[y0 : y0 + self._tile_h, x0 : x0 + self._tile_w] = t JPEG圧縮率指定 JPEGエンコードの GStreamer パイプラインに設定しています。 QUALITY = 50 ... quality=QUALITY, キューサイズ上限指定 self.prompt_queue: asyncio.Queue[ int ] = asyncio.Queue(maxsize=chat_maxsize) トークン数超過判定 try : answer = await asyncio.to_thread(self.chatter.chat, frame) ... except RateLimitError as e: logging.info(f "RateLimitError! {e}" ) await asyncio.sleep( 0.5 ) OpenAI問い合わせ JPEG画像をBase64エンコードしてリクエストに付与しています。 MODEL = "gpt-4o-mini" ... image_b64 = base64.b64encode(image).decode( "utf-8" ) messages: list [ChatCompletionMessageParam] = [ { "role" : "system" , "content" : self.system_prompt}, { "role" : "user" , "content" : [ { "type" : "image_url" , "image_url" : { "url" : f "data:image/jpeg;base64,{image_b64}" }, }, ], }, ] resp = self.client.chat.completions.create( model=MODEL, messages=messages, max_tokens= 400 , temperature= 0.3 , response_format={ "type" : "json_object" }, ) アップストリーム 要約結果をアップストリームしています。 await self.up.write_data_points( iscp.DataID(name=self.data_name_answer, type = "string" ), iscp.DataPoint( elapsed_time=elapsed_time, payload=payload.encode( "utf-8" ), ), ) await self.up.flush() おわりに SDKを利用して映像データをテキスト要約、生成AIでデータ分析しました。 また、プロンプトを差し替えて要約内容を変更できました。 AI開発が不要になるため、スマートメンテや巡回点検PoCを試しやすくなりそうです。 今回はインターネット経由でAPIエンドポイントを使用しています。 14 生成AI利用のネットワーク遅延やトークン数に縛られないため、Ollama などの検討も有効そうです。 tech.aptpod.co.jp tech.aptpod.co.jp リンク 本シリーズの過去記事はこちらからご覧ください。 SDK入門①〜社用車で走ったとこ全部見せます〜 :REST APIでデータ取得 SDK入門②〜データ移行ツールの作り方〜 :REST APIでデータ送信 SDK入門③〜RTSPで映像配信するぞ〜 :リアルタイムAPIでデータ取得 SDK入門④〜YOLOで物体検知しちゃう〜 :リアルタイムAPIでデータ送信 SDK入門⑤〜iPadでData Visualizerを見る会〜 :リアルタイムAPIでキャプチャデータ送信 SDK入門⑥〜最速最高度で計測する日〜 : AWS LambdaでREST APIデータ送信 SDK入門⑦〜計測リプレイツールの作り方〜 : REST APIでデータ取得、リアルタイムAPIでデータ送信 SDK入門⑧〜動画アップロードツールの作り方〜 :REST APIで映像データ送信 SDK入門⑨〜動画ダウンロードツールの作り方〜 :REST APIでデータ取得、ファイル化 今回、モデルはOpenAIの gpt-4o-mini を使用します。テキストも画像も入力できるマルチモーダルモデルです。視覚精度ではVLMが有利ですが、intdashから映像以外のデータも渡せることを想定して、マルチモーダルモデルを選択しています。 ↩ 同時にOpenAIのAPIトークン消費量も低減しています。 ↩ サンプルプログラムを GitHub にて公開しています。 ↩ 2024年2月に計測を行いました。 ↩ 一度収集した計測データを SDK入門⑦〜計測リプレイツールの作り方〜 でリアルタイムで再アップストリームしています。 ↩ 1時間ほどあります。飛ばしながら見てください。 ↩ たまに 道路幅:細 のように指定から崩れる場合があります。 ↩ 進行方向右側の映像のみで判定しています。左側にホームある駅が多かったので実態より少なく検出されているかもしれません。 ↩ プロンプトで与えた例 市街地/住宅地/農地/草地/山野 の区分けに引きずられてそうです。プロンプトの与え方次第でもっと有意義な分類ができるかもしれません。 ↩ 吹雪の数日後の2026年1月18日に計測を行いました。 ↩ こちらも計測データを SDK入門⑦〜計測リプレイツールの作り方〜 で再アップストリームしています。 ↩ 16分ほどです。再生速度を調整などして見てください。 ↩ 他の該当ポイントでは大きなフェンスが映っていたりするため、厳密性は高くなさそうです。 ↩ 消費トークンの従量課金です。開発を通じて、長時間データを流しっぱなしにしないように気をつけて使い、$4.40分を消費しました。 ↩
アバター
計測中の通話記録を残したいみなさん、 収集した音声データを分析したいみなさん、 こんにちは、ソリューションアーキテクトの伊勢です。 運行管理など発話によるコミュニケーションが必要な場面があります。 今回は運行モビリティデータとあわせて音声を収集できるアプリをご紹介します。 はじめに 音声通話アプリ(開発中) やってみた アプリ利用 いい点 改善点 ChatGPT解析 CSVダウンロード データ分析 おわりに はじめに 音声通話アプリ(開発中) iPhoneアプリ Speak Cast(仮) は音声データを intdash サーバーと送受信します。 1 アプリ画面(開発中) 通話ができるアプリは Zoom / Teams / LINE など色々ありますが、intdashで他の計測データと時系列をあわせてデータ保存できるのが特徴です。 主に以下の機能を持っています。 音声データ送信:PCM、AAC 音声テキスト化・データ送信:文節単位、文章単位 GNSSデータ送信:緯度、経度、高度、速度 通話先エッジからのデータ受信・音声データ、テキストデータ ミュート:自分、通話先エッジごと 音声データ送信設定 やってみた ちょうど社用車での出張がありましたので、以下の構成で試しました。 社用車の搭乗者:1名 遠隔監視者:3名 全体構成図 アプリ利用 実際のリアルタイム計測とプレイバック再生のようすです。 youtu.be 使ってみた感想です。 いい点 ちゃんと会話できている 手軽に音声を送受信できています。 計測に残る 過去データとして再生した際も会話としてなりたっています。 無音時はデータ送信されずに通信量節約 長時間計測だと嬉しい機能です。 改善点 テキスト化エンジンの精度・安定性 通話者の滑舌のせいか文字起こしされたテキストの誤りが目立ちます。 エンジンの見直しで改善される範囲と思います。 ChatGPT解析 この記事は実はここからが本番です。 音声データがテキスト化されることで、自然言語分析ができるようになります。 こちらの記事の方法でデータ分析を行います。 tech.aptpod.co.jp CSVダウンロード まずはData VisualizerからCSVファイルとして計測データをダウンロードします。 途中でエンジンを再始動して計測が区切られているため、時間範囲を指定します。 https://example.intdash.jp/vm2m/?playMode=storedData&startTime=2025-11-19T16:45:03.000%2B09:00&endTime=2025-11-19T21:48:01.000%2B09:00 CSVダウンロード 社用車の搭乗者 文字起こしテキスト GNSS 遠隔監視者 文字起こしテキスト CSVファイル データ分析 分析を始めます。 課題 走行経路は渋滞していたか。 ChatGPTにCSVファイルをアップロードして、基本情報を確認します。 音声通話アプリで収集した、自動車走行中のGNSSデータと複数名(車両搭乗者、遠隔監視者)の会話のCSVデータをアップロードします。 GNSSデータ(緯度経度、速度、高度、方位)、会話(音声文字起こし文)が含まれます。 課題:車両走行に渋滞していたか のためにデータ分析を行います。 以下を確認してください。 - 項目の一覧 - データ行数 - タイムスタンプ範囲 データの確認 データ項目を整えます。 データを整えてください。 - 見やすさのため、GNSSデータの項目名の"GNSS_"と"@"以降を削除してください。 - 見やすさのため、会話(音声文字起こし)データの項目名の"Transcription_Sentence@transcription/sentence"と"[", "]"を削除して、列名を発話者のみにください。 - 空の値が含まれます。GNSSデータについては直前の値を使用して穴埋め(forward fill)してください。 また、 - 分析結果のグラフは英語で、本文は日本語で表記してください。 データクレンジング 分析の前提として発話の信頼度を確認します。 搭乗者(ise)の発話・タイミングの信頼性を検証します。 車両の現在地点に関する発話がある場合、その発話内容と、その時間の緯度経度データから得られる地名はどのぐらい一致していますか。 車両の現在地点に関する発話とは、 「今、(地名、高速道路のICやPA名)にいます/出発しました/到着しました」のような発話を指します。 発話とGNSSの同期確認 逆ジオコーディング、緯度経度から住所が引けないので確認できないと言われました。 発話の件数が少ないため、逆ジオコーディングを行わず、地点にまつわる発話を地図上にプロットして目視でおおよそ確認します。 逆ジオコーディングを行わず、地名の発話の精度をおおよそ確認する方法を試します。 GNSSの緯度経度データを、PythonのFoliumを使ってプロットしてください。 現在位置(地名、高速道路のICやPA名)に関する発話があった時間帯の緯度経度には、その地点にピンを打ち、発話内容を記載してください。 出力後、発話内容のピンを何本打ったか教えてください。 地図に地点にまつわる発話をプロット だいたいの発話位置を確認 天気がどうだったかも聞いてみます。 お天気はいかが それでは、渋滞があったかを確認します。 発話とGNSSデータで分析してもらいます。 渋滞に関する発話の確認 GNSSも複合的に分析 地図上にも表示してもらいましたが、目立った渋滞区間はなかったようです。 FoliumでGNSS緯度経度をプロットし、 渋滞該当時間帯を強調表示(低速区間を色分け、発話ピン)してください。 地図上への渋滞発話のプロット 速度と発話地点をプロット 結論 東名高速は少し渋滞したが、会話はスムーズだった。 アプリ開発も、データと対話しながら止まらずに進む。 おわりに 高速道路を走行中も、intdash経由で複数人数の通話ができました。 また、計測の音声をテキスト化するひと工夫で、LLMの生成AIで分析できました。 音声以外のデータとあわせることで多角的な分析が手軽に可能になります。 次はさらにデータ量が多い映像を分析させてみたいと思います。 現在、企画検討・開発中です。機能やUIは現在のものから大きく変わる可能性があります。 ↩
アバター
2025年も残すところあとわずかとなりました。CTOの梶田です。 毎年恒例の振り返りブログ、今年も書いていきたいと思います! 本記事は aptpod Advent Calendar 2025 12月25日(最終日)の記事です。 Advent Calendar も継続して今年は8年目にもなりました! はじめに NTTドコモビジネス(旧NTT Com)様との資本業務提携 intdash V3 リリースとSaaS提供開始 -「スモールスタート」でより手軽に - 労働力不足に挑む:ロボティクスと建設DXの深化 2026年に向けて はじめに 今年のアドベントカレンダーも無事に完走することができました!執筆してくれたメンバー、読んでいただいた皆様に感謝します。 記事全体を見渡すと、今年は 「生成AI」 をテーマにした記事が多く見られました。 昨年から生成AIの予算を確保したり、社内の利用ガイドラインを整備したり。。。昨年はまだまだ「試す」というフェーズでした。 今年は生成AI自体の爆発的な進化もあり、単なる「技術的な興味」や「ツールの試用」から一歩進み、「業務フローへの統合」や「具体的成果」につながる利用へシフトしています。記事の内容的にも生成AI活用の話題が多く出てきたのが特徴でした。 2025年の産業界全体のトレンドも同様に「実用」へと変化しています。 社内の生成AIガイドライン 今回の AdventCalendar 2025 でも、生成AI事例についていくつか触れられていますので、ぜひご覧ください。 4日目: Figma Make でテックブログの OGP 作成を自動化 5日目: Claude + Playwright で実現するテスト自動化 ~ QAエンジニアのAI 活用記録 ~ 9日目: オンデバイスでLLM+RAGを動かす〜Ollama + Open WebUI + Open Notebook 10日目: ClaudeCodeを半年間実務で利用して感じた、開発現場が改善された4つのポイント 11日目: ブラウザの組み込み AI を試してみる 15日目: intdashマニュアルRAG化!正しく検索するデータパイプラインの全貌 16日目: Live VLM WebUIで試す、カメラ映像を「見て理解する」生成AI活用 22日目: -AI活用でデザインワークはどう変わるのか- Figma Makeを使ってみた 24日目: Claude Codeでドキュメント簡易校正 個人的には、以下のQA(品質保証)業務での生成AI活用の進展に期待しています! tech.aptpod.co.jp さて、2025年は、長年懸念されてきた 「2025年の崖」 がいよいよ現実の問題として対峙された1年でした。 経済産業省が警鐘を鳴らしてきたレガシーシステムの老朽化に加え、労働力不足や熟練技術者の引退といった「2025年問題」が顕在化を肌で感じる瞬間もいくつかありました。 また、IPA(情報処理推進機構)の「DX動向2025」レポート等でも指摘されているように、これまでの日本のDXは「内向き・部分最適」に留まりがちでしたが、今年は明確に 「外向き・全体最適」 へと舵を切る必要性に迫られる時流を強く感じました。 我々アプトポッドが主戦場としている 自動車産業におけるSDV(Software-Defined Vehicle)へのシフト や、 建設・インフラ業界における遠隔化・自動化 の流れは、まさにこの「崖」を乗り越え、産業構造全体を変革しようとする動きそのものです。 この激動の環境下で、アプトポッドは「社会課題解決のための実装」を強く推し進めました。 今回は、こうした進展や取り組みを中心に、この一年を振り返っていきたいと思います! NTTドコモビジネス(旧NTT Com)様との資本業務提携 今年最大のニュースは、11月に発表したNTTドコモビジネス株式会社様との資本業務提携です。 www.aptpod.co.jp 期初の方針で掲げていた「戦略的パートナーとの販売・開発拡大」が、最も強固な形で結実しました。 自動運転レベル4の社会実装や、建設機械の遠隔操作において、通信インフラの信頼性は生命線です。キャリア品質の通信インフラと、当社の高速データ伝送技術「intdash」が一体となることで、部分的なDXではなく、インフラを含めた「全体最適」なソリューション提供が可能になります。 直近では、12月に仙台市にて自動運転バスの実証実験が開始されています。 www.aptpod.co.jp intdash V3 リリースとSaaS提供開始 -「スモールスタート」でより手軽に - 7月には、主力製品のメジャーアップデートとなる intdash Version 3 をリリースしました。 機能面の進化(高信頼伝送や管理機能の強化)もさることながら、最大のトピックは SaaSモデルの提供開始 です。 www.aptpod.co.jp これまでは手軽に始めるには大きめなシステム構築が前提となるケースも多かったですが、SaaS版の登場により、初期費用を抑えた 「スモールスタート」 が可能になりました。 研究開発(R&D)でのデータ収集 小規模なPoC(概念実証) 手軽な時系列データの可視化 といったシーンで、より手軽に、スピーディに産業データの活用を始めていただける環境が整いました。「まずは試してみたい」という現場の声に応え、DXの第一歩をより身近なものとして支援しています。 使ってみたい!という方はぜひお問い合わせください! aptpod,Inc. Contact 労働力不足に挑む:ロボティクスと建設DXの深化 「2025年問題(労働力不足、熟練工不足)」に対する直接的な解として、ロボティクスと建設DXへの取り組みも加速しました。 建設DX: 昨年から続く日立建機様との「建設施工リアルタイムデジタルツイン基盤」の共同開発は、実運用を見据えたフェーズへと移行しています。現場の「省人化・遠隔化」は待ったなしの課題であり、我々の技術がその中核を担っています。 2025年もCSPI-EXPO にて日立建機様のブースにて、進化させたintdashを活用したリアルタイムデジタルツインのデモンストレーションを展示させていただきました。 www.aptpod.co.jp 以下の動画の冒頭にあるようなデモをLIVEで実演し、非常に大きな反響をいただきました。これをきっかけとした共創も着々と進んでいます。 www.youtube.com ロボティクス: 12月開催の 「Robot Innovation Week 2025 への出展」 など、不整地や屋内点検など、従来は人が行わざるを得なかった領域のロボット化を支援しています。 特に、テックブログでご紹介した「メンテナンス・レジリエンス展」のレポートは展示内容がわかりやすくまとめられていますので、ぜひご覧ください。 tech.aptpod.co.jp 2025年は四足歩行ロボットの反響は大きく、デモに大忙しでした。ロボットを活用した省人化、省力化、自動化といった潮流は強くなってきているのを感じており、2026年はさらに適用範囲が広がっていくと思われます。 二足歩行のヒューマノイドロボットも実用化に向けた動きが見られますが、すべてが一つにまとまるというよりは、適材適所、場所や用途、様々な条件によって最適なものが選ばれ、それらを統合管理する基盤が求められつつあると感じています。 2026年に向けて 2025年は、社会全体が「DXの成果」を厳しく問う年となり、アプトポッドとしてもパートナーとの資本業務提携を通じて、社会実装への覚悟を新たにした「飛躍の年」でした。 2026年は、これらの基盤を活かし、単なるミドルウェアベンダーから 「顧客の核心的課題を解決するソリューションパートナー」 への進化を加速させます。 お客様の事業変革(DX)に伴走し、高付加価値な提案と長期的な価値共創(LTVモデルの確立)を実現していく所存です。 「2025年の崖」を飛び越え、その先にある新たな産業の風景を、皆様と共に作っていきたいと思います。 自動運転、建設DX、そしてロボティクス。産業の現場が大きく変わろうとしている今、アプトポッドの技術がその変革の 「大動脈」 としてデータを巡らせ、産業全体を活性化できるよう、2026年も全速力で駆け抜けていきます。 来年も、アプトポッドにどうぞご期待ください! メリークリスマス!🎄 今年も一年、ありがとうございました。 それでは皆様、よいお年を!
アバター
こんにちは。テクニカルライターの篠崎です。 aptpod Advent Calendar 2025 の12月24日の記事を担当します。 今年も生成AIの話題を見聞きしない日はありませんでした。 製品ドキュメントの制作を担当している私も、日々の業務にAIを活用する可能性を模索してきました。 例えば、AIに資料を渡して文章を生成してもらったり、見本に沿って書き直してもらったり、テキストを処理するスクリプトを書いてもらったり、いろいろなことをやっています。 しかし、うまくいくこともあればいかないこともあり、期待通りにならない場合は「これは自分の指示の仕方が悪かったのだろうか、または、このような作業は現時点ではAIには向いていないのだろうか」と考え込んでしまうこともあります。 そんななかで、簡単なのに常に効果が見込めるものが1つあったので、それをご紹介します。 それは、誤字脱字の発見、校正です。 補足: 以下でご紹介するのは、「使い方の一例」です。これが最良の校正方法ということではありません。 また、今後のAIツールの進展により使い方は変わっていくことと思います。 本記事で対象にするドキュメント Claude Codeを使ってみる カスタムコマンドを設定してみる 実行する 効果 まとめ 本記事で対象にするドキュメント 私は弊社製品のドキュメントの制作を担当していますので、それを例にして説明します。 製品ドキュメントは、 Sphinx というツールで作成しています *1 。 原稿はテキストファイル( reStructuredText記法 )です。ソフトウェアのコードと同じように、Gitで管理しています。 この原稿をSphinxでHTML形式にビルドすることで、ユーザー向けのドキュメントサイトを作っています。 もちろん、制作時には何度も読み返しますし、必要に応じてAI以外の校正ツールも使いますが、それでも後から誤字や脱字に気づくことはあります。 こういった誤字脱字を見つけるのにAIが使えないかと思い、やってみることにしました。 Claude Codeを使ってみる AIを使って校正するのに最も簡単な方法としては、ChatGPTなどLLMベースのチャットボットの画面に原稿をペーストして(または原稿をアップロードして)、「誤字脱字がないか確認してください」と指示する、という方法が思いつきます。これはすぐにできますし、十分に役に立ちます。 でも、コピー&ペーストやアップロードをせずに校正したいと考え、コマンドラインベースのAIコーディングツール Claude Code を使うことにしました (Claude Code以外の類似ツールでも良いのですが、身近に使えるのがこれだったのでまずはこれでやってみました)。 Claude Codeのプロンプト上で「features/index.rstに誤字脱字がないか確認してください。」のようにファイル名を明示して指示するだけで、誤字脱字を見つけて修正してもらえます( .rst は原稿reStructuredTextファイルの拡張子です)。 「誤字脱字がないか確認してください」によるシンプルな校正 実際には、もうすこし詳しい指示をしたいですし、また、いつも一定の指示を簡単に行いたいです。 このような場合にカスタムコマンドという機能が便利でした。 カスタムコマンドを設定してみる カスタムコマンドは、あるひとかたまりの指示に名前を付けたものです。 / に続けてその名前(コマンド名)を入力することで、実行できます。 プロジェクト用のカスタムコマンドは、 .claude/commands/ で定義します。 例えば、以下のように定義します(この定義自体も、Claude Codeと相談しながら作りました)。 ファイル名: .claude/commands/proofread.md # /proofread コマンド ## 概要 指定されたディレクトリまたはファイル内の*.rstファイルについて、 タイポや書式エラーを体系的にチェックし修正するコマンドです。 コードではなく自然言語を扱います。 ## 使用方法 ``` /proofread <$ARGUMENTS(ディレクトリまたはファイル名)> ``` 例: - `/proofread apps` - appsディレクトリ内の全.rstファイルをチェック - `/proofread my-app/tutorials/index.rst` - 特定ファイルをチェック ## 処理手順 ### 0. ARGUMENTSの確認 $ARGUMENTSの指定がない場合、「/proofreadコマンドに続けて、 対象のディレクトリまたはファイルを指定してください。」と表示して終了します。 ### 1. チェック 修正の判断基準は以下の通りです。 #### 必ず修正すべきもの 1. **明確な誤字・脱字・スペルミス** - 文字の欠落、誤った文字 - 例:「こ方法」→「この方法」、「データをの保存」→「データの保存」 2. **文法的に明らかな誤り** - 助詞の誤用(文意が通じない場合のみ) - 主語述語の不一致 3. **技術的な誤り** - 単位表記の誤り - 例:「KG」→「kg」、「GByte」→「GB」 - 用語の明らかな誤用 #### 修正してはいけないもの 1. **reStructuredTextの形式は修正しない(ビルドエラーにならない限り現状維持)** - バッククォートの前後のスペースは修正しない - タイトル下線の長さは修正しない - toctreeの記法は修正しない - セクション見出しの記号の選択や数は修正しない 2. **表現上の軽微な問題は修正しない** - コロンの有無は修正しない - 文末表現の微調整はしない - 括弧の前後のスペースは修正しない 3. **構造は修正しない** - 見出しレベルの修正はしない - セクション構造の再編成はしない - ディレクティブの種類は修正しない 4. **その他修正不要なもの** - 「読みやすくなる」程度の改善はしない - 読点の位置は修正しない - 全角文字と半角文字の間のスペースの有無は修正しない - 長音符号の有無は修正しない - リンクlabel(例: `.. _my-reference-label:` )のスペルミスは修正しない ### 2. ログの記録 **1ファイルのチェックが終わるごとに、以下のコマンドを実行する** ```bash echo "チェックしたファイルのパス" >> proofread-log.txt ``` これによりログが記録されます。 ### 注意 **迅速化は行わない** - 効率化やパターン認識による省略は行わない - 全ファイルの全ての行を同じレベルの注意深さでチェックする - 間違いには傾向がないかもしれないため、先入観を持たずに全体を見る **1ファイルのみ処理** - 指定された1ファイルを完全に読み込み、内容を理解してチェックする **見落とし防止** - 「よくあるパターン」に頼らず、各行を個別に検証 - 技術的な文脈を理解してからタイポを判断 - 不明な点があれば、関連ファイルや前後の文脈を確認 最後の「注意」のところで、「迅速化は行わない」ことをしつこく書いています。 このような指示をしないと、多数のファイルを対象にした場合、「それまでに見つかった間違いをもとに、それと似た間違いがないか」の検索が始まることがありました。 例えば、助詞「の」が重複した「のの」を発見した後は、助詞の重複がないかを検索し、それがなければ問題なしとする、といった動きをしているように見えました。 しかし、ここではすべての行を念入りに確認してほしかったので、このようにしています。 *2 実行する これにより、例えば以下のようなコマンドで、校正が始まります。 # featuresディレクトリ内を校正 /proofread features 実行中、ファイルの書き換えやシェルコマンド実行については、実行してよいか確認があります(設定によります)。私は普段は原稿ファイルの書き換えは確認なしに行ってもらうようにしています。 そのうえで、完了後に修正結果を git diff で確認します。意図しない修正が行われているのを見つけたら、その部分だけリバートします。 確認してリバートするのは手間ですので、できるだけ、意図しない修正が発生するのを抑えたいですが、完全に抑えることはできていません。 どれだけ抑えられるかは、カスタムコマンドの定義次第かと思いますので、まだ工夫のしどころがありそうだと思っています。 効果 意図的に誤字脱字や間違いを含んだファイルを作ってテストしてみると、以下のような間違いを見つけることができました。 助詞の間違い: 「は」→「とは」(マークアップを挟んでいても検知) スペルミス: 「apt-gett」→「apt-get」 内容と合っていない: 「受信」→「送信」 特に最後に挙げた例は、単純な誤字脱字の修正ではなく、「送信について説明している箇所なのに、見出しが『受信』になっている」という指摘です。 なお、何度か試してみると、そのときごとに違う指摘になる場合があるので、いつもすべての間違いを見つけてくれるわけではないようです。 まとめ 上記のように、単純な誤字脱字のほかに、なかなか見つけにくい間違いまでも見つけてくれました。 これにより、ドキュメント公開前に「助かった!」という経験を何度かしました。 上に挙げたカスタムコマンド定義を見ていただけると分かるとおり、それほど特別なことを指示しているわけではないですが、このような準備をしておくだけでいつでも簡単に校正できます。独自ルールもほとんど入れていませんので、汎用的に使えます。 ただし、それなりにトークン消費があるので、大量のドキュメントを校正するときには注意が必要です。 そして、いつもすべての間違いを見つけてくれるわけではないので、過信はできません。 紹介は以上です。 ライターは十分に注意深く執筆しなければならないのはもちろんですが、自分のほかにもうひとり、いつでもすぐに原稿を読み返してくれるAIのアシスタントがいるというのは、ありがたいことだと思っています。 *1 : Sphinxは、HTMLもPDFも出力できて、カスタマイズ方法が豊富なのが魅力です。以前に本ブログでもご紹介しました: SphinxとLuaLaTeXで、日本語PDFマニュアルを作る Sphinx文書内で展開されるsubstitutionsやextlinksを活用する *2 : 他に、1ファイルごとに サブエージェント を起動して、別コンテキストでチェックしてもらう、という方法もあります。
アバター
aptpod Advent Calendar 2025 12月 23日の記事です。 お世話になっております。 株式会社アプトポッド メルマガ事務局山口です。 いつもメルマガを読んでくださる方にとっては、おなじみの冒頭ではじめてみました。 普段は営業管理室としてメルマガ配信やオフライン展示会の準備といったマーケティング業務や事業部内の業務フローの改善など、いわゆるセールスオペレーションのような仕事をしています。 そんな私は2022年12月にアプトポッドを退職し、その後2社経由して2025年2月に再入社(出戻り)しました。 本記事では一度退社して戻ってきた経緯や、どうしてアプトポッドに戻ってきたかについてお話しできればと思います。 前回のアプトポッド退職の経緯 徐々に明確化した価値観 復職のきっかけ 戻りやすさの理由 復職後の業務について まとめとメッセージ 展示会準備を終えて一息 前回のアプトポッド退職の経緯 2022年当時、アプトポッド社内で営業支援ツールの導入プロジェクトをリードさせてもらう機会がありました。 初めてのプロジェクト主導だったため、当初は進め方すらわからず、まさに手探りの状態からのスタートでした。終始悩みながらの推進でしたが、苦労した分、無事に導入が終わった瞬間の達成感は凄まじいものがあり、大きなやりがいを感じました。 その経験から、次第に「次は、もっと大きな規模のプロジェクトに挑戦してみたい」という意識が強くなりました。 とはいえ、そうそう大きな規模のプロジェクトが社内で発生するわけではなく、今後のキャリアを長い目で考えた時に、一度あえて環境を変えて挑戦することが、今の自分には必要ではないかと考えていた矢先に、友人から声をかけてもらいました。ただ、社内の人間関係はとても良く仕事もやりやすかったので、本当にこの会社を離れていいのかすごく悩みました。それでも、「チャンスがあるなら、今チャレンジしてみよう」と決意して転職に踏み切りました。 徐々に明確化した価値観 退職してすぐに、転職先での大規模な基幹システムのプロジェクトに参加しました。そこでは100人以上のメンバーが常時関わっており、基幹システムの関わる業務範囲や影響力は、ベンチャー企業では決して味わえない事業規模の経験でした。 一方で入って驚いたのはプロジェクト内で使用する業務フローやツールに関するルールの多さでした。 もちろん、これだけ多くの人間が関わるプロジェクトをトラブルなくスケジュール通りに遂行するためには、厳格なルールや統制が必要不可欠であることは十分に理解できましたが、ある程度自分の判断で動くことに慣れていた私にとって、全てが決められた枠組みの中でタスクをこなす業務スタイルより自分の裁量で工夫して仕事をするほうがやりがいや楽しさを感じることに気づかされました。 そこで、より自分の裁量で動ける自由な環境を求めて、アグリテック領域のスタートアップ企業へ転職しました。 アーリーステージだったので、社内の仕組みをゼロからつくる状態でした。 ここでは経理に関わる社内ルール作りやHubSpotを活用した営業フローの仕組みづくりなど会社全体のバックオフィスの体制を作り上げていきました。 自分の裁量で推進していく仕事はとても楽しく、やりがいもありましたし業務量が多いことや、求められるスピードが速い環境自体はすごく自分にあっていました。 しかし、入社してしばらく経った頃、会社の方針が大きく変わり、それまで認められていた裁量ある働き方が難しくなって しまいました。徐々に働くなかで自分のリズムで仕事に向き合えないもどかしさを感じ、いくらやりがいをもって業務に取り組んだとしても、パフォーマンスを最大化するためには柔軟に環境を変えて働くことが自分には必要でした。 復職のきっかけ そんな働き方の価値観が明確になってきた時期に、かつての同僚と食事に行く機会があり、仕事の相談などを話していると、「それならアプトポッドが一番合ってるし、戻ってみたらいいんじゃないか?」と声をかけてもらいました。 正直なところ、最初はあくまで会話の延長というか、話半分に聞いていました。「一度辞めた身だし、そう簡単に戻れるものではないだろう」と思っていたからです。 しかし、いざ本格的に転職活動を始め、他の様々な会社を調べて比較してみると、業務の裁量や働き方の柔軟性など、あらゆる面でアプトポッドほど自分の価値観やスタイルに合っている会社は他に見つかりませんでした。 そこで、前回アプトポッド在職時に一緒にプロジェクトを進めていた他のメンバーにも相談したところ「今のスキルセットなら、こういう業務をやってほしい」といった今後の仕事のイメージや歓迎の言葉をもらえたことで、前向きな気持ちになり、正式に戻ることを決めました。 戻りやすさの理由 退職後もつながりが続いていたことが、出戻りの心理的なハードルを大きく下げてくれました。 社内で開催されているバドミントンサークルに参加したり(現在もだいたい週1回で開催中)、近所に住むメンバーで集まってご飯を食べたりしてました。今思うと、在籍中よりも仕事以外のコミュニケーションが増えてたかもしれません。笑 そうした場で定期的に仕事の相談もできていたので、会社に戻ること自体への不安はあまりありませんでした。 ただ、私が辞めた後に入社された方も多くいらっしゃったので、そうした新しい方々の目に「出戻り社員」がどう映るのか、受け入れてもらえるかという点だけは少し心配していました。 復職後の業務について 現在は営業管理室で働いていますが、チームとしての大まかな目標はあるものの、そこに向けた「個人の目標」や「やり方」はある程度自由に決めることができます。 「ここが課題だから、こういう施策をやりたい」と手を挙げれば、頭ごなしに否定されることはまずありません。むしろ「それいいね、やってみよう」と背中を押してくれます。 「決められたことをやる窮屈さ」とは正反対で、自分で考えて行動できることに日々やりがいを感じています。 また業務に入ってみると新しい挑戦が4割ありつつ、残り6割は以前と同じ内容というバランスでした。 社内ルールやルーティン業務などは大きく変わっていなかったので、心配していたよりもスムーズに業務に戻ることができました。 入社してすぐに戦力として動けたのは出戻りならではの大きなメリットだと思います。 さらに他の会社で働いたからこそ、以前いた時には気づかなかった業務の課題や改善方法が見えたのも良かったです。 例えば、過去に自分が書いた業務マニュアルを読み返した時に「わかりにくいな」と気づいて修正したり... 一度外に出たことで客観的な視点が養われ、以前よりも良い仕事ができている実感があります。 今年は多い時で週1回展示会が開催されたので、備品確保に苦労しました。 まとめとメッセージ 最近では「アルムナイ採用」や「カムバック採用」といった言葉も耳にするようになり、一度退職した会社に戻るという選択肢が、世の中的にも少しずつ広がってきたように感じます。 ただ、言葉としては定着してきても、「実際、どんな理由で戻る決断をしたのか?」「戻ってみて、本当のところ働き心地はどうなのか?」といった、当事者のリアルな声を知る機会は、まだまだ少ないのが現状ではないでしょうか。 今回、私が一度外の世界に出て、悩みながら再びアプトポッドを選んだという一連のプロセスをお話しさせていただきました。この「出戻り」に至るまでの葛藤や気づきが、かつての私のようにキャリアに迷っている方や、復職を少しでも考えている方にとって、後悔しない会社選びをするための参考になれば幸いです。 (ちなみに、私の他にもう1名最近出戻りした社員が増えました!)
アバター
aptpod Advent Calendar 2025 12月22日の記事を担当しますデザインチームの高森です。 デザインチームでは普段Figmaを使用してデザインを行なっておりますが、先日 Figma Make というプロンプトを書くだけでAIがアプリケーションのデザインを作成してくれる機能が発表されました。 これまでのデザインプロセスが劇的に変わるのではないかと感じ、現時点で実際のデザインワークフローにどこまで活用できるかを探るため使ってみたので、その内容をまとめました。 現在のデザインプロセス 仮タスクでデザインワークをやってみる 1. ワイヤーフレームの作成とプロトタイプの作成 2. デザイン適用 3. デザイン調整とコンポーネント整理 使ってみた結果 おわりに 現在のデザインプロセス 案件によって内容やボリュームは異なりますが、デザインの依頼を受けてから提出するまでに、ざっくりと以下のようなプロセスがあります。 要件・仕様のインプット ワイヤーフレームの作成(画面構成、遷移、レイアウトの検討) プロトタイプの作成 デザイン検討 デザイン適用 プレゼンテーション デザイン調整 コンポーネント整理 新規に画面デザインしながら仕様を詰めていくようなケースでは、「ワイヤーフレームの作成」と「デザイン検討」を同時に進め、必要に応じて「プロトタイプ作成」という流れが多く、それぞれのプロセスに集中しきれないことが多くなります。既存アプリに機能を追加したり、ワイヤーに既存のデザインを落としんでいくようなケースでは上流の工程は省略されますが、デザインライブラリーを参照し他のアプリケーションでの使われ方も確認しながら作業する必要があります。いずれのケースでもそれなりに手間がかかっております。 Figma Makeにはプロトタイプ作成、ライブラリ連携というこれらの不満を解決してくれそうな機能がまさにありました。実際のデザインワークでどのように使っていけそうか試してみます。 仮タスクでデザインワークをやってみる 「既存のユーティリティアプリに問い合わせ機能を追加する」という仮タスクを設定し、以下のワークフローでFigma Makeを使ってみました。 ワイヤーフレームの作成とプロトタイプの作成 デザイン適用 デザイン調整とコンポーネント整理 既存のユーティリティアプリ【Meas Hub】 1. ワイヤーフレームの作成とプロトタイプの作成 まず、問い合わせ画面のデザインを検討していきます。以下のようにプロンプトを記載します 問い合わせフォームを作成してください。内容は以下 氏名入力 メールアドレス入力 問い合わせカテゴリーは計測に関する問い合わせ、取得データに関する問い合わせ、操作に関する質問、その他 詳細はテキストの自由記入 対応期限は日付入力とカレンダー入力の2種類の入力方法 「問い合わせフォームを作成」だけでも良いですが、作成したい内容が決まっているときは「〇〇をするための〜」や「以下の項目で〜」といった具体的な記述をした方が、イメージに合った画面に辿り着く回数が早いです。今回はデザイン作成後にライブラリ適用について検証したかったので、既存のライブラリ内にあるコンポーネント(テキストフィールドやカレンダー)を用いて作成されるよう記入しました。 出来上がった画面がこちらです。 出来上がった画面にプロンプトを追記して、意図していた箇所と異なる部分の修正やクリック後の表示なども指定していきました。 プロンプト例: "カレンダーアイコンをボックスの左端に移動してください" "送信ボタンクリック後、「送信が完了しました」というメッセージをトーストで表示してください" 上手く動かない箇所は、 "氏名を入力しているのにエラーが表示されます" といった書き方でも修正してくれました。 出来上がったものがこちらです。 youtu.be お問い合わせフォームのデザイン検討とプロトタイプ作成があっという間に完了しました。Figmaのプロトタイプ機能では任意のテキストや日付入力を再現することはむずかしく、細かな追加修正も時間がかかっていましたが、Figma Makeだと格段に早く作成できました。実際に操作しながら必要に感じたものをすぐに追加していけるので、操作感のブラッシュアップという目的も十分満たせていると感じました。以下の点も便利だと感じました 細かな指示なしに必要な機能がちゃんと考慮されてる(レスポンシブ対応、バリデーションチェック、初期状態やデータが0の時の状態作成など) ◯◯してください、クリックできません等のプロンプトでも修正してくれる 改善案を提示してもらって抜け漏れがないかの確認もできる 2. デザイン適用 既存のユーティリティアプリに追加できるよう、アプトポッドのライブラリを適用していきます。 aptpodのデザインライブラリ プロンプトの入力エリアから、ライブラリやイメージファイルが取り込めます。 アプトポッドのライブラリを読み込むと以下のようになりました。 ダークトーン、フォント、プライマリーカラーが適用されている様です。 全体的に目標のデザインと違っているので、以下のFigmaデータを追加してみました Figma データ 結果はこちらです。 ダイアログの背景色やテキストフィールドのフレームカラーが調整され、なんとなく近しい雰囲気になってきました。 カレンダーのデザインが異なっているので、カレンダーを直接選択、以下ライブラリ内のカレンダーコンポーネントを追加して、添付のデザインを参照するよう記入してみました。 カレンダーコンポーネント 結果はこちらです。 近しいデザインに変更してくれました。 正確なデザインにしていくには細かくやりとりしていく必要がありそうです。 ライブラリの構成に指定があるのではないかと思いFigmaが公開しているdesign system、appleが公開しているmacOS 26 UI kitをライブラリとして取り込みそちらも適用してみましたが、いずれもフォントやカラーは適用されるもオブジェクトのRやシャドウといった効果は1度では反映されず、何度か手直ししていく必要がありました。 ライブラリだけでは100%意図した通りのデザイン適用は難しそうでした。ライブラリの適用に関しては、Guidelinesにデザインルールを記載していくことで適用精度があがるそうなので、こちらの機能についても今後調べてみたいと思います。 3. デザイン調整とコンポーネント整理 Figma Makeで作成したデザインは画面右上のアイコンでコピー&ペーストしFigmaに移行できます。 コンポーネントのレイヤー構成を確認すると元のものと異なっておりました。 そのまま既存のデザインと同じように管理していると気が付きにくいため、変更が必要な際に注意が必要です。 使ってみた結果 現時点で実際のワークフローに取り込んでみたところ ワイヤーフレームの作成とプロトタイプの作成 → ◎ デザイン適用 → 難しい デザイン調整とコンポーネント整理 → 難しい 1に関しては驚くほど簡単に素早く作成できました。アプリの目的を伝えておけば、クリック後の遷移画面も自動で作成してくれ、グラフやマップも直接触れるものが確認できます。テキスト入力、ドラッグ&ドロップで並べ替えなど、Figmaのプロトタイプ機能では難しかった操作も簡単に作成でき、すぐにプロトタイプとして利用可能なクオリティのものを提示してくれました。 2と3に関しては、現時点ですぐに実利用することは難しそうでしたが、作成したプロトタイプにそのままライブラリを適用するというフローは、実現されればそれはそれは便利になりそうです。ライブラリが反映されるように構成を整理していく方法もあるかと思いますが、それよりも早くライブラリの取り込み機能がアップデートされるかもしれません。いずれにしろものすごい速さでAIの生成機能が進化しているため、実用的になる日は近いと思えます。 おわりに Figma Makeを使うと、アプリのデザインが誰でも簡単にできます。(無料アカウントで誰でも使えますのでぜひ使ってみてください!)数行のプロンプトを書くだけでハイレベルなデザインプロトタイプを簡単に、短時間で、デザイナーなしで作れます。 Figma MakeのようなAI機能が自動で作成してくれるデザインを前に、今後人間のデザイナーには今まで以上に審美眼が問われるのではないでしょうか。AI活用でデザインワークの自動化が進むことで、「機能が成立している」段階から「美しく」細部への配慮が行き届いたデザインを提供するための時間に費やすことができます。 AIを使って取れるようになった時間で審美眼を磨いていくこと が、これからのデザイナーに必要な能力になってくるのではないかと思いました。
アバター
aptpod Advent Calendar 2025 12月19日の記事です。 intdashグループの野本です。組込Linuxソフトウェア開発を担当しています。 本記事では、車載などのエッジ環境で利用可能なゲートウェイアプライアンス intdash Terminal System 2 向けに開発している デルタアップデート機能 について紹介します。 デルタアップデートとは デルタアップデートの仕組み 実際にデルタアップデートを試してみる 開発の裏側:ReadOnly Rootfs化の壁 読み取り専用なはずなのに差分が発生する systemdの設定をアップデート間で保持できない その他 まとめ デルタアップデートとは Terminal System 2は Mender によるOTAをサポートしています。 Menderを使用したOSアップデートは、 フルアップデート と デルタアップデート の2つの方法があります。 フルアップデートとデルタアップデート 現状、Terminal System 2のOSアップデートは、 ルートファイルシステム 全体を一括更新する フルアップデート のみサポートしています。 ルートファイルシステム全体のサイズは数GB程度あるため、帯域幅が限られるモバイル回線ではインストールに時間がかかったり、データ通信量が多くかかってしまうという課題があります。 デルタアップデートは、アップデート前後のルートファイルシステムの 差分のみをエッジコンピューターに送信する ことで、OSアップデートのインストール時間を短縮し、データ通信量を大幅に節約することができます。 参考: Mender docs - デルタアップデート デルタアップデートの仕組み デルタアップデートでは、アップデート前後のルートファイルシステムのバイナリ差分(Binary delta)を作成し、アップデートします(現在のMenderバージョンでは、 xdelta3 を使用してバイナリ差分を作成しています)。バイナリ差分を使用する以外は、通常のOSアップデートと仕組みは変わりません。 バイナリ差分は、デルタアップデートに対応したOSアップデートアーティファクトを2つ用意し、CLIツール( mender-binary-delta-generator )を使って生成します。 $ mender-binary-delta-generator -o delta-v1-v2.mender rootfs-v1.mender rootfs-v2.mender 生成されたバイナリ差分には、アップデート前後のルートファイルシステムのチェックサム値が定義されています。 +-------------------------------+ |Type: rootfs-image | |Version: v1 | |Checksum: 5bb84175 | | | |Provides | |rootfs-image.checksum: 5bb84175| +-------------------------------+ +--------------------------------+ +--------------------------------+ |Type: mender-binary-delta | |Type: rootfs-image | |Version: v2 | |Version: v2 | |Checksum: ff532419 | |Checksum: b9147deb5 | | | | | |Provides | |Provides | |rootfs-image.checksum: b9147deb5| |rootfs-image.checksum: b9147deb5| | | +--------------------------------+ |Depends: | ↑ 提供(Provides)は、フル/差分共にv2で共通 |rootfs-image.checksum: 5bb841755| ← 差分はv1に依存(Depends) +--------------------------------+ 参考: How checksums look in a working case この例からわかるように、バイナリ差分はデルタアップデートに対応したイメージが必要で(デルタアップデートに対応していないバージョンのバイナリ差分生成はできません)、かつ アップデート前後のバージョンに依存 します。現状では、アップデート前後のバージョンのバリエーション毎にバイナリ差分の生成が必要です。 なお、アップデート前後のバージョンに合わせて、 Menderサーバー側でバイナリ差分を生成する機能 もありますが、Hosted Menderではイメージサイズの制限や生成ジョブ実行時間に制限があり、現状Terminal System 2では使用できません。 また、差分を適用する仕組み上、ルートファイルシステムには変更を加えることはできません。そのため、デルタアップデートに対応したイメージは ルートファイルシステムが読み込み専用になります 。書き込みが必要なデータは、ルートファイルシステムとは別のデータ保持用のパーティション(Menderのdataパーティション)や外部ストレージなどを利用します。 もし、エッジコンピューターの稼働中にルートファイルシステムの内容を変更した場合、デルタアップデートを実行するとチェックサム検証でエラーが発生するので、注意しましょう(その場合でも、フルアップデートは可能です)。 実際にデルタアップデートを試してみる まず、デルタアップデートに対応したイメージをYoctoを使用してビルドします。 Yoctoでの組み込み方は非常に簡単で、以下の3ステップでデルタアップデートに対応したイメージのビルドができます。(実際は後述のエラーがたくさん出ました) IMAGE_FEATURES += "read-only-rootfs" でルートファイルシステムを読み込み専用にする(参考: Read-only root filesystem ) meta-mender-commerical レイヤーを追加 local.confにIMAGE_INSTALL、LICENSE_FLAGS_ACCEPTED、SRC_URIを追記 追記例: # Customizations for Mender delta-update support IMAGE_INSTALL:append = " mender-binary-delta " LICENSE_FLAGS_ACCEPTED:append = " commercial_mender-yocto-layer-license " SRC_URI:pn-mender-binary-delta = " file:// ${HOME} /mender-binary-delta-1.5.1.tar.xz " 上記のYocto対応を行った、開発中のEDGEPLANT T1向けOSイメージを2つ用意します。今回はパッチリリースを想定し、主要モジュール(coredバイナリ、intdash-edge-agent2コンテナ、device-connector-intdashコンテナ)を更新したイメージを用意しました。CLIツールを使ってバイナリ差分を生成してMenderサーバーにアップロードすると、バイナリ差分である mender-binary-delta という種類のアーティファクトが追加されます。 アップロードされたバイナリ差分( mender-binary-delta ) フルイメージは2.3GBありますが、今回のバイナリ差分では541MBとなり、 約1.8GB(76%)削減 されていることがわかります。 準備ができたので、デルタアップデートを試します。 デルタアップデートをする場合は、デプロイメントオプションで Generate and deploy Delta Artifacts where available のチェックボックスを有効化して、デプロイメントを作成する必要があります(チェックがない場合はフルアップデートされます)。 デルタアップデートのデプロイオプションを有効化する あとは通常のOSアップデートと同じようにデプロイメントを作成すると、バイナリ差分を利用したデルタアップデートができます。 デルタアップデート成功 デルタアップデートを使用することで、フルアップデートより少ないサイズでOSアップデートをすることができました。 開発の裏側:ReadOnly Rootfs化の壁 従来、ルートファイルシステムが読み込み専用であることを想定していなかったこともあり、開発中に以下のような問題が発生しました。 読み取り専用なはずなのに差分が発生する 問題 Yoctoで IMAGE_FEATURES += "read-only-rootfs" を指定して、ルートファイルシステムを読み込み専用にしているのに、EDGEPLANT T1にてなぜかルートファイルシステムが変更され、チェックサム不一致によりデルタアップデートに失敗するという現象が発生しました。 調査の結果、 /etc/machine-id というファイルが、初回起動時に書き換えられていることがわかりました。 起動時に読み取り専用になっていないタイミングがあり、書き換えられてしまっているようでした。 対処 Yoctoで IMAGE_FEATURES += "read-only-rootfs" を指定すると、 rootfs-postcommands.bbclass でカーネル起動引数に ro が指定され、これにより読み取り専用になります。 APPEND:append = '${@bb.utils.contains("IMAGE_FEATURES", "read-only-rootfs", " ro", "", d)}' ただし、EDGEPLANT T1などのNVIDIA Jetsonデバイスの場合、上記のYoctoデフォルトの APPEND 変数をカーネル起動引数として使用しておらず、meta-tegraで独自に定義されたカーネル起動引数変数 KERNEL_ARGS を使用していたため、上記変更が反映されていなかったことが原因でした。GitHubにも同様の issue がありますが、今回は以下のように local.conf にカーネル起動引数設定を追加することで対処しました。 KERNEL_ARGS:append = " ro" systemdの設定をアップデート間で保持できない 問題 従来、プロキシーなどの一部設定はsystemdサービス全体に反映させるため、ルートファイルシステムの /lib/systemd/system.conf.d/ に設定を入れて、OSアップデート時にマイグレーションするという対応を行っていました。デルタアップデートではルートファイルシステムが読み取り専用となったため、この方法が利用できなくなり、別の方法が必要となりました。 Terminal System 2では /etc がOverlayfsとなっているため、設定ファイルの置き場を /etc/systemd/system.conf.d/ に変更する案がありましたが、 systemdの起動よりOverlayfsのマウント処理のほうが遅く、 /etc の設定変更が反映されない という課題があり、実現できていませんでした。 対処 Yoctoには overlayfs-etc.bbclass というクラスがあることが判明し、これを使用することでsystemdが起動するより前に /etc をOverlayfsとしてマウントすることができました。このクラスでは、 /sbin/init をラッパースクリプトで置き換えることにより、systemdより先にマウント処理を実行しているようです。 有効化は IMAGE_FEATURES に overlayfs-etc を追加するだけです。 IMAGE_FEATURES += " overlayfs-etc " レシピの標準スクリプトでは作成されるOverlayfsのディレクトリ構造が従来と異なり、OSアップデート時に複雑なマイグレーションが必要になることから、以下のようにイメージレシピでカスタムテンプレート OVERLAYFS_ETC_INIT_TEMPLATE を指定して対応しました。 # overlayfs-etc settings # Mount /etc before systemd starts using preinit script # This ensures /etc/systemd/system/ configurations are recognized at boot OVERLAYFS_ETC_DEVICE = "${MENDER_DATA_PART}" OVERLAYFS_ETC_FSTYPE = "${@d.getVar('MENDER_DATA_PART_FSTYPE_TO_GEN') if d.getVar('MENDER_DATA_PART_FSTYPE') == 'auto' else d.getVar('MENDER_DATA_PART_FSTYPE')}" OVERLAYFS_ETC_MOUNT_POINT = "/data" OVERLAYFS_ETC_MOUNT_OPTIONS = "defaults" OVERLAYFS_ETC_USE_ORIG_INIT_NAME = "1" OVERLAYFS_ETC_CREATE_MOUNT_DIRS = "0" # Use custom preinit template to maintain existing directory structure (/data/overlay/etc) OVERLAYFS_ETC_INIT_TEMPLATE = "${THISDIR}/files/overlayfs-etc-preinit-ts2.sh.in" これにより、systemdの /etc 配下の設定変更が反映され、OSアップデートで保持されるようになりました。 その他 以下のようなルートファイルシステムに書き込む処理はすべてエラーするので、tmpfsに作成するようにしたり、 - TEMP_MOUNT_DIR_CANDIDATE=$(mktemp -d /mnt-XXXXXX) + TEMP_MOUNT_DIR_CANDIDATE=$(mktemp -d /tmp/mnt-XXXXXX) 非アクティブなパーティションでext4をマウント処理をする際に、ジャーナルの再生による書き込みを防ぐため ro,noload オプションを追加してチェックサムの不一致が発生しないようにしたり、 - mount "$INSTALL_PART" "$TEMP_MOUNT_DIR_CANDIDATE" + mount -o ro,noload "$INSTALL_PART" "$TEMP_MOUNT_DIR_CANDIDATE" などなど、細かい問題を見つけては修正を繰り返して、デルタアップデートに対応することができました。 まとめ 本記事では、intdash Terminal System 2 向けに開発中のデルタアップデート機能についてご紹介しました。 ルートファイルシステムを読み込み専用化し、差分のみを配信することで、アップデート時のデータ通信量を大幅に削減できることが確認できました。特に帯域が限られるモバイル回線環境においては、通信コストの削減だけでなく、アップデート時間の短縮による可用性の向上も期待できます。 現在、次リリースでの正式サポートに向けて、引き続き検証と開発を進めています。より使いやすく、効率的なエッジ運用を実現する機能にご期待ください。
アバター
aptpod Advent Calendar 2025 12月18日の記事です。 こんにちは、Automotiveグループのエンジニアの清原です。 車載システムとクラウドを連携させる際、 「車載器がそのままインターネットに接続できる」 とは限りません。 今回は、そういった前提条件の事例をご紹介します。 今回の事例では、 車両上で動作する既存システムの 車載器 WAN 上で稼働する intdash の間で、車両内の各種状態や制御情報を 双方向にやり取り する必要がありました。 しかし対象となる車載器は、以下のような制約を持つ既存システムでした。 MQTT による通信機能のみを備えている 車両内の閉塞網で稼働し、WAN への直接的な通信経路を持たない 本記事では、このような前提条件のもとで、 どのようなアーキテクチャを採用したのか MQTT と iSCP v2 をどう役割分担させたのか 実装をどこまでシンプルに保てたのか について紹介します。 要件と制約 要件 制約・非機能要件 システム全体構成 通信構成の概要 通信方式の選定(なぜ MQTT / iSCP v2 か) 車載器側の通信方式(MQTT) WAN 側の通信方式(iSCP v2) ゲートウェイでのプロトコル変換という設計判断 実装時に考慮した点・課題 実装対象の整理と役割分担 MQTT Broker の扱いに関する検討 Broker を内製する場合の課題 OSS Broker 採用の判断 MQTT プラグインの処理フロー 処理フロー概要 車載器 → intdash(上り方向) intdash → 車載器(下り方向) プラグイン構造の汎用性 実装を通して得られた知見 ゲートウェイ集約型アーキテクチャの有効性 OSS を活用した安定性と開発効率の両立 プラグイン方式による拡張性 まとめ 要件と制約 要件 本システムでは、以下の要件を満たす必要がありました。 車載器から各種状態情報を WAN 側へ送信できること WAN 側から車載器へ制御情報を送信できること 複数車両を同一の仕組みで扱える拡張性を持つこと 制約・非機能要件 一方で、車載環境および WAN 越し通信には次のような制約があります。 車載環境では通信の瞬断や遅延が発生し得る 制御情報を扱うため、一定の信頼性とリアルタイム性が求められる 車載器側の実装変更は最小限に抑えたい これらを踏まえ、通信方式およびシステム構成を検討しました。 システム全体構成 本システムは、以下の 3 つの要素で構成されています。 車載器 :車両内で各種状態監視を行う ゲートウェイ :車両内と WAN を中継 intdash :WAN 上で稼働するクラウド側システム 車載器は MQTT クライアントとして動作しますが、 インターネットへ直接接続することはできません。 そのため、車両内に配置したゲートウェイが、 車載器との MQTT 通信 WAN 側との通信 を中継する役割を担います。 このゲートウェイには弊社の車載コンピュータである EDGEPLANT T1 www.aptpod.co.jp を使用しています。 また弊社の intdash を使うことで、既存の車載システムをブラウザベースで遠隔監視・制御できるようになるというメリットを提供できるようになります。 通信構成の概要 図1に、本システムにおける通信構成の概要を示します。 図1:車載器と intdash 間の通信構成(全体像) 通信方式の選定(なぜ MQTT / iSCP v2 か) 本システムにおける通信方式の選定は、 ゼロベースで複数のプロトコルを比較検討した結果ではありません。 既存の車載システムおよび WAN 側システムが持つ前提条件を尊重した上で、 全体として最も現実的で拡張性のある構成を選択する という方針で設計を行いました。 車載器側の通信方式(MQTT) 車載器は既存システムであり、 車両内の各種状態に関わる情報の送受信には MQTT を用いる仕様となっていました。 本プロジェクトでは、 車載器の仕様変更は行わない 既存の通信方式をそのまま活用する という前提があったため、車載器側の通信方式として MQTT を採用しています。 MQTT は軽量な publish / subscribe モデルを採用しており、 通信の瞬断が起こりやすい環境 帯域や遅延に制約のあるネットワーク といった車載環境との親和性が高いプロトコルです。 結果として、既存車載器の仕様を尊重しつつ、 車載環境に適した通信方式をそのまま活用することができました。 WAN 側の通信方式(iSCP v2) 一方、WAN 上で稼働する弊社の intdash では、 リアルタイムデータ連携のための通信プロトコルとして iSCP v2 が採用されています。 iSCP v2 とは intdash 独自のリアルタイム通信プロトコルです。 そのため、intdash との連携においては、 iSCP v2 を用いた通信が前提条件となります。 iSCP v2 は、 双方向のリアルタイム通信 ストリーム指向のデータ連携 複数クライアントの同時接続 を想定したプロトコルであり、 複数車両を統合的に扱う本システムの要件と高い親和性を持っています。 ゲートウェイでのプロトコル変換という設計判断 このように、 車載器側では MQTT WAN 側では iSCP v2 という、異なる通信プロトコルが前提となっていました。 そこで本システムでは、 車両内に配置したゲートウェイをプロトコル変換点として位置づける 構成を採用しました。 この設計により、 既存の車載器を変更せずに利用可能 intdash 側の通信仕様を意識せずに車載器と連携可能 両者を疎結合に保ったまま接続可能 といったメリットを得ることができました。 本プロジェクトでは、 「新しい通信方式を導入すること」よりも、 既存システムをどうつなぐか を重視しています。 実装時に考慮した点・課題 実装対象の整理と役割分担 ゲートウェイには、WAN 上の intdash と通信を行うために、 弊社が提供する intdash Edge Agent2 を導入しています。 intdash Edge Agent2 は、 iSCP v2 を用いた intdash との通信 接続管理や再接続処理 といった WAN 側との連携に必要な機能を担います。 そのため、本構成において新たに実装が必要となったのは、 車載器と MQTT で通信を行う部分 でした。 車載器との通信には、 同じく弊社が提供する Device Connector を採用しています。 Device Connector はプラグイン方式で機能拡張が可能な仕組みを備えており、 今回実装したのは Device Connector の MQTT 対応プラグイン です。 本システムにおける役割分担は、以下の通りです。 intdash Edge Agent2 WAN 上の intdash との通信(iSCP v2) Device Connector(MQTT プラグイン) 車載器との MQTT 通信 メッセージの受信・送信および中継処理 MQTT Broker の扱いに関する検討 MQTT には、以下の要素が存在します。 Publisher Subscriber Broker そのため、MQTT プラグインを設計するにあたり、 MQTT Broker をどのように構成するか が重要な検討ポイントとなりました。 検討した構成案は、以下の2つです。 Device Connector 内に MQTT Broker を実装する MQTT Broker を別途用意し、各コンポーネントが接続する Broker を内製する場合の課題 Broker を内製する場合、 MQTT Broker として求められる仕様の実装コストが高い 安定性や耐障害性の検証に工数がかかる 運用実績のない Broker を車載環境で使うリスクがある といった懸念がありました。 特に、車載環境では通信の瞬断や再接続が発生しやすく、 Broker の安定性がシステム全体に与える影響は小さくありません。 OSS Broker 採用の判断 そこで今回は、 実績のある OSS の MQTT Broker である Mosquitto を利用する構成を採用しました。 この構成では、 Mosquitto が MQTT Broker として動作 車載器は Publisher / Subscriber として接続 Device Connector の MQTT プラグインも Mosquitto に接続 という役割分担になります。 この方式により、 MQTT Broker の信頼性を OSS に委ねられる Device Connector の実装をシンプルに保てる 運用・トラブルシュートの知見を活かせる といったメリットが得られました。 MQTT プラグインの処理フロー 本章では、Device Connector の MQTT 対応プラグインが、 どのように Publish / Subscribe を行い、 車載器と intdash 間の情報を中継しているのかを説明します。 処理フロー概要 MQTT プラグインは、以下の2つの役割を担います。 車載器からの情報を Subscribe して受信 intdash 側からの制御指示を Publish して車載器へ送信 通信の流れは、 車載器 → intdash(上り方向) intdash → 車載器(下り方向) の2系統に分かれます。 図2:MQTT Broker を用いた双方向通信の実装構成 車載器 → intdash(上り方向) 車載器が制御状態やステータス情報を MQTT で Publish MQTT Broker(Mosquitto)がメッセージを受信 Device Connector が該当トピックを Subscribe メッセージを内部形式に変換 intdash Edge Agent2 を介して iSCP v2 で intdash へ送信 intdash → 車載器(下り方向) intdash 上で生成された制御指示が iSCP v2 で配信 intdash Edge Agent2 がメッセージを受信 Device Connector が MQTT メッセージとして整形 MQTT Broker に Publish 車載器が Subscribe して制御指示を受信 プラグイン構造の汎用性 今回は MQTT を例に紹介しましたが、 同様の構成で 他の通信プロトコルにも対応可能 です。 対応するクライアントライブラリを利用し、 Device Connector のプラグインとして実装することで、 プロトコルごとの差異をゲートウェイ側で吸収 車載器ごとの仕様違いに柔軟に対応 といった構成を実現できます。 実装を通して得られた知見 ゲートウェイ集約型アーキテクチャの有効性 今回の構成では、車載器と WAN 側システムの間にゲートウェイを配置し、 車載器側:MQTT WAN 側:iSCP v2 という異なる通信方式をゲートウェイで吸収する設計を採用しました。 この構成により、 既存の車載器を変更せずに利用できる WAN 側システム(intdash)の通信仕様を車載器側に持ち込まない 車載環境特有の不安定な通信条件をゲートウェイで吸収できる といった効果が得られました。 特に、車載器が既存システムである場合、 「境界をどこに引くか」 が全体設計の難易度を大きく左右します。 今回のように、プロトコル変換点を明確にゲートウェイへ集約する設計は、 現実的かつ再利用性の高いアプローチだと感じています。 OSS を活用した安定性と開発効率の両立 MQTT Broker として Mosquitto を採用したことで、 Broker 実装に関する検討・実装コストの削減 MQTT としての挙動に対する信頼性の確保 トラブルシュート時に既存の知見を活用可能 といったメリットがありました。 車載環境では「正常系」よりも、 通信断 再接続 一時的な遅延 といった 非正常系の挙動 が重要になります。 実績のある OSS を適切に組み合わせることで、 システム全体の安定性と開発効率を両立できた点は、 本実装における大きな収穫でした。 プラグイン方式による拡張性 Device Connector のプラグイン方式を活用することで、 今回の MQTT 対応は比較的シンプルな実装で実現できました。 実際には、弊社が提供するプラグインテンプレートに対して、 MQTT クライアントライブラリ(今回は paho-mqtt)を利用 Publish / Subscribe の処理を追加 するだけで、車載器との通信を実現しています。 この構造は MQTT に限らず、 別のメッセージングプロトコル 独自プロトコル 将来的な通信方式の追加 にも同様に適用可能です。 「車載器ごとに通信方式が異なる」 という状況に対して、 ゲートウェイ側のプラグイン実装で柔軟に対応できる点は、 今後の拡張においても大きなメリットになると考えています。 まとめ 本記事では、車載環境において、 MQTT でしか通信できない既存の車載器 WAN 上で稼働する intdash をどのようにつなぎ、 双方向の車両情報連携を実現したかについて紹介しました。 ポイントを振り返ると、 既存システムの制約を前提として受け入れる プロトコル変換点をゲートウェイに集約する OSS や既存製品を適切に組み合わせる ことで、無理のない構成を実現しています。 車載システムでは、 通信が不安定になり得ること 将来的に車両やデバイスが増えること 構成変更が頻繁に発生すること を前提に設計する必要があります。 今回紹介した構成は、 そのような前提条件の中で 現場に持ち込みやすく、拡張しやすい 一つの実装例です。 車載エンジニアの方が、 「既存の車載器とクラウドをどうつなぐか」を考える際の 参考になれば幸いです。
アバター
既存ロボットに intdash を組み込む方法:UnitreeGo2-Wを例に aptpod Advent Calendar 2025 12月17日の記事です。 こんにちは、Roboticsグループのエンジニアの影山です。 今回は、最近外部向けのデモ等で活躍しているUnitreeGo2-W(以下 Go2W)をどのように、弊社サービスと連携させるために環境構築、機能を拡張しているか紹介したいと思います。 この記事を読んで頂くことで、Go2Wに限らず、既存のロボットにどのようにintdashを組み込むことができるのか、参考にして頂ければと思います。 構成 UnitreeGo2-WのHW構成 intdashとの連携 ROSとの連携 ロボットの遠隔制御 センシングデバイスの追加 THETA X ガスセンサー 物理的な搭載方法 構築時の注意点 Dockerとデバイスの接続 Dockerと動画エンコーダ/デコーダ まとめ 構成 実装の一例として、2025年7月に行われたメンテナンス・レジリエンス TOKYO 2025で展示したGo2Wのデモ構成を紹介していきたいと思います。 この展示会ではintdashとGo2Wを中心にいろいろな連携の可能性をデモさせて頂きました。 概要は以下のレポート記事を参照ください。 tech.aptpod.co.jp このデモでは、以下のようなGo2W上の要素をintdashと連携しました。 Go2Wの遠隔操作 頭部カメラ映像 LiDAR 360度カメラ ガスセンサー Go2Wの各種状態 連携の結果、DataVisualizerでGo2Wから取得したデータを以下のような形で可視化できました。 DataVisualizerでGo2Wから取得したデータを可視化した様子 以降、どのようにしてこのような連携を行ったのか、詳細を説明していきたいと思います。 UnitreeGo2-WのHW構成 センサーを搭載した状態のUnitreeGo2-W外観 Go2Wは、通常の歩行の制御などは本体側で行っており、ユーザ側で改変などはできない仕組みになっています。 Go2W本体のより詳細な情報は TechShare様の記事 を参考にたどっていただくのがよいと思います。 ユーザ側のアプリケーションを実行するために、オプションのDocking Stationを搭載して、その中で開発を行います。 Go2W環境のHW構成 Docking Stationは、中にはJetson Orin NXが搭載されており、図に示したように、Ethernetの端子や電源の端子を備えています。 内部にはおそらくEthernetのハブが存在しており、そのハブを経由して本体とEthernetを介してDDSで通信しています。 センサーなどを追加したい場合は、基本的にはUSBを利用して拡張することになります。USBの数も限られるため、複数のセンサーや、インターネット接続のためのデバイスを繋ぐためにはUSBハブが必要になります。 JetsonではUSBハブの電力給電が足りなかったりすると問題がおきることもあり、実際にはまったこともあったので、USBハブにはセルフパワーのものを用いています。 今回は、USBハブを介して、360度カメラのTHETA-X 、Arduinoで制御したガスセンサー2種を接続しています。今回利用したものはUSBハブだけでなくEthernetアダプタの機能もあるので、Go2Wの背中に搭載した小型のマルチ回線ルーター(MAX BR2 Micro)もそこに繋いでいます。 SIMを2枚搭載して、複数回線を束ねることで、実際に電波が輻輳しやすい展示会会場でも比較的安定した通信が実現できていました。 マルチ回線ルーターについて興味のある方は以下の記事を参照ください。 tech.aptpod.co.jp intdashとの連携 Docking Station内のJetsonはユーザで自由に開発可能です。弊社で購入したものにはUbuntu20.04およびFoxyとNoeticがインストールされています。 intdashと連携するためにintdash Edge Agent2を導入する必要があるため、Dockerを用いて環境を構築しています。Dockerを用いることで、OSのバージョンに限定があるようなドライバ等もコンテナを分けることで管理が容易になりますし、構成の管理や機能の追加もやりやすくなります。 以下には概念的なコンテナの構成図を示しています。 Dockerコンテナと周辺機器の構成 オレンジ色がコンテナを表しています。それらをDocker composeで束ねています。 intdash Edge Agent2 や、 intdash ROS2Bridge はAmazon ECRで公開しているので、簡単に環境を構成できます。 systemdのserviceファイルで、docker composeコマンドを実行するように設定して、自動でintdashへの接続が始まるようにしています。 ROSとの連携 ROS2トピックの送受信にはintdash ROS2Bridgeを用いています。 技術的な詳細は、以下の記事をご参照ください。 tech.aptpod.co.jp intdash ROS2Bridgeのコンテナを、 network_mode: host として起動することで、ホスト環境で動いているROS2とも疎通が可能になります。 実際のdocker-compose.ymlと設定例を以下のgithubのリポジトリで公開していますので、参照して頂くことで利用のイメージが付きやすいかと思います。 github.com ロボットの遠隔制御 遠隔制御については、基本的な考え方は以下の記事で紹介した内容のとおりとなっています。 tech.aptpod.co.jp Data Visualizerでは、Gamepadパーツがあり、ブラウザ上でPCに接続したゲームパッドの操作を読み取ることができます。ゲームパッド以外にも、マウスやキーボード操作にも対応しています。 操作の情報はROS2のJoyトピックデータ構造に合わせたMessagePack形式のデータに変換され、指定したエッジにダウンストリームすることができます。 Gamepadパーツ Go2Wでは、intdash ROS2Bridgeを経由することで、ROS2ノードが、操作情報をJoyトピックとして受信できるようにしています。 ROS2ノードが、受信したJoyトピックからGo2W向けの制御トピックを生成してPublishすることで、Go2W本体側を制御します。 センシングデバイスの追加 今回は、360度カメラのTHETA Xと、ガスセンサーをArduinoを経由して搭載しています。 THETA X THETA XではLinuxでビデオデバイスとして認識させるためのドライバの実装が公開されており、それを利用しました。 取得できた動画は4Kの高ビットレートストリームなので、再エンコードして画質を調整しています。 現在は、THETA Xから入力されたエクイレクタングラー形式のまま表示していますが、必要な変換を行うことで、普通の2D映像にすることも可能です。 ガスセンサー 最初は業務用のセンサーを搭載検討しましたが、サイズ的になかなか難しかったため、簡易的なガスセンサー(MQ-3、MQ-7)をユニバーサル基板に乗せ、ケースに入れて、Arduinoで制御するようにしました。 測定結果は、JSON文字列としてシリアル通信で定期的に送信するようにしています。 ガスセンサーの内部 物理的な搭載方法 今回はたくさんのデバイスを搭載するため、どのようにGo2Wの小さい背中に乗せるかが課題の一つでした。 背中に大きな箱を載せると、LiDARの視界を大きく遮ることが問題になります。 そのため、適切なサイズにアクリル板をカットして、それらを金属スペーサでつないで、専用の台座を作りました。 専用の台座を作る際は、簡易的なCADで高さ合わせなども行い、サイズを最適化しました。 tech.aptpod.co.jp CADでサイズ合わせ 構築時の注意点 Dockerとデバイスの接続 Dockerを用いる場合は、ホスト側での開発よりもアクセスできるリソースに制約があるため、デバイスとの接続には注意が必要です。 挿抜したときに、再検知可能かどうか、抜いた状態でもdocker composeがエラーにならないか、など異常系の確認も大切です。 Dockerと動画エンコーダ/デコーダ 動画エンコード処理にDockerを用いる場合、そのままではHWエンコーダにアクセスできません。そのためには利用するコンテナや起動時の設定に注意が必要です。詳しくは以下の記事が参考になります。 tech.aptpod.co.jp まとめ 本記事では、UnitreeGo2-W を例に、既存ロボットへ intdash を組み込み、外部システムと柔軟に連携するための実践的な構成とノウハウを紹介しました。 Jetson 上に Docker コンテナを用いた構成を取り、ROS・各種センサー・遠隔操作を疎結合にまとめることで、ハードウェアや OS の制約を受けにくい拡張性の高い構成を実現している点が、ポイントです。 このアプローチは UnitreeGo2-W に限らず、他の移動ロボットにも応用できます。 intdashと連携することで、データ可視化・遠隔操作・複数システム連携といった価値を加えることができます。 intdashを利用して、ロボットに新たなセンサーや用途を追加したい場合の一つの設計パターンとして、本記事の内容が参考になれば幸いです。
アバター
aptpod Advent Calendar 2025 12月16日の記事を担当します、Roboticsグループの久保田です。 Roboticsグループとして、最近では四足歩行ロボットを扱う機会が増えており、自律走行だけでなく、ロボットに搭載されたセンサーデータの活用が重要なテーマになっています。 四足歩行ロボットには、カメラやLiDARといった視覚・環境認識系のセンサーが標準的に搭載されています。これらのセンサーデータは、aptpodの製品である intdash を用いることで、時系列データとして継続的に収集・蓄積 することができます。 intdashでは、ロボットから送信されるカメラ映像、センサ値などをリアルタイムに集約し、ダッシュボード上で可視化・再生・分析 することが可能です。そのため、データを「単に取得する」だけでなく、どのように解釈し、価値ある情報として可視化・活用するか が重要なテーマとなっています。 今回は、 「見て理解できる」 のかを検証するツールとして、生成AIを用いた画像認識、VLM(Vision-Language Model) の利用を紹介します。 ちなみにaptpodにおけるロボットの事例は以下のブログで紹介されていますのでご参照ください。 tech.aptpod.co.jp 見て理解する VLM(Vision-Language Model)とは VLMのモデル Live VLM WebUIとは Live VLM WebUIのインストール Ollamaインストール VLMモデルインストール Live VLM WebUIインストール Live VLM WebUIの使用 構成 画像を説明させる 鳥の種類を説明させる 温度計の読み取り アナログ時計の読み取り モデル選定と性能評価 まとめ 見て理解する 生成AIでも、画像と自然言語を同時に扱える VLM(Vision-Language Model) が登場し、 画像の内容を文章で説明する 画像に関する質問に答える 物体や状況を柔軟に認識する といったことが可能になっています。 四足歩行ロボットにVLMを組み合わせれば、ロボットが“見て理解する”能力を持つことができ、 実現できれば、ロボット運用者にとっては次のような価値が期待できます。 現地に行かなくても状況把握が可能 :映像とVLMによる説明を通じて、遠隔地からでもロボット周辺の状況を直感的に理解できる トラブル時の原因追跡 :異常が発生した時点の映像・センサーデータ・認識結果を時系列で振り返ることで、原因分析が容易になる 実験ログの共有 :取得したデータやVLMの推論結果をログとして共有し、チーム内でのレビューや知見の蓄積につなげられる VLM(Vision-Language Model)とは VLMとは、画像(Vision) と 言語(Language) を同時に扱うAIモデルの総称です。 従来の画像認識モデル(分類・検出)と異なり、VLMは 画像を文章として説明できる 自然言語の質問に対して画像を参照しながら回答できる という特徴を持ちます。 一方で、VLMにはいくつか 苦手な点・注意点 も存在します。 数値の厳密性が求められるタスクは不得意 :計測値や目盛りの正確な読み取りでは誤差が生じることがある 視点・照明・解像度の影響を受けやすい :撮影条件によって認識結果が大きく変わる場合がある リアルタイム性とのトレードオフ :高精度なモデルほど推論時間やGPUメモリを多く消費する 確信度の扱いが難しい :出力が自然言語であるため、結果の信頼性を別途評価する仕組みが必要になる ロボット用途では、これらの特性を理解したうえで、人の判断を補助する役割としてVLMを活用する ことが重要になります。 VLMのモデル VLMには複数の系統・モデルが存在しますが、ロボット用途では特に次の点が重要になります。 推論に必要なGPUメモリ 推論速度(fps) 多言語対応 オープンソースかどうか 代表的なモデルとしては、次のようなモデルがあります。 Alibaba Qwen Google Gemma 3 Meta LLaMA モデル比較: github.com Live VLM WebUIとは 「どのモデルがどの程度の性能なのか」「Jetsonクラスのデバイスで実用になるのか」ということは、実際に試してみないとわかりません。 そこで今回使用したのが、NVIDIAがオープンソースで公開している「Live VLM WebUI」 です。 公式リポジトリ: github.com Live VLM WebUIは、 カメラ映像をリアルタイムに入力 複数のVLMモデルを切り替えて利用可能 Webブラウザから操作できる という特徴を持ち、 「モデル選定と性能評価」 を行うための実験環境として非常に便利です。 Live VLM WebUIのインストール Live VLM WebUIは、Jetson向けにDockerベースで提供されています。 Ollamaインストール ローカル環境でVLMを使用するためのプラットフォームであるOllamaをインストールします $ curl -fsSL https://ollama.com/install.sh | sh >>> Installing ollama to /usr/ local >>> Downloading Linux arm64 bundle ######################################################################## 100.0% >>> Downloading JetPack 5 components ######################################################################## 100.0% >>> Creating ollama user... >>> Adding ollama user to render group... >>> Adding ollama user to video group... >>> Adding current user to ollama group... >>> Creating ollama systemd service... >>> Enabling and starting ollama service... Created symlink /etc/systemd/system/default.target.wants/ollama.service → /etc/systemd/system/ollama.service. >>> NVIDIA JetPack ready. >>> The Ollama API is now available at 127 . 0 . 0 .1:11434. >>> Install complete . Run " ollama " from the command line. インストール後、systemdサービスで起動しています VLMモデルインストール 使用するモデルをダウンロードします $ ollama pull gemma3:4b pulling manifest pulling 9999d473417a: 100 % ▕███████████████████████████████████████████████████████████████████▏ 7 . 8 GB pulling 715415638c9c: 100 % ▕███████████████████████████████████████████████████████████████████▏ 269 B pulling d3ed60b91757: 100 % ▕███████████████████████████████████████████████████████████████████▏ 6 . 0 KB pulling fcc5a6bec9da: 100 % ▕███████████████████████████████████████████████████████████████████▏ 7 . 7 KB pulling fefc914e46e6: 100 % ▕███████████████████████████████████████████████████████████████████▏ 32 B pulling 1506fb8a7284: 100 % ▕███████████████████████████████████████████████████████████████████▏ 564 B verifying sha256 digest writing manifest success Live VLM WebUIインストール Live VLM WebUIをクローンします $ git clone https://github.com/nvidia-ai-iot/live-vlm-webui.git Cloning into ' live-vlm-webui ' ... remote: Enumerating objects: 958 , done . remote: Counting objects: 100 % ( 29 / 29 ) , done . remote: Compressing objects: 100 % ( 18 / 18 ) , done . remote: Total 958 ( delta 11 ) , reused 17 ( delta 9 ) , pack-reused 929 ( from 1 ) Receiving objects: 100 % ( 958 / 958 ) , 7 . 52 MiB | 17 . 23 MiB/s, done . Resolving deltas: 100 % ( 564 / 564 ) , done . クローンしたディレクトリに移動し、Dockerコンテナを起動します $ ./scripts/start_container.sh ═══════════════════════════════════════════════════════════════ Live-VLM-WebUI Docker Container Starter ═══════════════════════════════════════════════════════════════ 🔍 Checking Docker installation... ✅ Docker installed: Docker version 24 . 0 . 5 , build 24 . 0 .5-0ubuntu1~ 20 . 04 . 1 🔍 Detecting platform... Architecture: aarch64 OS: Linux Platform: NVIDIA Jetson Orin ( L4T R35 ) ═══════════════════════════════════════════════════════════════ Select Docker Image Version ═══════════════════════════════════════════════════════════════ 🔍 Fetching available versions... Available versions: [ 1 ] 0 . 2 .1-jetson-orin [ 2 ] 0 . 2 .0-jetson-orin [ 3 ] 0 . 1 .1-jetson-orin [ 4 ] 0 . 1 .0-jetson-orin 💡 Tip: Use --version flag to skip this prompt Example: ./scripts/start_container.sh --version 0 . 2 . 0 Select version number [ 1 ] or enter custom version: ✅ Selected version: 0 . 2 .1-jetson-orin 🐳 Docker Image: ghcr.io/nvidia-ai-iot/live-vlm-webui:0. 2 .1-jetson-orin Pull latest image from registry? ( y/N ) : 📥 Pulling ghcr.io/nvidia-ai-iot/live-vlm-webui:0. 2 .1-jetson-orin... 0 . 2 .1-jetson-orin: Pulling from nvidia-ai-iot/live-vlm-webui 895d322e8e59: Pull complete d6b7b064c0ba: Pull complete 4be337001fb6: Pull complete Digest: sha256:1cf1c89c868df8956f8f19ac7e0a05fd402d4f8ba173fc8924db8002cc83b160 Status: Downloaded newer image for ghcr.io/nvidia-ai-iot/live-vlm-webui:0. 2 .1-jetson-orin ghcr.io/nvidia-ai-iot/live-vlm-webui:0. 2 .1-jetson-orin ✅ Using image: ghcr.io/nvidia-ai-iot/live-vlm-webui:0. 2 .1-jetson-orin 🚀 Starting container... ✓ jtop socket found: /run/jtop.sock ═══════════════════════════════════════════════════════════════ 🐳 Docker Run Command: docker run -d --name live-vlm-webui --network host --privileged --runtime nvidia -v /run/jtop.sock:/run/jtop.sock:ro ghcr.io/nvidia-ai-iot/live-vlm-webui:0. 2 .1-jetson-orin ═══════════════════════════════════════════════════════════════ 46b16cc69f9ead521f678f38a8f0b1c1f967d008674df467529047b2cf9cf522 ✅ Container started successfully ! 🔍 Verifying jtop socket mount... ✓ jtop socket mounted correctly in container ═══════════════════════════════════════════════════════════════ 🌐 Access the Web UI at: Local: https://localhost:8090 Network: https:// 192 . 168 . 11 .39:8090 ═══════════════════════════════════════════════════════════════ 📋 Useful commands: View logs: docker logs -f live-vlm-webui Stop container: docker stop live-vlm-webui Remove container: docker rm live-vlm-webui Dockerコンテナが起動し、表示されたURL (ここでは https://192.168.11.39:8090 )にて、Webブラウザでアクセスできます Live VLM WebUIの使用 自宅で飼っているペットのオカメインコを対象として、VLMを使用してみました。 構成 今回の検証では、次の構成を使用しました。 reComputer J4012(Jetson Orin NX 搭載) SwitchBot 見守りカメラ Plus(5MP) VLMモデル:Google Gemma 3 四足歩行ロボット Unitree Go2のDocking Stationに搭載されている Jetson Orin NX相当のホストで RTSPストリームのカメラ映像をLive VLM WebUIに入力し、VLMによる画像理解を試してみます。 画像を説明させる 画像を説明させる カメラに写っている映像に対して この画像を一文で説明してください と指示すると、 鳥がその鳥かごの中で遊んでいる様子が、日本の障子模様の背景に写っています といった内容を、自然な文章で説明してくれます 鳥の種類を説明させる 鳥の種類を説明させる 次に、 鳥は映っていますか。鳥の種類を教えてください と質問すると、 写真に写っている鳥は、オカメインコである可能性が高いです。 特徴としては ・白と黒の模様 ・頭部が明るい色をしている ・丸い顔 などがあげられます。 といった情報を回答します。 温度計の読み取り 温度計の読み取り カメラに温度計を映し、 温度・湿度計はありますか。表示は上が気温、下が湿度です と質問します。 VLMは、 はい、画像の温度計と湿度計の表示は以下の通りです ・気温:19.9℃ ・湿度:30% デジタル表示を読み取り、温度・湿度を文章で回答してくれました。 OCR精度は高いです。 アナログ時計の読み取り アナログ時計の読み取り 続いて、アナログ時計をカメラに映し、 この画像に時計は映っていますか。時計の時刻を読み取ってください と質問しました。 VLMは、 時計の時刻は、12時21分です と、自然言語で回答しました。 見た目は判断できますが、針の角度を読み取るような、角度を数値として厳密に扱う幾何的推論は不得意なようです。 モデル選定と性能評価 モデルをいろいろ試してみましたが、Jetson Orin NX 16GBではgemma3:4bが性能を期待できそうです。 1フレームの解析が8秒程度となりました。 まとめ 本記事では、「見て理解できる」のかを検証するツールとして、生成AIを用いた画像認識、VLM(Vision-Language Model) の利用について紹介しました。 VLMを活用することで、ロボットから送信されるカメラ映像、センサ値などを、より人に近い形で理解・活用できる可能性 が見えてきました。 また、Live VLM WebUIによって簡単に試行錯誤ができ、モデルの選定や性能評価が捗りそうです。 生成AIの評価をする際には、ぜひ参考にしてみてください
アバター
はじめに:SA(ソリューションアーキテクト)の「マニュアル迷子」問題 こんにちは、奥山です。 「 aptpod Advent Calendar 2025 」の12月15日の記事です。 普段は intdash のソリューションアーキテクト(SA)として、お客様への技術提案やアーキテクチャ設計を行っています。 SAという仕事柄、製品の仕様やAPIの詳細について即答を求められることが多いのですが、 intdashのマニュアルやSDKリファレンスは膨大です。 正直なところ、すべてを脳内にインデックスするのは人間の限界を超えています。「あれ、あの設定値の上限いくつだっけ?」「このAPIのエラーコードの意味は?」と、日々PDFやWebマニュアルの海を彷徨うこともしばしば。 そこで、 「マニュアルの内容を全部知っているAI(RAG)」 を構築し、自分の「相棒」にすることにしました。 今回は、RAG(Retrieval-Augmented Generation)構築において最も重要であり、かつ泥臭い部分である 「データ収集からベクトル化までのパイプライン」 の実装について紹介します。 はじめに:SA(ソリューションアーキテクト)の「マニュアル迷子」問題 RAGは「前処理」で決まる アーキテクチャ概要 実装の「こだわり」ポイント クローラー:URL正規化で重複を排除する (fetch_urls.py) クリーニング:コードとテーブルを守る「プレースホルダー」 (html_to_text.py) チャンキング:文字数ではなく「トークン数」で切る (chunk_text.py) ベクトル化:APIレート制限との戦い (vectorize_and_store.py) 構築結果 実際にベクトルサーバーを検索してみる まとめ www.aptpod.co.jp RAGは「前処理」で決まる RAGを作ろうとすると、ついLLMのプロンプトや検索アルゴリズムに目が行きがちですが、実運用で使える精度を出すには 「いかに高品質なデータをVector DBに入れるか」 が勝負です。 単にHTMLをテキスト化して突っ込むだけでは、以下のような壁にぶつかります。 重複地獄 : URLパラメータ違いの同一ページが大量に検索に引っかかる。 構造崩壊 : コードブロックやテーブルがただの文字列になり、意味不明になる。 APIエラー : トークン数制限やレートリミットで処理が止まる。 これらを解決するために構築した、Pythonによるデータパイプラインを紹介します。 アーキテクチャ概要 今回作成したパイプラインは以下の4ステップ構成です。 パイプライン全体図 Crawler : マニュアルサイトを巡回し、HTMLを収集(URL正規化付き)。 Cleaner : HTMLからノイズを除去し、Markdownライクなテキストへ変換。 Chunker : LLMのトークン数に基づいてテキストを分割。 Embedder : OpenAI APIでベクトル化し、ChromaDBへ格納(リトライ処理付き)。 実装の「こだわり」ポイント ここからは、実際のコード(抜粋)を見ながら、それぞれの工程での工夫点を紹介します。 クローラー:URL正規化で重複を排除する ( fetch_urls.py ) Webマニュアルには、 ?state=... や #section-1 といった、コンテンツそのものには影響しないパラメータやフラグメントが付くことがあります。これらをそのまま収集すると、同じ内容のドキュメントが大量にDBに入ってしまいます。 そこで、収集前にURLを正規化する関数を実装しました。 # fetch_urls.py より抜粋 def normalize_url (url: str , params_to_remove: set [ str ] = { 'state' }) -> str : """ 指定されたクエリパラメータを除去し、残りのパラメータをソートしてURLを正規化する。 フラグメントも除去する。 """ parsed = urlparse(url) query_params = parse_qs(parsed.query) # 指定されたパラメータを除去 filtered_params = {k: v for k, v in query_params.items() if k not in params_to_remove} # 残りのパラメータをキーでソートして一貫性を保つ sorted_params = sorted (filtered_params.items()) new_query = urlencode(sorted_params, doseq= True ) # URLを再構築(フラグメントなし) normalized_parsed = parsed._replace(query=new_query, fragment= '' ) return urlunparse(normalized_parsed) これにより、無駄なAPIコストを削減し、検索結果の多様性を確保しています。 クリーニング:コードとテーブルを守る「プレースホルダー」 ( html_to_text.py ) BeautifulSoupなどで単純に get_text() すると、表組み(Table)やソースコード(Pre/Code)の構造が崩れてしまい、LLMが理解できないテキストになってしまいます。 そこで、 「重要な要素を一度プレースホルダーに置換して退避させ、テキスト化後に復元する」 という処理を入れました。 # html_to_text.py より抜粋 def clean_html (html: str ) -> str : # ... (省略) ... # --- コードブロック(<pre>)をプレースホルダーに置換 --- for i, pre_tag in enumerate (target_node.find_all( "pre" )): placeholder = f "__PRE_BLOCK_{uuid.uuid4()}__" code_text = pre_tag.get_text() # Markdownのコードブロック形式で保存 block_placeholders[placeholder] = f " \n ``` \n {code_text.strip()} \n ``` \n " pre_tag.replace_with(NavigableString(placeholder)) # ... (テキスト抽出処理) ... # --- プレースホルダーを元の整形済みテキストに戻す --- final_text = clean_text_with_placeholders for placeholder, replacement in block_placeholders.items(): final_text = final_text.replace(placeholder, replacement) return final_text このひと手間により、SDKのサンプルコードや仕様表が綺麗なMarkdown形式で保存され、RAGの回答精度が劇的に向上します。 チャンキング:文字数ではなく「トークン数」で切る ( chunk_text.py ) OpenAIのEmbeddingモデル( text-embedding-3-small )には、入力トークン数の上限(8191 tokens)があります。日本語は英語に比べて文字数とトークン数の乖離が大きいため、単純な文字数カットでは上限オーバーのエラーが出るリスクがあります。 ここでは tiktoken ライブラリを使用し、正確なトークン数ベースでチャンク分割を行っています。 # chunk_text.py より抜粋 import tiktoken tokenizer = tiktoken.encoding_for_model( "text-embedding-3-small" ) def get_token_count (text: str ) -> int : return len (tokenizer.encode(text)) # この関数を使って、TARGET_TOKEN_COUNT (例: 1000) に収まるように分割 また、文脈が途切れないよう OVERLAP_TOKEN_COUNT = 100 程度のオーバーラップ(重複区間)を持たせて分割しています。 ベクトル化:APIレート制限との戦い ( vectorize_and_store.py ) 数万件のチャンクを一気にベクトル化しようとすると、確実に OpenAI API の Rate Limit (429 Too Many Requests) に引っかかります。 これに対処するため、 Exponential Backoff(指数バックオフ) アルゴリズムによるリトライ処理を実装しました。 # vectorize_and_store.py より抜粋 def get_embeddings_with_retry (texts: list [ str ], model: str ) -> list [ list [ float ]] | None : retries = 0 backoff_time = 1.0 # 初期待機時間(秒) while retries < MAX_RETRIES: try : response = openai_client.embeddings.create( input =texts, model=model) return [e.embedding for e in response.data] except RateLimitError as e: print (f "Rate limit exceeded. Retrying in {backoff_time} seconds..." ) time.sleep(backoff_time + random.uniform( 0 , 0.1 )) # Jitterを追加 backoff_time *= 2 # 待機時間を倍にする # ... これにより、夜間にスクリプトを放置しておいても、エラーで止まることなく最後まで処理が完了するようになりました。 構築結果 このパイプラインで生成実行した結果、ローカルの output/chroma_db ディレクトリに ChromaDB が構築されました。 確認用スクリプト ( check.py ) で中身を見てみると… $ python check.py Checking ChromaDB at: .../output/chroma_db Available collections: [ 'intdash_docs_embeddings' ] Collection 'intdash_docs_embeddings' contains 15432 documents SDKのリファレンスからユーザーガイドまで、約1万5千件の知識チャンク が格納されたデータベースが完成しました! 実際にベクトルサーバーを検索してみる 以下は、構築したRAGシステムの検索をテストした際の結果です。 「intdashとは」 という質問を投げかけ、ベクトルデータベース(ChromaDB)から関連性の高い情報を5件取得しています。 Chunk1が最も関連性が高く、Chunk2.3.4と連なっていきます。 また、出力時はURLも添付することで生成AIなどがURLからフェッチすることも想定して添付するようにしています。 $ python3 qa.py --query "intdashとは" Loading vector store from: /workspaces/intdash-doc/output/chroma_db Vector store loaded successfully. Searching for top 5 chunks related to: 'intdashとは' Found 5 chunks. type = <class 'list'> docs = [生データ] --- Retrieved Chunks --- --- Chunk 1 --- Content: intdashとは intdashは、さまざまなIoTデータを低遅延に伝送するハイパフォーマンスなデータ伝送ミドルウェアです。 独自開発のプロトコルにより、帯域の細いモバイル回線でも確実かつリアルタイムにデータを伝送します。 産業用バスを流れる秒間数百〜数千点にもなる大量データにも対応しています。 intdashにより、自動車、ロボット、産業機械、人などの間で大量のデータを相互に低遅延で伝送できます。 多数のセンサーから収集した多様なデータを統一的に管理できるため、センサーフュージョン処理(複数のセンサーから得られたデータを統合して単一のセンサーでは得られない情報を得る)にも活用できます。 また、intdashが収集した時系列データは、活用しやすい形式でサーバーに保存されます。 ユーザーは、過去のデータをダッシュボードで可視化したり、解析用プログラムで処理したり、機械学習用のデータとして使用したりと、データを自在に活用することができます。 Source URL: https://xxxx.xxxx.xx/xxxxxxx/docs/2024R1L/ja/introduction/what-is-intdash.html Original File: manual_docs_2024R1L_ja_introduction_what-is-intdash.txt Chunk Index: 1 Token Count: 426 --- Chunk 2 --- Content: intdash-agentd help intdash-agentdコマンドの使い方を表示します。 利用方法 intdash-agentd help Source URL: https://xxxx.xxxx.xx/xxxxxxx/docs/2024R1L/ja/agent2/reference/cli/intdash-agentd/agentd-help.html Original File: manual_docs_2024R1L_ja_agent2_reference_cli_intdash-agentd_agentd-help.txt Chunk Index: 1 Token Count: 35 --- Chunk 3 --- Content: intdash-agent-tool help intdash-agent-tool help intdash-agent-toolコマンドの使い方を表示します。 Source URL: https://xxxx.xxxx.xx/xxxxxxx/docs/2024R1L/ja/agent2/reference/cli/intdash-agent-tool/agent-tool-help.html Original File: manual_docs_2024R1L_ja_agent2_reference_cli_intdash-agent-tool_agent-tool-help.txt Chunk Index: 1 Token Count: 32 --- Chunk 4 --- Content: intdash-agentctl help intdash-agentctlコマンドの使い方を表示します。 利用方法 intdash-agentctl help Source URL: https://xxxx.xxxx.xx/xxxxxxx/docs/2024R1L/ja/agent2/reference/cli/intdash-agentctl/help.html Original File: manual_docs_2024R1L_ja_agent2_reference_cli_intdash-agentctl_help.txt Chunk Index: 1 Token Count: 35 --- Chunk 5 --- Content: intdashサーバー構築・運用ガイド 本セクションでは、intdash API、intdashのフロントエンドアプリケーション、およびVisual M2M Data Visualizerの構築・運用方法についての情報を提供します。 対象読者 本サイトは、ネットワーク管理やサーバー管理についての基礎知識と、使用する構築方法についての知識(例: AMIを使用して構築する場合はAWSについての知識)を持っている方を対象としています。 Source URL: https://xxxx.xxxx.xx/xxxxxxx/docs/2024R1L/ja/server-setup-and-operation/index.html Original File: manual_docs_2024R1L_ja_server-setup-and-operation_index.txt Chunk Index: 1 Token Count: 180 まとめ 今回は、社内ドキュメントをRAG化するためのデータパイプラインの実装について紹介しました。 世の中には LlamaIndex などの便利なフレームワークもありますが、独自のマニュアル構造に合わせて前処理を細かく制御し、精度を極限まで高めたい場合、このようにPythonでスクリプトを組むアプローチは非常に有効です。 さらに、このRAGをMCP *1 (Model Context Protocol)サーバーを立ち上げて、Claude Codeなどから対話させるとさらに嬉しさ倍増します。みなさんもチャレンジしてみてください。 *1 : ClaudeなどのAIアシスタントと外部ツールを接続するための標準規格
アバター
aptpod Advent Calendar 2025 – 12 月 12 日の記事です。 こんにちは、intdashグループで組み込みソフトウェアを担当しているOchiaiです。 みなさんは、ディスプレイもキーボードもない「ヘッドレス」なエッジコンピューターの状態を確認する際、どうされていますか? わざわざノートPCを開いてLANケーブルで繋いだり、HDMIモニターと電源タップを探し回ったり……。現場でのこうした作業は、物理的な準備だけで一苦労です。 aptpodではこうした課題に対し、専用ディスプレイデバイスの仕組みも準備してあります。ただ今回はエンジニアの遊び心として、それとは別の仕組みでメンテナンス用キオスク端末を作ってみたいと思います。 作るもの 解決したい課題 車載器(エッジコンピューター)は、REST APIで制御可能な場合があります。しかし、現場でAPIを叩くにはPCが必要です。 そこで今回の以下のゴールとします。 簡単に持ち運びでき、ケーブルを1本繋ぐだけでGUI操作したい! アーキテクチャ Raspberry Piを使い、以下の構成を組みます。 物理層: USBケーブル1本で接続(給電 + イーサネット通信)。 OS層: Raspberry Piを「USB LANアダプタ」として認識させる(USB Ethernet Gadget)。 アプリ層: ブラウザ(Chromium)をキオスクモードで起動し、操作パネルを表示。 通信層: Nginxを挟むことで、WebアプリからスムーズにAPIを叩けるようにする。 システム構成図 今回は Raspberry Pi 4 Model B を使用します。 作ったもの 完成したハードウェア構成です。 ハードウェア構成 今回は、Raspberry Piに小型タッチディスプレイを取り付ける予定だったのですが、配送遅延によりタッチパネル化が間に合いませんでした。そのため、暫定的にHDMIディスプレイとUSBマウスを接続しています(つまり現段階ではゴール達成できていない…)。 それでは、動かしてみます。 まずは、エッジコンピューターのUSBポートにRaspberry Piを挿します。Raspberry Pi が起動し、しばらくすると、自動的にキオスクモードのブラウザでWebアプリが表示されます。 計測開始前 「計測開始」をクリックすると、計測開始のREST APIが実行され、ステータスが CONNECTED に変化しました。ステータスもステータス取得用のREST APIで取得しています。 計測開始後 Raspberry Piとエッジコンピューター間は、ケーブルを1本繋ぐだけでGUIで操作できました。 技術解説と構築手順 ここからは、実際にこの環境を構築するための「勘所」を、設定ファイル付きで解説します。 既存の技術の組み合わせですが、いくつかハマりポイントがあります。 1. Raspberry Piを「USBデバイス」化する 通常のRaspberry Piは「USBホスト(親)」ですが、Raspberry Pi 4 Model Bでは設定を変えることで「USBデバイス(子)」として振る舞えます。 OS( Raspberry Pi OS (64-bit) )を焼いたSDカードに対し、初回起動前に以下の編集を行います。 /boot/config.txt # USBコントローラー(dwc2)を有効化 dtoverlay=dwc2 /boot/cmdline.txt # 末尾に追加(イーサネットガジェットとしてロード) modules-load=dwc2,g_ether これで、Raspberry Piはホストマシンから見て「USB接続のLANアダプタ」として認識されるようになります。 2. ネットワークの設定 Raspberry Piのネットワーク設定を行います。 まずは、USB接続のLANアダプタに固定IPを割り当てます。 sudo nmcli connection add type ethernet ifname usb0 con-name usb-gadget ipv4.method manual ipv4.addresses 192.168.7.1/24 ipv6.method ignore Raspberry Pi側でDHCPサーバー(dnsmasq)を動かし、接続相手(車載器)に固定のIPアドレスを配布できるようにします。ここでは、「接続相手のインターネット接続を邪魔しない」 ような設定も行っています。 sudo apt update && sudo apt install -y dnsmasq cat << 'EOF' | sudo tee /etc/dnsmasq.d/usb0-dhcp.conf # 対象インターフェース interface=usb0 # DHCPで常に「192.168.7.2」のみを割り当てるようにする dhcp-range=192.168.7.2,192.168.7.2,255.255.255.0,1m dhcp-leasefile=/dev/null dhcp-authoritative # ホスト側のインターネット通信を邪魔しない設定 # これにより、ホストはインターネット接続に既存の経路(LTE/Wi-Fi)を使い続けられます。 dhcp-option=3 # option 6 (DNS Server) も通知しません。 dhcp-option=6 EOF 3. Nginxによる「CORS回避」と「API中継」 Webページ(localhost)から、異なるIPアドレス(192.168.7.2)のAPIを直接JavaScriptで叩くと、ブラウザのセキュリティ機能によりブロックされる(CORSエラー)ことがあります。 これを回避するため、Raspberry Pi内にWebサーバー(Nginx)を立て、リバースプロキシ 構成にします。 ブラウザは http://localhost/api/ にアクセスする。 Nginxがそれを http://192.168.7.2:8081/ (車載器APIベースURL)に中継する。 こうすることで、ブラウザから見れば「同じサーバー内の通信」に見えるため、CORSエラーを回避できます。 ついでにREST APIにアクセスするためのBasic認証のヘッダー付与もここで設定します。今回は admin:pass123 をbase64変換した YWRtaW46cGFzczEyMw== を固定で設定しています。 sudo apt update && sudo apt install -y nginx cat << 'EOF' | sudo tee /etc/nginx/sites-available/kiosk server { listen 80 default_server; listen [::]:80 default_server; server_name localhost; set $api_target "http://192.168.7.2:8081"; # 1. 作成したHTMLファイルを表示する設定 location / { # HTMLファイルを置く場所 root /var/www/html; index kiosk.html; try_files $uri $uri/ =404; } # 2. 通常のAPI通信用 # ブラウザから '/api/' で始まるアクセスが来たら、ここへ転送する location /api/ { # ここに実際のAPIサーバーのアドレスを記述します proxy_pass $api_target; # Basic認証設定 proxy_set_header Authorization "Basic YWRtaW46cGFzczEyMw=="; # その他の転送設定 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } EOF sudo unlink /etc/nginx/sites-enabled/default sudo ln -s /etc/nginx/sites-available/kiosk /etc/nginx/sites-enabled/default 4. アプリケーションとキオスクモード Webアプリは1つのHTMLファイルで作成します。Geminiに作ってもらったものです。 sudo install -d -m 0755 -o root -g root /var/www sudo install -d -m 0755 -o www-data -g www-data /var/www/html cat << 'EOF' | sudo -u www-data tee /var/www/html/kiosk.html <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>計測コントロールパネル</title> <style> /* === デザイン設定 (変更なし) === */ :root { --bg-color: #121212; --card-color: #1e1e1e; --text-color: #e0e0e0; --accent-green: #00d084; --accent-red: #ff4d4f; --gray: #666; } body { background-color: var(--bg-color); color: var(--text-color); font-family: "Helvetica Neue", Arial, sans-serif; margin: 0; display: flex; justify-content: center; align-items: center; height: 100vh; overflow: hidden; user-select: none; } #app { width: 80%; max-width: 800px; text-align: center; } .header h1 { margin-bottom: 40px; border-bottom: 2px solid #333; padding-bottom: 10px; } .status-panel { background-color: var(--card-color); padding: 30px; border-radius: 8px; margin-bottom: 40px; border: 1px solid #333; } .status-label { font-size: 1.2rem; color: #888; margin-bottom: 10px; } .status-value { font-size: 4rem; font-weight: bold; text-transform: uppercase; letter-spacing: 2px; } /* 状態に応じた色設定 */ .state-connected { color: var(--accent-green); text-shadow: 0 0 20px rgba(0, 208, 132, 0.5); } .state-disconnected { color: var(--gray); } .state-error { color: var(--accent-red); } .controls { display: flex; gap: 20px; } button { flex: 1; padding: 30px; font-size: 1.5rem; border: none; border-radius: 8px; cursor: pointer; color: white; font-weight: bold; transition: opacity 0.2s, transform 0.1s; } button:active { opacity: 0.7; transform: scale(0.98); } button:disabled { opacity: 0.3; cursor: not-allowed; transform: none; } .btn-start { background-color: var(--accent-green); } .btn-stop { background-color: var(--accent-red); } </style> </head> <body> <div id="app"> <div class="header"> <h1>計測コントロール</h1> </div> <div class="status-panel"> <div class="status-label">SYSTEM STATUS</div> <div id="status-display" class="status-value state-disconnected">---</div> </div> <div class="controls"> <button id="btn-start" class="btn-start" onclick="sendMeasureCommand('start')"> 計測開始 </button> <button id="btn-stop" class="btn-stop" onclick="sendMeasureCommand('stop')"> 計測停止 </button> </div> </div> <script> // === 設定エリア (ここだけ書き換えてください) === const CONFIG = { apiBase: '/api' }; // =========================================== // HTML要素の取得 const statusEl = document.getElementById('status-display'); const btnStart = document.getElementById('btn-start'); const btnStop = document.getElementById('btn-stop'); // 状態を取得して画面を更新する関数 async function fetchState() { try { const res = await fetch(`${CONFIG.apiBase}/agent/upstreams/-/state`); if (res.ok) { const data = await res.json(); // 配列の中に code: "connected" があるかチェック let isConnected = false; if (Array.isArray(data)) { isConnected = data.some(item => item.code === 'connected'); } // 画面更新 if (isConnected) { statusEl.textContent = 'CONNECTED'; statusEl.className = 'status-value state-connected'; } else { statusEl.textContent = 'DISCONNECTED'; statusEl.className = 'status-value state-disconnected'; } } } catch (e) { console.error('Fetch error:', e); statusEl.textContent = 'ERROR'; statusEl.className = 'status-value state-error'; } } // コマンド送信関数 (ボタンから呼ばれる) async function sendMeasureCommand(action) { // ボタンを一時的に無効化 setButtonsDisabled(true); try { const res = await fetch(`${CONFIG.apiBase}/docker/composes/measurement/${action}`, { method: 'POST' }); if (res.ok) { // 成功したらすぐに状態確認 setTimeout(fetchState, 1000); } else { alert(`コマンド失敗: ${res.status}`); } } catch (e) { alert('通信エラーが発生しました'); } finally { // ボタンを再度有効化 setButtonsDisabled(false); } } // ボタンの有効/無効を切り替えるヘルパー関数 function setButtonsDisabled(disabled) { btnStart.disabled = disabled; btnStop.disabled = disabled; } // --- 初期化処理 --- // ページ読み込み時に実行 window.addEventListener('DOMContentLoaded', () => { fetchState(); // 3秒ごとに定期更新 setInterval(fetchState, 3000); }); </script> </body> </html> EOF 次に、OS起動時にChromiumブラウザが全画面(キオスクモード)で立ち上がるよう設定します。 # 設定用ディレクトリの作成 mkdir -p ~/.config/autostart cat << 'EOF' | sudo tee ~/.config/autostart/kiosk.desktop [Desktop Entry] Type=Application Name=Kiosk Comment=Start Chromium in Kiosk Mode NoDisplay=false Exec=/usr/bin/chromium --kiosk --incognito --password-store=basic http://localhost/kiosk.html X-GNOME-Autostart-enabled=true EOF chromiumの設定は以下になります。 --kiosk: 全画面モード --incognito: キャッシュを残さないシークレットモード --password-store=basic: 起動時のキーリング解除ポップアップを回避 それから、raspi-config コマンドで「デスクトップへの自動ログイン有効化」と「スクリーンスリープの無効化」を行えば、完全な専用端末の出来上がりです。 5. ホスト側のネットワーク設定 エッジコンピューター側にRaspberry Piが接続された時に、自動的にUSB Ethernet GadgetのNICをupするように設定する必要があります。 今回はNetwork Managerを使用しているので、以下のように設定しました。 nmcli connection add type ethernet con-name kiosk-device ifname usb0 まとめ Raspberry PiをUSBポートに挿すだけで、電源供給、ネットワーク接続、REST APIによる情報送受信までが全自動で行われるメンテナンス用キオスク端末が完成しました。 今回は基本的な構成のみ紹介しましたが、デバイスの実体がLinuxマシンであるので拡張はさまざまあると思います。 自動診断機能: REST APIから情報を取得することでよくあるエラーが起きてないか即時診断 テザリング機能: オンボードのWi-Fiモジュールを使って、オフラインのエッジコンピューターにテザリング。 物理スイッチ: GPIOに緊急停止ボタンやログマーカーボタンを付ける。 今回の技術のように、クラウドだけでなく、ハードウェアに関する使いやすさまで考慮して一気通貫で提供できるのがaptpodの強みです。 気になった方は、ぜひお手元のRaspberry Piで試してみてください!
アバター
aptpod Advent Calendar 2025 12月11日の記事です。 こんにちは、開発本部 Visual M2M グループでフロントエンドエンジニアをしている遠藤です。 昨今、AI の勢いは目覚ましく、弊社でも日々の開発プロセスや自社プロダクトにどのように取り入れていくか検討を重ねています。その中でも、個人的に注目しているのがブラウザ組み込みの AI です。 本記事では、 Google Chrome が現在取り組んでいるブラウザ組み込み AI の動向に触れつつ、この記事の公開時点(2025年12月11日)で実際に利用できる機能を中心に紹介していきます。 Google Chrome の組み込み AI 提供されている API 動作要件 ブラウザ組み込み AI の API を試してみる TypeScript 型定義 可用性の確認 各 API の使い方 Language Detector API 基本的な使い方 Translator API 基本的な使い方 ストリーミング翻訳 Summarizer API 基本的な使い方 ストリーミング要約 おわりに 参考リンク Google Chrome の組み込み AI Google Chrome の組み込み AI は、ブラウザに組み込まれた AI モデルを、ブラウザ API を通して利用できる仕組みです。モデルのデプロイや管理、セルフホスティングを開発者が行わずに、アプリケーションに AI を組み込むことができます。 クライアントサイド(ブラウザ)で処理が完結するため、オフライン利用や機密データのローカル処理などのメリットがあります。また、従来のサーバーサイド AI よりも実装が簡潔になる可能性があり、AI 機能を手軽に取り込める点も魅力の一つです。 提供されている API 本記事公開時点(2025年12月11日)で Google Chrome が公開している組み込み AI の API は以下の通りです。最新のステータスは 公式ドキュメント を確認してください。 API 機能概要 Web 拡張機能 Translator API 言語翻訳 Chrome 138 Chrome 138 Language Detector API 言語自動判定 Chrome 138 Chrome 138 Summarizer API テキスト要約 Chrome 138 Chrome 138 Prompt API 汎用的なプロンプト Origin Trial Chrome 138 Writer API 特定のタスクに応じた文章生成 Origin Trial Origin Trial Rewriter API テキストのリライト Origin Trial Origin Trial Proofreader API テキストの校正 Origin Trial Origin Trial 本記事では、このうち現時点で利用できる Translator / Language Detector / Summarizer の 3 つについて使い方を紹介します。 なお、本記事は Google Chrome での利用を前提にしていますが、 Web Machine Learning Community Group にて標準化の取り組みが行われており、今後は他ブラウザでも利用可能になることが期待されています。 動作要件 Google Chrome の組み込み AI を利用するためには、モデルとハードウェアに関する要件があります。これらは今後更新される可能性があるため、最新の情報は 公式ドキュメント を参照してください。 ブラウザ組み込み AI の API を試してみる TypeScript 型定義 TypeScript を利用する場合は、次のパッケージを追加することで型補完とエラーチェックが有効になります。 npm install --save-dev @types/dom-chromium-ai 可用性の確認 各 API を利用する前に、非同期の availability() 関数を呼び出すことで、利用可能かどうかを確認できます。 // LanguageDetector API の可用性を確認する例 const availability = await LanguageDetector . availability () ; console . log ( availability ) ; availability() は以下の値を Promise で返します。 値 意味 available 利用可能 downloadable モデルのダウンロードが必要 downloading モデルのダウンロードを進行中 unavailable 利用不可(ブラウザがサポートしていない・動作要件を満たしていないなど) 各 API の使い方 現時点で Google Chrome で利用可能な Translator / Language Detector / Summarizer の API について、それぞれ使い方を確認していきます。 Language Detector API 入力テキストの言語を自動判定する API です。入力されたテキストに対して、使用されている言語を確率順にランク付けして返します。多言語フォームにおける入力言語判定や、後述する Translator API と組み合わせた自動翻訳などに活用できます。 基本的な使い方 if ( ! ( "LanguageDetector" in self )) return; // LanguageDetector API の可用性確認 const availability = await LanguageDetector . availability () ; if ( availability === "unavailable" ) return; if ( navigator . userActivation . isActive ) { // アプトポッド公式サイト(https://www.aptpod.co.jp/)より紹介文を抜粋 const input = "アプトポッドは高速双方向なデータストリーミング技術をベースに、産業向けの高速IoTプラットフォームミドルウェア、及びクライアントアプリケーションからエッジハードウェアまでワンストップに開発・提供しています。自動車、ロボット、建機、農機など、様々なモビリティや産業機器をクラウドへ接続し、高精細な遠隔データ収集と分析、リアルタイム監視、遠隔制御、およびデジタルツインの実現など、革新的な産業DXに貢献します。" ; // LanguageDetector インスタンスの作成 const detector = await LanguageDetector . create ({ monitor ( m ) { // モデルのダウンロード進捗を監視 m . addEventListener ( "downloadprogress" , ( e ) => { console . log ( `Downloaded ${ e . loaded * 100 } %` ) ; }) ; } , }) ; // 言語判定の実行 const results = await detector . detect ( input ) ; console . log ( JSON . stringify ( results , null , 2 )) ; /* [ { "confidence": 0.9980313777923584, "detectedLanguage": "ja" }, { "confidence": 0.0000766943849157542, "detectedLanguage": "und" } ] */ } detect() は、 { detectedLanguage, confidence } オブジェクトの配列を Promise で返します。 confidence は 0.0(信頼度が最も低い)から 1.0(信頼度が最も高い)までの値で表されます。 上記の例では、期待通り ja (日本語)が最も高い信頼度で検出されているのがわかります。 Translator API テキストを別の言語に翻訳する API です。アプリケーションの多言語化はもちろん、チャットやユーザー投稿コンテンツのオンデマンド翻訳などに活用できます。 基本的な使い方 if ( ! ( "Translator" in self )) return; // Translator API の可用性確認 const availability = await Translator . availability ({ sourceLanguage : "ja" , targetLanguage : "en" , }) ; if ( availability === "unavailable" ) return; if ( navigator . userActivation . isActive ) { // アプトポッド公式サイト(https://www.aptpod.co.jp/)より紹介文を抜粋 const input = "アプトポッドは高速双方向なデータストリーミング技術をベースに、産業向けの高速IoTプラットフォームミドルウェア、及びクライアントアプリケーションからエッジハードウェアまでワンストップに開発・提供しています。自動車、ロボット、建機、農機など、様々なモビリティや産業機器をクラウドへ接続し、高精細な遠隔データ収集と分析、リアルタイム監視、遠隔制御、およびデジタルツインの実現など、革新的な産業DXに貢献します。" ; // Translator インスタンスの生成 const translator = await Translator . create ({ sourceLanguage : "ja" , // BCP-47 言語コード targetLanguage : "en" , // BCP-47 言語コード monitor ( m ) { // モデルのダウンロード進捗を監視 m . addEventListener ( "downloadprogress" , ( e ) => { console . log ( `Downloaded ${ e . loaded * 100 } %` ) ; }) ; } , }) ; // 翻訳の実行 const result = await translator . translate ( input ) ; console . log ( result ) ; /* Based on high-speed, bidirectional data streaming technology, Aptpod develops and provides one-stop development and delivery of high-speed IoT platform middleware for industrial use, as well as client applications to edge hardware. Connect a variety of mobility and industrial equipment, including automobiles, robots, construction machinery and agricultural machinery to the cloud, and contribute to innovative industrial DX, including high-definition remote data collection and analysis, real-time monitoring, remote control, and realizing digital twins. */ } translate() は翻訳されたテキストを Promise で返します。 上記での例では、 create() 関数の引数で sourceLanguage と targetLanguage で指定した日本語から英語への翻訳が実行されているのがわかります。 ストリーミング翻訳 より長いテキストを翻訳する場合、ReadableStream を返す translateStreaming() を利用することもできます。 // 翻訳の実行 const stream = translator . translateStreaming ( input ) ; for await ( const chunk of stream ) { console . log ( chunk ) ; } Summarizer API 長いテキスト(記事、ドキュメント、チャット履歴など)を簡潔に要約する API です。長い記事の見出し表示や、会議議事録のサマリー作成などに活用できます。 基本的な使い方 if ( ! ( "Summarizer" in self )) return; // Summarizer API の可用性確認 const availability = await Summarizer . availability ({ type : "key-points" , // "tldr" / "teaser" / "key-points" / "headline" format : "markdown" , // "markdown" / "plain-text" length : "short" , // "short" / "medium" / "long" expectedInputLanguages : [ "ja" ] , // BCP-47 言語コード expectedContextLanguages : [ "ja" ] , // BCP-47 言語コード outputLanguage : "ja" , // BCP-47 言語コード }) ; if ( availability === "unavailable" ) return; if ( navigator . userActivation . isActive ) { // アプトポッド公式サイト - 代表挨拶(https://www.aptpod.co.jp/company/ceo/)より文章を抜粋 const input = `アプトポッドは「つなぐ」プロフェッショナルです 現在、自動車、建設、ロボティクスを中心とした様々な分野において、製造業の開発プロセスDX化やイノベーション創出といった数々のプロジェクトを、強みのIoT技術を駆使して、お客様とともに推進しています。当社の基幹製品である「intdash」は、あらゆる種類の産業データを超低遅延で伝送し、欠損なく時系列をそろえてサーバーに永続化するストリーミング技術を特徴としており、現在の多様なプロジェクトの中核を担っています。 数々のプロジェクトを進めるなかで多くのお客様に当社製品の実力を認めていただき、2018年の「intdash」リリース以来、大手製造業のお客様を中心として約50社100プロジェクトの実績を積み上げることができました。 「intdash」は、多種多様でマルチモーダルな産業データを、インターネットを介して、限りなくリアルタイムに、かつ、もとのデータに忠実に伝送するデータストリーミング技術の実現を目指しています。 当社のプリミティブなアプローチとしてはこれを実現するためにデータストリーミングのプロトコル開発から入り、製品の垂直水平統合を行っています。 インターネットが台頭して以降、現代社会を形成し、社会課題を解決していくには、データネットワークの活用が必要不可欠になってきています。 IoTの世界は、IT(人が情報を取得・発信する)だけで良かった時代から、OT(よりフィジカルなモノが通信する)の融合が必須となる時代に変貌しつつあります。今後さらに発展していくメタバース社会、つまりバーチャルワールドが現実世界に融合された社会においては、デジタルツインの社会実装も進行していきます。 そうしたある意味パラレルワールドを融合した社会を形成していくには、現実/仮想といった様々なレイヤやデータの種類を超えた、神経網や血管のようなマルチモーダルなデータネットワークが必要です。アプトポッドは産業シーンを主戦場として、未来志向のマルチモーダルデータネットワークを形成するための技術開発と、インフラプロダクト提供を目指しています。` ; // Summarizer インスタンスの生成 const summarizer = await Summarizer . create ({ type : "key-points" , // "tldr" / "teaser" / "key-points" / "headline" format : "markdown" , // "markdown" / "plain-text" length : "short" , // "short" / "medium" / "long" expectedInputLanguages : [ "ja" ] , // BCP-47 言語コード expectedContextLanguages : [ "ja" ] , // BCP-47 言語コード outputLanguage : "ja" , // BCP-47 言語コード sharedContext : "アプトポッドを知らない人に向けたわかりやすい説明" , // 要約の共有コンテキスト monitor ( m ) { // モデルのダウンロード進捗を監視 m . addEventListener ( "downloadprogress" , ( e ) => { console . log ( `Downloaded ${ e . loaded * 100 } %` ) ; }) ; } , }) ; // 要約の実行 const results = await summarizer . summarize ( input ) ; console . log ( results ) ; /* * アプトポッドは、自動車、建設、ロボティクスなど、様々な分野でIoT技術を活用し、製造業の開発プロセスDX化やイノベーション創出を支援しています。「intdash」という基幹製品は、産業データを低遅延でサーバーに永続化するデータストリーミング技術を特徴としています。 * 「intdash」は、インターネットを介して多様な産業データをリアルタイムに伝送するデータストリーミング技術を実現するために、データストリーミングプロトコルの開発から製品の垂直水平統合を行っています。これにより、大手製造業を中心とした約50社100プロジェクトの実績を積み上げてきました。 * 今後、メタバース社会やデジタルツインの社会実装が進む中で、アプトポッドは現実と仮想を超えたマルチモーダルなデータネットワークを形成するための技術開発とインフラプロダクト提供を目指しており、産業シーンを主戦場としています。 */ } summarizer.summarize() は要約テキストを Promise で返します。 create() 関数の引数で要約の言語や種類、長さ、出力形式などを指定できます。 上記の例では、 type に key-points 、 length に short を指定しているため、3 つの箇条書き形式の短い要約が生成されているのがわかります。 create() が引数として受け取れるパラメータには主に以下があります。 sharedContext : 要約に関する追加のコンテキスト type : 要約のタイプ(デフォルトは key-points ) tldr : 忙しい読者のために要約として作成された短く簡潔な概要 teaser : 最も興味深い点や魅力的な点を要約 key-points : 主要なポイントの箇条書き headline : 要点をとらえた記事の見出し format : 出力形式(デフォルトは markdown ) markdown : マークダウン形式 plain-text : プレーンテキスト形式 length : 要約の長さ(デフォルトは medium ) short : 短い要約 medium : 中程度の要約 long : 長い要約 ストリーミング要約 summarizeStreaming() を利用することで、ReadableStream を返すストリーミング要約も可能です。結果がリアルタイムで提供されるため、逐次的に要約を処理したい場合に便利です。 const stream = summarizer . summarizeStreaming ( input ) ; for await ( const chunk of stream ) { console . log ( chunk ) ; } おわりに ブラウザ組み込み AI によってクライアントサイドで処理が完結することは、プライバシー保護やオフラインでの利用、加えて実装面でも大きな魅力があると感じました。ユーザーの入力データを外部に送信せずに AI を利用できるため、従来のサーバーサイド AI とは異なる利点を得られます。 一方で、動作要件が通常よりもシビアであったり、ブラウザごとにサポート状況に違いが見られたりと、現時点では利用できる場面が限られます。また、Origin Trial 段階の API も多く、今後の仕様変更が発生する可能性もあります。 それでも、こうした機能が標準化され、より広く利用されるようになれば、フロントエンドの体験設計は大きく変わっていくと感じました。必ずしもクライアントサイド AI がサーバーサイド AI を置き換えるわけではなく、双方を併用するハイブリッドな構成を検討することで、より柔軟なアプリケーション設計が可能になるはずです。 intdash や Visual M2M Data Visualizer といったアプトポッドのプロダクトにおいても、活用できる余地は大きいと考えています。今後のアップデートを追いかけながら、具体的な活用方法を検討していきたいと思います。 参考リンク https://developer.chrome.com/docs/ai/built-in https://github.com/webmachinelearning
アバター
aptpod Advent Calendar 2025 12月10日の記事です。 intdashグループの呉羽です。普段はintdashサーバーの開発やintdashを利用したアプリケーションの開発を担当しています。主にGo言語を利用しています。 弊社では業務にAIを活用しており、その中でもエンジニアとして Claude Code を積極的に活用しております。そこで今回は、Claude Codeで実際の業務がどう楽になったかをご紹介します。 ※Claude Codeとは、プロンプトを元に自動でコーディングを行ってくれるAIエージェントです。具体的なチュートリアルや使い方は世に多く存在するので、ここでは割愛します。 Claude Code導入の背景 Claude Codeで楽になった4つの開発業務 1. 既存アプリケーションの改修が楽になった 2. ライブラリの調査を任せられるようになった 3. リファクタリングの敷居が下がった 4. 新規アプリケーション開発も楽になったが準備は必要 終わりに Claude Code導入の背景 Claude Code に生成してもらった ASCII Art 2025年6月以前、Claude Codeを利用するためには従量課金か月100ドルの定額プランが必要でしたが、月20ドルの定額プランでも利用できるようになりました。それがきっかけでエンジニア界隈で話題になり、私も試したところ好感触だったため、実務で利用し始めました。 Claude Code以前はGithubCopilotのような単純なコード補完だけを利用することが多く、いわゆるAIエージェントを前提とした開発手法ではありませんでした。しかしAIエージェントの便利さを覚えた現在では、それらを前提とした開発手法に切り替えています。 Claude Codeで楽になった4つの開発業務 1. 既存アプリケーションの改修が楽になった 普段の開発画面の例 まず既存アプリケーションの機能追加や改修が楽になりました。特に触れるのが初めてだったり久しぶりだったりするアプリケーションは、全体像の把握と改修箇所の特定に労力が必要です。そのような場面は、Claude Codeに指示して調査結果をまとめさせることで手間が省けるようになりました。 大規模な既存アプリケーションの改修の場合、 /init コマンドで生成したCLAUDE.md(AIエージェントが参照するアプリケーションの概要書)だけではなく、機能別にドキュメント化することが大切だと感じています。 アプリケーション全体にどのような機能があるか調査してもらう。 調査で得た大まかな機能ごとに、どのようなアーキテクチャやデータフローになっているかを調べさせ、Markdownとして保存してもらう。 CLAUDE.mdに保存したMarkdownのリンクを記載する。 こうしておくことで、AIエージェントが毎回大規模なコードリーディングを行うことなく効率的にアプリケーションの構造を把握してくれるようになり、時間やコンテキストの節約にもなります。 2. ライブラリの調査を任せられるようになった アプリケーションの開発で、何らかのサードパーティのライブラリを利用することはよくあります。しかし時折、望んでいる機能の実現可能性や、具体的な使い方を調べたいときがあります。その際は、ライブラリをgit cloneしてClaude Codeに調査を指示することで、手間を掛けずに調べられるようになりました。 また具体的なサンプルコードを書いてもらい、それを実装の際にAIエージェントに渡すことで、ライブラリを利用した実装を正確に行ってくれます。 3. リファクタリングの敷居が下がった 次にリファクタリングです。特にリファクタリングは、AIエージェントを利用する際に重要度が高まったと感じています。例えばAIエージェントが、既存のよくないコードに影響されたかのような実装をする場面を何度か体験しました。 その際はリファクタリングを検討しますが、このリファクタリング作業も楽になります。既存アプリケーションの改修と同様、リファクタリング前にはコードの意図やテストケースをClaude Codeに調査してまとめさせます。その上で人間の慎重なコードレビューは必要ですが、以前と比べると敷居が下がったのは間違いありません。 4. 新規アプリケーション開発も楽になったが準備は必要 いくつかの新規アプリケーションの開発でもClaude Codeを活用しました。ただ新規開発は既存アプリケーションの改修とは違い、AIが参照できる土台がありません。そのためClaude Codeにとっては判断材料が少なく、意図しない設計方針になることが多々あります。 それらを防ぐために、ガードレールとしていくつかの実装やドキュメントを予め書いておくことが大切です。また弊社では、社内で統一化されたログ構造やCLI構造にする必要があるため、社内ライブラリなどの説明も欠かせませんでした。逆にそれらを準備さえしておけば、後は既存アプリケーションと同様に効率的な開発が可能です。 終わりに 以上がAIエージェントを実務で活用した話です。半年間ほぼ毎日のように向き合ってきましたが、AIエージェントは自分の思い通りだけ動いてくれる銀の弾丸ではありませんでした。例えば、曖昧な仕様の理解のままに適切な指示を出すことは出来ませんし、指示を出したとしても望んだ通りのものは出来上がりません。人間が成果物の責任を持つ以上、どうしても人間側の理解や判断がボトルネックとして残ります。 それでも、AIエージェントが開発効率を良い方向に変えたことは間違いありません。人間がより本質的な部分に集中するための土台として、今後も適切な距離感で活用していきたいと考えています。
アバター
こんにちは、ソリューションアーキテクトの尾澤です。 この記事は 「aptpod Advent Calendar 2025」 の12月9日の記事です。 皆さんは ChatGPT や Gemini のような「LLMチャットツール」をどのように活用していますか?PDF資料を分析するためにNotebook LMのような「RAGツール」を活用する場面もあるでしょう。とても便利で、すでに仕事道具として手放せないという人も多いのではないでしょうか? 本記事では、これらのツールをSaaSに頼らずオンデバイスで動かせるように、必要な構成と手順を紹介していきます。 なぜオンデバイスなのか 情報漏洩リスクの回避 外部依存の回避 オフライン・エッジ用途への転用 デメリット 動作環境 本記事で使用した環境 LLMチャットを動かす モデルエンジン Ollamaとは LLMのモデル入手 動作確認 チャットUI Open WebUIとは 実際に使ってみる 違うモデルを使ってみる 検索機能をつける コンテナを終了する RAGツールを動かす RAGツールとは Open Notebookとは 初期設定 埋め込みモデルとは 埋め込みモデルの入手 モデルの設定 実際に使ってみる Macユーザの場合 Apple Siliconの方向性 まとめ なぜオンデバイスなのか オンデバイスでAIを動かすメリットは、大きく以下の3つが考えられます。 情報漏洩リスクの回避 SaaSは手軽で便利である一方、データが外部に送信されることが前提です。顧客資料・非公開の内部資料・個人情報などの機密データを「外部に出せない」という要件は珍しくありません。 また、今年2025年7月には、OpenAIのサム・アルトマンCEOが「ChatGPTには守秘義務がなく法的請求があれば会話内容が開示されるため、心の闇や秘密をすべてさらけ出すのはやめたほうがいい」と警告しています。 オンデバイスであれば、そもそもデータが外部に送信されることがないため、安心してAIを使えます。 外部依存の回避 SaaSを利用することは、サービス提供者によるモデル変更・料金改定・レートリミットなどに依存することにもなります。 オンデバイスであれば、そういった外部要因に影響されることなく、UIやツールチェーンを変更せずにモデルを入れ替えながらワークフローを維持できます。 オフライン・エッジ用途への転用 レイテンシ要件が厳しいユースケースや、ネットワーク帯域が弱い環境など、システム構成にSaaSを組み込めないことがあります。 オンデバイスであれば、場所を選ばずにAIを持ち込めます。 デメリット 最大のデメリットとして「SaaS独自のプロプライエタリなモデル」が使えないという点があります。 しかしその一方で、オープンなモデルが豊富なバリエーションで公開されています。 例えば、今年2025年8月にOpenAIが公開した「GPT-OSS」は、SaaSで提供していた「o3-mini」と同等の推論能力をベンチマークで示しており、こうした優れたモデルも次々と現れています。 このようなモデルを用途に応じて選定し、場合によっては追加でチューニングを行うことで、目的を実現することが十分に可能です。 オンデバイスAIの分野では、モデルの選択が重要なテーマになってきます。 動作環境 この記事では GPU を搭載した Windows 環境を前提に手順を紹介します。GPUの有無によって処理速度とユーザ体験が大きく変わるため、実用的な体験ができる環境で解説を進めます。 また、拡張性や可搬性を考慮して、すべてのコンポーネントをDockerコンテナで実行します。後述する docker-compose.yml はそのまま使えるようになっています。 本記事で使用した環境 Windows 11 CPU: Intel Core i7-14700F (2.10 GHz) GPU: NVIDIA GeForce RTX4070 Ti SUPER (16GB) RAM: 16GB Docker Desktop + NVIDIA Container Toolkit 実は、このGPU環境は、小学生の息子がマイクラのMODやらコマンドやらに興味を持ち始めたのをいいことに、それを口実にして家庭内稟議を通して購入したゲーミングPCです。 本当は私自身が、都市開発シミュレーションゲーム「Cities Skylines II」をやりたくて、虎視眈々と狙っていたのです。 この度、このマシンがゲーム以外の分野で活躍できることに興奮しております。 LLMチャットを動かす それでは早速、LLMチャットを動かしていきましょう。 以下のGitリポジトリをダウンロードしてください。 https://github.com/shumaijp/local-llm-chat-docker-compose services : ollama : # image: ollama/ollama:latest image : ollama/ollama:0.13.1 container_name : ollama-chat ports : - "11434:11434" volumes : - ollama-data:/root/.ollama deploy : resources : reservations : devices : - driver : nvidia count : 1 capabilities : [ gpu ] open-webui : # image: ghcr.io/open-webui/open-webui:main image : ghcr.io/open-webui/open-webui:v0.6.41 container_name : open-webui ports : - "3030:8080" environment : - OLLAMA_BASE_URL=http://ollama:11434 volumes : - open-webui-data:/app/backend/data depends_on : - ollama searxng : # image: searxng/searxng:latest image : searxng/searxng:2025.12.7-9d3ec9a2a container_name : searxng ports : - "8080:8080" volumes : - ./config/searxng:/etc/searxng:rw volumes : ollama-data : name : shared-ollama-data open-webui-data : モデルエンジン まずは、LLMの実行環境について説明します。 Ollamaとは Ollamaは「LLM版のDocker」と表現すると分かりやすいかもしれません。Dockerがコンテナを対象とするように、OllamaはLLMを対象として、実行エンジン・CLIツール・公開リポジトリを提供します。 LLMのモデル入手 OllamaのリポジトリにあるLLMは以下のURLで確認できます。 https://ollama.com/library Ollama Library それぞれのモデルには「8b」「20b」のようなタグが表記されています。タグにある数字はパラメータの数を示しており、AIモデルの脳細胞の数のようなものです。パラメータが多いほど賢く知識が豊富でアウトプットの質が高い反面、メモリやGPUなどのリソースをより多く消費します。 使用するときは「モデル名:タグ名」という形式で指定します。 今回はまず、軽量でポピュラーな「gemma2:2b」を使ってみます。コンテナを起動してCLIツールを使ってダウンロードしてみましょう。 docker compose up -d docker exec ollama-chat ollama pull gemma2:2b 動作確認 Ollamaコンテナが正しく動いているかCLIでAPIを叩いて確認してみましょう。 curl http://localhost:11434/api/generate \ -d '{"model": "gemma2:2b", "prompt": "Hello"}' 以下のように、レスポンスが返ってくれば正常に動いています。 {"model":"gemma2:2b","created_at":"2025-12-06T06:52:59.907909427Z","response":"Hello","done":false} {"model":"gemma2:2b","created_at":"2025-12-06T06:52:59.980637427Z","response":"!","done":false} {"model":"gemma2:2b","created_at":"2025-12-06T06:53:00.07509926Z","response":" 👋","done":false} .... チャットUI つぎに、UIについて説明します。 Open WebUIとは Open WebUIは、LLMを「ChatGPTっぽい快適なUIで使えるようにする」ためのWebアプリです。 LLMが「バックエンド」、Open WebUIが「フロントエンド」という役割分担になっています。 Open WebUIの環境変数(OLLAMA_BASE_URL)でOllamaをバックエンドに指定することで、チャット画面の裏側でLLMへのAPIリクエストが走る仕組みになっています。 実際に使ってみる コンテナが起動している状態で、 http://localhost:3030 にブラウザでアクセスしてみてください。 初回アクセス時は、ユーザ登録画面が表示されます。アカウントを作成してログインすると、下図のような ChatGPT ライクな画面が表示されます。 使用モデルが「gemma2:2b」になっていることを確認して、チャットを開始してみましょう。 gemma2:2bの結果 違うモデルを使ってみる 動作確認のために軽量な「gemma」を使いましたが、他にもMicrosoftの「phi」、Metaの「llama」、Alibabaの「Qwen」など多くのLLMが公開されており、GGUF(GPT-Generated Unified Format)というファイルフォーマット規格によって、これらを同じプラットフォーム上で切り替えて使えるようになっています。 試しにOpenAIの「gpt-oss:20b」をダウンロードして使ってみましょう。 docker exec ollama-chat ollama pull gpt-oss:20b ダウンロード済みのモデルは以下のコマンドで確認できます。 % docker exec ollama-chat ollama list NAME ID SIZE MODIFIED gpt-oss:20b 17052f91a42e 13 GB 3 minutes ago gemma2:2b 8ccf136fdd52 1.6 GB 4 hours ago モデルを「gpt-oss:20b」に切り替えて、先ほどと同じプロンプトを投げてみましょう。 gpt-oss:20bの結果 このように、モデルによって推論結果が大きく変わってきます。 検索機能をつける ChatGPTなどのLLMチャットが、もともと学習していない情報をウェブ検索で補って回答してくれるようになってから、利便性がグッとあがりましたよね。 オンデバイスでも、インターネットに出られる環境であれば同じことが可能です。 ユーザアイコンをクリックしたメニューから「設定」→「管理者設定」に進んで「ウェブ検索」を有効にし、「ウェブ検索エンジン」を指定します。APIキーを用意すれば、GoogleやPerplexityなどのSaaSも使えますが、今回は情報漏洩リスクの回避もひとつのテーマなので、メタ検索エンジンである「SearXNG」を使ってみましょう。すでに docker-compose.yml に定義してあるコンテナのそのエンドポイント「 http://searxng:8080 」を指定して、設定を保存してください。 ウェブ検索の設定 設定が終わったらプロンプトに戻ります。プロンプトの「Integration」のアイコンから「ウェブ検索」を有効にして、インターネットを参照しないとわからないようなことを尋ねてみましょう。 ウェブ検索ありの結果 ウェブから情報を取得して回答してくれました。メタ検索エンジンの SearXNG を介しているので、Google等に直接ユーザのIPアドレスやクエリが送信されることはありません。 コンテナを終了する 使い終わったらコンテナを終了させておいてください。 docker compose down 本記事では、ユースケースごとの分かりやすさと学習のしやすさのため、LLMチャット用とRAGツール用でDocker Compose環境をそれぞれ分けて紹介しています。 このあと説明するRAGツールでもOllamaコンテナを使用しますが、両方のユースケースを同時に起動するとポートが衝突するため、それぞれ別タイミングで起動する前提としています。 RAGツールを動かす チャットの次は、Notebook LMのようなRAGツールを動かしてみましょう。 RAGツールとは ChatGPTやGeminiは使ったことあるけど、Notebook LMは使ったことないという人もいるかもしれません。 RAGツールは、特定のウェブページやファイルを読み込ませることで、LLMに対象ドメインを与えて使う仕組みです。 LLMチャットツールが「世界中の知識をもとに答えるAI」であるのに対して、RAGツールは「渡された資料を読んで答えるAI」です。 Googleの「Notebook LM」は、この代表的なツールです。 Open Notebookとは Notebook LMのようなRAG体験をオンデバイスで再現できるのが 今回使用する「Open Notebook」です。 https://www.open-notebook.ai/ Open WebUIと同じように、裏側でLLMへのAPIリクエストが走る仕組みになっています。 以下のGitリポジトリをダウンロードしてください。 https://github.com/shumaijp/local-opennotebook-docker-compose services : ollama : # image: ollama/ollama:latest image : ollama/ollama:0.13.1 container_name : ollama-rag ports : - "11434:11434" volumes : - ollama-data:/root/.ollama deploy : resources : reservations : devices : - driver : nvidia count : 1 capabilities : [ gpu ] open_notebook : # image: lfnovo/open_notebook:v1-latest-single image : lfnovo/open_notebook:1.2.3-single container_name : open-notebook ports : - "8502:8502" # Web UI - "5055:5055" # API environment : - API_URL=http://127.0.0.1:5055 - OLLAMA_API_BASE=http://ollama:11434 # Database connection (required) - SURREAL_URL=ws://localhost:8000/rpc - SURREAL_USER=root - SURREAL_PASSWORD=root - SURREAL_NAMESPACE=open_notebook - SURREAL_DATABASE=production volumes : - notebook-data:/app/data - surreal-data:/mydata depends_on : - ollama volumes : ollama-data : name : shared-ollama-data notebook-data : surreal-data : なお、上記で使用しているイメージ(lfnovo/open_notebook:1.2.3-single)は、Open Notebookを構成するコンポーネントがすべて入っているオールインワン・イメージです。 中身は「フロントエンド」と「バックエンド(RAG)」と「データベース(SurrealDB)」で構成されており、バックエンドとデータベースだけ立ててAPI経由で活用する、という使い方も可能です。 初期設定 RAGを実現するためには、LLMの他に「埋め込みモデル(Embedding Model)」という別の役割のモデルが必要です。 埋め込みモデルとは 埋め込みモデルとは、テキストや画像などのデータを数値のベクトルに変換するモデルのことで、RAGが与えられた情報を解釈するために必要なものです。 意味的に近いデータがベクトル空間上でも近くに配置されることによって、データの類似性を理解できるようになるというものです。 埋め込みモデルの入手 それでは、コンテナを起動して埋め込みモデルをダウンロードしていきましょう。 今回は速度や精度のバランスが最も優れていると言われている「mxbai-embed-large」を使用します。 docker compose up -d docker exec ollama-rag ollama pull mxbai-embed-large なお、今回のコンテナ構成では、Ollama用にダウンロードしたモデルは「ollama-data」という名前付きボリュームに格納されるようになっています。これにより、RAGツール用の「ollama-rag」コンテナと、先ほどのチャット用の「ollama-chat」コンテナは、ダウンロードしたモデルを共有できるようになっています。 それでは、 http://localhost:8502 にブラウザでアクセスしてみてください。 Open Notebook モデルの設定 Open WebUIと違ってモデルを使うための初期設定が必要です。 左ペインの「MANAGE」→「Models」から「Model Management」の画面を開きます。 まず、「API Providers」のセクションで「Ollama」が有効になっていることを確認してください。 Open Notebookの設定画面 「Default Model Assignments」のセクションで、Open Notebookが使用する様々なモデルを指定しますが、そのためにモデルをあらかじめ登録しておく必要があります。 「Language Models」のセクションで、ダウンロード済みの「gpt-oss:20b」を言語モデルに登録しましょう。「+ Add Model」をクリックし、Providerを「Ollama」、Model Nameに「gpt-oss:20b」を入力して「Add Model」してください。 Language Modelの登録 「Embedding Models」のセクションで、ダウンロード済みの「mxbai-embed-large」を埋め込みモデルに登録しましょう。同じく画面下の「+ Add Model」をクリックし、Providerを「Ollama」、Model Nameに「mxbai-embed-large」を入力して「Add Model」してください。 Embedding Modelの登録 「Default Model Assignments」のセクションに戻ると、登録したモデルを指定できるようになっているので、必須マークがついている Chat Model と Transformation Model に「gpt-oss:20b」を、Embedding Model に「mxbai-embed-large」を設定してください。 モデル設定 実際に使ってみる それでは使ってみましょう。 本記事ではデモとして、MQTTのRFCドキュメントを分析対象にしてみます。 まず「+ New Notebook」からNotebookを作ります。 Notebookの作成 つぎに「+ Add Source」→「+ Add New Source」から、参照する資料をURL・ファイル・プレーンテキストのいずれかで追加します。 今回は、RFCのURL「 https://www.rfc-editor.org/rfc/rfc9431.html 」を指定します。 ソースの追加 登録したSourceのベクトル解析が終わったら質問してみましょう。 あえて「MQTTの」とは言わずに「QoS 0/1/2 の違いを要約して」と入力してみると、ちゃんとRFCを読んで「MQTTのQoS」について回答してくれました。 RAGの結果 一見あいまいな質問ですが、ChatGPTのように話題がズレることなく、与えられた情報の範囲内で答えを探してくれます。 このように、推論の前提となる情報源が「指定した資料」に限定されるため、一般的なLLMチャットにありがちなハルシネーションが起こりにくいのが、RAGの特徴です。 Macユーザの場合 本記事で解説したツールは、Apple Silicon Mac でも問題なく動作します。 手順もほぼ同じで、docker-compose.yml をGitリポジトリに用意した docker-compose-cpu.yml に読み替えるだけで動きます。 # GPU環境の場合 docker compose up -d # 非GPU環境の場合 docker compose -f docker-compose-cpu.yml up -d ただし、macOSではApple SiliconのGPUをDockerから使えないため、同じモデルでも推論速度は大きく低下します。 OllamaをDockerではなくシステム上で動かすことで、Apple SiliconのGPUが活かされある程度のパフォーマンスは期待できます。 brew install ollama ollama serve しかし、実際に普段業務で使用している M4 MacBook Pro でも試してみましたが、Metalサポートが進んでいるとはいえ、CUDA対応GPUに最適化されたモデルではやはり性能差は歴然です。 「まず手元の環境でLLMに触ってみたい」場合にはMacでも十分ですが、「日常的に生産性ツールとして使いたい」場合は、GPU環境のほうが圧倒的に快適です。 以下は「GPU Windows + Ollama on Docker」と「M4 Mac + Ollama on Docker」と「M4 Mac + Ollama on System」の3つの環境で同じプロンプトの性能を比較した表です。 環境 gemma2:2b gpt-oss:20b 推論リソース GPU Windows + Ollama on Docker 6.24 tokens/sec 2.11 tokens/sec CUDA/GPU M4 Mac + Ollama on System 1.60 tokens/sec 0.68 tokens/sec Metal/GPU M4 Mac + Ollama on Docker 0.45 tokens/sec 0.13 tokens/sec CPU Apple Siliconの方向性 ただし、この結果は決して「Applie Siliconが遅い」という話ではありません。 Apple Siliconはそもそも「AIをオンデバイスで高速かつ省電力で動かすこと」をターゲットに設計されています。 そのため、Macユーザが本来とるべきアプローチは、NVIDIAスタックで学習したモデルをそのまま動かすことではなく、それをCore ML 形式に最適化して macOS / iOS / visionOS などで動かすことです。 ですので、前述の比較表は優劣を比較するためのものではなく、あくまで参考値として見ていただけると幸いです。 すでに、Meta・Mistral・Alibaba などの主要モデルが Core ML 版を公開しており、Apple Silicon 上での AI 活用は来年以降もさらに実用化が進んでいきそうだと感じています。 本記事では「NVIDIAスタックで学習されたモデルを動かす」という前提のため Core ML 最適化には触れていませんが、こちらの動向も把握しておくと良いと思います。 まとめ LLMやRAGをオンデバイスで動かすことで、機密データをクラウドに出さずにAIの恩恵を受けられるだけでなく、それをそのままエッジに持ち込めるというメリットがあります。 これは産業IoTにおいても重要なテーマで、エッジ環境における「セキュリティ」「レイテンシ」「ネットワーク」などの要件を、AIにも適用できるということになります。 今回紹介した構成はデスクトップでの検証用途ですが、アプリケーション層とモデル層がAPI分離されているため、ゲートウェイやMEC環境への移植も可能です。 もし「こんなことをAIでできないか?」というアイデアが浮かんだ時、今回構築した環境がその入口になれば嬉しいです。
アバター
aptpod Advent Calendar 2025 12月8日の記事です。 こんにちは、デザインチームの上野です。 はじめに QGISをつかってGEO TIFFファイルを書き出す QGISとは QGISのインストール 地図の領域を設定しGEO TIFFファイルを書き出す XYZタイルを用意し、レイヤーに追加する 測地系を変更する 書き出ししたい領域を矩形レイヤとして定義する GEO TIFF画像を書き出す [おまけ]領域の緯度経度を抽出する BlenderでGEO TIFFを読み込んで3Dマップ加工する Blenderとは Blenderのインストール Blender GISをつかう プラグインをインストールする 画像のインポート 建物の追加 モデルの調整 道路のトレースとモデルの追加 マテリアルの変更 完成 まとめ はじめに 近年、自動運転、都市計画、拡張現実(AR)、ゲーム開発といった様々な分野で デジタルツイン の需要が高まっています。 2Dマップや平面図では表現しきれなかった奥行きや構造物などを立体的に見ることができる 3Dマップ はこれらの先端技術を実用化するための鍵となっています。 しかし、本格的な3Dマップを作るとなれば制作の工程は複雑になりコストも高価になりがちです。 そこで本記事では、地理空間データの処理・分析に特化した無償のオープンソースGISソフトウェア 「QGIS」 と、無料の3Dモデリングツール 「Blender」 を連携させ、効率的かつ低コストで3D地図を作成する手法を紹介します。 以下の画像はQGISとBlenderをつかって作ったオフィス周辺のエリアの3Dマップ画像です。 本記事の手順を実施すると、下図のような3Dマップが作成できます。 作成した3Dマップは地形や建物の立体モデルとしてAR/ゲーム/都市計画/ロボティクス/自動運転の背景などに活用できます。 例えば走行中の車両のGPSデータと連携することで空間を視覚的に捉えることができるなど2Dのみではできなかった体験が可能となります。 作り方ですが QGISをつかってGEO TIFFファイルを書き出す BlenderでGEO TIFFを読み込んで3Dマップ加工する と、とてもシンプルな手法で作成しています。 QGISをつかってGEO TIFFファイルを書き出す QGISとは QGISは無料で使えるオープンソースのデスクトップ型地理情報システムソフトウェアで、地図データの表示、作成、編集、そして分析などが行えるアプリケーションです。 qgis.org QGISのインストール まずはQGISをインストールします。 QGISの公式サイト( https://www.qgis.org/download/ )にアクセスします。 寄付をしない場合は「Skip it and go to download」ボタンをクリックします。 ダウンロードページで、「長期リリース版(LTR)」の「QGIS Standalone Installer (64 bit)」などのインストーラーをクリックしてダウンロードを開始します。 ダウンロード後、インストーラーにしたがってインストールを行います。 地図の領域を設定しGEO TIFFファイルを書き出す インストールが完了したらQGISを起動させます。 ここでQGISで書き出したGEO TIFFは、後述するBlender側で「地形を生成する元データ」として使用します。 XYZタイルを用意し、レイヤーに追加する XYZタイルは、Web地図を高速に表示するために、地図画像を一定の大きさのタイルに分割し、それをサーバーが提供する仕組みです。 QGISでは、「XYZ Tiles」接続を利用することで、インターネット経由で配信されているタイル地図を簡単にプロジェクトに追加し、背景地図として活用できます。 まず、XYZタイルを右クリックし、接続を追加します。 ここではGoogle Mapの衛星写真を使用したいと思います。 名称を入力後、URLに https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z} と入力し[OK]を押します。 他のタイル情報に接続したい場合は XYZ Tiles(地図タイル)のURL一覧 を参考にしてください。 追加した接続をレイヤーにドラッグすれば地図が表示されます。 測地系を変更する 地図やGISで測地系が異なると、同じ緯度・経度を指していても、地球上の実際の位置が数メートルから数百メートルずれて表示されてしまいます。 そのため、異なるデータソースを重ね合わせて使う際は、必ず同じ測地系に統一する必要があります。 まずはプロジェクトで使用される座標系の変更を行います。 画面右下の [EPSG:〜]と書かれている箇所をクリックします。 設定画面がでたら測地系を選択します。 日本では平面直角座標系が全国で使用されています。 各都道府県で割り当てられている系統がことなるので こちら を参考に変更してください。 今回作るモデルは東京エリアにあるので該当する JGD2000 Japan Plane Rectangle CS IX を指定し、OKを押します。 座標系が変更されました。 書き出ししたい領域を矩形レイヤとして定義する 座標系が変更できたら書き出したい領域を設定していきます。 左上にある[新規シェープファイルレイヤ]をクリックし、以下の画像のように編集してレイヤを追加します。 レイヤ追加後は編集できるようになります。 [編集モード切り替え]を行えばいつでも地物の追加や削除など再編集することができます。 地図上の2箇所をクリックすることで矩形が生成されます。 またレイヤのスタイルは細かく設定することができます。 GEO TIFF画像を書き出す 書き出したい矩形を作成できたらGEO TIFFとして書き出します。 [OK]を押したら自動的に設定されたディレクトリにGEO TIFF画像が書き出されます。 次はBlenderの作業に入ります。 [おまけ]領域の緯度経度を抽出する QGISを使うメリットの一つとして、座標データを取得できることが挙げられます。 先ほど作成した矩形の四隅の平面直角座標を取得してみます。 矩形レイヤを選択し[ベクタ]→[ジオメトリツール]→[頂点を抽出]を選択します。 入力レイヤが合っているか確認し[実行]を押します。 そうすると矩形の四隅が抽出された 出力レイヤ が追加されます。 出力レイヤを選択した状態で[フィールド計算機]を押します。 属性テーブルに追加するフィールド名や計算式を以下のように設定します。 フィールドの名前を入力(任意の文字列) フィールド型を[小数点付き数値]にし、フィールド長の小数点以下の桁数を調整 式に $x と入力 (Y座標の場合は $y ) X座標とY座標でそれぞれ行います。 出力レイヤを右クリックし、メニューから[属性テーブル]を押します。 別ウィンドウが立ち上がり、先ほど計算したX座標、Y座標がテーブルで表示されます。 このとき、出力レイヤのスタイルでラベルをつけておくとどこの座標がどの位置にあるか視覚的にわかりやすくなります。 取得した値は平面直角座標系をベースにしたものでメートル単位の値になっています。 国土地理院のツール などを使用し変換することで緯度経度の値を取得することができます。 今回は外部ツールを使いましたが、もしかしたらQGIS内で変換できるかもしれません。 今後まだまだ機能含めてQGIS周りを調査していきたいと思います。 以上のようにして領域の矩形の四隅の緯度経度を抽出することができました。 BlenderでGEO TIFFを読み込んで3Dマップ加工する ちょっとしたおまけを挟みましたが、改めてBlenderでの作業にはいります。 Blenderとは Blenderは、オープンソースで無料の統合型3Dコンテンツ制作ソフトウェアです。 幅広い機能により、Blenderは映画制作、ゲーム開発、建築ビジュアライゼーション、プロダクトデザイン、3Dマップの作成など、様々な分野で世界的に利用されています。 www.blender.jp Blenderのインストール Blenderをインストールします。 Blenderの公式サイト( https://www.blender.org/ )にアクセスします。 トップページにある「Download Blender」ボタンをクリックします。ダウンロードページで、ご自身のOS(Windows、macOS、Linux)に対応した最新版の「Blender x.xx」のボタンをクリックし、インストーラーをダウンロードします。 ダウンロード後インストーラー起動し、インストールを完了します。 Blenderをインストールできたら起動し、 Blender GIS というアドオンを使用して画像をインポートします。 Blender GISをつかう プラグインをインストールする まずはBlender GISを使うためにプラグインをローカルにダウンロードします。 Blender GIS を開き、Clone or DownloadからDownload ZIPを選びダウンロードしてください。 BlenderでBelnder GISが使えるようにインポートします。 [編集]→[プリファレンス]を開きます。 [アドオン]を開き、右上のメニューから[ディスクからインストール]を選び、先ほどダウンロードしたZIPファイルを直接指定します。 追加されたあとチェックボックスにチェックを入れることでBlender GISが使用できるようになります。 BlenderのUIにGISのメニューが追加されます。 画像のインポート プラグインが使えるようになったらQGISから書き出したGEO TIFFをインポートします。 [GIS]→[インポート]→[Georeferenced raster(.tif .jpeg .jp2 .png)]をクリックし、書き出したGEO TIFF画像を読み込みます。 画像がインポートされ地図のモデルが生成されました。 建物の追加 Blender GISにはOSMを取得して該当エリア内のオブジェクトを生成する機能があります。 その機能を使って今回は建物を追加します。 [GIS]→[Web geodata]→[Get OSM]をクリックします。 どのOSMを取得するのかON/OFFできるので[Bulidng]をONにします。 建物のメッシュが生成されました。 モデルの調整 あとは3Dマップを使用するケースに合わせてモデリングを調整していきます。 道路のトレースとモデルの追加 例えばGPSで道路を走行するケースの場合、道路を強調したいといった目的がでてくるかと思います。 Blender GISのOSMで生成される道路は以下の画像のように生成されます。 これだと車線や道路の幅といった道路の正確性の表現がわかりにくいので、Adobe Illustratorを使って衛星写真からトレースしSVG形式で書き出します。 BlenderでSVG画像を読み込みスケールや位置などを調整するとこのようになります。 今回は大雑把にトレースしましたが白線なども同様に追加することでより詳細度を高めることができます。 マテリアルの変更 あとはお好みに合わせて各オブジェクトのマテリアルを調整します。 完成 まとめ いかがでしたでしょうか? 3Dマップを作るにあたって、最初はどうやって領域の緯度経度など値の正確性をだすのか、建物などのメッシュを一つ一つ作らないといけないのか、などとても難しいものだと考えていましたが、QGISとBlenderを使うことで十分なクオリティでデータの正確性も担保でき、かなりコストを抑えて作成できることがわかりました。 どちらも無料のソフトウェアで、使っているユーザーも多く、ネット上に有識者によるドキュメントもたくさんあるので是非調べながら手元で色々と試してみてはいかがでしょうか。
アバター
aptpod Advent Calendar 2025 12月5日の記事です。 こんにちは、QAグループのKurahashiです。 突然ですが、リグレッションテストを毎回手作業でやるの大変じゃないですか? 画面遷移を一つずつ確認して、スプレッドシートにチェック入れて...正直もっと効率化できないかなと思いますよね。 ふと「AI に任せられないかな?」と思って Claude に相談してみたら、Claude 上で直接 Playwright が動いて、自動でテストを実行できるとのこと!! まだ、お試し段階ですが「これいけるな」という感触があったので記事にしました。 やってみたこと 実際の手順 やってみてわかったこと 今後の展開 まとめ おまけ やってみたこと 今回お試しで弊社製品の「Edge Finder」で画面遷移テストを、Claude と Playwright で自動化してみました。 Edge Finder とは intdashに接続しているデバイスが送信しているリアルタイムデータをブラウザから確認するためのWebアプリケーションです。 www.aptpod.co.jp 従来のやり方 手作業で画面を一個ずつクリック スプレッドシートにチェック 今回試した方法 テストケース(CSV)を Claude に渡す Claude が Playwright で自動実行 その前に、「どうやって Claude が Playwright 動かしてるの?」って疑問に思いますよね。 答えは MCP (Model Context Protocol) です。 簡単に言うと、Claude がローカルのツールを直接操作できる仕組みです。 ┌─────────────┐ │ Claude │ ← チャットで指示 └──────┬──────┘ │ MCP ┌──────┴──────┐ │ Playwright │ ← Claude が直接実行 └─────────────┘ なので先ずは、MCP の設定です。 ただ、これがくせ者で...私は苦労しました😅 環境は人それぞれなので、ここでは私が特にハマった3つのポイントを記載しておきます。 JSON構文の末尾コンマ抜けや括弧の閉じ忘れ。 @playwright/mcpとは別に npx playwright install が必須。 設定後のClaude App完全再起動を忘れずに! 実際の手順 「ここからが本番」と意気込みましたが、環境整備が終わった時点でほぼゴールでした。 試しに今まで使っていたテストケースを投げると自動でブラウザが起動しテストをサクッとこなしていきました。(あまりの早さに呆然と眺めているだけで終わってました...) 1.テストケースを CSV で用意 普段使ってる Googleスプレッドシートをそのまま CSV でダウンロード。 特別なフォーマットは不要です。 2.Claude に投げる Claude Desktop に CSV をアップロードして、こんな感じで頼みました。 このCSVのテストケースをPlaywrightで実行してください。 対象URL: https://*****.*****.jp/console/edges/ 3.Claude が自動実行 Claude が自動で以下の作業を進めてくれます。 CSV を読んで理解 Playwright のコードを生成(裏側で) 実際にブラウザ起動してテスト実行 結果をレポートにまとめてくれる(一部抜粋ですがこのような形で結果を出力してくれます) やってみてわかったこと ✅ とにかく楽 CSV 渡すだけで自動テスト完了 コード書く必要なし ✅ 試行錯誤しやすい エラーが出ても、その場で Claude に相談 会話しながら直せる ✅ プログラミング知識不要 コマンド打たなくていい エディタも不要 ✅ テストケースを詳しく書いていたのが正解だった 今まで「細かすぎかな?」って思っていましたが、これが Claude には助かるらしい。 前提条件がある → どの画面から始めればいいかわかる ボタン名が具体的 → どのボタンを押せばいいかわかる 期待値が明確 → 検証条件をそのまま実装できる 人間が読んでわかりやすいテストケースは、AI にもわかりやすい ってことですね。 今後の展開 今回はお試しで Edge Finder の画面遷移テストだけやってみました。 予想以上にうまくいったので、今後は 他製品の画面遷移テストにも適用 より複雑なテストにも挑戦 チーム全体で使えるように このあたりを進めていきたいと思います。 まとめ やってみた感想 思ってたより簡単だった CSV 渡すだけで自動化できた コード書かなくていいの、楽すぎる 「AI でテスト自動化とか難しそう」っと思っていましたが、実際やってみると全然そんなことなかったです。 特に「プログラミングはちょっと...」って人こそ、試してみる価値あると思います。 おまけ Claude に頼めば、こんなこともできます。 スクリーンショット撮影 テスト実行時に各画面のスクリーンショットも撮ってください レポート生成 結果をHTMLレポートにして保存してください 全部、会話で頼めます。便利。
アバター
aptpod Advent Calendar 2025 – 12 月 4 日の記事です。 この記事は、コーポレートマーケティング室デザイナーの tetsu が担当しています。 当チームでは、製品・案件向けアプリケーションの UI デザインをはじめ、マーケティングクリエイティブ、WEB サイトや当テックブログのデザイン・運用などを担当しています。 テック業界の師走といえばアドベントカレンダー。アプトポッドのテックブログでも毎年多くの記事が公開されます *1 が、その運用の中で地味に負荷が大きかったのが OGP(サムネイル画像)の作成 でした。 これまでは、デザイナーが毎回書き出し・設置を行っており、急ぎの差し替えや連携の手間が小さなストレスとして積み重なっていました。 そこで今年は Figma の新機能「Figma Make」 を活用し、OGP 作成から進捗管理までを自動化・アプリ化しました。この記事では、その構築プロセスを紹介します。 *2 www.figma.com これまでの OGP 制作フロー 作成ステップ 1. Figma Make の設定 2. デザインと基本機能の実装 ベースデザインの作成 画像生成機能の実装 テキスト配置の調整 3. 管理アプリとしての UI 拡張 4. Supabase 連携によるステータス管理 5. 書き出し画質の最適化 まとめ これまでの OGP 制作フロー 通常時のテックブログでは、記事内容に合わせて毎回サムネイルを手作業で制作しています。 コンテキストに合わせたイメージを作るため、記事を読む時間と画作りで1記事に1〜2時間までの工数を目標に制作しています。 普段のOGP しかしアドベントカレンダー期間中は、 毎日更新(時間制約) 全体の統一感(デザイン要件) という理由から、昨年までテンプレートを使用していました。 テンプレートの活用をした場合でも、執筆者とのやりとりや設置確認などで15分程度はかかります。 15分×20記事で300分程度は工数をかけていたことになります。 また、工数以上に急ぎの差し込み対応である場合が多く、常に他のタスクの手をとめる必要がありました。 2022〜2024年のアドベントカレンダーOGP 上記の課題を解決するために、今年はさらに一歩進め、テンプレートから“アプリ化”へと発展させた形です。 作成ステップ 1. Figma Make の設定 Figma Make の設定から 「実験モデル > Gemini 3 Pro」 を有効化します。 これにより、プロトタイプ上で高度な AI モデルを利用できるようになります。 Figma Make 設定画面 2. デザインと基本機能の実装 ベースデザインの作成 まず、アドベントカレンダー向けにクリスマスらしいベースデザインをラフで作ります。 この段階で 編集可能エリア(タイトル・著者名) をあらかじめ定義しておきます。 ベースデザイン 画像生成機能の実装 プロンプトで以下を指定します: サイズは 1200 × 630px(OGP 標準) プレビュー内容を JPG としてダウンロード可能 にする 日替わり感を出すため、アイコンや小物を可変にする 画像生成UI テキスト配置の調整 テキストの級数やフォントは UI から調整できますが、 位置・マージン調整は精度を高めるため数値指定 が必要でした。 Figma Make と数度やりとりし、うまくいかない箇所はコードを確認しながら値を指定しました。 3. 管理アプリとしての UI 拡張 ここまでは「デザインチームが書き出し、Figma に一覧化」という想定でした。しかしアドベントカレンダー期間中は、執筆者の編集待ちや掲載タイミングの調整が発生し、お互いに小さな負荷が生まれがちです。 そこで、執筆者自身が “自分の担当日を選び、その場で OGP を生成できる” 管理 UI を追加しました。 担当日を選択して OGP を作成 入力完了ステータスを一覧で可視化 一覧性のある管理UI 画像を生成する側のUIにも、管理UIとの導線や、現在編集している日付をわかりやすくするために右側に日付バーなどを追加しました。 最終的な画像生成UI これにより、執筆者は進捗を自分で管理でき、デザイナー側も状況を一目で把握できるようになりました。 4. Supabase 連携によるステータス管理 プロトタイプ段階ではステータスがブラウザローカルに保存されるだけで、実運用には不十分でした。 そこで、オープンソースの BaaS (Backend as a Service)である Supabase を採用し、進捗ステータスをクラウドで管理できるようにしました。 supabase.com Figma Make は API 連携まわりの操作性が良く、デザイン畑の人間でもほぼノーコードで扱える点が特に便利でした。 5. 書き出し画質の最適化 最終的にデザインの細部を調整し、出力処理を整えます。 普段はデザイナーが毎回 JPG 圧縮の度合いを調整していますが、今回は 設置作業を執筆者に委ねるため、品質設定をシステム側で固定 しています。 書き出し時に自動で Quality 0.8 の JPG に変換 画質とファイルサイズのバランスを安定化 これにより、誰が生成しても品質が揃い、運用負荷を最小化できます。 まとめ 今回の取り組みによって、師走の忙しい時期に細切れで発生していたデザイナーのタスクが大幅に軽減されました。執筆者も、自分のタイミングで OGP を生成・設置でき、全体の流れがスムーズになりました。 「運用フロー自体をデザインする」 ことで、チーム全体のコンテキストスイッチを減らせたことが最大の成果だと感じています。 Figma Make のようなツールを活用すれば、デザイナーでもアプリライクな課題解決を手軽に実装できます。ぜひ試してみてください。 *1 : アプトポッドのアドベントカレンダーは、12 月の平日毎日更新 *2 : この記事の内容は 2025 年 11 月現在の情報です
アバター
intdashグループの大久保です。 aptpod Advent Calendar 2025 の12月3日の記事を担当します。 Linuxでアプリケーションを開発するとき、その依存ライブラリ含めどう配布するかは悩ましい問題です。debのようなパッケージであれば、対応するディストリビューションごとにビルドしなければなりません。muslであれば単体で動作する実行バイナリを作れますが、依存するライブラリが多ければビルドが難しかったり、対応不可であったりします。 ここでは、Linuxのアプリケーション配布をするときに有用と思われるAppImageを紹介します。 AppImageとは テスト用アプリケーションの作成 debパッケージからAppImageへの変換 AppImageの動作 カスタムのAppRun まとめ AppImageとは AppImageは、1つの実行ファイルに依存するファイルを内包するようにし、ディストリビューションに関わらず単体で動作するようにしたパッケージフォーマットです。 https://appimage.org/ 今回は、debパッケージからAppImageを作成できる pkg2appimage を使用します。 テスト用アプリケーションの作成 開発言語は特に制約はありませんが、今回はAppImageパッケージを生成する途中でdebパッケージを経由する方法をとるので、debパッケージを簡単に作れるRustを今回は例に使います。他の方法でdebパッケージを作成してもらっても問題ありません。 ここではGTKを使用して画像を表示するシンプルなGUIアプリケーションを作成します。 Cargo.tomlは以下のように準備します。 [ package ] name = "appimage-test" authors = [ "Test <test@example.com>" ] version = "0.1.0" edition = "2024" license = "MIT" [ dependencies ] gtk4 = "0.10" # cargo-deb の設定 [ package.metadata.deb ] maintainer = "Test <test@example.com>" depends = "$auto" section = "misc" priority = "optional" assets = [ # コンパイルされたバイナリを /usr/bin に配置 [ "target/release/appimage-test" , "usr/bin/" , "755" ] , # desktopファイルを /usr/share/applications に配置 [ "appimage-test.desktop" , "usr/share/applications/" , "644" ] , # アイコンファイル (logo.png) を /usr/share/pixmaps/appimage-test.png として配置 [ "logo.png" , "usr/share/pixmaps/appimage-test.png" , "644" ] , ] main.rsは以下になります。注意点としては、画像ファイルにアクセスするとき /usr/share/pixmaps/appimage-test.png と絶対パスを指定するのではなく、実行ファイルのある場所 usr/bin からの相対パスで指定していることです。AppImageは実行時に一時的なパスにファイルを配置するため、絶対パスが機能しなくなります。 use gtk4 :: {Application, ApplicationWindow, Image, gdk_pixbuf :: Pixbuf, prelude :: * }; use std :: env; fn main () { let application = Application :: new ( Some ( "com.example.appimage-test" ), Default :: default ()); application. connect_activate (build_ui); application. run (); } fn build_ui (app: & Application) { let exe_path = env :: current_exe (). expect ( "Failed to get executable path" ); // 実行ファイルからの相対パスから usr/share/pixmaps/appimage-test.png を取得 let exe_dir = exe_path . parent () . expect ( "Failed to get executable directory" ); let image_path = exe_dir. join ( "../share/pixmaps/appimage-test.png" ); let pixbuf = Pixbuf :: from_file ( & image_path). unwrap_or_else ( | e | { panic! ( "Failed to load image file from path {:?}. Error: {}" , image_path, e ); }); let image = Image :: from_pixbuf ( Some ( & pixbuf)); let window = ApplicationWindow :: new (app); window. set_title ( Some ( "AppImage Test (GTK4)" )); window. set_default_size (pixbuf. width (), pixbuf. height ()); window. set_child ( Some ( & image)); window. present (); } Linuxのdesktopファイルを適当に用意し、appimage-test.desktopと名前をつけて保存しておきます。 [Desktop Entry] Version=1.0 Type=Application Name=AppImage Test GenericName=Test Application Comment=A simple GTK4 test application Exec=appimage-test Icon=appimage-test Terminal=false Categories=Utility;Development; StartupNotify=true 他に適当な画像を用意して logo.png として保存しておきます。 以上のものを用意すれば、 cargo-deb でdebパッケージを生成しましょう。target/debian以下にdebファイルが生成されているはずです。 $ cargo deb debパッケージからAppImageへの変換 debパッケージが用意できたら、以下のようなレシピファイルをyamlで作成します。レシピ内にUbuntuのバージョンが含まれますが、UbuntuのバージョンはdebパッケージのビルドとAppImageへの変換の実行するホスト環境、そしてレシピ内で統一しておいたほうが無難です。 app : appimage-test ingredients : dist : noble sources : - deb http://archive.ubuntu.com/ubuntu/ noble main universe debs : - /path/to/appimage-test_0.1.0-1_amd64.deb レシピファイルを作成したら変換用スクリプトをダウンロードしてきましょう。 Releases · AppImageCommunity/pkg2appimage · GitHub ここからダウンロードした実行バイナリで変換に失敗した場合は、GitHubにある 生のスクリプト を実行してやるとうまく行く場合があるようです。 $ wget https://raw.githubusercontent.com/AppImage/pkg2appimage/master/pkg2appimage $ chmod +x pkg2appimage $ ./pkg2appimage testrecipe.yml 変換に成功すれば、outディレクトリ内にAppImageファイルが作成されているはずです。 $ ls out/ AppImage_Test-0.1.0.glibc2.39-x86_64.AppImage これを実行すれば用意した画像を表示するシンプルなウィンドウが表示されます。 AppImageの動作 AppImageの特徴は、実行時に FUSE を用いて、動作に必要なファイル群を一時的にマウントして展開することです。その動作を見るためには、作成された実行ファイルに --appimage-mount オプションを付けて実行します。 $ ./AppImage_Test-0.1.0.glibc2.39-x86_64.AppImage --appimage-mount これを実行すると /tmp/.mount_XXXXX のようなディレクトリ内に、実行中そのAppImageが使うファイルがマウントされていることが確認できます。今回の例として作成したアプリケーションで、画像が /usr/share/pixmaps/appimage-test.png にあるのにソースコード上は相対パスを使ってアクセスしているのは、実行時欲しい画像が実際には /tmp/.mount_XXXXX に展開されているので、それにアクセスできるようにするためです。ここはアプリケーションの実装時に気を付けないといけない点です。また、デフォルトでは一部の基本的なライブラリ(libcなど)はパッケージに含まれず、実行OS側に存在しなければなりません。libfuseもインストールされている必要があります。 カスタムのAppRun AppImageパッケージを起動するとき、まずAppRunというファイルが実行されます。対象となるアプリケーションによっては、カスタムのAppRunを用意してやる必要があります。その場合は、レシピの script にAppRunを作成する処理を記述します。元のdebパッケージがdesktopやアイコンファイルを含んでいない場合、ここで作成処理を追加することもできます。 app : appimage-test ingredients : dist : noble sources : - deb http://archive.ubuntu.com/ubuntu/ noble main universe debs : - /path/to/appimage-test_0.1.0-1_amd64.deb script : - cat > AppRun <<\EOF - # !/bin/sh - HERE="$(dirname "$(readlink -f " ${ 0 }")")" - export LD_LIBRARY_PATH="${HERE}/usr/lib:${HERE}/usr/lib/x86_64-linux-gnu:${LD_LIBRARY_PATH}" - exec "${HERE}/usr/bin/appimage-test" "$@" - EOF - chmod a+x AppRun スクリプト内で LD_LIBRARY_PATH などの環境変数を設定し、最後に配置した実行ファイルを呼び出します。アプリケーションに合わせて設定する環境変数を変えましょう。また、デフォルトのAppRunに任せると、カレントディレクトリを変更されてしまうので、相対パスを引数に受け付けるCLIツールをAppImage化する場合はカスタムのAppRunを用意する方が無難です。 まとめ AppImageはLinuxで開発していると興味深い技術なのですが、パッケージ化するとなると日本語の情報がほとんど無いので今回取り上げてみました。いろいろ苦労させられることの多いLinuxでのアプリケーション配布における1つの選択肢として覚えておくのは良いかと思います。
アバター
計測データを動画プレイヤーで再生したいみなさん、 こんにちは。ソリューションアーキテクトの伊勢です。 こちらは aptpod Advent Calendar 2025 12月2日の記事です。 今回はintdashの計測データをMP4ファイルとして出力する方法をご紹介します。 はじめに 複数データの出力 MP4ファイルとは やってみた 出力パターン①:音声 出力パターン②:音声 + 映像 出力パターン③:音声 + 映像 + 字幕 起動オプション データ取得オプション 出力オプション 多重化オプション サンプルプログラム データ取得 データ変換 データ出力 MP4多重化 おわりに リンク はじめに intdashのMedia Explorerでは、計測の映像をMP4ファイルでダウンロードできます。 1 Media Explorer また、Meas Converterでは、車両データ解析で利用されるMDFファイルでダウンロードも可能です。 tech.aptpod.co.jp 複数データの出力 利用用途によっては複数データを統合してファイル出力したいケースもあります。 今回は、より複雑な構成のMP4ファイルを出力してみます。 MP4ファイルとは 動画や音声をひとつのファイルにまとめて扱える形式です。 再生時には内部の映像や音声を取り出し、タイムスタンプに基づいて同期再生します。 おおまかな構成は以下のようになっています。 今回はこれらのトラックを統合(多重化)します。 trak (video) : ビデオトラック trak (audio) : オーディオトラック trak (subtitle) : 字幕トラック MP4 File ├─ ftyp # ファイルタイプ情報 (ブランド識別) ├─ moov # メタ情報(再生に必要な情報の塊) │ ├─ mvhd # ムービーヘッダ(全体の時間情報など) │ ├─ trak (video) # ビデオトラック │ │ ├─ tkhd # トラックヘッダ │ │ ├─ mdia # メディア情報 │ │ │ ├─ mdhd # メディアヘッダ(時間単位など) │ │ │ ├─ hdlr # ハンドラ(映像/音声の区別) │ │ │ ├─ minf # メディア情報 │ │ │ │ ├─ stbl # サンプルテーブル │ │ │ │ │ ├─ stsd # サンプル記述子(コーデック情報) │ │ │ │ │ │ ├─ avc1 (AVC/H.264 codec box) │ │ │ │ │ │ │ ├─ avcC (AVCDecoderConfigurationRecord) │ │ │ │ │ │ │ │ ├─ SPS (Sequence Parameter Set) │ │ │ │ │ │ │ │ └─ PPS (Picture Parameter Set) │ │ │ │ │ ├─ stts # 時間情報(各サンプルのduration) │ │ │ │ │ ├─ stsc # チャンク構造 │ │ │ │ │ ├─ stsz # サンプルサイズ │ │ │ │ │ └─ stco # チャンクオフセット │ ├─ trak (audio) # オーディオトラック │ │ ├─ tkhd │ │ ├─ mdia │ │ │ ├─ mdhd │ │ │ ├─ hdlr │ │ │ ├─ minf │ │ │ │ ├─ stbl │ │ │ │ │ ├─ stsd # サンプル記述子(例: mp4a) │ │ │ │ │ ├─ stts │ │ │ │ │ ├─ stsc │ │ │ │ │ ├─ stsz │ │ │ │ │ └─ stco │ └─ trak (subtitle / others) # 他トラック (例: 字幕) │ ├─ mdat # メディアデータ本体(映像フレームや音声サンプル) │ ├─ [Video Sample 1] → H.264フレーム (AVCC形式: length-prefixed NALU) │ ├─ [Video Sample 2] → H.264フレーム │ ├─ ... │ ├─ [Audio Sample 1] → AACフレーム │ ├─ [Audio Sample 2] │ └─ ... └─ free / udta / meta # 追加のメタデータ(オプション) やってみた おおまかな構成です。 iPhoneアプリ intdash Motion で取得した計測データをもとにします。 データ種類ごとにファイルを出力し、最後にMP4ファイルに多重化します。 2 音声:PCMデータ → WAVファイル 映像:H.264データ → バイナリファイル 字幕:GNSSデータ(高度・速度・緯度経度) → SRTファイル 多重化後:MP4ファイル データフロー 出力パターン①:音声 シンプルな例として、音声ファイルをWAVファイルに出力します。 3 対象のエッジと開始時刻〜終了時刻を指定して実行します。 実行:音声 youtu.be 出力パターン②:音声 + 映像 続いて、複数トラックを多重化します。音声 + 映像を出力するパターンです。 実行:音声 + 映像 多重化で警告が出ています。 これは、開始時刻〜終了時刻を直接指定したためで、動画の基準点であるIDRフレームが最初に現れるまで映像フレームが生成されず、黒一色で表示されます。 4 [ h264 @ 0x157704b90 ] no frame! [ h264 @ 0x157704b90 ] non-existing PPS 0 referenced youtu.be 出力パターン③:音声 + 映像 + 字幕 最後に音声 + 映像 + 字幕を出力・多重化する例です。 計測を指定して、計測の開始〜終了時刻のデータを対象とします。 字幕の緯度経度を逆ジオコーディングで住所に変換するため、Google Geocoding APIのAPIキーを指定しています。 実行:音声 + 映像 + 字幕 youtu.be 起動オプション データ取得オプション --api_url required :サーバーURL --api_token required :APIトークン --project_uuid :プロジェクトUUID(省略時は Global Project) いずれか required 計測指定 --meas_uuid :計測UUID エッジ+時間範囲指定 --edge_uuid :エッジUUID --start :開始時刻、RFC3339形式 --end :終了時刻、RFC3339形式 出力オプション --outdir :出力先ディレクトリ(省略時は ./out ) --tracks :出力するトラック audio , video , subtitle を指定(複数可、省略時はすべて) --fps :映像の入力フレームレート(映像出力時、省略時は15) --gmap-api-key :Google Maps API Key(字幕出力時、逆ジオコーディングに使用、省略時は緯度経度を出力) 多重化オプション --mux :WAV / H.264 / SRT を MP4 に多重化(省略時は多重化なし) サンプルプログラム 多重化には SDK入門③ でインストールしたffmpegを利用しています。 クラスアーキテクチャ データ取得 intdash REST APIからのデータ取得はメモリ消費を抑えるため、 チャンク転送エンコーディング でリクエストしています。 api = measurement_service_data_points_api.MeasurementServiceDataPointsApi( self.client ) params: dict [ str , object ] = { "project_uuid" : self.project_uuid, "name" : self.meas_uuid if self.meas_uuid else self.edge_uuid, "time_format" : "ns" , "_preload_content" : False , # 全データロードの抑止 } if self.start: params[ "start" ] = self.start if self.end: params[ "end" ] = self.end if self.data_id_filter: params[ "data_id_filter" ] = self.data_id_filter stream = api.list_project_data_points(**params) データ変換 Motionで収集した音声を単純に繋げるだけだと、zzzzやppppと聞こえるジッタノイズが入っているため、除去します。 PCMデータサンプル間の時間揺らぎを等間隔サンプリングします。 f32 = decode_pcm_s16le(data_bytes) # bytes -> float32 f32_out = self.resampler.push_block(t_rel, f32) if f32_out.size: i16 = encode_pcm_s16le(f32_out) # float32 -> bytes データ出力 映像と音声はメモリに溜め込まず、1件ずつ出力しています。 if data_name == "1/pcm" : ... self._wav.write(i16) ... elif data_name == "1/h264" : ... self._h264.write(data_bytes) また、字幕はGNSSの複数項目を統合して生成します。 高度・速度・緯度経度・住所の字幕セグメントに集約します。 elif data_name == "1/gnss_speed" ... aggregator.update_speed(v) ... elif data_name == "1/gnss_altitude" : ... aggregator.update_altitude(alt) ... elif data_name == "1/gnss_coordinates" : ... changed, lat_q, lon_q = aggregator.update_latlon(lat, lon) ... aggregator.update_address(addr) 集約した字幕は高度が更新されたタイミング、約1秒に1回出力します。 seg = aggregator.on_tick(t_rel) # 高度は約1Hz if seg: self._write_segment(seg) 今回は1回だけREST APIへのリクエストして、データ名で処理を分岐しています。 5 for t_ns, _, data_name, data_bytes in self.reader.get_datapoints(): ... if data_name == "1/pcm" : ... elif data_name == "1/h264" : ... elif data_name == "1/gnss_speed" : ... elif data_name == "1/gnss_altitude" : ... elif data_name == "1/gnss_coordinates" : MP4多重化 音声・映像・字幕それぞれで最初のデータポイントの相対時刻が異なるため、計測開始からのオフセット(ffmpegの -itsoffset オプション)を与えて調整しています。 cmd += [ ... "-itsoffset" , f "{opts.v_offset:.6f}" , "-i" , str (inputs.video), ... cmd += [ "-itsoffset" , f "{opts.a_offset:.6f}" , "-i" , str (inputs.audio)] ... cmd += [ "-itsoffset" , f "{opts.s_offset:.6f}" , "-i" , str (inputs.subtitle)] おわりに 今回は、複数データを一般的な動画ファイルに多重化する例を紹介しました。 マルチモーダルデータも、SDKを使えば、自由に整形が可能です。 intdashはデータの時系列が管理されているため、出力時にもデータが同期されます。 なお、今回は海外eSIMを使ったiPhoneで計測を行いました。 海外での計測は以下の記事でも検証しています。 tech.aptpod.co.jp リンク 本シリーズの過去記事はこちらからご覧ください。 SDK入門①〜社用車で走ったとこ全部見せます〜 :REST APIでデータ取得 SDK入門②〜データ移行ツールの作り方〜 :REST APIでデータ送信 SDK入門③〜RTSPで映像配信するぞ〜 :リアルタイムAPIでデータ取得 SDK入門④〜YOLOで物体検知しちゃう〜 :リアルタイムAPIでデータ送信 SDK入門⑤〜iPadでData Visualizerを見る会〜 :リアルタイムAPIでキャプチャデータ送信 SDK入門⑥〜最速最高度で計測する日〜 : AWS LambdaでREST APIデータ送信 SDK入門⑦〜計測リプレイツールの作り方〜 : REST APIでデータ取得、リアルタイムAPIでデータ送信 SDK入門⑧〜動画アップロードツールの作り方〜 :REST APIで映像データ送信 Media ExplorerでのMP4ダウンロードについては、 SDK入門⑧ で解説しています。 ↩ サンプルプログラムを GitHub にて公開しています。Motionの計測データのみで動作確認しています。未回収データポイントがあると映像と音声がずれるなどの問題が起こりえます。 ↩ サンプリングレートを48kHzにしています。元計測の音声データにあわせる必要があります。 ↩ H.264のIDRフレームについては、 SDK入門⑧ で解説しています。 ↩ 字幕集約がなければ、音声、映像ごとにリクエストする方がシンプルかもしれません。 ↩
アバター