香橙派 Orange Pi AIpro を使用した YOLOv5 ベースの車両ナンバープレート認識システム構築

最近、組み込みシステムの知識を学習する機会があり、香橙派が新たにリリースした Orange Pi AIpro ボードに興味を持ち、実際に研究してみました。

1. 香橙派 Orange Pi AIpro 開発ボード紹介と実物確認

1.1 開発ボード仕様

昇騰AI技術ルート:高性能で低消費電力の4コア64ビットプロセッサとAIプロセッサを搭載し、高速処理と低消費電力を実現し、さまざまなAIアプリケーションに優れたパフォーマンスを提供します。

マルチチャネル出力、複数画面同時表示対応:デュアルHDMIビデオ出力に対応し、4K高解像度出力が可能。MIPI DSIディスプレイ出力と2つのMIPIカメラ入力もサポートしています。

豊富なインターフェース、容易な拡張性:MIPI DSI、MIPI CSI、USB 3.0、Type-C 3.0、HDMI 2.0、ギガビットイーサネット、SATA/NVMe SSD 2280用M.2スロットなどの人気インターフェースを備え、外部デバイス制御や拡張に利用できます。

幅広い応用分野、AIoT業界全般をカバー:AI教育実習、AIアルゴリズム検証、スマートカー、ロボットアーム、エッジコンピューティング、ドローン、人工知能、クラウドコンピューティング、AR/VR、インテリジェントセキュリティ、スマートホーム、インテリジェント交通などに適しています。

openEuler システム実行可能:効率的で安定した安全なシステム。openEuler システムを実行可能で、複数のハードウェアアーキテクチャと仮想化技術をサポートし、企業向けクラウドコンピューティングやエッジコンピューティングシーンに広く適用されます。

1.2 製品詳細図

表面
裏面

1.3 開封実物

付属品リスト
前面図

背面図

申請後すぐに配送され、開封すると急速充電器とデータケーブルが含まれており、ボードは標準的なケースに入れて二層の薄いスポンジで保護されており、静電気による損傷を防いでいます。実物の外観は非常に美しく、32GBのTFカードが既に搭載されており、openEulerイメージが事前に書き込まれています。

2. 開発展開の事前準備

2.1 イメージ紹介と書き込み

開発ボードはデフォルトでUbuntuシステムとopenEulerシステムをサポートしており、TFカードにはすでにopenEulerシステムが書き込まれているため、このシステムを使用して構築・展開を行います。Ubuntuシステムに慣れているユーザーはフォーマットして再度Ubuntuシステムを書き込むことも可能です。

公式が書き込みツールを提供しています。

2.2 開発ボード起動

電源接続後、開発ボードは自動的に起動します。

2.3 開発ボード接続

HDMIケーブルがないため、リモート接続方式で接続します。

  1. 開発ボードのIPアドレスを取得
  2. MobaXtermでリモート接続

3. YOLOv5ベースのナンバープレート認識システム

3.1 プロジェクト概要

本プロジェクトは、深層学習とYOLOv5に基づいたナンバープレート検出、認識、中国語ナンバープレート認識・検出を実現するシステムで、12種類の中国語ナンバープレートと二段式ナンバープレートをサポートする物体検出システムです。
プロジェクトオープンソースアドレス:Chinese_license_plate_detection_recognition

3.2 プロジェクト取得

  1. プロジェクトを格納するためのフォルダを作成

    mkdir yolov5
    
  2. 開発ボードシステムには既にgitがインストールされているため、直接gitコマンドでプロジェクトを取得

    git clone https://github.com/we0091234/Chinese_license_plate_detection_recognition.git
    
  3. 取得したプロジェクトディレクトリに移動

    cd Chinese_license_plate_detection_recognition
    
  4. 取得したプロジェクトには多くの依存関係が必要なため、以下のコマンドでダウンロード

    pip install -r ./requirements.txt -i https://mirrors.aliyun.com/pypi/simple
    

    多くの依存関係をダウンロードするため、しばらく待つ必要があります。

    緑色のSuccessfullyが表示されれば依存関係のダウンロードに成功したことを示します。

  5. これでプロジェクトの取得が完了しました。プロジェクトのディレクトリ構造を確認してみましょう。

    多くのファイルがあることが確認できます。

3.3 主要コード例

# -*- coding: UTF-8 -*-
import argparse
import time
from pathlib import Path
import os
import cv2
import torch
import torch.backends.cudnn as cudnn
from numpy import random
import copy
import numpy as np
from models.experimental import attempt_load
from utils.datasets import letterbox
from utils.general import check_img_size, non_max_suppression_face, apply_classifier, scale_coords, xyxy2xywh, \
    strip_optimizer, set_logging, increment_path
from utils.plots import plot_one_box
from utils.torch_utils import select_device, load_classifier, time_synchronized
from utils.cv_puttext import cv2ImgAddText
from plate_recognition.plate_rec import get_plate_result,allFilePath,init_model,cv_imread
from plate_recognition.double_plate_split_merge import get_split_merge

colors = [(255,0,0),(0,255,0),(0,0,255),(255,255,0),(0,255,255)]

def arrange_corner_points(pts):                   # 四つの点を左上、右上、右下、左下の順に並べる
    rectangle = np.zeros((4, 2), dtype = "float32")
    sum_values = pts.sum(axis = 1)
    rectangle[0] = pts[np.argmin(sum_values)]
    rectangle[2] = pts[np.argmax(sum_values)]
    diff_values = np.diff(pts, axis = 1)
    rectangle[1] = pts[np.argmin(diff_values)]
    rectangle[3] = pts[np.argmax(diff_values)]
    return rectangle

def perspective_transform(image, pts):                       # 透視変換でナンバープレートの小さな画像を得る
    rectangle = pts.astype('float32')
    (top_left, top_right, bottom_right, bottom_left) = rectangle
    width_a = np.sqrt(((bottom_right[0] - bottom_left[0]) ** 2) + ((bottom_right[1] - bottom_left[1]) ** 2))
    width_b = np.sqrt(((top_right[0] - top_left[0]) ** 2) + ((top_right[1] - top_left[1]) ** 2))
    max_width = max(int(width_a), int(width_b))
    height_a = np.sqrt(((top_right[0] - bottom_right[0]) ** 2) + ((top_right[1] - bottom_right[1]) ** 2))
    height_b = np.sqrt(((top_left[0] - bottom_left[0]) ** 2) + ((top_left[1] - bottom_left[1]) ** 2))
    max_height = max(int(height_a), int(height_b))
    destination = np.array([
        [0, 0],
        [max_width - 1, 0],
        [max_width - 1, max_height - 1],
        [0, max_height - 1]], dtype = "float32")
    matrix = cv2.getPerspectiveTransform(rectangle, destination)
    transformed = cv2.warpPerspective(image, matrix, (max_width, max_height))
    return transformed

def load_detection_model(weights, device):   # 検出モデルを読み込む
    model = attempt_load(weights, map_location=device)  # FP32モデルを読み込む
    return model

def convert_coordinates(img1_shape, coords, img0_shape, ratio_pad=None):  # 元の画像座標に戻す
    if ratio_pad is None:  # img0_shapeから計算
        scale = min(img1_shape[0] / img0_shape[0], img1_shape[1] / img0_shape[1])  # スケール = 古い / 新しい
        padding = (img1_shape[1] - img0_shape[1] * scale) / 2, (img1_shape[0] - img0_shape[0] * scale) / 2  # 幅高さのパディング
    else:
        scale = ratio_pad[0][0]
        padding = ratio_pad[1]

    coords[:, [0, 2, 4, 6]] -= padding[0]  # x方向パディング
    coords[:, [1, 3, 5, 7]] -= padding[1]  # y方向パディング
    coords[:, :8] /= scale
    coords[:, 0].clamp_(0, img0_shape[1])  # x1
    coords[:, 1].clamp_(0, img0_shape[0])  # y1
    coords[:, 2].clamp_(0, img0_shape[1])  # x2
    coords[:, 3].clamp_(0, img0_shape[0])  # y2
    coords[:, 4].clamp_(0, img0_shape[1])  # x3
    coords[:, 5].clamp_(0, img0_shape[0])  # y3
    coords[:, 6].clamp_(0, img0_shape[1])  # x4
    coords[:, 7].clamp_(0, img0_shape[0])  # y4
    return coords

def extract_plate_info(img, bbox, confidence, corner_points, class_id, device, recognition_model, with_color=False):  # ナンバープレート座標と四隅座標を取得し、ナンバープレート番号を認識
    height, width, channels = img.shape
    result_info = {}
    line_thickness = 1 or round(0.002 * (height + width) / 2) + 1  # 線/フォントの太さ

    x1 = int(bbox[0])
    y1 = int(bbox[1])
    x2 = int(bbox[2])
    y2 = int(bbox[3])
    plate_height = y2 - y1
    corner_array = np.zeros((4,2))
    region = [x1,y1,x2,y2]
    for i in range(4):
        point_x = int(corner_points[2 * i])
        point_y = int(corner_points[2 * i + 1])
        corner_array[i] = np.array([point_x,point_y])

    plate_class = int(class_id)  # ナンバープレートのタイプ、0は単層、1は二段式ナンバープレート
    roi_image = perspective_transform(img, corner_array)   # 透視変換でナンバープレートの小さな画像を得る
    if plate_class:        # 二段式ナンバープレートかどうかを判定、二段式の場合は分割して結合
        roi_image = get_split_merge(roi_image)
    if not with_color:
        plate_number, rec_probability = get_plate_result(roi_image, device, recognition_model, is_color=with_color)                 # ナンバープレートの小さな画像を認識
    else:
        plate_number, rec_probability, plate_color, color_confidence = get_plate_result(roi_image, device, recognition_model, is_color=with_color) 
    
    result_info['region'] = region                      # ナンバープレートROI領域
    result_info['detection_conf'] = confidence              # 検出領域のスコア
    result_info['corners'] = corner_array.tolist() # ナンバープレートの角座標
    result_info['plate_num'] = plate_number   # ナンバープレート番号
    result_info['rec_probability'] = rec_probability   # 各文字の確率
    result_info['roi_height'] = roi_image.shape[0]  # ナンバープレートの高さ
    result_info['plate_color'] = ""
    if with_color:
        result_info['plate_color'] = plate_color   # ナンバープレートの色
        result_info['color_confidence'] = color_confidence    # 色のスコア
    result_info['plate_category'] = plate_class   # 単層/二段式 0単層 1二段式
    
    return result_info

def detect_and_recognize_plates(model, source_image, device, recognition_model, image_size, with_color=False):# ナンバープレート情報を取得
    confidence_threshold = 0.3      # スコア閾値
    nms_iou_threshold = 0.5       # nmsのIOU値   
    results = []
    processed_image = copy.deepcopy(source_image)
    assert source_image is not None, 'Image Not Found ' 
    original_h, original_w = source_image.shape[:2]  # 元の高さ幅
    resize_ratio = image_size / max(original_h, original_w)  # 画像をimage_sizeにリサイズ
    if resize_ratio != 1:  # 常に縮小のみ、訓練時の補強では拡大も可能
        interpolation = cv2.INTER_AREA if resize_ratio < 1  else cv2.INTER_LINEAR
        processed_image = cv2.resize(processed_image, (int(original_w * resize_ratio), int(original_h * resize_ratio)), interpolation=interpolation)

    resized_size = check_img_size(image_size, s=model.stride.max())  # image_sizeを確認  

    processed = letterbox(processed_image, new_shape=resized_size)[0]           # 検出前処理、画像の長さ幅を32の倍数にする、例えば640X640に変更
    processed = processed[:, :, ::-1].transpose(2, 0, 1).copy()  # BGRからRGBへ、3x416x416に変換 画像のBGR配列をRGBに変換し、画像のH,W,C配列をC,H,Wに変換

    start_time = time.time()

    processed = torch.from_numpy(processed).to(device)
    processed = processed.float()  # uint8からfp16/32へ
    processed /= 255.0  # 0 - 255から0.0 - 1.0へ
    if processed.ndimension() == 3:
        processed = processed.unsqueeze(0)

    predictions = model(processed)[0]

    # NMSを適用
    predictions = non_max_suppression_face(predictions, confidence_threshold, nms_iou_threshold)

    # 検出結果を処理
    for i, detection in enumerate(predictions):  # 画像ごとの検出
        if len(detection):
            # ボックスをimg_sizeからim0サイズにスケーリング
            detection[:, :4] = scale_coords(processed.shape[2:], detection[:, :4], source_image.shape).round()

            # 結果を印刷
            for c in detection[:, -1].unique():
                n = (detection[:, -1] == c).sum()  # クラスごとの検出数

            detection[:, 5:13] = scale_coords_landmarks(processed.shape[2:], detection[:, 5:13], source_image.shape).round()

            for j in range(detection.size()[0]):
                bounding_box = detection[j, :4].view(-1).tolist()
                confidence = detection[j, 4].cpu().numpy()
                landmark_points = detection[j, 5:13].view(-1).tolist()
                class_number = detection[j, 13].cpu().numpy()
                result_info = extract_plate_info(source_image, bounding_box, confidence, landmark_points, class_number, device, recognition_model, with_color=with_color)
                results.append(result_info)
    return results

def visualize_results(source_image, results, with_color=False):   # ナンバープレート結果を描画
    result_text = ""
    for result in results:
        region = result['region']
        
        x, y, w, h = region[0], region[1], region[2]-region[0], region[3]-region[1]
        padding_width = 0.05*w
        padding_height = 0.11*h
        region[0] = max(0, int(x-padding_width))
        region[1] = max(0, int(y-padding_height))
        region[2] = min(source_image.shape[1], int(region[2]+padding_width))
        region[3] = min(source_image.shape[0], int(region[3]+padding_height))

        height_region = result['roi_height']
        corner_points = result['corners']
        plate_text = result['plate_num']
        if result['plate_category'] == 0:# 単層
            plate_text += " " + result['plate_color']
        else:                             # 二段式
            plate_text += " " + result['plate_color'] + "二段式"
        result_text += plate_text + " "
        for i in range(4):  # 重要なポイント
            cv2.circle(source_image, (int(corner_points[i][0]), int(corner_points[i][1])), 5, colors[i], -1)
        cv2.rectangle(source_image, (region[0], region[1]), (region[2], region[3]), (0,0,255), 2) # 枠を描画
        
        label_size = cv2.getTextSize(plate_text, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1) # フォントサイズを取得
        if region[0] + label_size[0][0] > source_image.shape[1]:                 # 表示されるテキストが境界を超えないように
            region[0] = int(source_image.shape[1] - label_size[0][0])
        source_image = cv2.rectangle(source_image, (region[0], int(region[1]-round(1.6*label_size[0][1]))), (int(region[0]+round(1.2*label_size[0][0])), region[1]+label_size[1]), (255,255,255), cv2.FILLED)# テキストボックスを描画、背景は白
        
        if len(result) >= 1:
            source_image = cv2ImgAddText(source_image, plate_text, region[0], int(region[1]-round(1.6*label_size[0][1])), (0,0,0), 21)
               
    print(result_text)
    return source_image

def get_video_info(capture):
    if capture.isOpened():
        frame_rate = capture.get(5)   # フレームレート
        total_frames = capture.get(7)  # 動画の総フレーム数
        duration = total_frames/frame_rate  # フレームレート/動画総フレーム数は時間、60で割ると分単位
        return int(frame_rate), int(total_frames), int(duration)    

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--detection_weights', nargs='+', type=str, default='weights/plate_detect.pt', help='model.pt path(s)')  # 検出モデル
    parser.add_argument('--recognition_weights', type=str, default='weights/plate_rec_color.pth', help='model.pt path(s)')# ナンバープレート認識+色認識モデル
    parser.add_argument('--include_color', type=bool, default=True, help='plate color')      # 色を認識するか
    parser.add_argument('--input_images', type=str, default='imgs', help='source')     # 画像パス
    parser.add_argument('--input_size', type=int, default=640, help='inference size (pixels)')  # ネットワーク入力画像サイズ
    parser.add_argument('--save_output', type=str, default='result', help='source')               # 画像結果保存場所
    parser.add_argument('--input_video', type=str, default='', help='source')                       # 動画パス
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")                     # GPUまたはCPUで認識
    opt = parser.parse_args()
    print(opt)
    output_path = opt.save_output                
    counter = 0
    if not os.path.exists(output_path): 
        os.mkdir(output_path)

    detection_model = load_detection_model(opt.detection_weights, device)  # 検出モデルを初期化
    plate_recognition_model = init_model(device, opt.recognition_weights, is_color=opt.include_color)      # 認識モデルを初期化
    # パラメータ数を計算
    detection_params = sum(p.numel() for p in detection_model.parameters())
    recognition_params = sum(p.numel() for p in plate_recognition_model.parameters())
    print("detection params: %.2fM, recognition params: %.2fM" % (detection_params/1e6, recognition_params/1e6))
    
    total_time = 0
    start_time = time.time()
    if not opt.input_video:     # 画像を処理
        if not os.path.isfile(opt.input_images):            # ディレクトリ
            image_list = []
            allFilePath(opt.input_images, image_list)  # このディレクトリのすべての画像ファイルパスをimage_listに読み込む
            for image_path in image_list:             # 画像ファイルを走査
                
                print(counter, image_path, end=" ")
                begin_time = time.time()               # 開始時間
                image = cv_imread(image_path)           # opencvで画像読み込み
                
                if image is None:                   
                    continue
                if image.shape[-1] == 4:               # 画像が4チャネルの場合、3チャネルに変換
                    image = cv2.cvtColor(image, cv2.COLOR_BGRA2BGR)
                detection_results = detect_and_recognize_plates(detection_model, image, device, plate_recognition_model, opt.input_size, with_color=opt.include_color)# ナンバープレートの検出と認識
                processed_image = visualize_results(image, detection_results)  # 結果を画像に描画
                image_filename = os.path.basename(image_path)  
                saved_path = os.path.join(output_path, image_filename)  # 画像保存パス
                end_time = time.time()
                processing_time = end_time - begin_time                         # 1枚の画像認識時間
                if counter:
                    total_time += processing_time 
                cv2.imwrite(saved_path, processed_image)               # opencvで認識画像を保存
                counter += 1
            print(f"合計時間 {time.time()-start_time} 秒, 平均画像処理時間 {total_time/(len(image_list)-1)}")
        else:                                          # 単体画像
                print(counter, opt.input_images, end=" ")
                image = cv_imread(opt.input_images)
                if image.shape[-1] == 4:
                    image = cv2.cvtColor(image, cv2.COLOR_BGRA2BGR)
                detection_results = detect_and_recognize_plates(detection_model, image, device, plate_recognition_model, opt.input_size, with_color=opt.include_color)
                processed_image = visualize_results(image, detection_results)
                image_filename = os.path.basename(opt.input_images)
                saved_path = os.path.join(output_path, image_filename)
                cv2.imwrite(saved_path, processed_image)  
        
    else:    # 動画を処理
        video_name = opt.input_video
        capture = cv2.VideoCapture(video_name)
        fourcc = cv2.VideoWriter_fourcc(*'MP4V') 
        fps = capture.get(cv2.CAP_PROP_FPS)  # フレームレート
        width, height = int(capture.get(cv2.CAP_PROP_FRAME_WIDTH)), int(capture.get(cv2.CAP_PROP_FRAME_HEIGHT))  # 幅高さ
        output_writer = cv2.VideoWriter('result.mp4', fourcc, fps, (width, height))  # 動画書き出し
        frame_counter = 0
        total_fps = 0
        rate, total_frames, duration = get_video_info(capture)
        if capture.isOpened():
            while True:
                tick_start = cv2.getTickCount()
                frame_counter += 1
                print(f"{frame_counter} フレーム", end=" ")
                ret, frame = capture.read()
                if not ret:
                    break
                original_frame = copy.deepcopy(frame)
                detection_results = detect_and_recognize_plates(detection_model, frame, device, plate_recognition_model, opt.input_size, with_color=opt.include_color)
                processed_frame = visualize_results(frame, detection_results)
                tick_end = cv2.getTickCount()
                inference_time = (tick_end - tick_start) / cv2.getTickFrequency()
                current_fps = 1.0 / inference_time
                total_fps += current_fps
                fps_string = f'fps:{current_fps:.4f}'
                
                cv2.putText(processed_frame, fps_string, (20,20), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,255,0), 2)
                output_writer.write(processed_frame)
                
        else:
            print("失敗")
        capture.release()
        output_writer.release()
        cv2.destroyAllWindows()
        print(f"全フレーム数 {frame_counter}, 平均FPS {total_fps/frame_counter} fps")

3.4 主要ポイント分析

  1. パラメータ初期化

    • 入力画像imgから高さh、幅w、チャネル数cを取得
    • 結果を格納するための空の辞書result_infoを初期化
    • 線/フォントの太さtlを計算し、画像サイズに基づいて動的に計算
  2. ナンバープレート位置と角座標の解析

    • xyxy(ナンバープレート境界ボックス座標、形式は[x1, y1, x2, y2])から左上と右下の座標を抽出
    • NumPy配列corner_arrayを初期化し、ナンバープレートの4つの角座標を格納
    • corner_points(ナンバープレート角座標リスト)を走査し、各角座標(x, y)をcorner_arrayに格納
  3. ナンバープレートタイプの取得

    • class_idからナンバープレートタイプを取得(0は単層、1は二段式ナンバープレート)
  4. 透視変換でナンバープレート小画像を取得

    • perspective_transform関数を使用してナンバープレート領域を透視変換し、ナンバープレートの正面画像roi_imageを取得
  5. 二段式ナンバープレート処理

    • ナンバープレートタイプが二段式(plate_classが1)の場合、get_split_merge関数を呼び出してナンバープレート小画像を分割・結合処理
  6. ナンバープレート認識

    • with_colorパラメータに基づいてナンバープレート色を認識するか決定
    • get_plate_result関数を呼び出してナンバープレート小画像roi_imageを認識し、ナンバープレート番号plate_number、認識確率rec_probability、および(with_colorがTrueの場合)ナンバープレート色plate_colorと色信頼度color_confidenceを取得
  7. 結果辞書構築

    • ナンバープレートの境界ボックスregion、検出信頼度confidence、角座標corners、ナンバープレート番号plate_number、認識確率rec_probability、ナンバープレート高さroi_image.shape[0]、ナンバープレート色(with_colorがTrueの場合)およびナンバープレートタイプplate_classなどの情報をresult_infoに格納
  8. 結果返却

    • ナンバープレート認識結果を含む辞書result_infoを返却

3.5 ナンバープレート検出モデル訓練

  1. 一定量のデータセットをダウンロード

    一定量のデータセットをダウンロードする必要があり、プロジェクト作成者が提供しています。必要であればプロジェクト作成者に連絡してください。形式はYOLOv5形式です。

    label x y w h  pt1x pt1y pt2x pt2y pt3x pt3y pt4x pt4y
    

    独自のデータセットはlablmeソフトウェア、create polygonsでナンバープレートの4点を注釈し、json2yolo.pyでデータセットをYOLO形式に変換して訓練できます。

  2. data/widerface.yamlのtrainとvalパスを変更し、自分のデータパスに置き換えます

    train: /your/train/path # あなたの訓練セットパスに修正
    val: /your/val/path     # あなたの検証セットパスに修正
    # クラス数
    nc: 2                 # ここでは2クラス分類、0 単層ナンバープレート 1 二段式ナンバープレート
    
    # クラス名
    names: [ 'single','double']
    
  3. 訓練コマンド

    python3 train.py --data data/widerface.yaml --cfg models/yolov5n-0.5.yaml --weights weights/plate_detect.pt --epoch 120
    

3.6 ナンバープレート認識モデル訓練

  1. データセットダウンロード
    データセットは自分で収集するか、プロジェクト作成者から提供を受けられます

  2. データセットにラベルを付けて、train.txtとval.txtを生成

    画像名付けは上図のように:ナンバープレート番号_番号.jpg その後、以下のコマンドを実行してtrain.txtval.txtを取得

    python plateLabel.py --image_path your/train/img/path/ --label_file datasets/train.txt
    python plateLabel.py --image_path your/val/img/path/ --label_file datasets/val.txt
    

    データ形式は以下の通り:

    train.txt

    /mnt/Gu/trainData/plate/new_git_train/CCPD_CRPD_ALL/冀BAJ731_3.jpg 5 53 52 60 49 45 43 
    /mnt/Gu/trainData/plate/new_git_train/CCPD_CRPD_ALL/冀BD387U_2454.jpg 5 53 55 45 50 49 70 
    /mnt/Gu/trainData/plate/new_git_train/CCPD_CRPD_ALL/冀BG150C_3.jpg 5 53 58 43 47 42 54 
    /mnt/Gu/trainData/plate/new_git_train/CCPD_CRPD_OTHER_ALL/皖A656V3_8090.jpg 13 52 48 47 48 71 45 
    /mnt/Gu/trainData/plate/new_git_train/CCPD_CRPD_OTHER_ALL/皖C91546_7979.jpg 13 54 51 43 47 46 48 
    /mnt/Gu/trainData/plate/new_git_train/CCPD_CRPD_OTHER_ALL/皖G88950_1540.jpg 13 58 50 50 51 47 42 
    /mnt/Gu/trainData/plate/new_git_train/CCPD_CRPD_OTHER_ALL/皖GX9Y56_2113.jpg 13 58 73 51 74 47 48 
    
  3. train.txt val.txtパスをlib/config/360CC_config.yamlに記述

    DATASET:
      DATASET: 360CC
      ROOT: ""
      CHAR_FILE: 'lib/dataset/txt/plate2.txt'
      JSON_FILE: {'train': 'datasets/train.txt', 'val': 'datasets/val.txt'}
    
  4. 訓練コマンド実行

    python train.py --cfg lib/config/360CC_config.yaml
    

3.7 画像認識テスト

  1. プロジェクト基本画像セット
    プロジェクトには既にいくつかのナンバープレート画像が保存されていますので、これらの画像でテストできます

  2. 画像検出コマンド実行

    python detect_plate.py --detect_model weights/plate_detect.pt  --rec_model weights/plate_rec_color.pth --image_path imgs --output result
    

    コマンド入力後、約3秒後にプログラムが継続的に認識結果を出力し、プログラム起動速度は非常に速いです。全体の認識時間は約8.5秒、1枚の画像認識速度は約0.5秒で、この速度は非常に速いです。

  3. 画像認識結果

3.8 動画認識テスト

  1. プロジェクト基本認識動画
    動画:

    YOLOv2ナンバープレート認識動画

    動画スクリーンショット:
    動画時間は約54秒、動画形式はmp4、動画内容は主に走行中の車両の後ろから道路の車両を撮影したものです。

  2. 動画をプロジェクトファイルにアップロード
    私はMobaXterm接続ソフトウェアを使用しましたが、ファイルアップロードが頻繁に停止するため、Xftpで再度接続して動画をアップロードしました。

  3. 動画検出コマンド実行

    python detect_plate.py --detect_model weights/plate_detect.pt  --rec_model weights/plate_rec_color.pth --video 2.mp4
    

    実行コマンド入力後、システムは約5秒後に起動し、動画をフレーム単位で処理します。
    処理中
    動画の後半には車がなくなるため、処理表示されるのは空フレームであり、意味がありませんのでここでは表示しません。
    54秒の動画で1501フレームに分割され、香橙派 Orange Pi AIproボードの処理速度は約2.7 fpsで、他の開発ボードと比較しても十分な速度です。

4. トラブル解決

  1. 書き込みエラー
    エラー表示

    問題が発生しました。元のイメージが圧縮されていた場合、破損していないか確認してください。
    The writer process ended unexpectedly

    スキップボタンをクリックすればOKです

  2. pingコマンド使用不可
    解決方法:

    sudo setcap cap_net_raw+p /bin/ping
    

    正常に解決

5. 使用体験とまとめ

使用感は非常に良かったです。ボードに電源を入れて起動後、5秒以内に冷却ファンのノイズは大きめですが、5秒後には音が非常に小さくなり、その後プロジェクトを実行している間も音量に大きな変化はなく、冷却性能も非常に優れており、全体の使用体験でボードの温度は常に通常の温度を維持し、高温になることはありませんでした。

個人的な体験以外にも、OrangePi AIproは卓越したプラグアンドプレイ機能により、開発プロセスを大幅に加速します。簡単な設定手順により、面倒なハードウェアデバッグ手順を省略できます。ソフトウェアエコシステム面では、香橙派コミュニティがリッチなリソースと詳しいドキュメントを持つエコシステムを構築しており、初心者は簡単にアクセスして適切なテキスト認識モデルとフレームワークを選択し、直感的なガイドを通じて開発ボードに迅速に展開できます。

タグ: YOLOv5 ナンバープレート認識 香橙派 OrangePi AI開発ボード

7月22日 00:50 投稿