オープンソースの卒業設計:車番号認識システムの実装

1. 車番号認識の原理とプロセス

車番号認識は、画像処理とパターン認識の理論に基づき、車両のナンバープレートを含む画像を分析・処理し、ナンバープレートの位置を特定し、さらにテキスト文字を抽出・認識する技術です。

典型的な車番号認識処理の流れは、画像取得、画像前処理、ナンバープレート検出、文字分割、文字認識、結果出力というステップで構成されます。各ステップは相互に補完し合い、それぞれが高い効率性と耐干渉性を確保することが、全体として満足のいく認識性能を達成するための鍵となります。

車番号認識システムの実装方法は主に2種類に分けられます。一つは静的画像認識、もう一つは動的ビデオストリーム認識です。静的画像認識は、画像品質、ナンバープレートの汚れや傾きなどの要因に制約されます。一方、動的ビデオストリーム認識はより高速な認識速度を要求し、特にモバイルデバイスでのリアルタイム認識を実現するには、さらなる性能最適化が必要となります。

車番号認識には6つの主要な処理ステップがありますが、その中核となるアルゴリズムは、ナンバープレート検出、文字分割、文字認識の3つのモジュールに集中しています。

1.1 ナンバープレート検出

ナンバープレート検出の主な作業は、静的画像またはビデオフレームからナンバープレートの位置を見つけ出し、画像から切り出して後続の処理モジュールに供給することです。ナンバープレート検出はシステム性能に大きな影響を与える重要な要素です。現在、ナンバープレート検出の方法は多種多様ですが、主に2つのカテゴリに分類できます。

1.2 画像処理に基づく検出方法

主なものには、(1)色に基づく検出方法(カラーエッジアルゴリズム、色距離と類似度アルゴリズムなど)、(2)テクスチャに基づく検出方法(ウェーブレットテクスチャ、水平勾配差分テクスチャなど)、(3)エッジ検出に基づく検出方法、(4)数学的形態学に基づく検出方法などがあります。

画像処理に基づく検出方法は、外界の干渉情報の影響を受けやすく、検出失敗の原因となります。例えば、色分析に基づく検出方法では、ナンバープレートの背景色と文字色が近い場合、背景からナンバープレートを抽出するのが困難になります。エッジ検出に基づく方法では、ナンバープレートのエッジの汚れも検出失敗を引き起こしやすくなります。外界の干渉情報は検出アルゴリズムを騙し、不要な候補領域を過剰に生成させ、システムの負荷を増大させる可能性があります。

1.3 機械学習に基づく検出方法

機械学習に基づく方法には、特徴エンジニアリングに基づく検出方法とニューラルネットワークに基づく検出方法などがあります。例えば、OpenCVが提供するHaar特徴量に基づくカスケード分類器を用いて、ナンバープレート検出システムをトレーニングできます。しかし、この方法はトレーニングに非常に時間がかかり、分類・検出の効率も低いため、現在の目標検出分野では、ニューラルネットワークに基づく方法が主流となっています。ニューラルネットワークを用いた検出方法では、主に畳み込みニューラルネットワーク(CNN)が目標の特徴を学習します。CNNは並進不変性を持っているため、学習過程で候補領域を補助的に用い、候補領域を分類することができます。正しく分類された候補領域が目標の位置となります。この種の方法にはRCNN、Fast R-CNN、SSDなどの多くの実装モデルがあります。

1.4 文字分割

文字分割のタスクは、複数列または複数行の文字画像から各文字を個別に切り出し、単一の文字画像にすることです。従来の文字分割アルゴリズムは、主に2つのカテゴリに分類できます:直接分割法と画像形態学に基づく分割法です。直接分割法はシンプルで、ナンバープレートの文字分布などの事前知識に基づき、基本的な投影アルゴリズムを補助的に使用して分割を実現します。形態学に基づく分割方法は、エッジ検出、膨張・収縮などの処理を用いて文字画像の位置を特定します。従来の文字分割アルゴリズムも外界の干渉に敏感であり、ナンバープレートの傾きや文字の汚れ・つながりなどに影響を受けやすいです。文字の正確な分割は文字認識の鍵となります。分割が正しければ、認識の精度を保証できます。一方、ニューラルネットワーク理論の継続的な発展に伴い、エンドツーエンドの画像分類認識技術も大きな進歩を遂げており、多くのOCRソフトウェアは徐々に従来の文字分割処理を脱却し、認識ネットワークが複数の文字を直接認識するようになっています。

1.5 文字認識

文字認識は、一つまたは複数の文字を含む画像から文字コードを抽出するプロセスです。文字認識の典型的な方法は、機械学習に基づく画像分類方法です。画像分類方法では、一つの画像は一つの分類しか出力できず、つまり一つの画像には一つの文字画像しか含まれていないことになります。これは文字分割が非常に高い精度を持つことを要求します。もう一つの認識方法は、エンドツーエンドの循環ニューラルネットワーク(RNN)に基づく文字認識方法です。この方法は、ナンバープレート全体の画像をネットワークに入力し、ニューラルネットワークが直接すべての文字を出力します。エンドツーエンドの方法は文字分割プロセスを直接排除し、文字分割の誤りによる安定性の損失を避けますが、同時にナンバープレートの傾きなどの他の干渉にも敏感です。

2. 機械学習を用いた車番号認識

前述のナンバープレート検出と文字分割については詳しく説明しません。ここでは、機械学習のうちサポートベクターマシン(SVM)を用いたナンバープレート文字認識の方法に焦点を当てます。

2.1 サポートベクターマシン(SVM)

SVMは、小サンプル、非線形、高次元のパターン認識問題に対して多くの独自の利点を持っています。

簡単に言えば、SVM分類アルゴリズムの本質は、サンプルの特徴空間の中で最適な超平面を見つけ出し、その超平面がすべてのクラスのサンプルとの距離の最小値を最大化することです。

以下の図に示すように、合計2つのクラスがあり、各クラスのサンプル数は5です。最適な超平面は、2つのクラスを分離し、かつ2つのクラスの中で分類面に最も近いサンプルと分類面との距離が最大となるものです。

要するに:SVMは本質的に一つのクラス分類器であり、サンプル空間で異なるクラスのサンプルを分離する超平面です。

2.2 SVMによる文字認識

定義

import cv2
import numpy as np
import os

class CharacterRecognizerSVM:
    def __init__(self, C=1, gamma=0.5):
        self.svm_model = cv2.ml.SVM_create()
        self.svm_model.setGamma(gamma)
        self.svm_model.setC(C)
        self.svm_model.setKernel(cv2.ml.SVM_RBF)
        self.svm_model.setType(cv2.ml.SVM_C_SVC)

    def train(self, samples, responses):
        self.svm_model.train(samples, cv2.ml.ROW_SAMPLE, responses)

呼び出し方法、データの投入

    def train_svm_models(self):
        # 英字と数字の認識
        self.english_digit_recognizer = CharacterRecognizerSVM(C=1, gamma=0.5)
        # 中国語文字の認識
        self.chinese_recognizer = CharacterRecognizerSVM(C=1, gamma=0.5)
        
        if os.path.exists("model_svm.dat"):
            self.english_digit_recognizer.svm_model.load("model_svm.dat")
            self.chinese_recognizer.svm_model.load("model_chinese.dat")

トレーニングとモデルの保存

        else:
            training_data = []
            training_labels = []
            
            for root, dirs, files in os.walk("train/chars2"):
                if len(os.path.basename(root)) > 1:
                    continue
                label_value = ord(os.path.basename(root))
                for filename in files:
                    filepath = os.path.join(root, filename)
                    char_image = cv2.imread(filepath)
                    char_image = cv2.cvtColor(char_image, cv2.COLOR_BGR2GRAY)
                    training_data.append(char_image)
                    training_labels.append(label_value)
            
            training_data = list(map(self.deskew, training_data))
            training_data = self.preprocess_hog(training_data)
            training_labels = np.array(training_labels)
            
            self.english_digit_recognizer.train(training_data, training_labels)
            self.chinese_recognizer.train(training_data, training_labels)
            
            self.english_digit_recognizer.svm_model.save("model_svm.dat")
            self.chinese_recognizer.svm_model.save("model_chinese.dat")

ナンバープレート文字のデータセットは以下の通りです。

これらはアルファベットのトレーニングデータで、同様にナンバープレートの省略形もあります:

コアコード

    def recognize_plate(self, plate_images, colors):
        recognized_chars = []
        roi_image = None
        plate_color = None
        
        for i, color in enumerate(colors):
            if color in ("blue", "yellow", "green"):
                plate_img = plate_images[i]
                gray_img = cv2.cvtColor(plate_img, cv2.COLOR_BGR2GRAY)
                # 黄色・緑色ナンバープレートの文字は背景より暗く、青色ナンバープレートとは逆なので、黄色・緑色ナンバープレートは反転が必要
                if color == "green" or color == "yellow":
                    gray_img = cv2.bitwise_not(gray_img)
                _, gray_img = cv2.threshold(gray_img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
                
                # 水平ヒストグラムのピークを検出
                x_histogram = np.sum(gray_img, axis=1)
                x_min = np.min(x_histogram)
                x_average = np.sum(x_histogram) / x_histogram.shape[0]
                x_threshold = (x_min + x_average) / 2
                wave_peaks = self.find_waves(x_threshold, x_histogram)
                
                if len(wave_peaks) == 0:
                    print("ピークが見つかりませんでした。")
                    continue
                
                # 水平方向で最も大きなピークをナンバープレート領域と見なす
                wave = max(wave_peaks, key=lambda x: x[1] - x[0])
                gray_img = gray_img[wave[0]:wave[1]]
                
                # 垂直ヒストグラムのピークを検出
                row_num, col_num = gray_img.shape[:2]
                # ナンバープレートの上下端1ピクセルを除去し、白い境界の影響を避ける
                gray_img = gray_img[1:row_num-1]
                y_histogram = np.sum(gray_img, axis=0)
                y_min = np.min(y_histogram)
                y_average = np.sum(y_histogram) / y_histogram.shape[0]
                y_threshold = (y_min + y_average) / 5 # 'U'と'0'のために閾値を小さく設定
                
                wave_peaks = self.find_waves(y_threshold, y_histogram)
                
                # ナンバープレートの文字数は6より大きいはず
                if len(wave_peaks) <= 6:
                    print("ピークが少なすぎます:", len(wave_peaks))
                    continue
                
                wave = max(wave_peaks, key=lambda x: x[1] - x[0])
                max_wave_dis = wave[1] - wave[0]
                
                # 左側のナンバープレートの端であるかどうかを判断
                if wave_peaks[0][1] - wave_peaks[0][0] < max_wave_dis / 3 and wave_peaks[0][0] == 0:
                    wave_peaks.pop(0)
                
                # 漢字を組み合わせて分離
                cur_dis = 0
                for i, wave in enumerate(wave_peaks):
                    if wave[1] - wave[0] + cur_dis > max_wave_dis * 0.6:
                        break
                    else:
                        cur_dis += wave[1] - wave[0]
                if i > 0:
                    wave = (wave_peaks[0][0], wave_peaks[i][1])
                    wave_peaks = wave_peaks[i+1:]
                    wave_peaks.insert(0, wave)
                
                # ナンバープレート上の区切り点を除去
                point = wave_peaks[2]
                if point[1] - point[0] < max_wave_dis / 3:
                    point_img = gray_img[:, point[0]:point[1]]
                    if np.mean(point_img) < 255 / 5:
                        wave_peaks.pop(2)
                
                if len(wave_peaks) <= 6:
                    print("ピークが少なすぎます:", len(wave_peaks))
                    continue
                
                separated_chars = self.separate_characters(gray_img, wave_peaks)
                
                for i, part_char in enumerate(separated_chars):
                    # 固定用のリベットである可能性
                    if np.mean(part_char) < 255 / 5:
                        print("区切り点の可能性")
                        continue
                    
                    part_char_original = part_char
                    padding_width = abs(part_char.shape[1] - self.SZ) // 2
                    
                    part_char = cv2.copyMakeBorder(part_char, 0, 0, padding_width, padding_width, cv2.BORDER_CONSTANT, value=[0, 0, 0])
                    part_char = cv2.resize(part_char, (self.SZ, self.SZ), interpolation=cv2.INTER_AREA)
                    
                    part_char = self.preprocess_hog([part_char])
                    
                    if i == 0:
                        resp = self.chinese_recognizer.svm_model.predict(part_char)
                        character = self.provinces[int(resp[0]) - self.PROVINCE_START]
                    else:
                        resp = self.english_digit_recognizer.svm_model.predict(part_char)
                        character = chr(resp[0])
                    
                    # 最後の文字がナンバープレートの端であるかどうかを判断、端は'1'と仮定
                    if character == "1" and i == len(separated_chars) - 1:
                        if part_char_original.shape[0] / part_char_original.shape[1] >= 7: # '1'が細すぎる場合は端と見なす
                            continue
                    
                    recognized_chars.append(character)
                
                roi_image = plate_img
                plate_color = color
                break
                
        return recognized_chars, roi_image, plate_color # 認識された文字、検出されたナンバープレート画像、ナンバープレートの色

3. ディープラーニングによる文字認識

認識段階は、私たちの自動ナンバープレート検出・認識システムの最後のステップです。認識は前のステップで得られた単一の文字画像に基づいて行われます。モデルはこれらの画像を予測し、最終的なナンバープレート番号を得ます。

トレーニングデータを可能な限り活用するため、各文字を個別に切り出し、ナンバープレート文字データセットを作成しました。このデータセットには11のクラス(数字0-9およびアラビア数字)が含まれ、各クラスには30~40枚の文字画像が含まれており、画像は28x28のPNG形式です。

次に、多層パーセプトロン(MLP)とK近傍分類器(KNN)を比較検討しました。研究結果によると、MLPの場合、隠れ層のニューロン数を増やすと、分類器の性能が向上します。同様に、KNNの場合も、近傍数を増やすと性能が向上します。しかし、KNNの調整可能性はMLPに比べてはるかに小さいため、最終的にこの段階では多層パーセプトロン(MLP)ネットワークを使用することにしました。

ネットワーク構造

キーコード

from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation, Flatten
from keras.layers import Conv2D, MaxPooling2D
from keras.optimizers import Adam
from keras import backend as K

K.set_image_data_format('channels_last')

import cv2
import numpy as np

# 文字インデックスの定義
char_index = {
    "京": 0, "沪": 1, "津": 2, "渝": 3, "冀": 4, "晋": 5, "蒙": 6, "辽": 7, "吉": 8, "黑": 9, "苏": 10, "浙": 11, "皖": 12,
    "闽": 13, "赣": 14, "鲁": 15, "豫": 16, "鄂": 17, "湘": 18, "粤": 19, "桂": 20, "琼": 21, "川": 22, "贵": 23, "云": 24,
    "藏": 25, "陕": 26, "甘": 27, "青": 28, "宁": 29, "新": 30, "0": 31, "1": 32, "2": 33, "3": 34, "4": 35, "5": 36,
    "6": 37, "7": 38, "8": 39, "9": 40, "A": 41, "B": 42, "C": 43, "D": 44, "E": 45, "F": 46, "G": 47, "H": 48,
    "J": 49, "K": 50, "L": 51, "M": 52, "N": 53, "P": 54, "Q": 55, "R": 56, "S": 57, "T": 58, "U": 59, "V": 60,
    "W": 61, "X": 62, "Y": 63, "Z": 64, "港": 65, "学": 66, "O": 67, "使": 68, "警": 69, "澳": 70, "挂": 71
}

chars = ["京", "沪", "津", "渝", "冀", "晋", "蒙", "辽", "吉", "黑", "苏", "浙", "皖", "闽", "赣", "鲁", "豫", "鄂", "湘", "粤", "桂",
        "琼", "川", "贵", "云", "藏", "陕", "甘", "青", "宁", "新", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A",
        "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P",
        "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "港", "学", "O", "使", "警", "澳", "挂"]

def create_character_recognition_model(num_classes):
    img_rows, img_cols = 23, 23
    
    model = Sequential()
    model.add(Conv2D(32, (5, 5), activation='relu', input_shape=(img_rows, img_cols, 1)))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Dropout(0.25))
    
    model.add(Conv2D(64, (3, 3), activation='relu'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Dropout(0.25))
    
    model.add(Conv2D(128, (3, 3), activation='relu'))
    model.add(Flatten())
    
    model.add(Dense(256, activation='relu'))
    model.add(Dropout(0.5))
    
    model.add(Dense(num_classes, activation='softmax'))
    
    model.compile(loss='categorical_crossentropy',
                  optimizer=Adam(),
                  metrics=['accuracy'])
    return model

# モデルの構築
model_general = create_character_recognition_model(65)
model_chinese = create_character_recognition_model(31)

# 事前学習済みの重みを読み込む
model_chinese.load_weights("./model/char_chi_sim.h5")
model_general.load_weights("./model/char_rec.h5")

def predict_single_character(image, position):
    image = cv2.resize(image, (23, 23))
    image = cv2.equalizeHist(image)
    image = image.astype(np.float32) / 255.0
    image = np.expand_dims(image, -1)
    
    if position != 0:
        prediction = model_general.predict(np.array([image]))
    else:
        prediction = model_chinese.predict(np.array([image]))
    
    prediction = np.array(prediction[0])
    
    offset = 0
    if position == 0:
        prediction = prediction[:31]
    elif position == 1:
        prediction = prediction[31+10:65]
        offset = 31 + 10
    else:
        prediction = prediction[31:]
        offset = 31
    
    max_index = prediction.argmax()
    
    return prediction.max(), chars[max_index + offset], max_index + offset

4. アルゴリズムの最適化と革新(ナンバープレートの傾き補正)

車番号認識システムにおいて、文字が正しく分割される前提は、ナンバープレート画像が水平であることです。これにより、水平投影と垂直投影が正常に行われます。ナンバープレートが傾き補正されていない場合、水平投影や垂直投影、さらにはリベットの処理さえも正常に行えなくなります。したがって、車両情報からナンバープレートを取得する最初のステップは、傾き角度を確認し、傾き補正を行うべきです。

元のナンバープレート画像は(ナンバープレート画像から、ナンバープレートに傾き角度があることがわかります):

車両内でのナンバープレートの大まかな位置を取得(複数の方法を使用可能、ここでは分析を省略)

ナンバープレート全体の画像データを抽出し、最初のステップの結果に基づき、車両内の大まかな位置情報を抽出します。

ナンバープレート検出については、私は2段階で行います。まず大まかな検出を行い、いくつかの前処理(例えば傾き補正)を行った後、2番目のステップで精確な検出を行い、ナンバープレートの位置情報のみを抽出します。

HSV色空間変換を利用し、ナンバープレート背景の青色領域の位置を取得し、ナンバープレートの大まかな情報画像を取得します。ナンバープレートの背景色は周囲の色と非常に明確な違いがあるため、ここではHSV色フィルタリングの方法を採用し、緑色背景画像をフィルタリングします。

水平方向の膨張処理。水平方向の膨張の目的は、エッジ検出を行い、文字情報をできるだけ除去し、ハフ変換の計算量を削減することです。

水平差分演算。これはエッジ検出に相当し、上記の処理後にエッジ検出を行います。

この時点でハフ変換を用いて直線を検出できます。ハフ変換の計算量は非常に大きいため、画像内の白点をできるだけ減らして計算量を削減する必要があります。そのため、前述のように多くの前処理を行いました。

以下の赤い線が検出された角度で、177度です(ハフ変換のコードは以下にあります)。

回転アルゴリズムを利用し、先ほど大まかに抽出したナンバープレートの位置を回転させます。回転後のナンバープレートにはジャギーが発生しますが、水平を確保できれば、水平投影と垂直投影を使用できます。

これは回転後のナンバープレートで、ジャギーが発生しています。画像解像度が低いため、補間演算は使用していません。

精確なナンバープレートの抽出

正常な文字分割

認識結果

5. GUIインターフェースのコード共有

インターフェースのコードを表示します。

import tkinter as tk
from tkinter.filedialog import askopenfilename
from tkinter import ttk
import cv2
from PIL import Image, ImageTk
import threading
import time
import numpy as np

class LicensePlateApp(ttk.Frame):
    image_path = ""
    view_height = 600
    view_width = 600
    last_update = 0
    processing_thread = None
    is_running = False
    camera = None
    color_mapping = {"green": ("緑ナンバー", "#55FF55"), "yellow": ("黄ナンバー", "#FFFF00"), "blue": ("青ナンバー", "#6666FF")}

    def __init__(self, master):
        super().__init__(master)
        self.master = master
        self.master.title("車番号認識")
        self.master.state("zoomed")
        
        frame_left = ttk.Frame(self)
        frame_right_top = ttk.Frame(self)
        frame_right_bottom = ttk.Frame(self)
        
        self.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        frame_left.pack(side=tk.LEFT, expand=True, fill=tk.BOTH)
        frame_right_top.pack(side=tk.TOP, expand=True, fill=tk.Y)
        frame_right_bottom.pack(side=tk.RIGHT, expand=False)
        
        ttk.Label(frame_left, text='元画像:').pack(anchor="nw")
        ttk.Label(frame_right_top, text='形状検出によるナンバープレート位置:').grid(column=0, row=0, sticky=tk.W)
        
        self.btn_from_image = ttk.Button(frame_right_bottom, text="画像から読み込み", width=20, command=self.load_from_image)
        self.btn_from_camera = ttk.Button(frame_right_bottom, text="カメラから読み込み", width=20, command=self.start_camera)
        self.btn_show_preprocess = ttk.Button(frame_right_bottom, text="前処理画像の表示", width=20, command=self.show_preprocess_image)
        
        self.image_label = ttk.Label(frame_left)
        self.image_label.pack(anchor="nw")
        
        self.plate_roi_label = ttk.Label(frame_right_top)
        self.plate_roi_label.grid(column=0, row=1, sticky=tk.W)
        ttk.Label(frame_right_top, text='形状検出認識結果:').grid(column=0, row=2, sticky=tk.W)
        self.recognition_result_label = ttk.Label(frame_right_top, text="", font=('Times', '20'))
        self.recognition_result_label.grid(column=0, row=3, sticky=tk.W)
        self.color_label = ttk.Label(frame_right_top, text="", width="20")
        self.color_label.grid(column=0, row=4, sticky=tk.W)
        
        ttk.Label(frame_right_top, text='色検出によるナンバープレート位置:').grid(column=0, row=5, sticky=tk.W)
        self.plate_roi_color_label = ttk.Label(frame_right_top)
        self.plate_roi_color_label.grid(column=0, row=6, sticky=tk.W)
        ttk.Label(frame_right_top, text='色検出認識結果:').grid(column=0, row=7, sticky=tk.W)
        self.recognition_result_color_label = ttk.Label(frame_right_top, text="", font=('Times', '20'))
        self.recognition_result_color_label.grid(column=0, row=8, sticky=tk.W)
        self.color_color_label = ttk.Label(frame_right_top, text="", width="20")
        self.color_color_label.grid(column=0, row=9, sticky=tk.W)
        
        self.btn_from_camera.pack(anchor="se", pady=5)
        self.btn_from_image.pack(anchor="se", pady=5)
        self.btn_show_preprocess.pack(anchor="se", pady=5)
        
        self.plate_recognizer = CharacterRecognizerSVM()
        self.plate_recognizer.train_svm_models()

    def get_photo_image(self, img_bgr):
        img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
        pil_image = Image.fromarray(img_rgb)
        photo_image = ImageTk.PhotoImage(image=pil_image)
        
        width = photo_image.width()
        height = photo_image.height()
        if width > self.view_width or height > self.view_height:
            width_factor = self.view_width / width
            height_factor = self.view_height / height
            factor = min(width_factor, height_factor)
            width = int(width * factor)
            if width <= 0: width = 1
            height = int(height * factor)
            if height <= 0: height = 1
            pil_image = pil_image.resize((width, height), Image.ANTIALIAS)
            photo_image = ImageTk.PhotoImage(image=pil_image)
        
        return photo_image

    def display_plate(self, result, roi, color, label_roi, label_result, label_color):
        if result:
            roi_rgb = cv2.cvtColor(roi, cv2.COLOR_BGR2RGB)
            pil_roi = Image.fromarray(roi_rgb)
            self.photo_roi = ImageTk.PhotoImage(image=pil_roi)
            label_roi.configure(image=self.photo_roi, state='normal')
            label_result.configure(text=str(result))
            self.last_update = time.time()
            try:
                color_info = self.color_mapping[color]
                label_color.configure(text=color_info[0], background=color_info[1], state='normal')
            except KeyError:
                label_color.configure(state='disabled')
        elif self.last_update + 8 < time.time():
            label_roi.configure(state='disabled')
            label_result.configure(text="")
            label_color.configure(state='disabled')

    def load_from_image(self):
        self.is_running = False
        self.image_path = askopenfilename(title="認識する画像を選択", filetypes=[("JPEG", "*.jpg"), ("PNG", "*.png")])
        if self.image_path:
            img_bgr = cv2.imread(self.image_path)
            processed_img, original_img = self.plate_recognizer.preprocess_image(img_bgr)
            self.photo_image = self.get_photo_image(img_bgr)
            self.image_label.configure(image=self.photo_image)
            
            result_contour, roi_contour, color_contour = self.plate_recognizer.recognize_by_contour(processed_img, original_img)
            result_color, roi_color, color_color = self.plate_recognizer.recognize_by_color(original_img, original_img, processed_img)
            
            self.display_plate(result_color, roi_color, color_color, self.plate_roi_color_label, self.recognition_result_color_label, self.color_color_label)
            self.display_plate(result_contour, roi_contour, color_contour, self.plate_roi_label, self.recognition_result_label, self.color_label)

    def start_camera(self):
        if self.is_running:
            return
        if self.camera is None:
            self.camera = cv2.VideoCapture(0)
            if not self.camera.isOpened():
                tk.messagebox.showwarning('警告', 'カメラの起動に失敗しました!')
                self.camera = None
                return
        
        self.processing_thread = threading.Thread(target=self.camera_thread)
        self.processing_thread.setDaemon(True)
        self.processing_thread.start()
        self.is_running = True

    def camera_thread(self):
        self.is_running = True
        last_prediction = time.time()
        while self.is_running:
            ret, img_bgr = self.camera.read()
            if not ret:
                break
            self.photo_image = self.get_photo_image(img_bgr)
            self.image_label.configure(image=self.photo_image)
            
            if time.time() - last_prediction > 2:
                result, roi, color = self.plate_recognizer.recognize_plate(img_bgr)
                self.display_plate(result, roi, color, self.plate_roi_label, self.recognition_result_label, self.color_label)
                last_prediction = time.time()
        print("カメラスレッド終了")

def on_closing():
    if hasattr(app, 'is_running') and app.is_running:
        app.is_running = False
        app.processing_thread.join(2.0)
    app.master.destroy()

if __name__ == '__main__':
    root = tk.Tk()
    app = LicensePlateApp(root)
    root.protocol("WM_DELETE_WINDOW", on_closing)
    root.mainloop()

タグ: OpenCV 畳み込みニューラルネットワーク サポートベクターマシン 車番号認識 Python

6月22日 16:11 投稿