環境設定
import torch
import torchvision
from torchvision import transforms, datasets
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import DataLoader, Dataset
import os
import PIL
import pathlib
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# デバイス設定
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"使用デバイス: {device}")
データ準備
1. データディレクトリの設定
# データパスの設定
data_dir = './license_plate_data/'
data_dir = pathlib.Path(data_dir)
# クラス名の取得
data_paths = list(data_dir.glob('*'))
class_names = [str(path).split(os.sep)[-1].split("_")[1].split(".")[0] for path in data_paths]
print(f"クラス名: {class_names}")
2. データ可視化
# 日本語フォント設定
plt.rcParams['font.sans-serif'] = ['Yu Gothic'] # 日本語表示用
plt.rcParams['axes.unicode_minus'] = False # 負号表示用
# データサンプルの表示
plt.figure(figsize=(14, 5))
plt.suptitle("ナンバープレートデータサンプル", fontsize=15)
data_paths_str = [str(path) for path in data_paths]
for i in range(18):
plt.subplot(3, 6, i+1)
images = plt.imread(data_paths_str[i])
plt.imshow(images)
plt.axis('off')
plt.tight_layout()
plt.show()
3. ラベルの数値化
4. カスタムデータセットの作成
class PlateDataset(Dataset):
def __init__(self, labels, paths, transform=None):
self.labels = labels
self.paths = paths
self.transform = transform
def __len__(self):
return len(self.labels)
def __getitem__(self, idx):
image = Image.open(self.paths[idx]).convert('RGB')
label = self.labels[idx]
if self.transform:
image = self.transform(image)
return image, label
# 前処理パイプライン
transform_pipeline = transforms.Compose([
transforms.Resize([224, 224]),
transforms.ToTensor(),
transforms.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]
)
])
# データセットの作成
full_dataset = PlateDataset(all_labels, data_paths_str, transform_pipeline)
5. データ分割
# 訓練データとテストデータに分割
train_size = int(0.8 * len(full_dataset))
test_size = len(full_dataset) - train_size
train_dataset, test_dataset = torch.utils.data.random_split(full_dataset, [train_size, test_size])
# データローダーの作成
batch_size = 16
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=True)
print(f"訓練データ数: {len(train_dataset)}")
print(f"テストデータ数: {len(test_dataset)}")
print(f"バッチ数(訓練): {len(train_loader)}")
print(f"バッチ数(テスト): {len(test_loader)}")
# データ形状の確認
for images, labels in test_loader:
print(f"画像形状: {images.shape}")
print(f"ラベル形状: {labels.shape}")
break
モデル構築
カスタムCNNモデル
class PlateRecognitionNet(nn.Module):
def __init__(self):
super(PlateRecognitionNet, self).__init__()
# 畳み込み層
self.conv1 = nn.Conv2d(3, 16, kernel_size=5, stride=1, padding=0)
self.bn1 = nn.BatchNorm2d(16)
self.conv2 = nn.Conv2d(16, 16, kernel_size=5, stride=1, padding=0)
self.bn2 = nn.BatchNorm2d(16)
self.pool = nn.MaxPool2d(2, 2)
self.conv3 = nn.Conv2d(16, 32, kernel_size=5, stride=1, padding=0)
self.bn3 = nn.BatchNorm2d(32)
self.conv4 = nn.Conv2d(32, 32, kernel_size=5, stride=1, padding=0)
self.bn4 = nn.BatchNorm2d(32)
# 全結合層
self.fc1 = nn.Linear(32 * 50 * 50, label_length * char_set_size)
self.reshape_layer = ReshapeLayer([label_length, char_set_size])
def forward(self, x):
x = F.relu(self.bn1(self.conv1(x)))
x = F.relu(self.bn2(self.conv2(x)))
x = self.pool(x)
x = F.relu(self.bn3(self.conv3(x)))
x = F.relu(self.bn4(self.conv4(x)))
x = self.pool(x)
x = x.view(-1, 32 * 50 * 50)
x = self.fc1(x)
x = self.reshape_layer(x)
return x
# リシェイプ層の定義
class ReshapeLayer(nn.Module):
def __init__(self, shape):
super(ReshapeLayer, self).__init__()
self.shape = shape
def forward(self, x):
return x.view(x.size(0), *self.shape)
# モデルの初期化
model = PlateRecognitionNet().to(device)
print(f"モデルを{device}に配置しました")
# モデルのサマリー表示
from torchsummary import summary
summary(model, (3, 224, 224))
モデル学習
1. 損失関数と最適化手法
# 損失関数と最適化手法
optimizer = torch.optim.Adam(model.parameters(), lr=0.0001, weight_decay=0.0001)
criterion = nn.CrossEntropyLoss()
# 評価関数
def evaluate_model(model, data_loader, criterion):
model.eval()
total_loss = 0.0
num_batches = len(data_loader)
with torch.no_grad():
for images, labels in data_loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
loss = criterion(outputs, labels)
total_loss += loss.item()
avg_loss = total_loss / num_batches
print(f"平均損失: {avg_loss:.6f}")
return avg_loss
# 訓練関数
def train_model(model, train_loader, criterion, optimizer):
model.train()
for batch_idx, (images, labels) in enumerate(train_loader):
images, labels = images.to(device), labels.to(device)
# 勾配の初期化
optimizer.zero_grad()
# 順伝播
outputs = model(images)
loss = criterion(outputs, labels)
# 逆伝播
loss.backward()
optimizer.step()
# 1000バッチごとに損失表示
if batch_idx % 1000 == 0:
print(f'バッチ [{batch_idx}/{len(train_loader)}] 損失: {loss.item():.6f}')
2. 学習の実行
結果分析
学習プロセスの可視化
# 学習曲線のプロット
plt.figure(figsize=(10, 6))
plt.plot(range(1, num_epochs+1), test_losses, 'b-', label='テスト損失')
plt.xlabel('エポック数')
plt.ylabel('損失')
plt.title('学習曲線')
plt.legend()
plt.grid(True)
plt.show()
まとめ
本記事では、PyTorchを使用した自動車ナンバープレート認識システムを実装しました。CNNモデルを構築し、データの前処理からモデルの学習、評価までを一通り実施しました。特に文字の埋め込み処理においては、中国のナンバープレートに特化した文字セットを定義し、それをベクトル化する方法を取り入れました。モデルの形状調整においては、-1を指定して自動的に次元を適合させる手法を用いています。
この実装は、交通監視システムや駐車場管理システムなど、実際の応用場面でのナンバープレート認識に活用可能です。