RT-DETRとは?
(画像は、Geminiで作成されたものです)
RT-DETRの概要
これまで、エッジデバイスやリアルタイム処理が求められる現場において、物体検出アルゴリズムの絶対的な王者といえば「YOLO(You Only Look Once)」シリーズでした。精度と速度の絶妙なバランスから、多くの開発者がYOLOを選択してきたことでしょう。
しかし今、その勢力図を大きく塗り替える可能性を秘めたゲームチェンジャーが登場しました。それが、Baidu(百度)が発表した「RT-DETR(Real-Time DEtection TRansformer)」です。このモデルの詳細は、原論文である「DETRs Beat YOLOs on Real-time Object Detection」で詳しく解説されています。
RT-DETRは、画像認識分野で圧倒的な精度を誇る「Vision Transformer(ViT)」のアーキテクチャを採用しながら、リアルタイム処理(リアルタイム性)を初めて実現した物体検出モデルです。
これまで、Transformerベースのモデル(DETRなど)は精度が高い一方で、計算コストが膨大で推論速度が遅く、リアルタイム用途には不向きだとされてきました。Baiduの研究チームは、この課題を画期的なアプローチで克服し、YOLOシリーズ(YOLOv8など)と同等以上の推論速度と精度を両立させることに成功しました。
なぜRT-DETRが注目されているのか?
RT-DETRの最大の魅力は、単にTransformerを高速化したことだけではありません。
従来のYOLOなどのCNNベースのモデルでは、推論後に重複した検出結果を排除するためのNMS(Non-Maximum Suppression:非最大値抑制)という後処理が不可欠でした。しかし、このNMSは推論速度のボトルネックになりやすく、ハイパーパラメータの調整も煩雑です。
RT-DETRは、Transformerの特性を活かした「NMSフリー(後処理不要)」のエンドツーエンドなアーキテクチャを採用しています。これにより、推論速度の遅延を劇的に削減し、よりシンプルで安定したパイプラインの構築を可能にしました。
「精度か、速度か」という妥協はもう必要ありません。RT-DETRは、自動運転、ロボティクス、監視システムなど、リアルタイム性と高い信頼性が求められるあらゆるコンピュータビジョン・タスクに新たな可能性をもたらします。
RT-DETRの主な特徴:3つの技術的ブレイクスルー
RT-DETRがYOLOの牙城を崩し、リアルタイムTransformerの限界を突破できた背景には、Baiduの研究チームによる主に3つの革新的な技術アプローチがあります。
1. NMSフリーな完全エンドツーエンド・アーキテクチャ
従来のYOLOなどのCNNベースモデルでは、1つの物体に対して大量の予測ボックス(バウンディングボックス)が出力されるため、 後処理として「NMS(非最大値抑制)」を用いて重複を排除する必要がありました。 しかし、RT-DETRはTransformerの「オブジェクトクエリ(Object Queries)」という仕組みを活用。 モデル自身が最初から「重複のない最適なボックス」を直接予測するため、NMSが完全に不要(NMSフリー)になりました。 これにより、後処理にかかっていた計算遅延がゼロになり、パイプラインが大幅にシンプル化されています。
2. 効率的なハイブリッド・エンコーダ(Hybrid Encoder)
Transformerがリアルタイム処理に向かない最大の理由は、画像全体の自己アテンション(Self-Attention)計算に膨大なコストがかかる点にありました。 RT-DETRでは、マルチスケール(異なる解像度)の特徴量を効率的に処理するため、 高解像度(空間情報)と低解像度(セマンティック情報)をインテリジェントに分離・結合する「ハイブリッド・エンコーダ」を導入。 必要な計算量を劇的に削減しつつ、高い検出精度を維持することに成功しました。
3. IoUに配慮したクエリ選択(IoU-Aware Query Selection)
DETR系モデルは、デコーダに「どの場所を重点的に見ればよいか」を指示する初期クエリを選択します。 RT-DETRでは、単に分類スコアが高いだけでなく、物体の位置正確性(IoUスコア)も同時に考慮してクエリを選択する「IoU-Aware Query Selection」を採用しました。 これにより、より確実で高品質な特徴量をデコーダに渡すことができ、検出精度の底上げを実現しています。
YOLOに対するRT-DETRの優位性
- 実効速度の安定性(NMSの罠がない)
YOLOの推論速度(FPS)は、画像内の物体数に大きく依存します。 例えば、お祭りの混雑した風景など物体が大量にある画像では、 NMSの計算処理が激増してFPSがガクンと落ちる弱点がありました。RT-DETRはNMSが不要なため、画面に物体がいくつあろうが、常に一定かつ高速な推論速度を維持できます。 - 柔軟な「精度と速度のトレードオフ」調整
RT-DETRは、モデルを再学習させることなく、デコーダのレイヤー数を変更するだけで、推論時の「精度」と「速度」をオンザフライで柔軟に調整できるという、YOLOにはないユニークな特性を持っています。
想定される3つの活用シーン
RT-DETRの「NMSフリーによる安定した高速性」と「Transformerならではの高いコンテキスト理解力」は、これまでYOLOが担ってきた現場だけでなく、より高度な要件が求められる次世代のユースケースで真価を発揮します。
1. 自動運転・ADAS(先進運転支援システム)
自動運転において、推論速度の「ブレ」は致命傷になり得ます。これまでのCNNベースのモデルでは、人や車が密集する交差点などでNMSの処理負荷が跳ね上がり、FPS(フレームレート)が急激に低下するリスクがありました。 RT-DETRはNMSが不要なため、視界にオブジェクトがいくつあろうと推論速度が常に一定です。これにより、システム全体のレイテンシ予測が容易になり、より安全で信頼性の高い自動運転システムの構築が可能になります。
2. スマートファクトリーと高速外観検査
製造業のラインでは、ベルトコンベア上を高速で流れる部品の異常を瞬時に検知する必要があります。RT-DETRは、ハイブリッド・エンコーダによる高解像度画像の効率的な処理能力を備えているため、微小な傷や欠陥(小さな物体)の検出にも強力な適性を持っています。さらに、推論時のデコーダ層の数を調整することで、「今日はラインが速いから速度優先」「今日は精密検査だから精度優先」といったように、再学習なしで精度と速度のバランスを現場で即座に変更できる点も大きな強みです。
3. ドローン空撮・リモートセンシング(海上監視など)
ドローンからの空撮映像や衛星画像は、背景が複雑で検出対象(船や車など)が非常に小さく密集しているのが特徴です。RT-DETRはTransformer特有の「グローバルな文脈理解(画像全体の関係性を把握する力)」に優れており、このような過酷な環境下や、データが限られた海上物体検出などにおいても、YOLOシリーズを凌駕する高い検出精度を発揮することが研究で示されています。
RT-DETRの処理概要
(画像は、Geminiで作成されたものです)
YOLOの常識を打ち破るシンプルさ
YOLO11をはじめとするこれまでのYOLOシリーズと、RT-DETRの最大の違いは「推論パイプライン(処理の流れ)」そのものにあります。
画像が入力されてから物体が検出されるまでのプロセスを、YOLOの標準的なフローと比較しながら分かりやすく解説します。
YOLO(従来型)の一般的な処理フロー
YOLOシリーズは非常に優秀ですが、推論の最終段階でどうしても「無駄」が発生する構造になっていました。
- バックボーン(Backbone): 入力画像からCNNで特徴を抽出します。
- ネック(Neck): 異なる大きさの物体を捉えるため、特徴量を統合・調整します。
- ヘッド(Head): 画像全体に対して「大量の予測ボックス」と「クラス確率」を出力します(数千〜数万個の候補が出ます)。
- 後処理(NMS: 非最大値抑制): 重複している大量の予測ボックスを計算して削り落とし、最終的な結果に絞り込みます(ここが遅延のボトルネックになります)。
RT-DETRの革新的な処理フロー
RT-DETRは、上記の「4. 後処理(NMS)」を根底から消し去り、完全なエンドツーエンド(入口から出口まで直結)のフローを実現しています。
Step 1. バックボーン(高速な特徴抽出)
YOLOと同様に、まずは実績のある軽量・高速なCNN(HGNetv2やResNetなど)を用いて、入力画像から視覚的な特徴を素早く抽出します。
Step 2. ハイブリッド・エンコーダ(特徴量の最適化)
抽出された特徴量を、RT-DETR独自の「ハイブリッド・エンコーダ」に流し込みます。ここでは、高い解像度(空間情報)と低い解像度(意味情報)をTransformerの技術を使って効率的に混ぜ合わせ、デコーダへ渡す準備を整えます。
Step 3. トランスフォーマー・デコーダ(クエリによる直接探索)
ここが最大の違いです。画面全体に予測をばらまくのではなく、「オブジェクト・クエリ」と呼ばれる決まった数(例:300個)の検出枠が、画像内の特徴的な部分へ直接探しに行きます。 さらに「位置の正確さ(IoU)」を加味して優秀なクエリだけを選別するため、非常にスマートに物体を捉えます。
Step 4. ダイレクト出力(完全NMSフリー!)
デコーダから出てきた結果が、そのまま最終的なバウンディングボックスとクラス分類の結果になります。 重複したボックスを後から消すための面倒なNMS計算は一切行いません。
処理フローから見るRT-DETRのメリット
パイプラインの構造を比較すると、RT-DETRがいかにスマートなアプローチを採用しているかが一目でわかります。
| 比較項目 | YOLOシリーズ | RT-DETR |
|---|---|---|
| 出力されるボックス数 | 数千〜数万個(重複多数) | 事前に設定した固定数(例: 300個、重複なし) |
| 後処理(NMS) | 必須(しきい値の調整が煩雑) | 不要(完全排除) |
| 推論速度の安定性 | 画面内の物体数が多いとFPSが落ちる | 物体数に関わらずFPSが常に一定で安定 |
| パイプライン構造 | 後処理に依存した多段構成 | シンプルで直感的なエンドツーエンド |
YOLOが「とりあえず画面全体に大量の網を投げて、後から被った網を取り除く」という力技のアプローチだとすれば、RT-DETRは**「最初から狙いすました場所に、必要な数だけピンポイントで網を投げる」**という無駄のないアプローチを採用しています。この洗練された推論フローこそが、リアルタイム性と高い精度を両立させている最大の理由です。
RT-DETRの実装(概念的なシンプルな実装)
ここでは、RT-DETRが内部でどのような処理を行っているのか、PyTorchを用いた擬似的なコードで解説します。深層学習モデルがどのように特徴を抽出・強調しているのか、そのエッセンスを掴んでみましょう。
※実装コードに関する注釈
本記事で紹介するコードは、RT-DETRの革新的な処理フロー(AIFI、ハイブリッドエンコーダ、IoU考慮のクエリ選択、Deformable Attention、NMSフリー)の「中核となる概念」を1つのPythonスクリプトで直感的に理解できるよう、PyTorchの標準機能のみで再現したミニマルな実装です。プロダクション環境向けの公式実装とは細部のレイヤー構造やC++拡張モジュールの有無が異なりますが、処理の全体像を掴むには最適な構成となっています。
RTDETR_Coreの定義
以下のコードでは、RT-DETRモデルの中核となる RTDETR_Core クラスをPyTorchで定義します。このクラスは、後述するエンコーダ(RTDETR_Encoder)とデコーダ(SimpleDeformableAttention)を統合し、入力画像から最終的な物体検出の予測(クラス分類とバウンディングボックス)を出力するまでの一連のフォワードパス処理を実装します。
import torch
import torch.nn as nn
import torch.nn.functional as F
import torchvision.models as models
from torchvision import transforms
from torchvision.ops import sigmoid_focal_loss, generalized_box_iou, box_convert
from scipy.optimize import linear_sum_assignment
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from skimage import data
class RTDETR_Core(nn.Module):
def __init__(self, num_classes=1, num_queries=300, hidden_dim=256):
super().__init__()
self.num_queries = num_queries
# 画像からマルチスケール特徴を抽出するエンコーダ
self.encoder = RTDETR_Encoder(hidden_dim)
# デコーダに渡すクエリを選択するための予測ヘッド (Two-Stage Query Selection)
self.enc_class_head = nn.Linear(hidden_dim, 1) # 物体らしさを予測
self.enc_bbox_head = nn.Linear(hidden_dim, 4) # ボックス位置を予測
# オブジェクトクエリを処理するデコーダ層
self.deform_attn = SimpleDeformableAttention(hidden_dim) # 画像特徴を参照する変形可能アテンション
self.query_self_attn = nn.MultiheadAttention(hidden_dim, num_heads=8, batch_first=True) # クエリ同士の自己アテンション
self.ffn = nn.Sequential( # FeedForward Network
nn.Linear(hidden_dim, hidden_dim * 4),
nn.ReLU(),
nn.Linear(hidden_dim * 4, hidden_dim)
)
# 最終的な分類とバウンディングボックスを出力する予測ヘッド
self.class_head = nn.Linear(hidden_dim, num_classes)
self.bbox_head = nn.Sequential(
nn.Linear(hidden_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, 4),
nn.Sigmoid() # ボックス座標を0-1に正規化
)
def forward(self, x):
B = x.shape[0]
# Step 1: エンコーダで画像からマルチスケール特徴を抽出
features = self.encoder(x)
p4 = features[1] # 中間解像度の特徴マップを代表として使用
# 特徴マップをシーケンス形式に変換: (Batch, Height*Width, Channels)
p4_flat = p4.flatten(2).permute(0, 2, 1)
# Step 2: Two-Stage Query Selection
# エンコーダ出力から直接、物体らしさのスコアとボックス位置を予測
enc_scores = self.enc_class_head(p4_flat).squeeze(-1)
enc_bboxes = self.enc_bbox_head(p4_flat).sigmoid()
# スコア上位K個をデコーダ用の初期クエリとして選抜
k = min(self.num_queries, enc_scores.shape[1])
_, topk_idx = torch.topk(enc_scores, k, dim=1)
# 選抜されたクエリに対応する特徴と参照点を取得
batch_idx = torch.arange(B).unsqueeze(1).expand(-1, k)
content_queries = p4_flat[batch_idx, topk_idx]
ref_points = enc_bboxes[batch_idx, topk_idx][..., :2] # 参照点はボックスの中心座標(cx, cy)
# Step 3: デコーダでオブジェクトクエリを更新 (1層のみの簡易版)
# 3-1. クエリ同士で自己アテンションを実行
q, _ = self.query_self_attn(content_queries, content_queries, content_queries)
q = content_queries + q # 残差接続
# 3-2. 変形可能アテンションで画像特徴をサンプリング
cross_out = self.deform_attn(q, ref_points, p4)
q = q + cross_out # 残差接続
# 3-3. FFNでクエリ特徴を更新
q = q + self.ffn(q)
# Step 4: 最終予測
# 更新されたクエリから分類スコアとボックス座標を予測
outputs_class = self.class_head(q)
outputs_coord = self.bbox_head(q)
return {'pred_logits': outputs_class, 'pred_boxes': outputs_coord}
この RTDETR_Core クラスは、RT-DETRモデルの全体的な処理フローを定義する心臓部です。
初期化 (__init__)
コンストラクタでは、モデルを構成する主要なコンポーネントを初期化します。
- self.encoder: 概要で触れた「ハイブリッド・エンコーダ」の役割を担う RTDETR_Encoder のインスタンスです。
- self.enc_class_headとself.enc_bbox_head: 論文の重要な特徴である「IoUに配慮したクエリ選択 (IoU-Aware Query Selection)」を簡易的に実装するための線形層です。エンコーダが出力した特徴マップから、直接「物体らしさ(クラス)」と「位置(ボックス)」を予測し、デコーダへ渡すクエリを選抜するために使います。
- self.deform_attn: 計算量を削減する鍵となる SimpleDeformableAttention モジュールです。
- self.query_self_attn: オブジェクトクエリ同士の関係性を学習するための、標準的な自己アテンション層です。
- self.class_headとself.bbox_head: デコーダを通過したクエリから、最終的な分類スコアとバウンディングボックスの座標を予測するヘッド部分です。
順伝播 (forward)
forward メソッドでは、データがモデルを流れる一連の処理を定義します。
- まず、self.encoder(x) で入力画像からマルチスケールの特徴マップを抽出します。
- 次に、特徴マップから物体らしさをスコアリングし、torch.topk を用いてスコアの高い上位 k 個のクエリ候補 (content_queries) と、その参照点 (ref_points) を選び出します。これがTwo-Stage Query Selectionの核となる処理です。
- 選ばれたクエリは、まずクエリ同士で self.query_self_attn によって情報を交換し、その後 self.deform_attn を使って画像特徴量から効率的に情報を収集します。
- 最後に、self.class_head と self.bbox_head が、最終的な予測結果であるロジットとバウンディングボックス座標を生成します。これらは辞書形式で返却され、後の損失計算や推論に利用されます。
RTDETR_Encoderの定義
ここでは、RT-DETRの性能の鍵となるハイブリッド・エンコーダを RTDETR_Encoder クラスとして実装します。このクラスは、torchvision の ResNet をバックボーンとしてマルチスケールの特徴量を抽出し、論文で提案されたAIFI(Attention-based Intra-scale Feature Interaction)とCCFM(CNN-based Cross-scale Feature-fusion Module)の概念を、Transformerエンコーダと畳み込み層を組み合わせて簡易的に再現します。
class RTDETR_Encoder(nn.Module):
def __init__(self, hidden_dim=256):
super().__init__()
# バックボーン: torchvisionのResNet-18からマルチスケール特徴量(C3, C4, C5)を抽出
resnet = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)
self.layer2 = nn.Sequential(*list(resnet.children())[:6]) # C3特徴マップ (stride 8)
self.layer3 = resnet.layer3 # C4特徴マップ (stride 16)
self.layer4 = resnet.layer4 # C5特徴マップ (stride 32)
# 各特徴マップのチャンネル数をhidden_dimに統一するプロジェクション層
self.proj3 = nn.Conv2d(128, hidden_dim, 1)
self.proj4 = nn.Conv2d(256, hidden_dim, 1)
self.proj5 = nn.Conv2d(512, hidden_dim, 1)
# AIFI: 最も抽象的な特徴(C5)にのみTransformerを適用し、計算量を削減
encoder_layer = nn.TransformerEncoderLayer(d_model=hidden_dim, nhead=8, batch_first=True)
self.aifi = nn.TransformerEncoder(encoder_layer, num_layers=1)
# CCFM: CNNベースで高速にマルチスケール特徴を融合 (FPNのような構造)
self.fuse_p4_p5 = nn.Conv2d(hidden_dim * 2, hidden_dim, 3, padding=1)
self.fuse_p3_p4 = nn.Conv2d(hidden_dim * 2, hidden_dim, 3, padding=1)
def forward(self, x):
# Step 1: バックボーンで特徴を抽出
c3 = self.layer2(x)
c4 = self.layer3(c3)
c5 = self.layer4(c4)
# Step 2: チャンネル数を統一
p3 = self.proj3(c3)
p4 = self.proj4(c4)
p5 = self.proj5(c5)
# Step 3: AIFI (Attention-based Intra-scale Feature Interaction)
# p5をシーケンス形式に変換してTransformer Encoderに入力
B, C, H, W = p5.shape
p5_flat = p5.flatten(2).permute(0, 2, 1) # (B, H*W, C)
p5_aifi = self.aifi(p5_flat).permute(0, 2, 1).view(B, C, H, W)
# Step 4: CCFM (CNN-based Cross-scale Feature-fusion Module)
# トップダウン形式で特徴を融合
p5_up = F.interpolate(p5_aifi, size=p4.shape[-2:], mode='nearest')
p4_fused = self.fuse_p4_p5(torch.cat([p4, p5_up], dim=1))
p4_up = F.interpolate(p4_fused, size=p3.shape[-2:], mode='nearest')
p3_fused = self.fuse_p3_p4(torch.cat([p3, p4_up], dim=1))
# 3つのスケールの特徴マップをリストで返す
return [p3_fused, p4_fused, p5_aifi]
この RTDETR_Encoder は、論文で提案された「効率的なハイブリッド・エンコーダ」の概念を実装したものです。計算コストの高い自己アテンションと、軽量な畳み込みを巧みに組み合わせることで、高い精度と速度を両立させます。
初期化 (__init__)
- バックボーン: torchvision.models.resnet18 をバックボーンとして利用し、その中間層から3つの異なる解像度の特徴マップ(C3, C4, C5)を抽出します。
- プロジェクション層: バックボーンから抽出した各特徴マップはチャンネル数が異なるため、nn.Conv2d (カーネルサイズ1x1) を使って、後段の処理のために次元数 (hidden_dim) を統一します。
- AIFI (Attention-based Intra-scale Feature Interaction): nn.TransformerEncoder として定義されています。RT-DETRの重要な工夫は、このコストの高いTransformer処理を、最も解像度が低く意味的情報が豊富な特徴マップ (p5) のみに適用する点です。これにより、計算量を大幅に削減しています。
- CCFM (CNN-based Cross-scale Feature-fusion Module): nn.Conv2d を用いた融合層として実装されています。これは、AIFIで処理された特徴と、より解像度が高い(空間情報が豊富な)特徴マップを、FPN(Feature Pyramid Network)のようなトップダウン方式で効率的に融合する役割を担います。
順伝播 (forward)
- まず、入力画像 x をResNetバックボーンに通し、c3, c4, c5 の特徴マップを得ます。
- 次に、プロジェクション層で各特徴マップのチャンネル数を揃え、p3, p4, p5 を生成します。
- p5 のみに対し、シーケンス形式に変換(flatten と permute)してからAIFI(self.aifi)を適用し、グローバルな文脈情報を捉えます。
- 最後に、AIFI処理済みの p5_aifi を順次アップサンプリング(F.interpolate)しながら、より解像度の高い p4、p3 と畳み込みベースで融合(CCFM)していきます。
- 結果として、3つの異なるスケールの特徴マップをリスト形式でデコーダに渡します。
SimpleDeformableAttentionの定義
以下のコードでは、RT-DETRの計算効率と精度を両立させる上で極めて重要な SimpleDeformableAttention クラスを定義します。これは、オリジナルのTransformerのように画像全体の特徴量に対してアテンションを計算するのではなく、オブジェクトクエリが予測した少数の参照点周辺からピンポイントで特徴をサンプリングする『変形可能なアテンション』の概念を、PyTorchの grid_sample 関数を用いてシンプルに実装したものです。
class SimpleDeformableAttention(nn.Module):
def __init__(self, hidden_dim=256, num_points=4):
super().__init__()
self.num_points = num_points
# クエリから「サンプリング座標のオフセット」と「各点の重要度」を予測する線形層
self.offset_net = nn.Linear(hidden_dim, num_points * 2)
self.weight_net = nn.Linear(hidden_dim, num_points)
self.proj_out = nn.Linear(hidden_dim, hidden_dim)
def forward(self, query, ref_points, value_map):
"""
Args:
query (Tensor): オブジェクトクエリ (B, NumQueries, C)
ref_points (Tensor): クエリの基準点 (B, NumQueries, 2) (正規化済)
value_map (Tensor): 画像特徴マップ (B, C, H, W)
"""
B, Q, C = query.shape
_, _, H, W = value_map.shape
# Step 1: 各クエリが見るべき座標のオフセットを予測
offsets = self.offset_net(query).view(B, Q, self.num_points, 2)
# Step 2: 実際に特徴をサンプリングする座標を計算
# (grid_sample用に座標を-1〜1の範囲にスケール)
sample_coords = (ref_points.unsqueeze(2) + offsets) * 2.0 - 1.0
# Step 3: grid_sampleで画像特徴をピンポイントで抽出 (Deformable Attentionの肝)
sampled_features = F.grid_sample(value_map, sample_coords, align_corners=False)
sampled_features = sampled_features.permute(0, 2, 3, 1) # (B, Q, num_points, C)
# Step 4: 各サンプリング点の重要度(アテンション重み)を予測
weights = F.softmax(self.weight_net(query), dim=-1).unsqueeze(-1)
# Step 5: サンプリングした特徴を重み付け和で集約
out = (sampled_features * weights).sum(dim=2)
return self.proj_out(out)
この SimpleDeformableAttention クラスは、Transformerの膨大な計算量を削減するための鍵となる「変形可能アテンション」のメカニズムを、PyTorchの機能でシンプルに実装したものです。
初期化 (__init__)
- self.offset_net: 各オブジェクトクエリが、画像特徴マップ上の「どの位置の情報を参照すべきか」というサンプリング点のオフセット(ズレ)を予測するための線形層です。
- self.weight_net: 予測した各サンプリング点の重要度(アテンションの重み)を計算するための線形層です。
- self.proj_out: サンプリングして重み付けされた特徴量を、最終的に出力するための線形層です。
順伝播 (forward)
forward処理が変形可能アテンションの核心部分です。
- まず、self.offset_net がオブジェクトクエリ query を受け取り、サンプリング点のオフセット offsets を予測します。
- 次に、基準点である ref_points にこのオフセットを加算し、実際に特徴量を抽出する座標 sample_coords を計算します。
- そして、PyTorchの F.grid_sample 関数を用いることで、画像全体ではなく、計算された sample_coords の位置からのみ、ピンポイントで特徴量を効率的にサンプリングします。これが、画像全体にアテンションをかける標準的なTransformerとの決定的な違いであり、計算量を劇的に削減できる理由です。
- 最後に、self.weight_net で各サンプリング点の重要度 weights を予測し、サンプリングした特徴量と重み付け和を取ることで、クエリに必要な情報を集約します。
ハンガリアンマッチングのための関数の定義
以下の compute_loss 関数では、DETRファミリーの学習における核心部である、予測と正解のマッチングおよび損失計算を実装します。ここでは scipy.optimize.linear_sum_assignment を用いたハンガリアンアルゴリズムにより、モデルが出力した多数の予測の中から、最も正解に近いものを対応付けます。その後、マッチしたペアに対して分類損失(Focal Loss)、およびバウンディングボックス回帰損失(L1 Loss + GIoU Loss)を計算し、モデルを訓練します。
def compute_loss(outputs, target_labels, target_boxes):
pred_logits = outputs['pred_logits'][0] # 予測ロジット (NumQueries, NumClasses)
pred_boxes = outputs['pred_boxes'][0] # 予測ボックス (NumQueries, 4) [cx, cy, w, h]
# --- Step 1: マッチングコストの計算 ---
# モデルの全予測と全正解ラベル間のコスト行列を作成する
# 分類コスト: 予測されたクラス確率に基づくコスト
prob = pred_logits.sigmoid()
cost_class = -prob[:, target_labels]
# 回帰コスト1 (L1): ボックス座標のL1距離
cost_bbox = torch.cdist(pred_boxes, target_boxes, p=1)
# 回帰コスト2 (GIoU): ボックスの形状も考慮したIoUベースのコスト
pred_boxes_xyxy = box_convert(pred_boxes, in_fmt="cxcywh", out_fmt="xyxy")
tgt_boxes_xyxy = box_convert(target_boxes, in_fmt="cxcywh", out_fmt="xyxy")
cost_giou = -generalized_box_iou(pred_boxes_xyxy, tgt_boxes_xyxy)
# 3つのコストを重み付けして合算
C = 2.0 * cost_class + 5.0 * cost_bbox + 2.0 * cost_giou
C = C.detach().cpu().numpy()
# --- Step 2: ハンガリアンマッチング ---
# コストが最小になるように、予測と正解の最適なペアを見つける
src_idx, tgt_idx = linear_sum_assignment(C)
# --- Step 3: 損失の計算 ---
# マッチしたペアに対してのみ損失を計算する
# 損失1 (分類): Focal Loss
# マッチした予測は正解クラスを、他は「背景」クラスとして学習
num_classes = pred_logits.shape[-1]
target_classes_onehot = torch.zeros_like(pred_logits)
target_classes_onehot[src_idx, target_labels[tgt_idx]] = 1.0
loss_ce = sigmoid_focal_loss(pred_logits, target_classes_onehot, reduction='sum') / max(1, len(src_idx))
# 損失2 (回帰): L1 Loss + GIoU Loss
loss_bbox = F.l1_loss(pred_boxes[src_idx], target_boxes[tgt_idx])
loss_giou = 1.0 - torch.diag(generalized_box_iou(pred_boxes_xyxy[src_idx], tgt_boxes_xyxy[tgt_idx])).mean()
# 最終的な損失を重み付けして合計
return loss_ce + 5.0 * loss_bbox + 2.0 * loss_giou
この compute_loss 関数は、RT-DETR(およびDETRシリーズ)の学習方法を象徴する、最も重要な部分です。NMSフリーを実現するための「集合ベース損失(Set-based Loss)」を計算します。
処理は大きく2つのステップに分かれます。
1. マッチングコスト計算とハンガリアンマッチング
- まず、モデルが出力した全予測(pred_logits, pred_boxes)と、全ての正解ラベル(target_labels, target_boxes)との間の「マッチングコスト」を計算します。コストは、以下の3つの要素を重み付け加算して算出されます。
- 分類コスト (cost_class): クラス予測の正確さ(Focal Lossベース)。
- L1コスト (cost_bbox): バウンディングボックス座標のL1距離。
- GIoUコスト (cost_giou): バウンディングボックスの形状と位置の類似度。
- 次に、このコスト行列を scipy.optimize.linear_sum_assignment に渡します。
- これがYOLOとの決定的な違いです。 この関数(ハンガリアンアルゴリズム)は、300個の予測クエリとN個の正解ボックスの間で、コストが最小になるような最適な一対一のペアを見つけ出します。YOLOが推論時にNMSで大量の候補を削るのに対し、DETRは学習時に「1つの正解に対し、責任を持つ予測はただ1つ」という関係性を強制します。これにより、マッチしなかった大多数の予測は「物体なし(背景)」として学習され、推論時のNMSが不要になるのです。
2. 損失計算
- ハンガリアンマッチングによって得られた最適なペア(インデックス src_idx と tgt_idx)に対してのみ、実際の損失を計算します。
- 分類損失 (loss_ce): sigmoid_focal_loss を使用します。マッチした予測は対応する正解クラスを、マッチしなかった予測は「背景」を学習するように仕向けます。Focal Lossにより、圧倒的多数である背景サンプルの影響が抑制され、学習が安定します。
- 回帰損失 (loss_bbox, loss_giou): マッチしたペアのバウンディングボックスに対し、L1損失とGIoU損失の両方を計算し、より正確な位置と大きさになるよう学習させます。
- 最終的に、これらの損失に重みを付けて合計した値が、全体の損失として返されます。
モデルの定義と学習
これまでに定義した各コンポーネントを統合し、実際にモデルを訓練する準備をします。以下のコードでは、RTDETR_Core モデルをインスタンス化し、最適化アルゴリズムとして AdamW を設定します。さらに、skimage と torchvision を用いてサンプル画像を準備し、1枚の画像と正解ボックスに対してモデルを過学習させることで、損失計算が正しく機能し、学習が進むことを確認します。
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = RTDETR_Core(num_classes=1, num_queries=300).to(device)
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4)
# 画像の準備
image_np = data.cat()
transform = transforms.Compose([
transforms.ToPILImage(),
transforms.Resize((256, 256)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
image_tensor = transform(image_np).unsqueeze(0).to(device)
# 教師データ (正規化座標 cx, cy, w, h)
gt_boxes = torch.tensor([[0.5, 0.5, 0.6, 0.8]], dtype=torch.float32).to(device)
gt_labels = torch.tensor([0], dtype=torch.long).to(device)
print("--- 学習開始 (Overfitting on 1 Image) ---")
model.train()
for epoch in range(150):
optimizer.zero_grad()
outputs = model(image_tensor)
loss = compute_loss(outputs, gt_labels, gt_boxes)
loss.backward()
optimizer.step()
if (epoch + 1) % 30 == 0:
print(f"Epoch {epoch + 1}/150, Loss: {loss.item():.4f}")
このコードブロックでは、これまでに定義したモジュールと関数を組み合わせて、実際にモデルの学習を行います。ここでは、単一の画像に対してモデルを「過学習(Overfitting)」させることで、パイプライン全体が正しく機能しているか(損失が減少し、学習が進むか)を簡易的に検証します。
-
デバイス設定、モデルとオプティマイザの初期化:
- まず、torch.device を使って、利用可能なデバイス(GPUまたはCPU)を決定します。
- 次に、RTDETR_Core モデルをインスタンス化し、.to(device)で指定のデバイスに送ります。
- 学習のための最適化アルゴリズムとして、torch.optim.AdamW を設定します。
-
データ準備:
- skimage.data.cat() を使ってサンプル画像を読み込み、torchvision.transforms を用いてリサイズ、テンソルへの変換、正規化といった一連の前処理を行います。
- 学習の目標となる正解バウンディングボックス (gt_boxes) と正解ラベル (gt_labels) を手動で定義します。
-
学習ループ:
- model.train() でモデルを学習モードに切り替えます。
- forループの中で、標準的なPyTorchの学習サイクルを回します。
- optimizer.zero_grad(): 前のステップで計算された勾配をリセットします。
- model(image_tensor): モデルに画像を入力し、予測結果 outputs を得ます。
- compute_loss(...): 予測と正解ラベルから損失 loss を計算します。
- loss.backward(): 損失を逆伝播させ、各パラメータの勾配を計算します。
- optimizer.step(): 計算された勾配に基づいてモデルの重みを更新します。
- 30エポックごとに損失の値を表示し、学習が順調に進んでいるか(損失が減少しているか)を確認します。
推論と可視化
学習が完了したモデルを評価モードに切り替え、実際に物体検出の推論を実行します。以下のコードでは、同じ入力画像に対してフォワードパスを実行し、モデルの出力を受け取ります。RT-DETRの最大の特徴であるNMSフリーを実証するため、後処理では単純な信頼度の閾値のみで最終的な検出結果をフィルタリングし、その結果を matplotlib を用いて元の画像上に描画します。
print("\n--- 推論開始 (NMS-Free) ---")
model.eval()
with torch.no_grad():
outputs = model(image_tensor)
# Focal Lossの仕様上、SoftmaxではなくSigmoidで確率化
probs = outputs['pred_logits'][0].sigmoid()
pred_boxes = outputs['pred_boxes'][0]
cat_probs = probs[:, 0]
# NMSなし: 単純な閾値カット
keep = cat_probs > 0.5
final_boxes = pred_boxes[keep].cpu().numpy()
final_scores = cat_probs[keep].cpu().numpy()
# 可視化処理
fig, ax = plt.subplots(1, figsize=(8, 8))
ax.imshow(image_np)
ax.set_title("RT-DETR Core Concepts Visualization", fontsize=16)
H, W = image_np.shape[:2]
for box, score in zip(final_boxes, final_scores):
cx, cy, w, h = box
x_min = (cx - w / 2) * W
y_min = (cy - h / 2) * H
width = w * W
height = h * H
rect = patches.Rectangle(
(x_min, y_min), width, height,
linewidth=3, edgecolor='#ff00ff', facecolor='none'
)
ax.add_patch(rect)
ax.text(
x_min, y_min - 8, f"CAT: {score:.2f}",
color='white', fontsize=12, weight='bold',
bbox=dict(facecolor='#ff00ff', alpha=0.8, edgecolor='none', pad=2)
)
plt.axis('off')
plt.tight_layout()
plt.show()
学習済みのモデルを用いて、実際に推論を行い、その結果を可視化します。このコードブロックの最大のポイントは、後処理がいかにシンプルであるか、という点です。
-
推論の実行:
- model.eval() でモデルを評価モードに設定し、torch.no_grad() のコンテキスト内で順伝播を実行して、予測結果 outputs を取得します。
- 分類ヘッドの出力 pred_logits は生のスコア値であるため、sigmoid 関数を適用して0から1の確率値 probs に変換します。
-
NMSフリーの後処理:
- ここがRT-DETRの真骨頂です。コードを見ていただくと分かる通り、torchvision.ops.nms のような複雑なNMS(非最大値抑制)関数は一切呼び出していません。
- keep = cat_probs > 0.5 という単純な信頼度の閾値(スレッショルド)処理だけで、表示するバウンディングボックスを決定しています。
- これは、学習段階のハンガリアンマッチングによって「1つの物体につき1つのクエリだけが反応する」ようにモデルが訓練されているため可能になる、エレガントなアプローチです。
-
可視化:
- 最後に、生き残った予測 final_boxes をループ処理し、matplotlib を使って元の画像上に矩形とクラス名、スコアを描画します。
- モデルが出力するボックス座標は画像サイズに対して正規化された形式(中心x, 中心y, 幅, 高さ)であるため、ピクセル単位に変換してから描画している点に注意してください。
実行結果
上記のコードを実行すると以下の結果を得られます。
--- 学習開始 (Overfitting on 1 Image) ---
Epoch 30/150, Loss: 0.2179
Epoch 60/150, Loss: 0.3544
Epoch 90/150, Loss: 0.2899
Epoch 120/150, Loss: 0.2227
Epoch 150/150, Loss: 0.2023

上記のコードを実行すると、コンソールには学習の進捗を示す損失(Loss)が30エポックごとに出力されます。損失が順調に減少していることから、モデルが正解データに適合しようと学習している様子がわかります。
最終的に、学習済みのモデルによる推論結果が matplotlib によって可視化されます。表示された画像では、入力された猫の画像に対して、高い信頼度スコア(この例では0.73)を持つバウンディングボックスが正確に描画されています。これにより、私たちが実装したRT-DETRのコアコンセプトが正しく機能し、単一のサンプルに対して物体検出タスクを学習・実行できたことが確認できます。
実際のHugging Faceモデルを用いた推論テスト
これまでの概念実装に加え、ここではtransformersライブラリを使って、Hugging Face Hubで公開されている公式の学習済みRT-DETRモデル(PekingU/rtdetr_r50vd)をロードし、より実践的な推論を行います。AutoImageProcessorによる前処理から、モデルの推論、post_process_object_detectionを用いた後処理まで、わずかなコードで高品質な物体検出が実現できることを示します。
import torch
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from PIL import Image
from skimage import data
# 安全なインポートのためにAutoクラスを使用
from transformers import AutoImageProcessor, AutoModelForObjectDetection
def main():
print("画像を読み込んでいます...")
image_np = data.cat()
image = Image.fromarray(image_np)
print("モデルをロードしています...")
model_id = "PekingU/rtdetr_r50vd"
# AutoImageProcessor と AutoModelForObjectDetection を使用
processor = AutoImageProcessor.from_pretrained(model_id)
model = AutoModelForObjectDetection.from_pretrained(model_id)
print("推論を実行中...")
inputs = processor(images=image, return_tensors="pt")
with torch.no_grad():
outputs = model(**inputs)
target_sizes = torch.tensor([image.size[::-1]]) # (height, width)
results = processor.post_process_object_detection(
outputs,
target_sizes=target_sizes,
threshold=0.5
)[0]
print("結果を描画します...")
fig, ax = plt.subplots(1, figsize=(8, 8))
ax.imshow(image)
ax.set_title("RT-DETR Object Detection (NMS-Free)", fontsize=16)
for score, label, box in zip(results["scores"], results["labels"], results["boxes"]):
box = [round(i, 2) for i in box.tolist()]
x_min, y_min, x_max, y_max = box
width = x_max - x_min
height = y_max - y_min
rect = patches.Rectangle(
(x_min, y_min), width, height,
linewidth=3, edgecolor='#00ff00', facecolor='none'
)
ax.add_patch(rect)
label_name = model.config.id2label[label.item()]
text = f"{label_name.upper()}: {score.item():.2f}"
ax.text(
x_min, y_min - 8, text,
color='black', fontsize=12, weight='bold',
bbox=dict(facecolor='#00ff00', alpha=0.8, edgecolor='none', pad=2)
)
plt.axis('off')
plt.tight_layout()
plt.show()
if __name__ == "__main__":
main()
このコードブロックでは、Hugging Faceの transformers ライブラリが提供する便利な高レベルAPIを使って、学習済みの公式RT-DETRモデルによる推論を簡単に行う方法を示します。
-
モデルとプロセッサのロード:
- AutoImageProcessor.from_pretrained と AutoModelForObjectDetection.from_pretrained を使うことで、Hugging Face Hub上のモデルID ("PekingU/rtdetr_r50vd") を指定するだけで、モデルに合わせた画像の前処理オブジェクトと、学習済みのモデル本体を自動的にダウンロードして準備できます。
-
推論と後処理:
- 準備した processor に画像を入力すると、モデルが要求する形式(テンソルの形状や正規化など)に自動で変換してくれます。
- モデルの推論後、最も注目すべきは processor.post_process_object_detection 関数です。この関数は、モデルからの生の出力 outputs を受け取るだけで、以下の後処理を全て実行してくれます。
- 信頼度スコアに基づき、閾値(ここでは threshold=0.5)以下の予測を破棄する。
- モデルが出力した正規化済みのバウンディングボックス座標を、元の画像のピクセル座標に変換する。
- これにより、自前で後処理コードを書くことなく、非常に簡単に最終的な検出結果 results を得ることができます。
-
可視化:
- 後処理済みの results オブジェクトには、検出された各物体のスコア、ラベル、ボックス座標が綺麗に格納されています。
- model.config.id2label を使うと、数値のクラスラベルを人間が読める文字列(例:
1→"cat")に簡単に変換でき、これを matplotlib で描画しています。
実行結果
上記のコードを実行すると以下の結果を得られます。
画像を読み込んでいます...
モデルをロードしています...
推論を実行中...
結果を描画します...

transformersライブラリを使ってHugging Face Hubの公式モデルを実行すると、上記のような結果が得られます。
コンソールには処理の進行状況が出力され、最終的に matplotlib のウィンドウに検出結果が描画されます。
注目すべきは、私たちが自前で実装した概念実証モデルとは異なり、この公式モデルは大規模なデータセット(COCO)で事前学習済みであるという点です。そのため、追加の学習を一切行うことなく、与えられた画像に対して「cat」というクラスを非常に高い信頼度(0.97)で正しく検出できています。
このように、Hugging Faceのエコシステムを活用することで、強力な学習済みモデルをわずか数十行のコードで利用できることがわかります。
ライセンスに関する注記
Baiduが公開しているRT-DETRの公式ソースコード、およびHugging Face Hubで提供されている事前学習済みモデル(PekingU/rtdetr_r50vdなど)は、Apache-2.0 ライセンスに準拠して提供されています。商用利用をはじめ、本技術をプロジェクトやプロダクトに導入する際は、ライセンス規約に従って適切にご利用ください。
まとめ
本記事では、YOLOシリーズの強力な対抗馬として登場したリアルタイム物体検出モデル「RT-DETR」について、その革新的なアーキテクチャから具体的な実装までを深く掘り下げました。
記事を通じて、以下の内容をステップバイステップで学習しました。
- RT-DETRのコアコンセプト理解: NMSフリーを実現するエンドツーエンド設計や、計算効率と精度を両立するハイブリッド・エンコーダ、IoUに配慮したクエリ選択など、RT-DETRを支える主要な技術的ブレークスルーを学びました。
- PyTorchによる概念実装:
RTDETR_Core,RTDETR_Encoder,SimpleDeformableAttentionといった主要モジュールをスクラッチで実装し、ハンガリアンマッチングを用いた損失計算など、モデル内部のデータフローをコードレベルで確認しました。 - Hugging Faceによる実践的な推論:
transformersライブラリを活用し、公式の学習済みモデルをわずかなコードで呼び出し、追加学習なしで高精度な物体検出を実行できることを実証しました。
RT-DETRの登場は、単なる「新しいモデルの発表」ではなく、「リアルタイム物体検出=YOLO一択」という常識の終焉、そして「リアルタイムTransformer時代」の幕開けを意味しています。オープンソースコミュニティでは、すでにより高精度なRT-DETRv2や、基盤モデルの知識を活用した派生モデルの研究開発が活発に進んでいます。
NMSフリーというエレガントな設計、Transformerでありながらリアルタイム性を実現した技術力、そして再学習なしで速度を調整できる柔軟性は、これからのコンピュータビジョン開発において非常に強力な武器となります。今から物体検出のプロジェクトを始めるのであれば、YOLOだけでなく、この「RT-DETR」も確実に検証リストに入れておくべき最重要モデルと言えるでしょう。
本記事の文章・構成の一部に生成AIを使用しています。