Grounding DINOとは?

(画像は、Geminiで作成されたものです)
Grounding DINOの概要
Grounding DINOは、2023年3月に発表された原論文「Grounding DINO: Marrying DINO with Grounded Pre-Training for Open-Set Object Detection」で提案された、Transformerベースの物体検出モデルである「DINO」と、言語と画像の結びつきを学習する「グラウンディング事前学習(Grounded Pre-Training)」を融合させることで開発された、強力なオープンセット物体検出器(Open-Set Object Detector)です。
従来のクローズドセット物体検出(Closed-Set Object Detection)は、事前に定義された特定のカテゴリ(例えばCOCOデータセットの80クラスなど)のオブジェクトを検出することに特化していました。しかし、未知のオブジェクトに対応するためには、新しいカテゴリのデータを大量に集めて再学習する必要がありました。Grounding DINOは、このクローズドセット検出器に「言語」の概念を導入することで、未知の概念に対する汎化(オープンセット概念の汎化)を実現しています。これにより、人間が入力するカテゴリ名や参照表現(Referring expressions、属性を含む詳細な説明文など)を用いて、任意のオブジェクトをゼロショットで検出することが可能になります。
言語と視覚という2つの異なるモダリティを効果的に融合させるため、Grounding DINOでは検出器のアーキテクチャを3つのフェーズに概念的に分割し、それぞれで緊密な融合(tight modality fusion)を行うアプローチを提案しています。具体的には、特徴量を強化する「Feature Enhancer」、言語情報を元に初期クエリを選択する「Language-guided Query Selection」、そして最終的な予測に向けた「Cross-modality Decoder」の3つのモジュールによって構成されています。
学習においては、物体検出データ、グラウンディングデータ、そして画像キャプションデータを含む大規模なデータセットで事前学習(Pre-training)が行われています。その結果、Grounding DINOはオープンセット物体検出および参照表現理解(REC: Referring Expression Comprehension)のベンチマークにおいて極めて高い性能を発揮します。例えば、COCOデータセットのゼロショット検出ベンチマークでは 52.5 AP を達成し、ODinW(Object Detection in the Wild)のゼロショットベンチマークでは平均 26.1 AP という新記録を樹立しました。
また、この言語を通じた柔軟な検出能力は、単なる物体検出にとどまらず、Stable Diffusionのような画像生成モデル(Generative models)と組み合わせることで、指定したオブジェクトのみを正確に置き換えるといった高度な画像編集(Image Editing)アプリケーションへの応用も提示されています。
Grounding DINOの処理概要

(画像は、Geminiで作成されたものです)
Grounding DINOは、「画像」と「テキスト(検出したいカテゴリ名や説明文)」を同時に入力として受け取り、ターゲットとなるオブジェクトのバウンディングボックスを予測します。 このプロセスを実現するため、アーキテクチャは「デュアルエンコーダ・シングルデコーダ(dual-encoder-single-decoder)」構成を採用しており、大きく以下の4つのステップで処理が進行します。
- Image & Text Backbone(画像およびテキスト特徴の抽出)
入力画像は、Swin Transformerなどの画像バックボーンによりマルチスケールの視覚特徴量(vanilla image features)に変換されます。 同時に、入力テキストはBERTなどのテキストバックボーンにより言語特徴量(vanilla text features)に変換されます。 この段階では、まだそれぞれのモダリティは独立して処理されています。 - Feature Enhancer(特徴エンハンサーによるモダリティの融合)
抽出された視覚とテキストの初期特徴量を融合させます。 このモジュールでは、それぞれの自己アテンション(Self-Attention)処理に加え、画像からテキストへ(image-to-text)、 およびテキストから画像へ(text-to-image)のクロスアテンションを適用します。 これにより、画像内のどの領域がテキストのどの単語に対応するかというセマンティックな結びつき(アライメント)が、ネットワークの初期段階から強力に学習・強化されます。 - Language-Guided Query Selection(言語主導のクエリ選択)
デコーダに入力するための「クエリ(オブジェクトの検出候補)」を初期化するフェーズです。 入力テキストと関連性の高いオブジェクトを効果的に抽出するため、エンコーダから出力された画像特徴とテキスト特徴の内積を計算し、 テキストと最も関連度が高いと判断された画像特徴の上位 個(DINOと同様にデフォルトでは900個)を抽出し、 デコーダクエリとして初期化します。 - Cross-Modality Decoder(クロスモダリティデコーダによる予測の精緻化)
初期化されたクエリを、画像とテキストの両方の特徴を用いて更新・精緻化していく最終フェーズです。 各デコーダ層には「画像へのクロスアテンション」に加えて、テキスト情報を注入するための「テキストへのクロスアテンション」が組み込まれています。 これにより、視覚的な境界(バウンディングボックス)の精密化と、言語的な意味(どのフレーズに該当するか)の理解を同時に深め、最終的なオブジェクトの座標と該当フレーズを出力します。
Grounding DINOの構成技術要素(詳細)
Grounding DINOが、未知のオブジェクトに対する驚異的なゼロショット検出性能と、 言語による柔軟な指定(グラウンディング)を両立させている背景には、 「言語と視覚の緊密な融合(Tight modality fusion)」と、テキスト入力の工夫があります。 ここでは、論文で提案されている主要な構成技術要素の詳細を解説します。
-
Feature Enhancer(特徴エンハンサー)
従来のオープンセット物体検出器の多くは、最終的な出力層(ヘッド)付近でのみ画像とテキストの特徴を照合していましたが、 Grounding DINOはより浅い段階(ネックモジュール)から積極的に両者を融合させます。-
詳細な仕組み:
画像バックボーンからの視覚特徴と、テキストバックボーンからの言語特徴を受け取り、 複数のエンハンサー層を通過させます。各層では、画像に対する Deformable Self-Attention(変形可能自己アテンション)と、 テキストに対する標準的な Self-Attention に加え、 「画像からテキストへ(image-to-text)」および「テキストから画像へ(text-to-image)」のクロスアテンションモジュールが組み込まれています。 -
技術的な効果:
このモジュールにより、モデルはネットワークの初期段階から「画像内のどのピクセルが、テキストのどの単語と関連しているか」を学習し、 モダリティ間の特徴を強力に位置合わせ(アライメント)することができます。
-
-
Language-Guided Query Selection(言語主導のクエリ選択)
DETRベースのモデルにおいて、デコーダに入力する「クエリ(オブジェクトの候補)」をどのように初期化するかは非常に重要です。 Grounding DINOは、入力されたテキスト情報を活用して、より関連性の高い初期クエリを選択するモジュールを導入しました。-
詳細な仕組み:
画像特徴 とテキスト特徴 の内積を計算し、関連度スコアを求めます。 そして、テキストと最も関連性が高いと判定された画像特徴の上位 個のインデックス を抽出します。数式では以下のように表現されます。 -
技術的な効果:
抽出された特徴を元に、デコーダクエリの「位置部分(動的アンカーボックス)」を初期化します。 これにより、無関係な背景ではなく、テキストプロンプトで指定された対象物に最初から焦点を絞った状態でデコーダ処理を開始できるため、 検出精度と収束速度が向上します。
-
-
Cross-Modality Decoder(クロスモダリティデコーダ)
初期化されたクエリを、画像とテキストの特徴を用いて最終的なバウンディングボックスに精緻化する要のモジュールです。- 詳細な仕組み:
ベースとなるDINOのデコーダ層を改良し、テキスト情報をクエリに注入するための「テキストへのクロスアテンション層(Text Cross-Attention)」を新たに追加しています。 つまり、各デコーダ層は Self-Attention Image Cross-Attention Text Cross-Attention FFN という順序で情報を処理します。 - 技術的な効果:
各予測フェーズでクエリが視覚情報だけでなく言語情報とも継続的に照合されるため、 モダリティ間のアライメントがさらに深まり、複雑な参照表現(例:「左側のテーブルの上で眠っている猫」)に対しても正確な境界ボックスを出力することが可能になります。
- 詳細な仕組み:
-
Sub-Sentence Level Text Feature(サブセンテンスレベルのテキスト特徴表現)
Grounding DINOの学習プロセスにおいて、複数のカテゴリ名をテキストプロンプトとして入力する際の問題を解決するための革新的なアプローチです。- 詳細な仕組み:
従来の「文レベル(Sentence level)」の表現では細かい単語の情報が失われ、 「単語レベル(Word level)」の表現(すべてのカテゴリ名を単に連結したもの)では、 無関係なカテゴリ名同士(例えば "cat" と "baseball glove")がアテンション計算で不必要に影響を与え合ってしまうという問題がありました。 Grounding DINOは、アテンションマスクを導入して無関係なカテゴリ名間のアテンションを遮断する「サブセンテンスレベル(Sub-sentence level)」の表現を採用しています。 - 技術的な効果:
これにより、無関係な単語からのノイズを排除しつつ、各単語のきめ細かい特徴(Fine-grained information)を保持したまま学習を進めることができ、ゼロショット転移性能が大幅に向上しました。
- 詳細な仕組み:
Grounding DINOの実装(概念的なシンプルな実装)
ここでは、Grounding DINOの論文で提案された「モダリティの緊密な融合」を再現するために、 PyTorchを用いた擬似的なコードで極小モデル MiniGroundingDINO を構築します。 画像とテキストがどのように融合し、テキスト主導で検出が行われるのか、 そのアーキテクチャのエッセンスを掴んでみましょう。
MiniGroundingDINOの定義
以下のコードでは、PyTorchの nn.Module を継承して、Grounding DINOの主要な構成要素をカプセル化した MiniGroundingDINO クラスを定義します。このクラスは、画像とテキストの特徴量をそれぞれ線形変換し、後続する3つのコアコンポーネント(FeatureEnhancer, LanguageGuidedQuerySelection, CrossModalityDecoder)に渡して、最終的なバウンディングボックスを予測する一連の流れを実装します。
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
from transformers import AutoProcessor, AutoModelForZeroShotObjectDetection
from skimage import data, color
import cv2
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
class MiniGroundingDINO(nn.Module):
def __init__(self, embed_dim=256, num_queries=100):
super().__init__()
# 画像とテキストをそれぞれ線形射影
self.img_proj = nn.Linear(3, embed_dim)
self.txt_proj = nn.Embedding(100, embed_dim)
# 3つの主要コンポーネントを初期化
self.enhancer = FeatureEnhancer(embed_dim)
self.query_select = LanguageGuidedQuerySelection(embed_dim, num_queries)
self.decoder = CrossModalityDecoder(embed_dim)
def forward(self, img_tensor, txt_tokens, txt_mask=None):
# 1. 特徴抽出
img_feat = self.img_proj(img_tensor)
txt_feat = self.txt_proj(txt_tokens)
# 2. 特徴融合
img_feat, txt_feat = self.enhancer(img_feat, txt_feat, txt_mask)
# 3. クエリ選択
queries = self.query_select(img_feat, txt_feat)
# 4. デコーダによるバウンディングボックス予測
bboxes = self.decoder(queries, img_feat, txt_feat)
return bboxes
MiniGroundingDINOクラスは、Grounding DINOのアーキテクチャ全体を統括するnn.Moduleです。
__init__ (初期化):
- img_proj: 画像が持つRGBの3チャンネルを、モデル内部で扱う統一された次元(embed_dim)に変換するための線形層です。
- txt_proj: テキストトークン(単語ID)を、同じくembed_dim次元のベクトルに変換するための埋め込み層です。
- enhancer, query_select, decoder: 後ほど定義する3つの主要なサブモジュールをインスタンス化します。
forward (順伝播):
- 受け取った画像テンソル(img_tensor)とテキストトークン(txt_tokens)を、それぞれの射影層に通して特徴ベクトル img_feat と txt_feat を得ます。
- これらの特徴をenhancerモジュールに入力し、モダリティ間で融合・強化された新しい特徴ベクトルを受け取ります。
- 強化された特徴をquery_selectモジュールに渡し、テキスト情報に基づいて絞り込まれたオブジェクトクエリ(queries)を生成します。
- 最後に、このクエリと融合後の特徴をdecoderに入力し、最終的なバウンディングボックス(bboxes)を予測して返します。
FeatureEnhancerの定義
以下のコードでは、Grounding DINOの中核的なアイデアである「モダリティの緊密な融合」を担う FeatureEnhancer モジュールを定義します。このモジュールは、画像とテキストそれぞれに自己アテンションを適用した後、双方向のクロスアテンション(画像からテキストへ、テキストから画像へ)を実行することで、ネットワークの浅い段階から両者の特徴を強力に位置合わせします。
class FeatureEnhancer(nn.Module):
def __init__(self, embed_dim=256):
super().__init__()
# 各アテンション層を初期化
self.img_self_attn = nn.MultiheadAttention(embed_dim, num_heads=8, batch_first=True)
self.txt_self_attn = nn.MultiheadAttention(embed_dim, num_heads=8, batch_first=True)
self.img_to_txt_attn = nn.MultiheadAttention(embed_dim, num_heads=8, batch_first=True)
self.txt_to_img_attn = nn.MultiheadAttention(embed_dim, num_heads=8, batch_first=True)
def forward(self, img_feat, txt_feat, txt_mask=None):
# 1. 各モダリティ内で自己アテンションを適用
# Deformable Attentionの代わりに標準的なMultiheadAttentionで代用
img_out, _ = self.img_self_attn(img_feat, img_feat, img_feat)
# テキストにはサブセンテンスマスクを適用し、不要な単語間の関連を遮断
txt_out, _ = self.txt_self_attn(txt_feat, txt_feat, txt_feat, attn_mask=txt_mask)
# 2. 双方向のクロスアテンションで特徴を融合
# テキスト特徴をクエリとして、画像から関連情報を抽出
txt_enhanced, _ = self.img_to_txt_attn(query=txt_out, key=img_out, value=img_out)
# 画像特徴をクエリとして、テキストから関連情報を抽出
img_enhanced, _ = self.txt_to_img_attn(query=img_out, key=txt_out, value=txt_out)
# 元の特徴に、抽出した情報を加算(Residual Connection)
return img_feat + img_enhanced, txt_feat + txt_enhanced
FeatureEnhancerは、画像とテキストの特徴を初期段階で強力に結びつけるためのモジュールです。
__init__ (初期化):
- 画像とテキストそれぞれのself_attn(自己アテンション)層と、img_to_txt_attn(画像→テキスト)、txt_to_img_attn(テキスト→画像)という2つのクロスアテンション層をnn.MultiheadAttentionで定義します。
forward (順伝播):
- まず、画像特徴(img_feat)とテキスト特徴(txt_feat)が、それぞれ独立して自己アテンション層を通過します。これにより、各モダリティ内部の文脈情報が強化されます。このとき、テキストの自己アテンションにはオプションでtxt_maskを適用でき、これによりサブセンテンス(独立したカテゴリ名)間の不要な関連計算を防ぎます。
- 次に、双方向のクロスアテンションを実行します。img_to_txt_attnでは、テキスト特徴(txt_out)を「問い(Query)」として、画像特徴(img_out)の中から関連する情報を「検索」し、テキスト特徴を強化します。逆もまた同様です。
- 最後に、元の特徴量にクロスアテンションによって得られた情報を加算する「残差接続(Residual Connection)」を行い、融合された特徴量を返します。これにより、勾配消失を防ぎつつ、効果的に特徴を強化できます。
LanguageGuidedQuerySelectionの定義
ここでは、テキスト情報を使って検出対象のオブジェクト候補(クエリ)を効率的に絞り込む LanguageGuidedQuerySelection モジュールを実装します。画像特徴とテキスト特徴の類似度を計算し、最も関連性の高い画像領域の特徴をデコーダの初期クエリとして選択することで、無関係な背景領域にリソースを割くことなく、精度の高い検出を目指します。
class LanguageGuidedQuerySelection(nn.Module):
def __init__(self, embed_dim, num_queries=100):
super().__init__()
self.num_queries = num_queries
# クエリの「内容」部分を学習可能な埋め込みとして定義
self.content_queries = nn.Embedding(num_queries, embed_dim)
def forward(self, img_feat, txt_feat):
# 1. 画像とテキストの類似度を計算
# img_feat: (Batch, NumImgTokens, Dim), txt_feat: (Batch, NumTxtTokens, Dim)
# -> sim: (Batch, NumImgTokens, NumTxtTokens)
sim = torch.bmm(img_feat, txt_feat.transpose(1, 2))
# 2. 各画像トークンがどれだけテキスト全体と関連しているかをスコア化
# 各画像トークンごとに、最も関連度の高いテキストトークンとのスコアを取得
max_sim = sim.max(dim=-1)[0] # (Batch, NumImgTokens)
# 3. 類似度スコア上位の画像トークンを選択
topk_idx = torch.topk(max_sim, self.num_queries, dim=1)[1] # (Batch, NumQueries)
# 4. 選択した画像特徴を「位置」クエリとして抽出
B, N_i, C = img_feat.shape
batch_idx = torch.arange(B).unsqueeze(1).expand(-1, self.num_queries)
positional_queries = img_feat[batch_idx, topk_idx, :] # (Batch, NumQueries, Dim)
# 5. 「内容」クエリと「位置」クエリを結合
# ブロードキャストを利用して、バッチ内の全サンプルに同じcontent_queriesを加算
queries = self.content_queries.weight.unsqueeze(0) + positional_queries
return queries
LanguageGuidedQuerySelectionは、テキストプロンプトをヒントにして、デコーダが注目すべきオブジェクト候補(クエリ)を賢く選択するためのモジュールです。
__init__ (初期化):
- num_queries: 検出したいオブジェクトの最大数。
- content_queries: オブジェクトの「内容」や「種類」を表現するための学習可能な埋め込みベクトルです。これは位置情報とは独立しています。
forward (順伝播):
- torch.bmm(バッチ行列積)を使って、画像特徴img_featと転置したテキスト特徴txt_feat.transpose(1, 2)の内積を計算し、類似度行列simを生成します。
- sim.max(dim=-1)により、各画像トークン(画像内の各領域)に対して、最も関連の強いテキストトークンとの類似度スコアを求めます。これにより、テキスト全体と意味的に近い画像領域ほど高いスコアを持つようになります。
- torch.topkを使い、類似度スコアが高い上位num_queries個の画像トークンのインデックスtopk_idxを抽出します。
- 抽出したインデックスを使って、元の画像特徴img_featから該当する特徴ベクトルを抜き出します。これが、デコーダクエリの「位置情報」を担うpositional_queriesとなります。
- 最後に、学習可能なcontent_queriesと、画像から抽出したpositional_queriesを加算することで、内容と位置の両方の情報を持った最終的なクエリを生成します。
CrossModalityDecoderの定義
以下のコードでは、最終的なバウンディングボックス予測を行う CrossModalityDecoder を定義します。このデコーダは、標準的なTransformerデコーダの構造(自己アテンション、画像へのクロスアテンション、FFN)に加えて、テキスト特徴に対するクロスアテンション層を追加している点が特徴です。これにより、オブジェクトの位置を特定しながら、それがテキストのどの部分に対応するのかを継続的に参照し、予測をより正確なものにします。
class CrossModalityDecoder(nn.Module):
def __init__(self, embed_dim=256):
super().__init__()
# 各アテンション層とFFNを初期化
self.self_attn = nn.MultiheadAttention(embed_dim, num_heads=8, batch_first=True)
self.img_cross_attn = nn.MultiheadAttention(embed_dim, num_heads=8, batch_first=True)
self.txt_cross_attn = nn.MultiheadAttention(embed_dim, num_heads=8, batch_first=True)
self.ffn = nn.Sequential(nn.Linear(embed_dim, 512), nn.ReLU(), nn.Linear(512, embed_dim))
# バウンディングボックス (cx, cy, w, h) を予測するためのヘッド
self.bbox_head = nn.Sequential(nn.Linear(embed_dim, 128), nn.ReLU(), nn.Linear(128, 4), nn.Sigmoid())
def forward(self, queries, img_feat, txt_feat):
# 1. クエリ同士の自己アテンション
q, _ = self.self_attn(queries, queries, queries)
queries = queries + q # 残差接続
# 2. 画像特徴へのクロスアテンション
q, _ = self.img_cross_attn(query=queries, key=img_feat, value=img_feat)
queries = queries + q # 残差接続
# 3. テキスト特徴へのクロスアテンション (Grounding DINOの核心部)
q, _ = self.txt_cross_attn(query=queries, key=txt_feat, value=txt_feat)
queries = queries + q # 残差接続
# 4. FFN (Feed-Forward Network)
queries = queries + self.ffn(queries)
# 5. 最終的なクエリからバウンディングボックスを予測
bboxes = self.bbox_head(queries)
return bboxes
CrossModalityDecoderは、入力されたクエリを画像とテキストの両方の情報を使って精緻化し、最終的なバウンディングボックスを出力するモジュールです。
__init__ (初期化):
- self_attn: クエリ同士の関係性を学習する自己アテンション層。
- img_cross_attn: クエリを「問い」として画像特徴から関連情報を抽出するクロスアテンション層。
- txt_cross_attn: クエリを「問い」としてテキスト特徴から関連情報を抽出するクロスアテンション層。これがGrounding DINOの独自性です。
- ffn: 特徴量を非線形変換する順伝播ネットワーク。
- bbox_head: 精緻化されたクエリベクトルを受け取り、それを4つの数値(中心x, 中心y, 幅, 高さ)で表現されるバウンディングボックス座標に変換する予測ヘッドです。nn.Sigmoidで出力を0〜1の範囲に正規化します。
forward (順伝播):
処理は、標準的なTransformerデコーダ層のパイプラインに従います。
- 自己アテンション: クエリ(queries)が相互作用し、重複する検出候補などを抑制します。
- 画像クロスアテンション: クエリが画像特徴(img_feat)を参照し、オブジェクトの正確な「位置」情報を掴みます。
- テキストクロスアテンション: クエリがテキスト特徴(txt_feat)を参照し、オブジェクトの「意味」や「カテゴリ」情報を反映させます。
- FFN: アテンション層からの出力をさらに処理し、表現力を高めます。
各ステップの後には残差接続(queries = queries + q)が適用され、情報の損失を防ぎながらクエリが段階的に精緻化されていきます。最終的に、更新されたクエリがbbox_headを通過して、バウンディングボックス座標が出力されます。
Sub-Sentence Level Text Feature(アテンションマスク)の定義
以下のコードでは、複数のカテゴリ名を同時にプロンプトとして入力した際に、無関係な単語同士がアテンション計算で影響を与え合わないようにするための「サブセンテンスレベル」のアテンションマスクを生成する関数 create_sub_sentence_mask を定義します。例えば「cat . dog .」という入力に対し、「cat」と「dog」がそれぞれ独立した概念として扱われるように、カテゴリの範囲外のアテンションを遮断するバイナリマスクを作成します。
def create_sub_sentence_mask(num_tokens, category_spans):
# Args:
# num_tokens (int): テキストプロンプトの総トークン数。
# category_spans (list of list of int): 各カテゴリのトークン範囲を示すリスト。
# 例: "cat . dog ." が [[0, 1], [2, 3]] のように分割される場合。
# 1. 最初はすべてのトークン間のアテンションをブロックするマスクを作成
# (True = アテンションを計算しない)
mask = torch.ones((num_tokens, num_tokens), dtype=torch.bool)
# 2. 同じカテゴリに属するトークン間でのみアテンションを許可
# (False = アテンションを計算する)
for span in category_spans:
start, end = span
mask[start:end+1, start:end+1] = False
return mask
この関数は、FeatureEnhancer 内のテキスト自己アテンション層で使用するアテンションマスクを生成します。
- 引数:
- num_tokens: プロンプト全体のトークン長。
- category_spans:
[[start1, end1], [start2, end2], ...]という形式のリスト。各内部リストは、一つの独立したカテゴリ(サブセンテンス)がトークン列のどこからどこまでを占めるかを示します。
- 処理:
- まず、torch.ones を使って、すべての要素が
Trueの(num_tokens, num_tokens)行列を作成します。PyTorchのMulti-Head Attentionでは、マスクの値がTrueの位置のアテンション計算が無視されるため、これは「すべてのアテンションをブロックする」状態を意味します。 - 次に、category_spansで指定された各範囲(span)をループ処理します。範囲内のインデックスに対応するマスク行列のスライス(mask[start:end+1, start:end+1])に対して
Falseを設定します。
- まず、torch.ones を使って、すべての要素が
- 戻り値:
- これにより、同じカテゴリに属するトークン同士の領域だけが
False(アテンション計算を許可)となり、異なるカテゴリのトークン間ではTrue(アテンション計算をブロック)のままのマスク行列が完成します。
- これにより、同じカテゴリに属するトークン同士の領域だけが
サンプルデータ、教師データと前処理
ここでは、自作した MiniGroundingDINO の動作を検証するための準備を行います。まず、Hugging Faceの transformers ライブラリから公式の Grounding DINO モデルをロードし、skimage のサンプル画像(猫)に対して「cat」というプロンプトで推論を実行します。そして、得られたバウンディングボックスを「正解ラベル」として抽出し、自作モデルの学習に使える形式(テンソル化、座標正規化など)に変換します。
# GPUが利用可能であればGPUを、そうでなければCPUを使用
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Using device: {device}")
# 1. サンプル画像(skimageの猫の画像)を準備
image = data.chelsea()
if image.ndim == 2:
image = color.gray2rgb(image) # グレースケールならRGBに変換
# 2. テキストプロンプトを設定
text_prompt = "cat."
# 3. Hugging Faceから公式Grounding DINOモデルをロードし、教師データを生成
print(f"Hugging FaceのGrounding DINOで '{text_prompt}' の教師ボックスを抽出します...")
dino_processor = AutoProcessor.from_pretrained("IDEA-Research/grounding-dino-base")
dino_model = AutoModelForZeroShotObjectDetection.from_pretrained("IDEA-Research/grounding-dino-base").to(device)
dino_inputs = dino_processor(images=image, text=text_prompt, return_tensors="pt").to(device)
with torch.no_grad():
dino_outputs = dino_model(**dino_inputs)
# 4. 予測結果を後処理し、最も確信度の高いボックスを教師データとして採用
target_sizes = torch.tensor([image.shape[:2]]).to(device)
dino_results = dino_processor.image_processor.post_process_object_detection(
dino_outputs, threshold=0.3, target_sizes=target_sizes
)[0]
best_box_idx = dino_results["scores"].argmax()
bbox = dino_results["boxes"][best_box_idx].tolist()
print(f"抽出された正解ボックス: {bbox}")
# 5. 自作モデル(MiniGroundingDINO)用の入力データを作成
H, W, _ = image.shape
# 計算量を削減するため画像をリサイズし、(Batch, NumTokens, Channels)の形式に変形
img_resized = cv2.resize(image, (64, 64))
img_tensor = torch.tensor(img_resized, dtype=torch.float32).view(1, -1, 3).to(device) / 255.0
# テキストはダミーのID列として表現
txt_tokens = torch.tensor([[1, 2, 3]], dtype=torch.long).to(device)
# 6. 教師ボックスを正規化([0, 1]の範囲でcx, cy, w, h形式に変換)
xmin, ymin, xmax, ymax = bbox
cx = (xmin + xmax) / 2.0 / W
cy = (ymin + ymax) / 2.0 / H
bw = (xmax - xmin) / W
bh = (ymax - ymin) / H
target_bbox = torch.tensor([[[cx, cy, bw, bh]]], dtype=torch.float32).to(device)
# 7. サブセンテンスマスクを作成("cat ." を1つのカテゴリとして扱う)
category_spans = [[0, 2]] # トークン0から2までが1カテゴリ
txt_mask = create_sub_sentence_mask(num_tokens=3, category_spans=category_spans).to(device)
このコードブロックでは、MiniGroundingDINOを学習させるための準備として、画像とテキストのサンプルデータ、そして正解となる教師データを一式作成します。
- サンプル画像の準備: skimage.data.chelsea() を使って猫のサンプル画像を読み込みます。
- プロンプト設定: 検出したいオブジェクトとして text_prompt = "cat." を設定します。
- 教師データの生成: Hugging Faceの AutoProcessor と AutoModelForZeroShotObjectDetection を使い、事前学習済みの
IDEA-Research/grounding-dino-baseモデルをロードします。この公式モデルに画像とプロンプトを入力し、推論を実行することで、「猫」のバウンディングボックス座標を自動で取得します。 - ボックスの選定: 推論結果の中から、dino_results["scores"].argmax() を使って最も確信度が高いボックスを一つ選び、これを正解の教師データ bbox とします。
- 入力テンソルの作成:
- 画像: 学習の計算負荷を軽減するため、cv2.resizeで画像を64x64に縮小します。その後、view(1, -1, 3) を使って
(1, 64*64, 3)という形状のテンソル img_tensor に変換します。これは、各ピクセルが3チャンネルを持つトークン列のように扱われます。 - テキスト: 本来はトークナイザを使いますが、ここでは概念実証のため、txt_tokens = torch.tensor([[1, 2, 3]]) のようにダミーのID列を定義します。
- 画像: 学習の計算負荷を軽減するため、cv2.resizeで画像を64x64に縮小します。その後、view(1, -1, 3) を使って
- 教師ボックスの正規化: 公式モデルから得られたピクセル単位の座標 bbox を、画像の幅 W と高さ H で割ることで正規化します。さらに、形式を(xmin, ymin, xmax, ymax)から、モデルの出力形式に合わせた(中心x, 中心y, 幅, 高さ)に変換し、target_bbox を作成します。
- マスクの作成: create_sub_sentence_mask 関数を呼び出し、今回のプロンプト "cat ." が単一のカテゴリであることを示すアテンションマスク txt_mask を生成します。
モデルの学習
以下のコードでは、準備したデータを用いて MiniGroundingDINO モデルの学習(サニティチェック)を行います。モデルをインスタンス化し、損失関数(L1Loss)とオプティマイザ(Adam)を定義した上で、シンプルな学習ループを実行します。このチェックの目的は、損失が適切に減少し、モデルがテキストプロンプトと画像領域のマッピングを学習できる能力があることを確認することです。
# 1. モデル、損失関数、オプティマイザを初期化
# num_queries=1 は、今回は1つのオブジェクト(猫)のみを検出するため
model = MiniGroundingDINO(embed_dim=128, num_queries=1).to(device)
criterion = nn.L1Loss() # 予測と教師ボックスの座標差を計算
optimizer = optim.Adam(model.parameters(), lr=1e-3)
# 2. 学習ループを実行
epochs = 150
print("\nMiniGroundingDINOのサニティチェックを開始します...")
model.train() # モデルを学習モードに設定
for epoch in range(epochs):
optimizer.zero_grad() # 勾配をリセット
# 3. モデルによる予測
# サブセンテンスマスクも入力
pred_bboxes = model(img_tensor, txt_tokens, txt_mask=txt_mask)
# 4. 損失の計算と逆伝播
loss = criterion(pred_bboxes, target_bbox)
loss.backward()
optimizer.step()
# 30エポックごとに損失を表示
if (epoch + 1) % 30 == 0:
print(f"Epoch [{epoch+1:3d}/{epochs}], Loss (L1): {loss.item():.4f}")
このコードは、MiniGroundingDINOモデルの学習プロセスを示しています。目的は、モデルが与えられた1枚の画像とテキストプロンプトに対して、教師データとして設定したバウンディングボックスを正しく予測できるように「過学習」させることです。これにより、設計したアーキテクチャで勾配が正常に流れ、学習が可能であることを確認します(サニティチェック)。
- 初期化:
- MiniGroundingDINO: モデルをインスタンス化します。今回は猫という単一オブジェクトのみを対象とするため、num_queries=1に設定します。
- nn.L1Loss: 損失関数としてL1損失(Mean Absolute Error)を使用します。これは、予測したボックス座標と教師ボックス座標の各要素の差の絶対値を合計するもので、ボックス回帰タスクで一般的に用いられます。
- optim.Adam: パラメータを更新するための最適化アルゴリズムとしてAdamを選択します。
- 学習ループ:
- model.train(): PyTorchモデルを学習モードに切り替えます。
- optimizer.zero_grad(): 前のイテレーションで計算された勾配をリセットします。
- pred_bboxes = model(...): モデルに画像、テキスト、マスクを入力し、バウンディングボックスを予測させます。
- loss = criterion(...): 予測pred_bboxesと正解target_bboxの間のL1損失を計算します。
- loss.backward(): 損失をネットワークの各パラメータに逆伝播させ、勾配を計算します。
- optimizer.step(): 計算された勾配に基づいてモデルのパラメータを更新します。
推論の実行
学習が完了した MiniGroundingDINO モデルを使って推論を実行し、その結果を可視化します。モデルから予測されたバウンディングボックスの座標を元の画像サイズに変換し、matplotlib を使って教師データ(緑色の枠)と予測結果(赤色の破線枠)を一枚の画像に重ねて描画することで、モデルが正しくオブジェクトの位置を学習できたかを確認します。
# 1. モデルを評価モードに切り替え
model.eval()
# 2. 勾配計算を無効にして推論を実行
with torch.no_grad():
pred_bbox = model(img_tensor, txt_tokens)[0, 0].cpu().numpy()
# 3. 予測された正規化座標を元の画像サイズに戻す
# (cx, cy, w, h) -> (xmin, ymin, xmax, ymax)
p_cx, p_cy, p_w, p_h = pred_bbox
p_xmin = (p_cx - p_w / 2.0) * W
p_ymin = (p_cy - p_h / 2.0) * H
p_xmax = (p_cx + p_w / 2.0) * W
p_ymax = (p_cy + p_h / 2.0) * H
# 4. matplotlibで結果を可視化
fig, ax = plt.subplots(1, 1, figsize=(6, 5))
ax.imshow(image) # 元画像を表示
# 教師ボックスを緑色の実線で描画
rect_target = patches.Rectangle((xmin, ymin), xmax - xmin, ymax - ymin, linewidth=2, edgecolor='green', facecolor='none', label='Target (HF DINO)')
# 予測ボックスを赤色の破線で描画
rect_pred = patches.Rectangle((p_xmin, p_ymin), p_xmax - p_xmin, p_ymax - p_ymin, linewidth=2, edgecolor='red', linestyle='--', facecolor='none', label='Pred (MiniDINO)')
ax.add_patch(rect_target)
ax.add_patch(rect_pred)
plt.legend()
plt.title(f"MiniGroundingDINO Inference (Prompt: '{text_prompt}')")
plt.axis('off')
plt.show()
このコードブロックでは、学習済みのMiniGroundingDINOの性能を評価するために推論と可視化を行います。
- 評価モードへの切り替え: model.eval() を呼び出し、モデルを評価モードに設定します。これにより、Dropout層などが無効化されます。
- 推論の実行: torch.no_grad() のコンテキスト内でモデルを呼び出します。これにより、勾配計算が不要になるため、メモリ消費量が削減され、計算が高速になります。予測結果 pred_bbox を
.cpu().numpy()でCPU上のNumPy配列に変換します。 - 座標の逆変換: モデルが出力したバウンディングボックス pred_bbox は、(中心x, 中心y, 幅, 高さ) の形式で、かつ0〜1の範囲に正規化されています。これを可視化のために、元の画像のピクセル座標 (xmin, ymin, xmax, ymax) に変換します。
- 結果の可視化:
matplotlibを使用して結果を描画します。- ax.imshow(image): 元の猫の画像を表示します。
- patches.Rectangle: 教師データ(Hugging Faceモデルの予測)を緑色の実線の四角形として、自作モデルの予測を赤色の破線の四角形として作成します。
- ax.add_patch: 作成した四角形を画像に追加します。
- plt.legend() と plt.title() で凡例とタイトルを設定し、plt.show()で最終的な画像を表示します。
実行結果
上記のコードを実行すると、コンソールにはロード状況が出力され、以下のような可視化画像が表示されます。
Hugging FaceのGrounding DINOで 'cat.' の教師ボックスを抽出します...
抽出された正解ボックス: [-2.094688892364502, -0.36396682262420654, 409.96636962890625, 299.622314453125]
MiniGroundingDINOのサニティチェックを開始します...
Epoch [ 30/150], Loss (L1): 0.0139
Epoch [ 60/150], Loss (L1): 0.0057
Epoch [ 90/150], Loss (L1): 0.0034
Epoch [120/150], Loss (L1): 0.0117
Epoch [150/150], Loss (L1): 0.0036
このサニティチェックの結果は、モデルが意図通りに機能していることを示しています。Lossの値が 0.0139 から 0.0036 まで着実に減少しており、これは実装したFeature Enhancer、Language-Guided Query Selection、Cross-Modality Decoderのパイプラインが、テキストプロンプトを手掛かりに画像内の特定領域をマッピングし、誤差逆伝播を通じてその関係性を正しく学習できていることを意味します。最終的な可視化結果で、緑色(正解)のボックスと赤色(予測)のボックスがほぼ完全に重なっていることからも、モデルの表現力が十分であることが確認できます。
実際のHugging Faceライブラリを用いた推論テスト
以下のコードでは、Hugging Faceの transformers パッケージを使用して、実用的なオープンセット物体検出の推論を実行します。skimageのサンプル画像(宇宙飛行士)に対し、複数のオブジェクト("human face . flag . rocket .")をピリオド区切りで同時に指定し、Grounding DINO がそれらを一度に検出できるかを確認します。この方法により、公式リポジトリから手動で重みをダウンロードする手間なく、数行のコードで最新のモデルを手軽に利用できます。
import torch
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from skimage import data, color
from transformers import AutoProcessor, AutoModelForZeroShotObjectDetection
print("Hugging Faceからモデルをロードして推論を実行します...")
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 1. 画像の準備(scikit-imageの内蔵データから宇宙飛行士の画像を読み込み)
image = data.astronaut()
if image.ndim == 2:
image = color.gray2rgb(image)
# 2. プロンプトの準備
# 複数の異なるオブジェクトを検出したい場合は、ピリオド(.)で区切って入力
text_prompt = "human face . flag . rocket ."
print(f"テキストプロンプト: '{text_prompt}'")
# 3. モデルとプロセッサのロード
model_id = "IDEA-Research/grounding-dino-base"
processor = AutoProcessor.from_pretrained(model_id)
model = AutoModelForZeroShotObjectDetection.from_pretrained(model_id).to(device)
# 4. 入力データの前処理と推論
inputs = processor(images=image, text=text_prompt, return_tensors="pt").to(device)
with torch.no_grad():
outputs = model(**inputs)
# 5. 後処理(元の画像サイズに合わせてボックス座標を逆変換)
target_sizes = torch.tensor([image.shape[:2]]).to(device)
# thresholdを調整することで、検出の厳密さをコントロール可能
results = processor.image_processor.post_process_object_detection(
outputs, threshold=0.3, target_sizes=target_sizes
)[0]
# 6. 結果の可視化
fig, ax = plt.subplots(1, 1, figsize=(8, 8))
ax.imshow(image)
# 検出されたすべてのボックス、ラベル、スコアをループで描画
for score, label, box in zip(results["scores"], results["labels"], results["boxes"]):
box = [round(i, 2) for i in box.tolist()]
xmin, ymin, xmax, ymax = box
width, height = xmax - xmin, ymax - ymin
# バウンディングボックスを描画(赤枠)
rect = patches.Rectangle((xmin, ymin), width, height, linewidth=2, edgecolor='red', facecolor='none')
ax.add_patch(rect)
# ラベルと確信度スコアをボックスの上にテキスト描画
label_text = f"{label}: {score:.2f}"
ax.text(xmin, ymin - 5, label_text, color='white', fontsize=12, fontweight='bold',
bbox=dict(facecolor='red', alpha=0.6, edgecolor='none', pad=2))
plt.title(f"Grounding DINO Inference\nPrompt: '{text_prompt}'", fontsize=14)
plt.axis("off")
plt.show()
このコードは、Hugging Faceのtransformersライブラリを使って、学習済みのGrounding DINOモデルによる実践的な推論を行う手順を示しています。
- 画像の準備: skimage.data.astronaut()でサンプル画像を読み込みます。
- プロンプトの準備: text_promptに、検出したい複数のオブジェクト名をピリオド(
.)で区切って指定します。これにより、モデルは一度の推論で複数の異なるカテゴリを同時に検出します。 - モデルとプロセッサのロード: AutoProcessorとAutoModelForZeroShotObjectDetectionを使い、
IDEA-Research/grounding-dino-baseのIDを指定するだけで、モデルの重みと、画像・テキストの前処理を行うプロセッサを自動的にダウンロードして初期化します。 - 前処理と推論: processorに画像とテキストを渡すと、モデルが要求する形式(テンソルのリサイズ、正規化、トークン化など)に一括で変換してくれます。変換後のinputsをモデルに渡して推論を実行します。
- 後処理: モデルの生出力outputsを、プロセッサのpost_process_object_detectionメソッドで処理します。このメソッドは、モデルの出力(正規化された座標など)を、人間が解釈しやすい形式(ピクセル単位の座標、ラベル、スコア)に変換してくれます。threshold引数で、指定した確信度スコア以上の検出結果のみをフィルタリングできます。
- 可視化:
matplotlibを使い、検出された各オブジェクトのバウンディングボックスと、対応するラベルおよび確信度スコアを元の画像に重ねて描画します。
この実装コードでは、以下のポイントに注目してください。
複数オブジェクトの同時プロンプト: Grounding DINOの言語バックボーン(BERT)を最大限に活かすため、"human face . flag . rocket ." のようにピリオド区切りで複数の対象を入力しています。これにより、モデルは1回の推論パス(フォワード)で、画像内の別々のオブジェクトを同時に探し出します。
AutoProcessorによる抽象化: AutoProcessorは、入力画像の適切なリサイズや正規化、テキストのトークナイズといった複雑な前処理をすべて裏側で行います。
post_process_object_detectionの活用: モデルが出力する生の座標データは正規化(の範囲など)されているため、このメソッドに元の画像サイズ(target_sizes)と閾値(threshold=0.3)を渡すことで、指定した確信度以上の検出結果だけを、描画用のピクセル座標(xmin, ymin, xmax, ymax)として安全に抽出できます。
実行結果
上記のコードを実行すると、コンソールにはロード状況が出力され、以下のような可視化画像が表示されます。
Hugging Faceからモデルをロードして推論を実行します...
テキストプロンプト: 'human face . flag . rocket .'

実行結果の画像を確認すると、事前に「顔」や「国旗」専用のモデルとして学習させていないにもかかわらず、入力したテキストの意味を正しく理解し、指定した3つのオブジェクトをそれぞれ極めて高い精度で検出していることがわかります。
SAMが「ピクセルレベルの輪郭(マスク)抽出」に長けているのに対し、Grounding DINOは「言語を通じた意味的な位置特定(グラウンディング)」において最強の基盤モデルです。両者をパイプラインとして組み合わせる(Grounded-SAM)ことで、テキストだけで任意のオブジェクトを高精度に切り抜くシステムが簡単に構築できるようになります。
ライセンスに関する注記
本記事で紹介したソフトウェアおよびライブラリは、以下のライセンスに基づき提供されています。
- Grounding DINO: 本体のコードおよび公式の学習済みモデルは Apache License 2.0 の下で公開されています。これは商用利用にも寛容なライセンスです。
- scikit-image: サンプル画像の読み込みに使用した
skimageライブラリは、修正BSDライセンス(3-Clause BSD License) の下で公開されています。こちらも商用利用が可能です。
これらのライセンスは、本記事執筆時点での情報です。実際にこれらのソフトウェアをご自身のプロジェクトで利用する際は、必ず公式リポジトリ等で最新のライセンス条項をご確認ください。
まとめ
本記事では、テキストプロンプトによって任意のオブジェクトをゼロショットで検出できる強力なオープンセット物体検出器、Grounding DINOについて、その仕組みから実践的な使い方までを解説しました。
この記事を通じて、以下の内容を学びました。
- Grounding DINOのコア技術: Transformerベースの検出器DINOに言語の理解力を融合させるための3つの主要コンポーネント(Feature Enhancer, Language-guided Query Selection, Cross-Modality Decoder)の役割を理解しました。
- 概念モデルのスクラッチ実装: PyTorchを使い、各コンポーネントを簡易的に実装することで、画像とテキストの特徴がどのように融合され、オブジェクトクエリが言語情報によって導かれるのかをコードレベルで確認しました。
- Hugging Faceによる実践的な推論:
transformersライブラリを利用し、わずか数行のコードで学習済みモデルを呼び出し、複数のカテゴリ名を一度に指定して物体検出を実行できる手軽さとその性能を体感しました。
Grounding DINOは、単に物体を検出するだけでなく、「言語で指示する」という直感的なインターフェースで画像の世界を操作する基盤技術です。本記事で紹介したように、Grounded-SAMのように他のモデルと組み合わせることで、さらに高度な画像編集や分析タスクへと応用が広がります。ぜひ、ご自身のプロジェクトでもこの強力なモデルを活用してみてください。
本記事の文章・構成の一部に生成AIを使用しています。