YOLOV8車牌辨識

      在〈YOLOV8車牌辨識〉中尚無留言

車牌辨識的正確率

車牌辨識能否完全正確,決定此 app 可否商業化,很不幸的,無法完全 100% 正確。

車牌辨識的正確率受限於影像大小、位置、角度、拍攝光線亮度。所以停車場為了要提高辨識精準度,一定會在車子的進出入口,在特定距離、特定角度進行拍攝。在這二個限定條件下,就可以提高精準度,但還是會有例外。所以繳費時,車主只能輸入 4 位車牌數字號碼,然後在眾多照片中選擇是那一台車,再進行付費。

如果是路上隨機拍攝的 Video 影片辨識呢? 這就有很多調校的方式,目前還沒有絕對精準的方法。

車牌位置偵測方法

OpenCv-python

網路上關於車牌位置的偵測,許多介紹都是使用 opencv-python 的灰階化(BGR2GRAY)->高斯模糊(cv2.GaussianBlur)->邊緣檢測(cv2.Canny)->輪廓檢測(cv2.findCoutours),最後定出車牌的四方矩型位置。這種方法其實無法商業化,尤其是車牌是白底黑字,然後車身又是白色,就完全無用。

YOLOv8

使用 YOLOv8 訓練車牌辨識是最精準的方式,此方法適用各國的車型及顏色。

安裝套件

要執行 yolo v8 版,需安裝如下套件,請依序執行如下指令

pip install torch==2.0.1+cu118 torchvision==0.15.2+cu118 torchaudio===2.0.2+cu118 -f https://download.pytorch.org/whl/cu118/torch_stable.html
pip install ultralytics pytesseract

下載訓練資料

請到 kaggle 下載 car-plate-license 車牌圖片訓練資料,解開後,將 images 及 annotations 目錄移到專案中的 car 目錄下。解開 .zip 檔後,將 “archive” 目錄改為 “car”。

xml2txt

car/annotations 下的資料為 xml 格式,需將 xml 轉成 txt 格式,請新增 xml2txt.py 檔

import os
import xml.etree.ElementTree as ET
from xml.dom.minidom import parse
path = "./car/annotations"
classes = {"licence":0}
labels_path="./car/labels"
if not os.path.exists(labels_path):
    os.mkdir("./car/labels")
#train_path=os.path.join(labels_path,"licence")
train_path=labels_path
if not os.path.exists(train_path):
    os.mkdir(train_path)
for annotations in os.listdir(path):
    dom = parse(os.path.join(path,annotations))
    root = dom.documentElement
    filename = ".txt".join(root.getElementsByTagName("filename")[0].childNodes[0].data.split(".png"))
    image_width = root.getElementsByTagName("width")[0].childNodes[0].data
    image_height = root.getElementsByTagName("height")[0].childNodes[0].data
    with open("./car/labels/"+filename,"w") as r:
        for items in root.getElementsByTagName("object") :
            name = items.getElementsByTagName("name")[0].childNodes[0].data
            xmin = items.getElementsByTagName("xmin")[0].childNodes[0].data
            ymin = items.getElementsByTagName("ymin")[0].childNodes[0].data
            xmax = items.getElementsByTagName("xmax")[0].childNodes[0].data
            ymax = items.getElementsByTagName("ymax")[0].childNodes[0].data
            x_center_norm = ((int(xmin)+int(xmax)) / 2 ) / int(image_width)
            y_center_norm = ((int(ymin)+int(ymax))/2) / int(image_height)
            width_norm = ((int(xmax)-int(xmin))/int(image_width))
            height_norm = ((int(ymax)-int(ymin))/int(image_height))
            r.write(str(classes[name])+" ")
            r.write(str(x_center_norm)+" ")
            r.write(str(y_center_norm)+" ")
            r.write(str(width_norm)+" ")
            r.write(str(height_norm)+"\n")

執行完畢後,會產生 car/labels 目錄,裏面的檔案就是轉換後的 txt 檔

分割訓練及驗証資料

新增 split.py 檔,將 90% 的圖片列入 train.txt, 10% 為 val.txt

import os
import random
import shutil
data_path='./car'
train_path='./train'
valid_path='./valid'
if os.path.exists(train_path):
    shutil.rmtree(train_path)
if os.path.exists(valid_path):
    shutil.rmtree(valid_path)
os.makedirs(os.path.join(train_path, 'images'))
os.makedirs(os.path.join(train_path, 'labels'))
os.makedirs(os.path.join(valid_path, 'images'))
os.makedirs(os.path.join(valid_path, 'labels'))

files=[os.path.splitext(file)[0]
       for file in os.listdir(os.path.join(data_path, "images"))]
random.shuffle(files)
mid=int(len(files)*0.8)
for file in files[:mid]:
    source=os.path.join(data_path, "images", f'{file}.png')
    target=os.path.join(train_path,"images", f'{file}.png')
    print(source, target)
    shutil.copy(source, target)

    source=os.path.join(data_path, "labels", f'{file}.txt')
    target=os.path.join(train_path,"labels", f'{file}.txt')
    print(source, target)
    shutil.copy(source, target)

for file in files[mid:]:
    source=os.path.join(data_path, "images", f'{file}.png')
    target=os.path.join(valid_path,"images", f'{file}.png')
    print(source, target)
    shutil.copy(source, target)

    source=os.path.join(data_path, "labels", f'{file}.txt')
    target=os.path.join(valid_path,"labels", f'{file}.txt')
    print(source, target)
    shutil.copy(source, target)

執行完後,會在 car 目錄產生 train.txt 及 val.txt 二個檔案

data.yaml 資料集設定

在專案根目錄下新增 data.yaml,加入如下設定

train: e:/python_ai/car/train/images
val: e:/python_ai/car/valid/images
nc: 1
names: [ 'license']

訓練模型

新增 train.py 檔,代碼如下

import os
import shutil
import time
from ultralytics import YOLO

if __name__=='__main__':
    train_path="./runs/detect/train"
    if os.path.exists(train_path):
        shutil.rmtree(train_path)
    model = YOLO("yolov8l.pt")
    print("開始訓練 .........")
    t1=time.time()
    model.train(data="./data.yaml", epochs=150, imgsz=640)
    t2=time.time()
    print(f'訓練花費時間 : {t2-t1}秒')#2131.11984705925秒, epochs : 121
    path=model.export()
    print(f'模型匯出路徑 : {path}')

訓練完成後,會在專案下的 runs/detect/train/weights 產生 best.pt 模型。

無法訓練的人,請由如下網址下載 : car.pt

車牌偵測及辨識

車牌位置偵測及英數字辨識代碼如下

import os
import platform
import pylab as plt
import cv2
import numpy as np
import pytesseract
from PIL import Image, ImageFont, ImageDraw
from ultralytics import YOLO

def text(img, text, xy=(0, 0), color=(0, 0, 0), size=20):
    pil = Image.fromarray(img)
    s = platform.system()
    if s == "Linux":
        font =ImageFont.truetype('/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc', size)
    elif s == "Darwin":
        font = ImageFont.truetype('....', size)
    else:
        font = ImageFont.truetype('simsun.ttc', size)
    ImageDraw.Draw(pil).text(xy, text, font=font, fill=color)
    return np.asarray(pil)

model=YOLO('./runs/detect/train/weights/best.pt')
path="./valid/images"
plt.figure(figsize=(12,9))
for i,file in enumerate(os.listdir(path)[0:6]):
    full=os.path.join(path, file)
    print(full)
    img=cv2.imdecode(np.fromfile(full, dtype=np.uint8), cv2.IMREAD_COLOR)
    img=img[:,:,::-1].copy()

    results=model.predict(img, save=False)
    boxes=results[0].boxes.xyxy
    for box in boxes:
        #box=box.cpu().numpy()
        x1 = int(box[0])
        y1 = int(box[1])
        x2 = int(box[2])
        y2 = int(box[3])
        cv2.rectangle(img,(x1, y1), (x2, y2), (0,255,0) , 2)

        tmp=cv2.cvtColor(img[y1:y2,x1:x2].copy(), cv2.COLOR_RGB2GRAY)
        license = pytesseract.image_to_string(tmp, lang='eng', config='--psm 11')
        img=text(img, license, (x1, y1-20), (0,255,0),25)
    plt.subplot(2,3,i+1)
    plt.axis("off")
    plt.imshow(img)
plt.savefig("yolov8_car.jpg")
plt.show()

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *