Safie Engineers' Blog!

Safieのエンジニアが書くブログです

【Python×Rust】マルチカメラトラッキングを10倍高速化!PyO3とmaturin導入実践ログ

この記事は Safie Engineers' Blog! Advent Calendar 2025 11日目の記事です。

はじめに

今年も残すところあとわずかとなりましたね。AI開発部の田中です。
現在は、複数のカメラ映像を統合して解析するシステムのR&D(研究開発)に取り組んでいます。

画像認識AIの分野ではPythonが主流ですが、「Pythonでロジックを書いたら、処理が遅すぎてリアルタイム性が確保できない」という壁にぶつかったことはありませんか?

今回は、6台のカメラを使ったリアルタイムマルチカメラトラッキングを実装する過程でまさにその問題に直面し、一部の処理をRustに置き換えることで劇的な高速化を実現した話を紹介します。

「Rustは難しそう」と思っている方でも、Pythonと連携させるだけなら意外とハードルは高くありません。ぜひ最後までお付き合いください。

リアルタイムマルチカメラトラッキングとは

リアルタイムマルチカメラトラッキング(オンラインマルチカメラトラッキング)とは、RTSPなどで接続した複数のカメラから現在の映像を取得し、リアルタイムに以下の処理を行う技術です。

  1. 検出: カメラごとに被写体の領域(バウンディングボックス)を検出
  2. 追跡: カメラ内で被写体を追跡し、カメラ内のIDを割り当て
  3. 特徴抽出: 被写体の見た目の特徴量(ReID特徴量など)を抽出
  4. 統合: 全カメラの情報を統合し、エリア全体で「被写体がどこにいるか」を一意に特定

これらを「次のフレームの映像が来る前」に完了させる必要があります。

今回目指したスペックは、「ネットワークカメラ6台、各20FPS」です。つまり、全カメラの処理を 50ミリ秒(1000ms / 20fps)以内 に完了させなければ、遅延が発生してしまいます。

システム構成と技術スタック

システムの大まかな構成図は以下の通りです。

処理は大きく「推論部」と「統合部」に分かれています。

1. 推論部(映像のデコード・AI推論)

GPUリソースを最大限に活かすため、NVIDIAの技術を採用しています。

  • DeepStream SDK
    • GStreamerベースのマルチメディアフレームワーク。動画のデコードやDNN推論をパイプライン処理で高速に実行可能です。
  • DeepStream Python Apps

2. 統合部(トラッキング・ID統合)

推論結果を受け取り、ロジックベースで被写体の同一性判定を行います。

  • Python: 全体の制御・グルーコード
  • Rust: 計算コストの高いマッチング処理を担当
  • PyO3 & maturin: PythonとRustを繋ぐためのツール群

Pythonでの限界とRustへの転換

推論部に関しては、DeepStreamの恩恵によりPythonバインディング経由でも十分高速でした。

問題が発生したのは、後段の「統合部」です。

複数のカメラから検出された被写体の位置や特徴量を総当たりで比較し、同一被写体判定を行う処理(マッチング処理)において、Pythonの処理速度がボトルネックになりました。 カメラが増え、被写体が増えれば増えるほど計算量は指数関数的に増加します。

純粋なPython実装でテストしたところ、被写体が多いシーンでは処理に数百ミリ秒かかってしまいました。目標の50ミリ秒を大幅に超えており、映像はカクつき、処理落ちは避けられない状態です。

なぜRustを選んだか

NumPyなどを使ったベクトル化も検討しましたが、条件分岐を含む複雑なロジックだったため、コンパイル言語への置き換えを検討しました。そこで白羽の矢が立ったのがRustです。

  • C++並みの高速性: メモリ安全性を担保しながら爆速で動く。
  • PyO3のエコシステム: Pythonとの連携が非常に容易で、既存のPythonコードの一部だけをRust関数に置き換える「部分的な導入」がしやすい。

「全体を書き直すのは無理だが、重いループ処理だけRustに任せよう」という戦略です。

PyO3とmaturinを使った実装フロー

ここからは、実際に今回採用した開発フローを紹介します。 パッケージ管理には、最近話題の高速なツール uv を使用しました。

前提ツール

  • uv: Pythonのパッケージ管理・プロジェクト管理ツール
  • Rust: コンパイラ言語(cargoなどが含まれます)

1. プロジェクトのセットアップ

maturin を使えば、Rustのプロジェクト作成からPythonパッケージとしてのビルドまで一気通貫で行えます。

# 作業ディレクトリへ移動
cd ${WORK_DIR}

# maturinのインストール(uv toolを使用)
uv tool install maturin

# PyO3バインディングを含む新規プロジェクト作成
# --mixed オプションでPythonとRustのハイブリッド構成を作成
uvx maturin new -b pyo3 --mixed study_maturin

cd study_maturin

# 仮想環境の作成(Python 3.12指定)
uv venv -p 3.12

2. Rustコードの実装 (src/lib.rs)

デフォルトで生成されるコードです。

use pyo3::prelude::*;

/// 2つの数値を足して文字列として返す関数
#[pyfunction]
fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
    Ok((a + b).to_string())
}

/// Pythonモジュールとしての定義
#[pymodule]
fn study_maturin(m: &Bound<'_, PyModule>) -> PyResult<()> {
    // Pythonから呼べる関数として登録
    m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
    Ok(())
}

3. Pythonからの呼び出し (main.py)

ビルドされたRustの関数は、通常のPythonモジュールと同じようにimportして使用できます。

import study_maturin

def main():
    a = 2
    b = 3
    # Rustで実装された関数を呼び出し
    result = study_maturin.sum_as_string(a, b)
    print(f"The sum of {a} and {b} is: {result}")

if __name__ == "__main__":
    main()

4. ビルドと実行

開発モード(maturin develop)でビルドすると、現在の仮想環境にパッケージがインストールされます。本番性能を出すため --release を付けています。

# キャッシュクリア(念のため)
uv cache clean

# リリースビルドでインストール
uvx maturin develop --release

# Pythonスクリプト実行
uv run main.py

実行結果:

The sum of 2 and 3 is: 5

このように、非常に少ない手順でRustの関数をPythonから呼び出すことができました。 今回は単純な足し算ですが、実際の業務ではここに「マルチカメラのID統合ロジック」を実装しました。

Rust側で構造体を定義してPythonのクラスとして扱ったり、型ヒント(.pyiファイル)を生成してVSCodeでの補完を効かせたりすることも可能です。

導入結果:10倍以上の高速化を実現

統合部のボトルネックとなっていた解析ロジックをRustに置き換えた結果、パフォーマンスは劇的に改善しました。

  • Before (Python): 1フレームあたり 300ms超(ピーク時)
  • After (Rust): 1フレームあたり 30ms以内

約10倍以上の高速化を達成し、目標としていた「6台・20FPS(50ms以内)」の処理時間を余裕を持ってクリアすることができました。これにより、リアルタイムで滑らかなトラッキングが実現できています。

まとめ

「Pythonは遅い」と諦める前に、ボトルネック部分だけをRustにオフロードするアプローチは非常に有効でした。

特に PyO3maturin (そして uv)のエコシステムのおかげで、PythonエンジニアにとってもRust導入の敷居はかなり低くなっていると感じます。

今回はロジックの一部のみの置き換えでしたが、今後はRustで処理する領域を広げ、システム全体の堅牢性とパフォーマンス向上に挑戦していきたいと思います。

© Safie Inc.