PyTorchを用いた自動車ナンバープレート認識システムの実装

環境設定

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を指定して自動的に次元を適合させる手法を用いています。

この実装は、交通監視システムや駐車場管理システムなど、実際の応用場面でのナンバープレート認識に活用可能です。

タグ: PyTorch ディープラーニング 画像認識 ナンバープレート認識 CNN

6月24日 16:48 投稿