🦉

NVIDIA Jetson Orin Nanoでリアルタイム画像解析 - NanoOWLを読む

2024/12/02に公開

この記事は「PY - PARTY TECH STUDIO Advent Calendar 2024」の2日目の記事です。

はじめに

私は普段、PARTY TECH STUDIOのPYで主にサービス系のテクニカルディレクターやバックエンドまわりを担当しています。しかし最近、ひょんなことからAIを使った画像解析のプロジェクトに参加することになりました。
そこでNVIDIA Jetson Orin NanoでNanoOWLを使ってリアルタイム画像解析を試してみて、なかなか面白かったので使い方と中身について簡単に紹介します。

NanoOWLとは

NVIDIA自身がAIモデルやライブラリをJetson Orinシリーズ向けに最適化したチュートリアルをNVIDIA Jetson AI Labというサイトに公開しています。
ここではLLMやVision Transformersのものが公開されており、そのなかのひとつがNanoOWLです。

NanoOWLはOWL-ViTをリアルタイムにうごかすための最適化が施されています。OWL-ViTは簡単にいうと画像内の対象物をテキストで検索・検出できるAIモデルです。
そのためこれらを使うことで検出対象物の学習なしに対象物を表すテキスト(例えば car など)を入力するだけで画像内の写るその対象物を検出できます。

https://www.jetson-ai-lab.com/vit/tutorial_nanoowl.html

百聞は一見にしかず。上記ページのNanoOWLのデモ動画を見ればその簡単さが実感できます。

NanoOWLを使ってみる

利用にはJetson Orinシリーズが必要です(Jetson Orin Nanoでも9万円前後します。ちょっとお高い…)。Jetson Orin Nanoが手に入ってしまえば、NanoOWLをとりあえず動かすのはめちゃくちゃ簡単です。

https://www.jetson-ai-lab.com/vit/tutorial_nanoowl.html

Jetson Orin Nanoの初期セットアップ後、上記ページの手順に従って jetson-containers をクローンして指定のコマンドを実行するだけでうごきます。簡単!

ただ簡単に使えるようにまとめられてる分、実際の処理の内容が見えにくくなっています。
そこで jetson-containers がなんなのか、NanoOWLはどういう処理をしているのか、を書いていきます。

jetson-containersとは

jetson-containers はNVIDIAが提供しているJetson向けのコンテナイメージを管理するためのリポジトリです。Jetsonで使えるAIモデルやライブラリがコンテナイメージとして提供されており、jetson-containers を通せばコマンド一発でDockerコンテナを起動してAIモデルを使うことができます。

https://github.com/dusty-nv/jetson-containers/tree/master

NanoOWLは jetson-containers run --workdir /opt/nanoowl $(autotag nanoowl) というコマンドで実行できますが、このとき下記にあるDcokerfileが使われています。

https://github.com/dusty-nv/jetson-containers/tree/master/packages/vit/nanoowl

そしてNanoOWLのソースコードは下記リポジトリで管理されています

https://github.com/NVIDIA-AI-IOT/nanoowl

NanoOWLの処理を読む

ここからはNanoOWLの処理をかいつまんで読んでいきます。

エンジンファイルの生成

jsetson-containers で起動したコンテナでNanoOWLの実行するとき引数に ../../data/owl_image_encoder_patch32.engine を指定します。この.engineファイルは推論モデルから生成されたTensorRTでの処理を高速化するためのランタイムです。これがあることでJetson上で高速な論が可能になります。

このファイル自体は jetson-containers を利用している場合は起動時に下記コマンドで生成されています。

https://github.com/dusty-nv/jetson-containers/blob/master/packages/vit/nanoowl/Dockerfile#L27-L31

プロンプトの分解

NanoOWLでは検出したい対象を下記のようなプロンプトで指定します。

[a face [a nose, an eye, a mouth]]

[a face (interested, yawning / bored)]

(indoors, outdoors)

たとえば [a face [a nose, an eye, a mouth]] のように入れ子にすると、画像内に顔が写っている場合にその顔の部分について鼻、目、口が検出できます。

また () を使うとクラスタライズされたものをできます。たとえば [a face (interested, yawning / bored)] のようにすると、顔が写っている場合にその顔が興味を持っているか、あくびをしているか、退屈しているかを検出できます。

NanoOWLのサンプルコードでは、起動したウェブページで入力したプロンプトはWebsocketで送信され、Treeオブジェクトに変換されます。

https://github.com/NVIDIA-AI-IOT/nanoowl/blob/main/examples/tree_demo/tree_demo.py#L88-L102

Treeオブジェクトの中では以下のような構造に分解されます。

# 入力
[a person, a car(black)]

# オブジェクト
{'nodes': [{'op': 'detect', 'input': 0, 'outputs': [1, 2]}, {'op': 'classify', 'input': 2, 'outputs': [3]}], 'labels': ['image', 'a person', 'a car', 'black']}

分解された各ラベルは predictor.encode_clip_text(tree)(クラスタライズラベル用) predictor.encode_owl_text(tree)(物体ラベル用)でそれぞれベクトル化されます。

この構造を知っておくと、各検出対象ごとに閾値を変更したり、検出結果のプレビューにスコアを表示するなどのカスタマイズがやりやすくなります。

物体検出

OpenCVで取得したフレーム画像は下処理されたあと、ROIを切り出してベクトル化し、物体検出をしています。このとき画像内に写るすべての物体(と思われる範囲)を出力しています。

https://github.com/NVIDIA-AI-IOT/nanoowl/blob/main/nanoowl/tree_predictor.py#L119-L121

https://github.com/NVIDIA-AI-IOT/nanoowl/blob/main/nanoowl/owl_predictor.py#L265-L272

このあと、ベクトル化したプロンプトのラベルと画像で関連性の評価を行い、スコアが閾値よりも高いものだけを抽出し、結果として出力しています。

https://github.com/NVIDIA-AI-IOT/nanoowl/blob/main/nanoowl/owl_predictor.py#L274-L315

サンプルコードのままでは閾値はすべてのラベルで共通になっているので、ラベルごとに閾値を変更したい場合はこのTreeオブジェクトの構造とこのdecodeの処理をカスタマイズすることで可能になります。

ちなみに、プロンプトで入れ子構造を使っている場合、親のラベルが検出された範囲で画像を切り抜き、再度画像の下処理をして、子のラベルで評価を行います。そのため、入れ子を多用すると処理がかなり重くなります。

参考(試したカスタマイズ)

かなりかいつまんで処理を紹介しましたが、(NVIDIAのサンプルにしては!)きれいなコードで読みやすく、カスタマイズがしやすくなっていました。色々検証していく中で、現場でも使えそうなものがあったので簡単に紹介します。

OWLv2への変更

NanoOWLはOWL-Vitがベースになっていますが、コードほぼそのままでOWLv2に変更することができました。

https://huggingface.co/docs/transformers/model_doc/owlv2

OWLv2のモデルでエンジンファイルを生成して、NanoOWLにそれを渡せば実行できるはずです。結果は非常によく、検出精度が驚くほど上がります。

ただ、実行時のメモリ容量が増えるため、Jetson Orin Nanoでは実行できませんでした。試すにはJetson AGX Orinなどのメモリ容量が大きいモデルが必要です(お高い…)。

リファレンス画像による検出

NanoOWLはテキストラベルによって物体を検出できますが、カスタマイズすると画像検索のようにリファレンス画像を指定してそれに近い物体をリアルタイムで検出することもできました。

実は本家OWL-ViTにはリファレンス画像の指定した範囲の特徴と近い部分を検出するサンプルがあります。

https://github.com/google-research/scenic/tree/main/scenic/projects/owl_vit#inference-playground

それを参考に、画像内の特定範囲をあらかじめベクトル化しておき、それを関連性の評価に利用することでテキストと同時に画像よる検出も可能になりました。テキストラベルで表現しにくいものや、検出してくれないものを対象としたい場合に使えそうです。

GitHubで編集を提案
PY

Discussion