Curve Mirror YOLOX-Tiny(カーブミラー YOLOX-Tiny)
モデルラボ(インフラインテリジェンス)が公開している物体検出モデルです。 車に載せたスマートフォンやドライブレコーダーで撮った道路の画像から、カーブミラー(道路反射鏡)を見つけます。 カーブミラーの位置の台帳を作るときの、下調べに使うことを考えて作りました。
- 見つけるもの: カーブミラー(
curve_mirror) - 構造: YOLOX-Tiny(パラメータ約503万個)
- 学習: Mapillary に投稿された日本国内の路上の画像。事前学習の重みは使っていない(最初から学習)
- 入力: 1280×736のRGB画像(0〜255)
- 出力: 候補200件の枠(
boxes)と、クラスごとのスコア(scores) - 使うしきい値: 0.66
- ライセンス: Apache-2.0
- 紹介のページ: https://infra-intelligence.com/services/modellab/curve-mirror-yolox-tiny/
何を見つけるか
カーブミラーを1つの枠で囲みます。枠の付け方は、カーブミラーの載り方で2通りあります。
- オレンジの専用の支柱に載っているとき: 鏡と、見えている支柱をまとめて囲む、縦長の1つの枠。 1本の支柱に鏡が2面あれば、2面と支柱をまとめて1つの枠にします。
- それ以外(電柱・建物・塀などに付いた鏡、支柱が見えない鏡): 鏡だけを囲む枠。
鏡の裏側(裏板と金具)が写っているときも、支柱や腕からカーブミラーだと分かるものは見つけます。
2つ目のクラスhard_negativeは、丸い標識の裏や街灯の笠など、カーブミラーと紛らわしい物です。
誤報を減らすために学習させたクラスで、ここで出た検出はカーブミラーとして数えません。
ファイル
| ファイル | 中身 |
|---|---|
curve_mirror_yolox_tiny_v1.pth |
PyTorchの重み(torch.load(weights_only=True)で開ける) |
model.py |
ネットワークの定義と、入力の作り方・後処理の例(torch・torchvision・Pillowだけで動く) |
curve_mirror_yolox_tiny_v1.onnx |
ONNX(opset 17、入力1280×736で固定) |
coreml/curve_mirror_yolox_tiny_v1.mlpackage |
CoreML(mlprogram、FLOAT16、iOS 17以降、入力1280×736で固定) |
config.json |
入出力・クラス・後処理の設定 |
MODEL_CONTRACT.md |
入出力の決まり |
eval/ |
評価の結果と、形式ごとの出力の一致の記録 |
examples/predict_onnx.py |
ONNXで動かす例 |
LICENSE / NOTICE |
ライセンスと帰属表示 |
使い方
ONNX
pip install onnxruntime numpy pillow
python examples/predict_onnx.py curve_mirror_yolox_tiny_v1.onnx frame.jpg
PyTorch
pip install torch torchvision pillow
python model.py curve_mirror_yolox_tiny_v1.pth frame.jpg
Pythonから使う例です。
import torch
from PIL import Image
from model import load_detector, letterbox, postprocess, to_original
detector = load_detector("curve_mirror_yolox_tiny_v1.pth") # 入力 1280×736
image = Image.open("frame.jpg")
x, scale = letterbox(image, detector.input_size) # 縦横比を保って縮め、右と下を灰色で埋める
with torch.no_grad():
boxes, scores = detector(x) # [200,4], [200,2]
for score, box in postprocess(boxes, scores): # しきい値 0.66
print(round(score, 2), to_original(box, scale, image.size))
torch 2.13.0 で動くことを確かめています。
CoreML(iOS 17以降)
coreml/curve_mirror_yolox_tiny_v1.mlpackageをXcodeのプロジェクトに追加します。
入力はimage(1280×736のRGB画像、0〜255のまま)、出力はboxesとscoresです。
FLOAT16で計算するため、PyTorchとはわずかに値が違います(eval/coreml_parity.json)。
入力の作り方
画像を縦横比を保って縮め、左上に置き、右と下の余りを灰色(画素値114)で埋めます。
- 16:9の画像(1920×1080など): 1280×720に縮め、下に16画素の灰色を足して1280×736にします。評価と同じ作り方です。
- 4:3の画像(2048×1536など): 評価では、長辺を1280に縮めた1280×960をそのまま入れました。
これはPyTorch(
model.py)でだけできます(load_detector(..., input_size=(1280, 960))、python model.pyの既定)。 ONNXとCoreMLは入力が1280×736で固定なので、4:3の画像は981×736に縮めて入れることになります。 検証の4:3の画像(223枚)で比べると、1280×960で入れたときは見つけた率0.658・誤報3.18件、 981×736で入れたときは見つけた率0.549・誤報2.55件でした(誤報はカーブミラーが写っていない画像100枚あたり)。
詳しくはMODEL_CONTRACT.mdを見てください。
出力と後処理
boxesは[200, 4]で、各行が入力画像の画素の座標[x1, y1, x2, y2]です。
scoresは[200, 2]で、列はcurve_mirror・hard_negativeの順です。
モデルは、スコアの最大値が大きい順に候補を200件選んで出力します。
しきい値とNMS(重なりの除去)はかけていません。
候補が200件に足りないときは、残りの行を[-1, -1, -1, -1]で埋めています。boxes[i][0] < 0の行は捨ててください。
評価では、次の後処理をしました。model.pyのpostprocess()とexamples/predict_onnx.pyが同じ処理です。
- 行ごとに、スコアが大きい方のクラスを選ぶ。
hard_negativeになった行はカーブミラーとして数えない。 - クラスごとにNMS(IoU 0.65)をかける。
curve_mirrorのスコアが0.66以上のものをカーブミラーとする。
しきい値0.66は、検証の画像で「カーブミラーが写っていない画像100枚あたりの誤報が3件以下」になる一番低い値です。 評価用の画像を見る前に決めました(検証では見つけた率0.650、誤報は100枚あたり2.82件)。 見逃しを減らしたいときはしきい値を下げ、誤報を減らしたいときは上げてください。
評価
学習にも検証にも使っていない、別の7地域の画像3,997枚で、最後に1回だけ評価しました。 カーブミラーの枠は465個、カーブミラーが写っていない画像は3,616枚です。 同じカーブミラーが学習と評価の両方に入らないように、地域ごと分けています。
| 項目 | 値 |
|---|---|
| 見つけた率(recall、しきい値0.66) | 0.641(298/465) |
| 適合率(precision、しきい値0.66) | 0.661 |
| 誤報(カーブミラーが写っていない画像100枚あたり) | 2.74件 |
| AP50 | 0.618 |
| AP50-95 | 0.384 |
枠の種類別・大きさ別です。大きさは、画像の長辺を2048画素にしたときの鏡の幅です。
| 区分 | 枠の数 | 見つけた率(0.66) | AP50 |
|---|---|---|---|
| 支柱込みの枠 | 290 | 0.710(206/290) | 0.607 |
| 鏡だけの枠 | 175 | 0.526(92/175) | 0.418 |
| 小(幅32〜64画素) | 0.658(148/225) | 0.559 | |
| 中(幅64〜128画素) | 0.706(113/160) | 0.504 | |
| 大(幅128画素以上) | 0.463(37/80) | 0.174 |
地域別の見つけた率は、新潟市・静岡市・岡山市・熊本市・東京都大田区の一部(最初の5地域)で0.654(17/26、誤報2.15件)、奈良市の一部で0.645(220/341、誤報2.82件)、那覇市の一部で0.622(61/98、誤報3.16件)でした。
予測の枠と正解の枠が IoU 0.5 以上で重なれば、見つけたとしました。
数値の元データはeval/にあります。
評価についての注意
- 誤報は多めに出ています。 カーブミラーが写っていないとした画像は、人が目で確かめていません。 誤報のうちスコアの高い48件を見ると、16件ははっきりカーブミラーが写っていました。 本当の誤報は2.74件より少なく、適合率は0.661より高いと考えています。
- 評価用の画像の3分の2は4:3で、1280×960で入れました(「入力の作り方」を参照)。
- 最初の5地域はカーブミラーが26個しかなく、この地域だけの数値はぶれが大きいです。
- 検証の画像での見つけた率(0.650)より低く出ています。評価用の地域は、学習の地域と街並みが違うためと考えています。
苦手なこと・使うときの注意
- 近くで大きく写ったカーブミラーを見落としやすいです(大きい鏡の見つけた率0.463、AP50 0.174)。
- 電柱や建物に付いた鏡(鏡だけの枠)は、オレンジの支柱に載った鏡より見落としやすいです。 鏡の裏側が2面並んでいると、片方しか見つけないことがあります。画面の端で切れた鏡も苦手です。
- 丸い看板・マーク、街灯の丸い笠、標識の裏、丸い格子を、カーブミラーと間違えることがあります。
- 夜間の画像では評価していません。 雨・雪・霧の場面での成績も分かりません。
- 学習も評価も日本国内の画像だけです。ほかの国の道路では成績が落ちると考えてください。
- 画像1枚ずつの検出です。同じカーブミラーが続くコマに何度も写るので、台帳にするときは位置でまとめてください。
- 交通の安全にかかわる判断を、このモデルの結果だけで行わないでください。台帳を作るときは、人が写真で確かめる前提で使ってください。
学習データ
Mapillary(https://www.mapillary.com/)に投稿された、日本国内の路上の画像を使いました。 画像のライセンスはCC BY-SA 4.0です。 学習には15地域から、学習2,440枚・検証513枚を選びました。 学習と検証は、同じ場所が両方に入らないように、地図の区画で分けています。
| 分割 | 画像 | カーブミラーが写った画像 | 紛らわしい物だけ | 何も写っていない | 支柱込みの枠 | 鏡だけの枠 | 紛らわしい物の枠 |
|---|---|---|---|---|---|---|---|
| 学習 | 2,440 | 793 | 840 | 807 | 549 | 361 | 1,095 |
| 検証 | 513 | 159 | 184 | 170 | 94 | 92 | 230 |
- ラベルは作者が付け、評価用の画像は目で確かめました(カーブミラーが写っていないとした画像を除く)。
- 小さすぎる鏡(長辺2048画素にしたときの幅が32画素未満)は、正解にも誤りにも数えていません。
- 画像とラベル(データセット)は、この公開物には含めていません。画像そのものは重みに含まれていません。
- 帰属表示: Contains imagery from Mapillary contributors, licensed CC BY-SA 4.0(https://www.mapillary.com/)。投稿者の一覧は`NOTICE`にあります。
学習の設定
- 初期値: 乱数で初期化(事前学習の重みは使っていない)
- エポック: 200 エポック学習し、検証の鏡の AP50 が一番よかったエポック 105 の重み(重みの移動平均 EMA 0.9998)を使った
- バッチ: 8
- 最適化: AdamW(学習率 1e-3、最初の 3 エポックで上げ、コサインで 5% まで下げる。重み減衰 5e-4、BN とバイアスは 0)
- 損失: 5×IoU 損失+objectness 損失+クラス損失(正例の割り当ては SimOTA)
- 入力: 1280×736 の切り出し。長辺 1280 を基準の縮尺にし、×0.75〜1.25 の縮尺のゆらぎ
- 拡張: 切り出し・左右反転・明るさ・コントラスト・彩度・モザイク(確率 0.5、最後の 15 エポックは止める)
- カーブミラーが写った画像を 1.5 倍に増やした
- 乱数の種: 0
- 計算機: Apple Silicon(PyTorch MPS)
ライセンスと帰属表示
重みとコードはApache-2.0です(LICENSE)。
著作権表示と帰属はNOTICEにまとめています。
学習に使った画像はCC BY-SA 4.0ですが、画像そのものは公開物に含めていません。
学習済みの重みは画像そのものではないと考え、重みはApache-2.0で公開しています。
この考え方は作者の解釈で、これを確かめた判例はありません。
学習に使った画像への帰属として、Mapillaryとその投稿者をNOTICEに記しています。
ネットワークの構造はYOLOX-Tinyです。
model.pyは、公式のYOLOX(Megvii、Apache-2.0)の構造とモジュール名に合わせて書いています。
事前学習の重みは使っていません。
構造を引用するときは、次の文献を挙げてください。
@article{ge2021yolox,
title={YOLOX: Exceeding YOLO Series in 2021},
author={Ge, Zheng and Liu, Songtao and Wang, Feng and Li, Zeming and Sun, Jian},
journal={arXiv preprint arXiv:2107.08430},
year={2021}
}
問い合わせ
インフラインテリジェンス(https://infra-intelligence.com/) contact@infra-intelligence.com
English summary
Curve Mirror YOLOX-Tiny detects traffic convex mirrors ("curve mirrors") in road images taken from a vehicle (car-mounted smartphone or dashcam), for building asset inventories. Published by Model Lab (Infra Intelligence). Project page: https://infra-intelligence.com/services/modellab/curve-mirror-yolox-tiny/
- What it detects (
curve_mirror): when the mirror stands on a dedicated orange pole, ONE tall box covering the mirror(s) and the visible orange pole; otherwise a box around the mirror only. The second classhard_negative(look-alikes such as the backs of round signs or street-lamp shades) is not a mirror; ignore detections of that class. - Architecture: YOLOX-Tiny (5.03 M parameters), trained from scratch (no pretrained weights).
- Input
image: float32[1,3,736,1280], RGB, raw 0–255; normalization is inside the model. Resize keeping the aspect ratio, place top-left, pad right/bottom with 114. 16:9 images become 1280×720 + 16 padding rows, exactly as in the evaluation. In the evaluation, 4:3 images were fed at 1280×960 (possible withmodel.py; ONNX/CoreML are fixed at 1280×736, so 4:3 images become 981×736 — on the 4:3 validation images recall was 0.658 at 1280×960 vs 0.549 at 981×736). - Outputs:
boxes[200,4](x1,y1,x2,y2in input pixels) andscores[200,2](curve_mirror,hard_negative). Top 200 candidates; no threshold, no NMS. Unused rows haveboxes[i][0] < 0. - Post-processing: argmax class per row, class-wise NMS (IoU 0.65), keep
curve_mirrorwith score ≥ 0.66 (chosen on validation data as the lowest threshold with ≤ 3 false alarms per 100 negative images, before looking at the test set). - Held-out test (3,997 images from 7 Japanese areas not used for training, split by region; 465 mirror boxes; 3,616 negative images), threshold 0.66: recall 0.641 (298/465), precision 0.661, 2.74 false alarms per 100 negative images, AP50 0.618, AP50-95 0.384. With orange pole: recall 0.710, AP50 0.607. Mirror only: recall 0.526, AP50 0.418. Large mirrors (≥ 128 px at 2048 px long side) are weak: recall 37/80, AP50 0.174. Negative test images were not human-reviewed (some contain real mirrors), so false alarms are likely overestimated.
- Limitations: large/near mirrors, mirrors on utility poles or buildings, cut-off mirrors; night, rain and snow not evaluated; Japan only. Not for safety-critical decisions.
- Training data: street-level images from Mapillary contributors in Japan (CC BY-SA 4.0): 2,440 training and 513 validation images from 15 areas. Labels were made by the author; the test labels were checked by eye. The dataset is not included in this release. Contains imagery from Mapillary contributors, licensed CC BY-SA 4.0 (https://www.mapillary.com/).
- License: Apache-2.0 for weights and code. Architecture: YOLOX (Megvii, Apache-2.0). Contact: contact@infra-intelligence.com
- Downloads last month
- 21