micomia株式会社でAIエンジニアをしている松久保です。
本記事では、OpenCVとYOLOを用いて構築した車両カウントシステムについてご紹介します。どのような技術を使い、どのような仕組みで車両を検出・追跡しカウントしているのか、実装のポイントも含めてわかりやすく解説していきます。
1. はじめに
今回作成したシステムは、ノートパソコン 上でも動作する軽量構成であり、主に Python・Ultralytics YOLO・OpenCV というシンプルな技術スタックで実現しています。
車両検出 & 分類 → 追跡 → ライン交差によるカウント という処理フローで動作し、交通量調査やインフラ監視などの用途にも応用できる実用的な仕組みになっています。
2. 使用技術について
本システムは Python をベースに、主に YOLO(Ultralytics YOLO11) と OpenCV を組み合わせて構築しています。
YOLOは軽量・高速・高精度な物体検出モデルで、車両の検出 (Detection)と 追跡 (Tracking) を一貫して行うことができます。
OpenCVは、動画の読み込み、描画、ライン交差のチェック、そして最終的な動画出力など、処理全体の制御を行っています。
アーキテクチャ
本システムは「入力動画 → 検出・追跡 → ライン交差判定 → カウント」というシンプルな処理パイプラインで構成されています。
入力動画のフレーム分割
Python と OpenCV を用いて入力動画を読み込み、1フレームずつ画像として取り出します。以降の処理はすべてフレーム単位で行われます。
YOLOによる車両検出・トラッキング
各フレームに対して YOLO を適用し、車・バイク・バス・トラックを検出・分類します。track モードを使用することで、各車両に対して位置(バウンディングボックス)、クラスID、トラッキングIDが付与され、同一車両をフレーム間で一貫して追跡できます。
カウントライン交差の判定
画像中に1本のカウントライン(本実装では画像中央の水平線)を設置し、各トラッキングIDごとに「前フレームの位置」と「現在フレームの位置」を比較します。車両の位置がカウントラインの上側から下側へ移動したタイミングで、その車両をカウントします。
3. コード・実装の解説
ライブラリのインポートと基本設定
必要なライブラリのインポートと、モデルや入力・出力ファイルのパスなどの基本設定を行います。
import cv2
from ultralytics import YOLO
# Config
MODEL = "yolo11l.pt"
INPUT_VIDEO = "input_path"
OUTPUT_VIDEO = "output_path"モデル読み込みと動画入出力の初期化
YOLOモデルをロードし、OpenCVで動画の読み込み・書き込みの準備をします。
model = YOLO(MODEL)
cap = cv2.VideoCapture(INPUT_VIDEO)
if not cap.isOpened():
print("Error: Could not open video.")
exit()
fps = int(cap.get(cv2.CAP_PROP_FPS))
w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(OUTPUT_VIDEO, fourcc, fps, (w, h))カウント用の変数定義
車種ごとのカウンタや、トラッキングIDの履歴を管理するための変数を定義します。
class_counts = {
'car': 0,
'motorcycle': 0,
'bus': 0,
'truck': 0
}
CLASS_NAMES = {2: 'car', 3: 'motorcycle', 5: 'bus', 7: 'truck'}
counted_ids = {}
track_history = {}メインループ:フレームごとの処理
動画を1フレームずつ処理し、検出 → 追跡 → ライン交差判定 → カウント表示を行います。
while True:
ret, frame = cap.read()
if not ret:
print("End of video stream.")
break
result = model.track(frame, classes=[2, 3, 5, 7], persist=True, tracker="bytetrack.yaml")
annotated_frame = result[0].plot()ライン交差の判定とカウントロジック
各トラッキングIDごとに「前フレームの位置」と「現在の位置」を比較し、ラインより上側 → ラインより下側に移動したタイミングでカウントします。
line_y = h // 2
cv2.line(annotated_frame, (0, line_y), (w, line_y), (0, 255, 255), 2)
if result[0].boxes.id is not None:
boxes = result[0].boxes.xywh.cpu()
track_ids = result[0].boxes.id.int().cpu().tolist()
class_ids = result[0].boxes.cls.int().cpu().tolist()
for box, track_id, class_id in zip(boxes, track_ids, class_ids):
class_name = CLASS_NAMES.get(class_id)
if not class_name:
continue
car_position_y = int(box[1])
current_side = 'top' if car_position_y < line_y else 'down'
if track_id in counted_ids:
continue
if track_id in track_history:
prev_side = track_history[track_id]
if prev_side == 'top' and current_side == 'down':
class_counts[class_name] += 1
counted_ids[track_id] = class_name
track_history[track_id] = current_sideカウント結果の描画と動画出力
最後に、フレーム上にカウント結果を描画し、動画として保存します。
total_count = sum(class_counts.values())
count_text = f"TOTAL: {total_count}"
details_text = " | ".join([f"{k}: {v}" for k, v in class_counts.items()])
cv2.putText(annotated_frame, count_text, (10, 50), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 0, 255), 3)
cv2.putText(annotated_frame, details_text, (10, 100), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 0), 2)
cv2.imshow('YOLO11l Detection', annotated_frame)
out.write(annotated_frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
out.release()
cv2.destroyAllWindows()
print(f"Processing done. Saved to {OUTPUT_VIDEO}")4. 実行結果

YOLO と OpenCV を組み合わせて、シンプルな構成で動作する車両カウントシステムを実装しました。
YOLOによる検出と ByteTrack を利用した追跡により、車両がカウントラインを通過するタイミングを高い精度で捉えることができました。
Python と軽量な技術スタックのみで動作するため、ローカル環境でも手軽に試せるほか、交通量調査や映像解析の自動化など、さまざまな用途に応用できます。


.webp%3Falt%3Dmedia%26token%3Dbc168691-5281-4eab-8c08-ed7fe5624582&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D44d272c7-1e7a-46d7-86de-dc2bec67a3e4&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D8f9453ad-d017-4640-8b7b-4c5d55391f46&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D904686c4-e792-4c6a-b5ce-e7648fd53404&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D8db330f1-3fab-48b7-8dcf-dd8c6f47836a&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3Ddef3da75-721c-4e42-8cd8-f06795bb771f&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D8fec979d-a6dc-4d03-960f-330f997108a7&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D6bddee16-071d-41fd-8a4b-f2026bcff617&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D9ba70870-6b39-4eab-b9c2-e126ef08cb09&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D8a795f56-e8a9-4be1-937b-65c1a89922b3&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3Dbe65d589-2f4c-4ad8-82a1-7ddb73af2620&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D0aa104f5-928b-4ddf-a535-d8574b7667a8&w=3840&q=75)

.webp%3Falt%3Dmedia%26token%3D45131e3d-4777-421a-a556-bcc8d462dfe1&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D09daf923-4a62-4c31-af6f-f3d99a9f635b&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D0e63e93b-1558-48a5-8b24-d1ae7cfa487f&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D240fcc34-b7e7-4c86-b294-fdfc2a66db21&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3Db288c602-2902-4f93-8eca-903ec8f86440&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D6040f2d9-1a54-476c-9999-5e0aeb8e8a74&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D3c4b1b17-9fec-405e-9e02-8d34961b6f16&w=3840&q=75)