ConvNeXt V2による画像分類(入力:動画像)(ImageNet 1000クラス)(ソースコードと説明と利用ガイド)
【概要】 ConvNeXt V2による動画のリアルタイム画像分類プログラム。ImageNet 1000クラスで分類し、Top-5結果を確信度別に色分け表示する。動画ファイル、カメラ、サンプル動画に対応。Tiny~Largeの4モデルから選択可能。分類結果を自動保存。
プログラム利用ガイド
1. このプログラムの利用シーン
動画や画像に写っている物体を自動的に認識・分類したい場合に使用する。動画ファイルの内容分析、ウェブカメラによるリアルタイム物体認識、研究や教育目的での画像分類デモンストレーションに適用できる。
2. 主な機能
- リアルタイム画像分類:動画の各フレームをImageNet 1000クラスで分類し、結果を画面に表示する。
- Top-5結果表示:最も可能性の高い分類結果を上位5位まで確信度付きで表示する。
- 確信度による色分け:分類結果の信頼度に応じて色分け表示し、判定の確実性を視覚化する。
- モデル選択機能:処理速度と精度のバランスに応じて4つのConvNeXt V2モデルから選択できる。
- 結果保存機能:分類結果をテキストファイル(result.txt)に自動保存する。
3. 基本的な使い方
- モデル選択:
プログラム起動時に表示される1-4の数字でモデルを選択する。1(Tiny)は軽量、4(Large)は精度重視である。
- 入力源選択:
0(動画ファイル)、1(カメラ)、2(サンプル動画)から入力方法を選択する。
- 分類実行:
映像が表示され、画面左上に分類結果が表示される。各結果には確信度と色分けが適用される。
- 終了:
qキーを押してプログラムを終了する。結果はresult.txtに保存される。
4. 便利な機能
- GPU自動検出:CUDA対応GPUが利用可能な場合は自動的に使用し、処理を高速化する。
- 統計情報表示:処理フレーム数と検出されたクラス数をリアルタイムで表示する。
- 分類履歴保存:全フレームの分類結果と統計情報をテキストファイルに記録する。
- 日本語表示対応:Windows環境での日本語フォント表示に対応している。
Python開発環境,ライブラリ類
ここでは、最低限の事前準備について説明する。機械学習や深層学習を行う場合は、NVIDIA CUDA、Visual Studio、Cursorなどを追加でインストールすると便利である。これらについては別ページ https://www.kkaneko.jp/cc/dev/aiassist.htmlで詳しく解説しているので、必要に応じて参照してください。
Python 3.12 のインストール
インストール済みの場合は実行不要。
管理者権限でコマンドプロンプトを起動(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行する。管理者権限は、wingetの--scope machineオプションでシステム全体にソフトウェアをインストールするために必要である。
REM Python をシステム領域にインストール
winget install --scope machine --id Python.Python.3.12 -e --silent
REM Python のパス設定
set "PYTHON_PATH=C:\Program Files\Python312"
set "PYTHON_SCRIPTS_PATH=C:\Program Files\Python312\Scripts"
echo "%PATH%" | find /i "%PYTHON_PATH%" >nul
if errorlevel 1 setx PATH "%PATH%;%PYTHON_PATH%" /M >nul
echo "%PATH%" | find /i "%PYTHON_SCRIPTS_PATH%" >nul
if errorlevel 1 setx PATH "%PATH%;%PYTHON_SCRIPTS_PATH%" /M >nul【関連する外部ページ】
Python の公式ページ: https://www.python.org/
AI エディタ Windsurf のインストール
Pythonプログラムの編集・実行には、AI エディタの利用を推奨する。ここでは,Windsurfのインストールを説明する。
管理者権限でコマンドプロンプトを起動(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行して、Windsurfをシステム全体にインストールする。管理者権限は、wingetの--scope machineオプションでシステム全体にソフトウェアをインストールするために必要となる。
winget install --scope machine Codeium.Windsurf -e --silent【関連する外部ページ】
Windsurf の公式ページ: https://windsurf.com/
必要なライブラリのインストール
コマンドプロンプトを管理者として実行(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行する
pip install -U torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126
pip install timm opencv-python pillow
ConvNeXt V2による画像分類プログラム(ImageNet 1000クラス)
概要
ConvNeXt V2による画像分類プログラムは、ImageNet 1000クラスの画像分類をリアルタイムで実行する。動画ファイル、カメラ、サンプル動画を入力として受け取り、各フレームに対してConvNeXt V2モデルを適用して分類結果を表示する。
主要技術
ConvNeXt V2
Woo et al.(2023)が開発した畳み込みニューラルネットワークアーキテクチャ[1]。Global Response Normalization(GRN)層とMasked Autoencoder(MAE)による自己教師あり事前学習を組み合わせ、チャネル間の特徴競合を強化する。
timm(PyTorch Image Models)
PyTorch用の事前学習済みモデルライブラリ。ConvNeXt V2の学習済みモデルと標準的なデータ変換機能を提供する。
技術的特徴
- Global Response Normalization(GRN)
チャネル間の特徴競合を強化する正規化層。全体特徴集約、特徴正規化、特徴較正の3段階で構成される。
- Masked Autoencoder事前学習
ImageNet-22kでの自己教師あり事前学習後、ImageNet-1kでファインチューニングを行う転移学習。
- 複数モデルサイズ対応
Tiny(29Mパラメータ)からLarge(198Mパラメータ)まで4つのモデルサイズを選択可能である。
実装の特色
- ハイブリッドテキスト描画システム
OpenCVとPILを組み合わせ、日本語フォントによる分類結果表示を実現する。
- 確信度による色分け表示
分類結果の確信度に応じて緑(0.7以上)、黄(0.5以上)、オレンジ(0.3以上)、赤(0.3未満)で色分け表示する。
- Top-5分類結果表示
各フレームの分類結果上位5位までを確信度と共に表示する。
参考文献
[1] Woo, S., Debnath, S., Hu, R., Chen, X., Liu, Z., Kweon, I. S., & Xie, S. (2023). ConvNeXt V2: Co-designing and Scaling ConvNets with Masked Autoencoders. Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR), 16133-16142. https://arxiv.org/abs/2301.00808
ソースコード
"""
プログラム名: ConvNeXt V2による画像分類プログラム(ImageNet 1000クラス)
特徴技術名: ConvNeXt V2
出典: Woo, S., et al. (2023). ConvNeXt V2: Co-designing and Scaling ConvNets with Masked Autoencoders.
CVPR 2023. https://arxiv.org/abs/2301.00808
特徴機能: Global Response Normalization (GRN) - チャネル間の特徴競合を強化し、
Masked Autoencoder (MAE) による自己教師あり事前学習で表現力を向上
学習済みモデル: convnextv2_base.fcmae_ft_in22k_in1k(timm実装版、ImageNet-22k→1k事前学習済み、約89Mパラメータ)
URL: https://huggingface.co/timm/convnextv2_base.fcmae_ft_in22k_in1k
特徴技術および学習済モデルの利用制限: **CC BY-NC 4.0ライセンス(非商用利用のみ)。商用利用には制限があります。必ず利用者自身で利用制限を確認すること。**
方式設計:
関連利用技術:
- timm(PyTorch Image Models): 学習済みモデル提供
- OpenCV: 動画・カメラ入力とリアルタイム表示
- PIL/Pillow: 画像前処理と日本語フォント描画
- tkinter: ファイル選択UI
入力と出力: 入力: 動画(ユーザは「0:動画ファイル,1:カメラ,2:サンプル動画」のメニューで選択.0:動画ファイルの場合はtkinterでファイル選択.1の場合はOpenCVでカメラが開く.2の場合はhttps://raw.githubusercontent.com/opencv/opencv/master/samples/data/vtest.aviを使用)、出力: OpenCV画面でリアルタイム表示、各フレームごとにprint()による分類結果表示、プログラム終了時result.txtファイル保存
処理手順: 1.動画入力の取得・前処理(RGB変換、timm標準変換)→2.ConvNeXt V2モデルによる推論実行→3.Top-5分類結果の算出・日本語表示→4.リアルタイム画面描画・結果保存
前処理、後処理: 前処理:timm標準データ変換(正規化、リサイズ)によるImageNet互換形式変換、後処理:ソフトマックス確率変換、Top-k選択、日本語フォント描画
調整を必要とする設定値: MODEL_NAME(学習済みモデル選択)、TOP_K(表示する上位分類結果数、デフォルト5)、FONT_SIZE(表示サイズ)
将来方策: プログラム内でのモデル性能比較機能(複数ConvNeXt V2モデルの精度・速度測定)
その他の重要事項: Windows環境対応、DirectShowバックエンド使用(Windows環境時)、ImageNet-1000クラス全て分類可能
前準備:
pip install -U torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126
pip install timm opencv-python pillow
"""
import cv2
import numpy as np
import torch
import timm
import torch.nn.functional as F
import tkinter as tk
from tkinter import filedialog
from PIL import Image, ImageDraw, ImageFont
import urllib.request
import time
import sys
import io
from datetime import datetime
import threading
# Windows文字エンコーディング設定
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', line_buffering=True)
# GPU/CPU自動選択
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f'デバイス: {str(device)}')
# GPU使用時の最適化
if device.type == 'cuda':
torch.backends.cudnn.benchmark = True
# モデル情報の構造化
MODEL_INFO = {
'convnextv2_base.fcmae_ft_in22k_in1k': {
'name': 'ConvNeXt V2 Base',
'params': '89M',
'top1_acc': '86.8%',
'desc': 'デフォルト推奨モデル'
},
'convnextv2_tiny.fcmae_ft_in22k_in1k': {
'name': 'ConvNeXt V2 Tiny',
'params': '29M',
'top1_acc': '83.0%',
'desc': '軽量'
},
'convnextv2_nano.fcmae_ft_in22k_in1k': {
'name': 'ConvNeXt V2 Nano',
'params': '15M',
'top1_acc': '82.0%',
'desc': '超軽量'
},
'convnextv2_large.fcmae_ft_in22k_in1k_384': {
'name': 'ConvNeXt V2 Large',
'params': '198M',
'top1_acc': '87.3%',
'desc': '高精度'
}
}
# 調整可能な設定値
MODEL_NAME = 'convnextv2_base.fcmae_ft_in22k_in1k'
TOP_K = 5
FONT_PATH = 'C:/Windows/Fonts/meiryo.ttc'
FONT_SIZE = 18
WINDOW_NAME = "ImageNet 1000-Class Classification"
# ImageNet クラス名リスト
IMAGENET_CLASSES = [
'tench', 'goldfish', 'great white shark', 'tiger shark', 'hammerhead', 'electric ray', 'stingray', 'cock', 'hen', 'ostrich',
'brambling', 'goldfinch', 'house finch', 'junco', 'indigo bunting', 'robin', 'bulbul', 'jay', 'magpie', 'chickadee',
'water ouzel', 'kite', 'bald eagle', 'vulture', 'great grey owl', 'European fire salamander', 'common newt', 'eft', 'spotted salamander', 'axolotl',
'bullfrog', 'tree frog', 'tailed frog', 'loggerhead', 'leatherback turtle', 'mud turtle', 'terrapin', 'box turtle', 'banded gecko', 'common iguana',
'American chameleon', 'whiptail', 'agama', 'frilled lizard', 'alligator lizard', 'Gila monster', 'green lizard', 'African chameleon', 'Komodo dragon', 'African crocodile',
'American alligator', 'triceratops', 'thunder snake', 'ringneck snake', 'hognose snake', 'green snake', 'king snake', 'garter snake', 'water snake', 'vine snake',
'night snake', 'boa constrictor', 'rock python', 'Indian cobra', 'green mamba', 'sea snake', 'horned viper', 'diamondback', 'sidewinder', 'trilobite',
'harvestman', 'scorpion', 'black and gold garden spider', 'barn spider', 'garden spider', 'black widow', 'tarantula', 'wolf spider', 'tick', 'centipede',
'black grouse', 'ptarmigan', 'ruffed grouse', 'prairie chicken', 'peacock', 'quail', 'partridge', 'African grey', 'macaw', 'sulphur-crested cockatoo',
'lorikeet', 'coucal', 'bee eater', 'hornbill', 'hummingbird', 'jacamar', 'toucan', 'drake', 'red-breasted merganser', 'goose',
'black swan', 'tusker', 'echidna', 'platypus', 'wallaby', 'koala', 'wombat', 'jellyfish', 'sea anemone', 'brain coral',
'flatworm', 'nematode', 'conch', 'snail', 'slug', 'sea slug', 'chiton', 'chambered nautilus', 'Dungeness crab', 'rock crab',
'fiddler crab', 'king crab', 'American lobster', 'spiny lobster', 'crayfish', 'hermit crab', 'isopod', 'white stork', 'black stork', 'spoonbill',
'flamingo', 'little blue heron', 'American egret', 'bittern', 'crane', 'limpkin', 'European gallinule', 'American coot', 'bustard', 'ruddy turnstone',
'red-backed sandpiper', 'redshank', 'dowitcher', 'oystercatcher', 'pelican', 'king penguin', 'albatross', 'grey whale', 'killer whale', 'dugong',
'sea lion', 'Chihuahua', 'Japanese spaniel', 'Maltese dog', 'Pekinese', 'Shih-Tzu', 'Blenheim spaniel', 'papillon', 'toy terrier', 'Rhodesian ridgeback',
'Afghan hound', 'basset', 'beagle', 'bloodhound', 'bluetick', 'black-and-tan coonhound', 'Walker hound', 'English foxhound', 'redbone', 'borzoi',
'Irish wolfhound', 'Italian greyhound', 'whippet', 'Ibizan hound', 'Norwegian elkhound', 'otterhound', 'Saluki', 'Scottish deerhound', 'Weimaraner', 'Staffordshire bullterrier',
'American Staffordshire terrier', 'Bedlington terrier', 'Border terrier', 'Kerry blue terrier', 'Irish terrier', 'Norfolk terrier', 'Norwich terrier', 'Yorkshire terrier', 'wire-haired fox terrier', 'Lakeland terrier',
'Sealyham terrier', 'Airedale', 'cairn', 'Australian terrier', 'Dandie Dinmont', 'Boston bull', 'miniature schnauzer', 'giant schnauzer', 'standard schnauzer', 'Scotch terrier',
'Tibetan terrier', 'silky terrier', 'soft-coated wheaten terrier', 'West Highland white terrier', 'Lhasa', 'flat-coated retriever', 'curly-coated retriever', 'golden retriever', 'Labrador retriever', 'Chesapeake Bay retriever',
'German short-haired pointer', 'vizsla', 'English setter', 'Irish setter', 'Gordon setter', 'Brittany spaniel', 'clumber', 'English springer', 'Welsh springer spaniel', 'cocker spaniel',
'Sussex spaniel', 'Irish water spaniel', 'kuvasz', 'schipperke', 'groenendael', 'malinois', 'briard', 'kelpie', 'komondor', 'Old English sheepdog',
'Shetland sheepdog', 'collie', 'Border collie', 'Bouvier des Flandres', 'Rottweiler', 'German shepherd', 'Doberman', 'miniature pinscher', 'Greater Swiss Mountain dog', 'Bernese mountain dog',
'Appenzeller', 'EntleBucher', 'boxer', 'bull mastiff', 'Tibetan mastiff', 'French bulldog', 'Great Dane', 'Saint Bernard', 'Eskimo dog', 'malamute',
'Siberian husky', 'dalmatian', 'affenpinscher', 'basenji', 'pug', 'Leonberg', 'Newfoundland', 'Great Pyrenees', 'Samoyed', 'Pomeranian',
'chow', 'keeshond', 'Brabancon griffon', 'Pembroke', 'Cardigan', 'toy poodle', 'miniature poodle', 'standard poodle', 'Mexican hairless', 'timber wolf',
'white wolf', 'red wolf', 'coyote', 'dingo', 'dhole', 'African hunting dog', 'hyena', 'red fox', 'kit fox', 'Arctic fox',
'grey fox', 'tabby', 'tiger cat', 'Persian cat', 'Siamese cat', 'Egyptian cat', 'cougar', 'lynx', 'leopard', 'snow leopard',
'jaguar', 'lion', 'tiger', 'cheetah', 'brown bear', 'American black bear', 'ice bear', 'sloth bear', 'mongoose', 'meerkat',
'tiger beetle', 'ladybug', 'ground beetle', 'long-horned beetle', 'leaf beetle', 'dung beetle', 'rhinoceros beetle', 'weevil', 'fly', 'bee',
'ant', 'grasshopper', 'cricket', 'walking stick', 'cockroach', 'mantis', 'cicada', 'leafhopper', 'lacewing', 'dragonfly',
'damselfly', 'admiral', 'ringlet', 'monarch', 'cabbage butterfly', 'sulphur butterfly', 'lycaenid', 'starfish', 'sea urchin', 'sea cucumber',
'wood rabbit', 'hare', 'Angora', 'hamster', 'porcupine', 'fox squirrel', 'marmot', 'beaver', 'guinea pig', 'sorrel',
'zebra', 'hog', 'wild boar', 'warthog', 'hippopotamus', 'ox', 'water buffalo', 'bison', 'ram', 'bighorn',
'ibex', 'hartebeest', 'impala', 'gazelle', 'Arabian camel', 'llama', 'weasel', 'mink', 'polecat', 'black-footed ferret',
'otter', 'skunk', 'badger', 'armadillo', 'three-toed sloth', 'orangutan', 'gorilla', 'chimpanzee', 'gibbon', 'siamang',
'guenon', 'patas', 'baboon', 'macaque', 'langur', 'colobus', 'proboscis monkey', 'marmoset', 'capuchin', 'howler monkey',
'titi', 'spider monkey', 'squirrel monkey', 'Madagascar cat', 'indri', 'Indian elephant', 'African elephant', 'lesser panda', 'giant panda', 'barracouta',
'eel', 'coho', 'rock beauty', 'anemone fish', 'sturgeon', 'gar', 'lionfish', 'puffer', 'abacus', 'abaya',
'academic gown', 'accordion', 'acoustic guitar', 'aircraft carrier', 'airliner', 'airship', 'altar', 'ambulance', 'amphibian', 'analog clock',
'apiary', 'apron', 'ashcan', 'assault rifle', 'backpack', 'bakery', 'balance beam', 'balloon', 'ballpoint', 'Band Aid',
'banjo', 'bannister', 'barbell', 'barber chair', 'barbershop', 'barn', 'barometer', 'barrel', 'barrow', 'baseball',
'basketball', 'bassinet', 'bassoon', 'bathing cap', 'bath towel', 'bathtub', 'beach wagon', 'beacon', 'beaker', 'bearskin',
'beer bottle', 'beer glass', 'bell cote', 'bib', 'bicycle-built-for-two', 'bikini', 'binder', 'binoculars', 'birdhouse', 'boathouse',
'bobsled', 'bolo tie', 'bonnet', 'bookcase', 'bookshop', 'bottlecap', 'bow', 'bow tie', 'brass', 'brassiere',
'breakwater', 'breastplate', 'broom', 'bucket', 'buckle', 'bulletproof vest', 'bullet train', 'butcher shop', 'cab', 'caldron',
'candle', 'cannon', 'canoe', 'can opener', 'cardigan', 'car mirror', 'carousel', 'carpenter\'s kit', 'carton', 'car wheel',
'cash machine', 'cassette', 'cassette player', 'castle', 'catamaran', 'CD player', 'cello', 'cellular telephone', 'chain', 'chainlink fence',
'chain mail', 'chain saw', 'chest', 'chiffonier', 'chime', 'china cabinet', 'Christmas stocking', 'church', 'cinema', 'cleaver',
'cliff dwelling', 'cloak', 'clog', 'cocktail shaker', 'coffee mug', 'coffeepot', 'coil', 'combination lock', 'computer keyboard', 'confectionery',
'container ship', 'convertible', 'corkscrew', 'cornet', 'cowboy boot', 'cowboy hat', 'cradle', 'crane', 'crash helmet', 'crate',
'crib', 'Crock Pot', 'croquet ball', 'crutch', 'cuirass', 'dam', 'desk', 'desktop computer', 'dial telephone', 'diaper',
'digital clock', 'digital watch', 'dining table', 'dishrag', 'dishwasher', 'disk brake', 'dock', 'dogsled', 'dome', 'doormat',
'drilling platform', 'drum', 'drumstick', 'dumbbell', 'Dutch oven', 'electric fan', 'electric guitar', 'electric locomotive', 'entertainment center', 'envelope',
'espresso maker', 'face powder', 'feather boa', 'file', 'fireboat', 'fire engine', 'fire screen', 'flagpole', 'flute', 'folding chair',
'football helmet', 'forklift', 'fountain', 'fountain pen', 'four-poster', 'freight car', 'French horn', 'frying pan', 'fur coat', 'garbage truck',
'gasmask', 'gas pump', 'goblet', 'go-kart', 'golf ball', 'golfcart', 'gondola', 'gong', 'gown', 'grand piano',
'greenhouse', 'grille', 'grocery store', 'guillotine', 'hair slide', 'hair spray', 'half track', 'hammer', 'hamper', 'hand blower',
'hand-held computer', 'handkerchief', 'hard disc', 'harmonica', 'harp', 'harvester', 'hatchet', 'holster', 'home theater', 'honeycomb',
'hook', 'hoopskirt', 'horizontal bar', 'horse cart', 'hourglass', 'iPod', 'iron', 'jack-o\'-lantern', 'jean', 'jeep',
'jersey', 'jigsaw puzzle', 'jinrikisha', 'joystick', 'kimono', 'knee pad', 'knot', 'lab coat', 'ladle', 'lampshade',
'laptop', 'lawn mower', 'lens cap', 'letter opener', 'library', 'lifeboat', 'lighter', 'limousine', 'liner', 'lipstick',
'Loafer', 'lotion', 'loudspeaker', 'loupe', 'lumbermill', 'magnetic compass', 'mailbag', 'mailbox', 'maillot', 'maillot (tank suit)',
'manhole cover', 'maraca', 'marimba', 'mask', 'matchstick', 'maypole', 'maze', 'measuring cup', 'medicine chest', 'megalith',
'microphone', 'microwave', 'military uniform', 'milk can', 'minibus', 'miniskirt', 'minivan', 'missile', 'mitten', 'mixing bowl',
'mobile home', 'Model T', 'modem', 'monastery', 'monitor', 'moped', 'mortar', 'mortarboard', 'mosque', 'mosquito net',
'motor scooter', 'mountain bike', 'mountain tent', 'mouse', 'mousetrap', 'moving van', 'muzzle', 'nail', 'neck brace', 'necklace',
'nipple', 'notebook', 'obelisk', 'oboe', 'ocarina', 'odometer', 'oil filter', 'organ', 'oscilloscope', 'overskirt',
'oxcart', 'oxygen mask', 'packet', 'paddle', 'paddlewheel', 'padlock', 'paintbrush', 'pajama', 'palace', 'panpipe',
'paper towel', 'parachute', 'parallel bars', 'park bench', 'parking meter', 'passenger car', 'patio', 'pay-phone', 'pedestal', 'pencil box',
'pencil sharpener', 'perfume', 'Petri dish', 'photocopier', 'pick', 'pickelhaube', 'picket fence', 'pickup', 'pier', 'piggy bank',
'pill bottle', 'pillow', 'ping-pong ball', 'pinwheel', 'pirate', 'pitcher', 'plane', 'planetarium', 'plastic bag', 'plate rack',
'plow', 'plunger', 'Polaroid camera', 'pole', 'police van', 'poncho', 'pool table', 'pop bottle', 'pot', 'potter\'s wheel',
'power drill', 'prayer rug', 'printer', 'prison', 'projectile', 'projector', 'puck', 'punching bag', 'purse', 'quill',
'quilt', 'racer', 'racket', 'radiator', 'radio', 'radio telescope', 'rain barrel', 'recreational vehicle', 'reel', 'reflex camera',
'refrigerator', 'remote control', 'restaurant', 'revolver', 'rifle', 'rocking chair', 'rotisserie', 'rubber eraser', 'rugby ball', 'rule',
'running shoe', 'safe', 'safety pin', 'saltshaker', 'sandal', 'sarong', 'sax', 'scabbard', 'scale', 'school bus',
'schooner', 'scoreboard', 'screen', 'screw', 'screwdriver', 'seat belt', 'sewing machine', 'shield', 'shoe shop', 'shoji',
'shopping basket', 'shopping cart', 'shovel', 'shower cap', 'shower curtain', 'ski', 'ski mask', 'sleeping bag', 'slide rule', 'sliding door',
'slot', 'snorkel', 'snowmobile', 'snowplow', 'soap dispenser', 'soccer ball', 'sock', 'solar dish', 'sombrero', 'soup bowl',
'space bar', 'space heater', 'space shuttle', 'spatula', 'speedboat', 'spider web', 'spindle', 'sports car', 'spotlight', 'stage',
'steam locomotive', 'steel arch bridge', 'steel drum', 'stethoscope', 'stole', 'stone wall', 'stopwatch', 'stove', 'strainer', 'streetcar',
'stretcher', 'studio couch', 'stupa', 'submarine', 'suit', 'sundial', 'sunglass', 'sunglasses', 'sunscreen', 'suspension bridge',
'swab', 'sweatshirt', 'swimming trunks', 'swing', 'switch', 'syringe', 'table lamp', 'tank', 'tape player', 'teapot',
'teddy', 'television', 'tennis ball', 'thatch', 'theater curtain', 'thimble', 'thresher', 'throne', 'tile roof', 'toaster',
'tobacco shop', 'toilet seat', 'torch', 'totem pole', 'tow truck', 'toyshop', 'tractor', 'trailer truck', 'tray', 'trench coat',
'tricycle', 'trimaran', 'tripod', 'triumphal arch', 'trolleybus', 'trombone', 'tub', 'turnstile', 'typewriter keyboard', 'umbrella',
'unicycle', 'upright', 'vacuum', 'vase', 'vault', 'velvet', 'vending machine', 'vestment', 'viaduct', 'violin',
'volleyball', 'waffle iron', 'wall clock', 'wallet', 'wardrobe', 'warplane', 'washbasin', 'washer', 'water bottle', 'water jug',
'water tower', 'whiskey jug', 'whistle', 'wig', 'window screen', 'window shade', 'Windsor tie', 'wine bottle', 'wing', 'wok',
'wooden spoon', 'wool', 'worm fence', 'wreck', 'yawl', 'yurt', 'web site', 'comic book', 'crossword puzzle', 'street sign',
'traffic light', 'book jacket', 'menu', 'plate', 'guacamole', 'consomme', 'hot pot', 'trifle', 'ice cream', 'ice lolly',
'French loaf', 'bagel', 'pretzel', 'cheeseburger', 'hotdog', 'mashed potato', 'head cabbage', 'broccoli', 'cauliflower', 'zucchini',
'spaghetti squash', 'acorn squash', 'butternut squash', 'cucumber', 'artichoke', 'bell pepper', 'cardoon', 'mushroom', 'Granny Smith', 'strawberry',
'orange', 'lemon', 'fig', 'pineapple', 'banana', 'jackfruit', 'custard apple', 'pomegranate', 'hay', 'carbonara',
'chocolate sauce', 'dough', 'meat loaf', 'pizza', 'potpie', 'burrito', 'red wine', 'espresso', 'cup', 'eggnog',
'alp', 'bubble', 'cliff', 'coral reef', 'geyser', 'lakeside', 'promontory', 'sandbar', 'seashore', 'valley',
'volcano', 'ballplayer', 'groom', 'scuba diver', 'rapeseed', 'daisy', 'yellow lady\'s slipper', 'corn', 'acorn', 'hip',
'buckeye', 'coral fungus', 'agaric', 'gyromitra', 'stinkhorn', 'earthstar', 'hen-of-the-woods', 'bolete', 'ear', 'toilet tissue'
]
# BGR→RGB色変換のヘルパー関数
def bgr_to_rgb(color_bgr):
"""BGRカラーをRGBカラーに変換"""
return (color_bgr[2], color_bgr[1], color_bgr[0])
# クラスごとの色生成(HSVからBGRに変換)
def generate_class_colors(num_classes):
colors = []
for i in range(num_classes):
hue = int(180.0 * i / num_classes)
hsv = np.uint8([[[hue, 255, 255]]])
bgr = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)[0][0]
colors.append((int(bgr[0]), int(bgr[1]), int(bgr[2])))
return colors
# 1000クラス分の色を生成
CLASS_COLORS = generate_class_colors(1000)
# 日本語フォント設定
font_main = ImageFont.truetype(FONT_PATH, FONT_SIZE)
# グローバル変数
frame_count = 0
results_log = []
class_counts = {}
model = None
transforms = None
class ThreadedVideoCapture:
"""スレッド化されたVideoCapture(常に最新フレームを取得)"""
def __init__(self, src, is_camera=False):
if is_camera:
self.cap = cv2.VideoCapture(src, cv2.CAP_DSHOW)
fourcc = cv2.VideoWriter_fourcc('M', 'J', 'P', 'G')
self.cap.set(cv2.CAP_PROP_FOURCC, fourcc)
self.cap.set(cv2.CAP_PROP_FPS, 60)
else:
self.cap = cv2.VideoCapture(src)
self.grabbed, self.frame = self.cap.read()
self.stopped = False
self.lock = threading.Lock()
self.thread = threading.Thread(target=self.update, args=())
self.thread.daemon = True
self.thread.start()
def update(self):
"""バックグラウンドでフレームを取得し続ける"""
while not self.stopped:
grabbed, frame = self.cap.read()
with self.lock:
self.grabbed = grabbed
if grabbed:
self.frame = frame
def read(self):
"""最新フレームを返す"""
with self.lock:
return self.grabbed, self.frame.copy() if self.grabbed else None
def isOpened(self):
return self.cap.isOpened()
def get(self, prop):
return self.cap.get(prop)
def release(self):
self.stopped = True
self.thread.join()
self.cap.release()
def display_program_header():
print('=' * 60)
print('=== ConvNeXt V2画像分類プログラム ===')
print('=' * 60)
print('概要: ImageNet 1000クラス分類をリアルタイムで実行')
print('機能: ConvNeXt V2による画像分類(ImageNet 1000クラス)')
print('技術: Global Response Normalization (GRN), Masked Autoencoder事前学習')
print('操作: qキーで終了')
print('出力: 各フレームごとに処理結果を表示し、終了時にresult.txtへ保存')
print()
def get_confidence_color(prob):
"""確信度に応じた色を返す"""
if prob >= 0.7:
return (0, 255, 0)
elif prob >= 0.5:
return (0, 255, 255)
elif prob >= 0.3:
return (0, 165, 255)
else:
return (0, 0, 255)
def draw_classification_results(frame, classifications):
"""画像分類の描画処理"""
texts_to_draw = []
texts_to_draw.append({
'text': f'画像分類結果 (上位{TOP_K}位):',
'org': (10, 30),
'color': (255, 255, 255),
'font_type': 'main'
})
for i, cls in enumerate(classifications):
color = get_confidence_color(cls['conf'])
result_text = f'{i+1}位: {cls["name"]} ({cls["conf"]:.3f})'
texts_to_draw.append({
'text': result_text,
'org': (10, 60 + i * 25),
'color': bgr_to_rgb(color),
'font_type': 'main'
})
frame = draw_texts_with_pillow(frame, texts_to_draw)
info_text = f"Classes: {len(classifications)} | Frame: {frame_count}"
cv2.putText(frame, info_text, (10, frame.shape[0] - 20),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
return frame
def format_classification_output(classifications):
"""画像分類の出力フォーマット"""
if len(classifications) == 0:
return 'count=0'
else:
parts = []
for cls in classifications:
class_name = cls['name']
conf = cls['conf']
parts.append(f'class={class_name},conf={conf:.3f}')
return f'count={len(classifications)}; ' + ' | '.join(parts)
def draw_texts_with_pillow(bgr_frame, texts):
"""テキスト描画, texts: list of dict with keys {text, org, color, font_type}"""
img_pil = Image.fromarray(cv2.cvtColor(bgr_frame, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(img_pil)
for item in texts:
text = item['text']
x, y = item['org']
color = item['color']
draw.text((x, y), text, font=font_main, fill=color)
return cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2BGR)
def classify_image(frame):
"""共通の分類処理(前処理、推論、分類を実行)"""
global model, transforms
pil_image = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
input_tensor = transforms(pil_image).unsqueeze(0).to(device)
with torch.no_grad():
outputs = model(input_tensor)
probabilities = F.softmax(outputs, dim=1)
topk_prob, topk_indices = torch.topk(probabilities, TOP_K)
topk_prob = topk_prob.cpu().numpy()[0]
topk_indices = topk_indices.cpu().numpy()[0]
curr_classifications = []
for i, (class_index, confidence) in enumerate(zip(topk_indices, topk_prob)):
if class_index < len(IMAGENET_CLASSES):
name = IMAGENET_CLASSES[class_index]
curr_classifications.append({
'conf': confidence,
'class': class_index,
'name': name
})
return curr_classifications
def process_video_frame(frame, timestamp_ms, is_camera):
"""動画用ラッパー"""
classifications = classify_image(frame)
global class_counts
for cls in classifications:
name = cls['name']
if name not in class_counts:
class_counts[name] = 0
class_counts[name] += 1
frame = draw_classification_results(frame, classifications)
result = format_classification_output(classifications)
return frame, result
def video_frame_processing(frame, timestamp_ms, is_camera):
"""動画フレーム処理(標準形式)"""
global frame_count
current_time = time.time()
frame_count += 1
processed_frame, result = process_video_frame(frame, timestamp_ms, is_camera)
return processed_frame, result, current_time
# プログラムヘッダー表示
display_program_header()
# モデル選択(対話的実装)
print("\n=== ConvNeXt V2モデル選択 ===")
print('使用するConvNeXt V2モデルを選択してください:')
model_list = list(MODEL_INFO.keys())
for i, key in enumerate(model_list, 1):
info = MODEL_INFO[key]
print(f'{i}: {info["name"]} ({info["params"]} params, Top-1 {info["top1_acc"]}) - {info["desc"]}')
print()
model_choice = ''
while model_choice not in ['1', '2', '3', '4']:
model_choice = input("選択 (1/2/3/4) [デフォルト: 1]: ").strip()
if model_choice == '':
model_choice = '1'
break
if model_choice not in ['1', '2', '3', '4']:
print("無効な選択です。もう一度入力してください。")
selected_model = model_list[int(model_choice) - 1]
print(f"\nConvNeXt V2モデルをロード中...")
try:
model = timm.create_model(selected_model, pretrained=True)
model = model.to(device)
model.eval()
data_config = timm.data.resolve_model_data_config(model)
transforms = timm.data.create_transform(**data_config, is_training=False)
print(f"\n分類可能なクラス数: {len(IMAGENET_CLASSES)}")
print(f"モデル情報: {MODEL_INFO[selected_model]['name']} ({MODEL_INFO[selected_model]['params']} params, Top-1 {MODEL_INFO[selected_model]['top1_acc']})")
print("モデルのロード完了")
except Exception as e:
print(f"モデルのロードに失敗しました: {e}")
raise SystemExit(1)
# 入力選択
print("\n=== ConvNeXt V2リアルタイム画像分類(ImageNet 1000クラス) ===")
print("0: 動画ファイル")
print("1: カメラ")
print("2: サンプル動画")
choice = input("選択: ")
is_camera = (choice == '1')
if choice == '0':
root = tk.Tk()
root.withdraw()
path = filedialog.askopenfilename()
if not path:
raise SystemExit(1)
cap = cv2.VideoCapture(path)
elif choice == '1':
cap = ThreadedVideoCapture(0, is_camera=True)
else:
SAMPLE_URL = 'https://raw.githubusercontent.com/opencv/opencv/master/samples/data/vtest.avi'
SAMPLE_FILE = 'vtest.avi'
print('サンプル動画をダウンロード中...')
urllib.request.urlretrieve(SAMPLE_URL, SAMPLE_FILE)
cap = cv2.VideoCapture(SAMPLE_FILE)
if not cap.isOpened():
print('動画ファイル・カメラを開けませんでした')
raise SystemExit(1)
# フレームレートの取得とタイムスタンプ増分の計算
if is_camera:
actual_fps = cap.get(cv2.CAP_PROP_FPS)
print(f'カメラのfps: {actual_fps}')
timestamp_increment = int(1000 / actual_fps) if actual_fps > 0 else 33
else:
video_fps = cap.get(cv2.CAP_PROP_FPS)
timestamp_increment = int(1000 / video_fps) if video_fps > 0 else 33
# メイン処理
print('\n=== 動画処理開始 ===')
print('操作方法:')
print(' q キー: プログラム終了')
start_time = time.time()
last_info_time = start_time
info_interval = 10.0
timestamp_ms = 0
total_processing_time = 0.0
try:
while True:
ret, frame = cap.read()
if not ret:
break
timestamp_ms += timestamp_increment
processing_start = time.time()
processed_frame, result, current_time = video_frame_processing(frame, timestamp_ms, is_camera)
processing_time = time.time() - processing_start
total_processing_time += processing_time
cv2.imshow(WINDOW_NAME, processed_frame)
if result:
if is_camera:
timestamp = datetime.fromtimestamp(current_time).strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
print(f'{timestamp}, {result}')
else:
print(f'Frame {frame_count}: {result}')
results_log.append(result)
if is_camera:
elapsed = current_time - last_info_time
if elapsed >= info_interval:
total_elapsed = current_time - start_time
actual_fps = frame_count / total_elapsed if total_elapsed > 0 else 0
avg_processing_time = (total_processing_time / frame_count * 1000) if frame_count > 0 else 0
print(f'[情報] 経過時間: {total_elapsed:.1f}秒, 処理フレーム数: {frame_count}, 実測fps: {actual_fps:.1f}, 平均処理時間: {avg_processing_time:.1f}ms')
last_info_time = current_time
if cv2.waitKey(1) & 0xFF == ord('q'):
break
finally:
print('\n=== プログラム終了 ===')
cap.release()
cv2.destroyAllWindows()
if results_log:
with open('result.txt', 'w', encoding='utf-8') as f:
f.write('=== ConvNeXt V2画像分類結果 ===\n')
f.write(f'処理フレーム数: {frame_count}\n')
f.write(f'使用モデル: {selected_model}\n')
f.write(f'モデル情報: {MODEL_INFO[selected_model]["name"]} ({MODEL_INFO[selected_model]["params"]} params, Top-1 {MODEL_INFO[selected_model]["top1_acc"]})\n')
f.write(f'使用デバイス: {str(device).upper()}\n')
if device.type == 'cuda':
f.write(f'GPU: {torch.cuda.get_device_name(0)}\n')
f.write(f'\n分類されたクラス一覧:\n')
for class_name, count in sorted(class_counts.items()):
f.write(f' {class_name}: {count}回\n')
f.write('\n')
f.write('\n'.join(results_log))
print(f'\n処理結果をresult.txtに保存しました')
print(f'分類されたクラス数: {len(class_counts)}')
実験・研究スキルの基礎:パソコンで学ぶ画像分類実験
1. 実験・研究のスキル構成要素
実験や研究を行うには、以下の5つの構成要素を理解する必要がある。
1.1 実験用データ
このプログラムでは動画ファイルまたはカメラ映像が実験用データである。
1.2 実験計画
何を明らかにするために実験を行うのかを定める。
計画例:
- モデルサイズが分類精度に与える影響を確認する
- モデルサイズが処理速度に与える影響を確認する
- Top-5分類結果における確信度の分布を分析する
- 第1位と第2位の確信度差から判定の確実性を評価する
- 誤分類が発生しやすい物体の傾向を見つける
1.3 プログラム
実験を実施するためのツールである。このプログラムはConvNeXt V2モデルとImageNet 1000クラス分類を使用している。
- プログラムの機能を理解して活用することが基本である
- 基本となるプログラムを出発点として、将来、様々な機能を自分で追加することができる
1.4 プログラムの機能
このプログラムはモデル選択により画像分類の精度と速度を制御する。
入力パラメータ:
- モデル選択:Nano(15M)、Tiny(29M)、Base(89M)、Large(198M)の4種類から選択
- 入力源選択:動画ファイル、カメラ、サンプル動画
出力情報:
- リアルタイム映像表示(画面左上に分類結果を重畳表示)
- Top-5分類結果(最も可能性の高い上位5クラスを確信度付きで表示)
- 確信度による色分け表示(緑:0.7以上、黄:0.5以上、オレンジ:0.3以上、赤:0.3未満)
- 処理統計情報(フレーム数、検出クラス数)
モデルの特性:
- モデルサイズが大きいほど精度が向上するが処理時間が増加する
- Nano:超軽量で高速だが精度は低い
- Base:精度と速度のバランスが良い
- Large:最高精度だが処理時間が長い
1.5 検証(結果の確認と考察)
プログラムの実行結果を観察し、モデルの影響を考察する。
基本認識:
- モデルを変えると結果が変わる。その変化を観察することが実験である
- 「良い結果」「悪い結果」は目的によって異なる
観察のポイント:
- 第1位の分類結果は正しいか
- Top-5に正解クラスが含まれているか
- 第1位と第2位の確信度差はどの程度か
- 確信度が低い場合(0.3未満)は何が原因か
- 処理速度(フレームレート)はどの程度か
- モデルサイズを変えると結果はどう変化するか
2. 間違いの原因と対処方法
2.1 プログラムのミス(人為的エラー)
プログラムがエラーで停止する
- 原因:構文エラー、必要なライブラリがインストールされていない
- 対処方法:エラーメッセージを確認し、提供されたコードと比較する
モデルのロードに失敗する
- 原因:ネットワーク接続の問題、または選択したモデル名が誤っている
- 対処方法:インターネット接続を確認し、モデル名が正しいか確認する
映像が表示されない
- 原因:動画ファイルの読み込みに失敗している、またはカメラが利用できない
- 対処方法:ファイルパスを確認する。カメラ使用時はアクセス許可を確認する
モデルのダウンロードに時間がかかる
- 原因:初回実行時にConvNeXt V2モデルをダウンロードしている
- 対処方法:これは正常な動作である。ダウンロードが完了するまで待つ
2.2 期待と異なる結果が出る場合
明らかに正しいはずの物体が誤分類される
- 原因:画像が不鮮明、物体が小さい、照明条件が悪い、または物体の向きが通常と異なる
- 対処方法:これはモデルの限界を理解する機会である。どのような条件で誤分類が発生するか記録する
確信度が全体的に低い(0.5未満)
- 原因:入力画像がImageNetの学習データと大きく異なる、または複数の物体が混在している
- 対処方法:これは正常な動作である。モデルが判断に迷っている状況を理解する機会である
Top-5の中に似たクラスが複数含まれる
- 原因:ImageNetには似た概念のクラスが多数存在する(犬の品種など)
- 対処方法:これは正常な動作である。モデルが類似クラスを区別している様子を観察する
処理速度が遅い
- 原因:Largeモデルを使用している、またはGPUが利用できない
- 対処方法:GPUが利用可能か確認する。速度を優先する場合はNanoまたはTinyモデルを選択する
3. 実験レポートのサンプル
モデルサイズと分類精度の関係分析
実験目的:
テスト映像内の物体に対して、モデルサイズが分類精度と処理速度に与える影響を定量的に評価する。
実験計画:
同一の動画ファイルに対して4種類のモデル(Nano、Tiny、Base、Large)をそれぞれ実行し、分類結果と処理時間を比較する。
実験方法:
プログラムを実行し、各モデルで以下の項目を記録する:
- 第1位の分類結果が正解である割合(正解率)
- Top-5内に正解が含まれる割合(Top-5正解率)
- 第1位の平均確信度
- フレームあたりの平均処理時間
実験結果:
| モデル | パラメータ数 | 正解率 | Top-5正解率 | 平均確信度 | 平均処理時間 | 総合評価 |
|---|---|---|---|---|---|---|
| Nano | 15M | xx% | xx% | x.xx | xxms | x |
| Tiny | 29M | xx% | xx% | x.xx | xxms | x |
| Base | 89M | xx% | xx% | x.xx | xxms | x |
| Large | 198M | xx% | xx% | x.xx | xxms | x |
考察:
- (例文)Nanoモデルは処理速度が最も速いが、正解率がxx%と低く、確信度も低い傾向が見られた。類似した物体の区別が困難であった
- (例文)Baseモデルは正解率xx%、Top-5正解率xx%を達成し、精度と速度のバランスが良好であった。平均確信度もx.xxと比較的高く、判定の信頼性が高い
- (例文)Largeモデルは最高精度を示したが、処理時間がBaseモデルのx倍であり、リアルタイム処理には向かない可能性がある
- (例文)モデルサイズを大きくするほど精度は向上するが、処理時間も増加するというトレードオフの関係が明確に確認できた
結論:
(例文)本実験では、Baseモデルが精度と速度の両面で最もバランスの取れた選択であることが確認できた。リアルタイム処理を重視する場合はTinyモデル、精度を最優先する場合はLargeモデルが適切である。用途に応じてモデルを選択する必要性が実証された。