車牌辨識的正確率
車牌辨識能否完全正確,決定此 app 可否商業化,很不幸的,無法完全 100% 正確。
車牌辨識的正確率受限於影像大小、位置、角度、拍攝光線亮度。所以停車場為了要提高辨識精準度,一定會在車子的進出入口,在特定距離、特定角度進行拍攝。在這二個限定條件下,就可以提高精準度,但還是會有例外。所以繳費時,車主只能輸入 4 位車牌數字號碼,然後在眾多照片中選擇是那一台車,再進行付費。
如果是路上隨機拍攝的 Video 影片辨識呢? 這就有很多調校的方式,目前還沒有絕對精準的方法。
車牌位置偵測方法
OpenCv-python
網路上關於車牌位置的偵測,許多介紹都是使用 opencv-python 的灰階化(BGR2GRAY)->高斯模糊(cv2.GaussianBlur)->邊緣檢測(cv2.Canny)->輪廓檢測(cv2.findCoutours),最後定出車牌的四方矩型位置。這種方法其實無法商業化,尤其是車牌是白底黑字,然後車身又是白色,就完全無用。
YOLO
使用 YOLO 訓練車牌辨識是最精準的方式,此方法適用各國的車型及顏色。
安裝套件
要執行 yolo v8 版,需安裝如下套件,請依序執行如下指令
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu129 --no-cache-dir
pip install ultralytics pytesseract
下載訓練資料
請到 kaggle 下載 car-plate-license 車牌圖片訓練資料,亦可由本站下載 archive.zip。解開 .zip 檔後,會產生 archive 目錄。
xml2txt
archive/annotations 下的資料為 xml 格式,需將 xml 轉成 txt 格式,請新增 xml2txt.py 檔
import os
import shutil
import xml.etree.ElementTree as ET
from xml.dom.minidom import parse
anno_path = "./archive/annotations"
classes = {"licence":0}
labels_path="./archive/labels"
if os.path.exists(labels_path):
shutil.rmtree(labels_path)
os.mkdir(labels_path)
for annotations in os.listdir(anno_path):
dom = parse(os.path.join(anno_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(os.path.join(labels_path, 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")
執行完畢後,會產生 archive/labels 目錄,裏面的檔案就是轉換後的 txt 檔
分割訓練及驗証資料
新增 split.py 檔,將 90% 的資料置入 train, 10% 放置到 val。
import os
import random
import shutil
archive_path='./archive'
dataset_path="./dataset"
images_path=os.path.join(dataset_path, "images")
labels_path=os.path.join(dataset_path, "labels")
if os.path.exists(dataset_path):
shutil.rmtree(dataset_path)
for path in [images_path, labels_path]:
for subdir in ['train', 'val']:
os.makedirs(os.path.join(path, subdir))
files=[os.path.splitext(file)[0]
for file in os.listdir(os.path.join(archive_path, "images"))]
random.shuffle(files)
mid=int(len(files)*0.9)
for i, file in enumerate(files):
source=os.path.join(archive_path, "images", f'{file}.png')
if i < mid:
target=os.path.join(images_path,"train", f'{file}.png')
else:
target=os.path.join(images_path,"val", f'{file}.png')
print(source, target)
shutil.copy(source, target)
source=os.path.join(archive_path, "labels", f'{file}.txt')
if i < mid:
target=os.path.join(labels_path,"train", f'{file}.txt')
else:
target=os.path.join(labels_path,"val", f'{file}.txt')
print(source, target)
shutil.copy(source, target)
執行完後,會在 dataset 目錄產生 images 及 labels 二個目錄,裏面分別有 train 及 val 二個目錄。
data.yaml 資料集設定
在 ./dataset 目錄下新增 data.yaml,加入如下設定
train: e:/python/car/dataset/images/train val: e:/python/car/dataset/images/val 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("yolo26l.pt")
print("開始訓練 .........")
t1=time.time()
model.train(data="./dataset/data.yaml", epochs=150, imgsz=640, workers=8)
t2=time.time()
print(f'訓練花費時間 : {t2-t1}秒')
path=model.export()
print(f'模型匯出路徑 : {path}')
訓練完成後,會在專案下的 runs/detect/train/weights 產生 best.pt 模型。
無法訓練的人,請由如下網址下載 : runs.zip。下載後請解壓縮至此。
車牌偵測及辨識
車牌位置偵測及英數字辨識代碼如下,底下代碼可以按一下滑鼠左鍵切換到下一張圖。
測試圖片請由本站下載 images.zip,下載完請解壓縮至此。
import os, cv2, pytesseract, pylab as plt, numpy as np
from PIL import Image, ImageFont, ImageDraw
from ultralytics import YOLO
from cv import cv
def text(img, text, xy=(0, 0), color=(0, 0, 0), size=20):
pil = Image.fromarray(img)
font = ImageFont.truetype('simsun.ttc', size)
ImageDraw.Draw(pil).text(xy, text, font=font, fill=color)
return np.asarray(pil).copy()
def detect(file):
img=cv.read(file)[:,:,::-1].copy()
results=model.predict(img, save=False)
boxes=results[0].boxes
for box in boxes:
x1, y1, x2, y2 = box.xyxy[0].cpu().numpy().astype(int)
img=cv2.rectangle(img,(x1, y1), (x2, y2), (0,255,0) , 2)
detect_img=cv.crop(img, (x1, y1), (x2, y2))
detect_img=cv2.cvtColor(detect_img, cv2.COLOR_RGB2GRAY)
num = pytesseract.image_to_string(detect_img, lang='eng', config='--psm 11')
img=text(img, num, (x1, y1-20), (0,255,0),25)
return img
def onclick(event):
global current
current=(current+1)%len(files)
img=detect(files[current])
im.set_data(img)
fig.canvas.draw()
model=YOLO('./runs/detect/train/weights/best.pt')
path="./images"
files=[os.path.join(path, file) for file in os.listdir(path)]
current=0
fig, ax = plt.subplots()
im = ax.imshow(detect(files[current]))
fig.canvas.mpl_connect('button_press_event', onclick)
ax.axis("off")
plt.show()
