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が同じ処理です。

  1. 行ごとに、スコアが大きい方のクラスを選ぶ。hard_negativeになった行はカーブミラーとして数えない。
  2. クラスごとにNMS(IoU 0.65)をかける。
  3. 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 class hard_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 with model.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,y2 in input pixels) and scores [200,2] (curve_mirror, hard_negative). Top 200 candidates; no threshold, no NMS. Unused rows have boxes[i][0] < 0.
  • Post-processing: argmax class per row, class-wise NMS (IoU 0.65), keep curve_mirror with 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
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Paper for infra-intelligence/curve-mirror-yolox-tiny