diff --git a/.idea/DemoProject02.iml b/.idea/DemoProject02.iml index d0876a7..909438d 100644 --- a/.idea/DemoProject02.iml +++ b/.idea/DemoProject02.iml @@ -2,7 +2,7 @@ - + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 7d61941..aef59c4 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,5 +3,5 @@ - + \ No newline at end of file diff --git a/TestSet/中、大型汽车/川A802V6.jpg b/TestSet/中、大型汽车/川A802V6.jpg new file mode 100644 index 0000000..60daa88 Binary files /dev/null and b/TestSet/中、大型汽车/川A802V6.jpg differ diff --git a/TestSet/中、大型汽车/沪A33333.jpg b/TestSet/中、大型汽车/沪A33333.jpg new file mode 100644 index 0000000..cf84ae7 Binary files /dev/null and b/TestSet/中、大型汽车/沪A33333.jpg differ diff --git a/TestSet/军警、应急车辆/粤S3461.jpg b/TestSet/军警、应急车辆/粤S3461.jpg new file mode 100644 index 0000000..a83380c Binary files /dev/null and b/TestSet/军警、应急车辆/粤S3461.jpg differ diff --git a/TestSet/军警、应急车辆/苏C985A.jpg b/TestSet/军警、应急车辆/苏C985A.jpg new file mode 100644 index 0000000..85ecc94 Binary files /dev/null and b/TestSet/军警、应急车辆/苏C985A.jpg differ diff --git a/TestSet/场内车辆/粤AD0053.jpeg b/TestSet/场内车辆/粤AD0053.jpeg new file mode 100644 index 0000000..4b5869c Binary files /dev/null and b/TestSet/场内车辆/粤AD0053.jpeg differ diff --git a/TestSet/场内车辆/辽B04494.jpg b/TestSet/场内车辆/辽B04494.jpg new file mode 100644 index 0000000..8466f38 Binary files /dev/null and b/TestSet/场内车辆/辽B04494.jpg differ diff --git a/TestSet/外籍车/京A3429I.jpg b/TestSet/外籍车/京A3429I.jpg new file mode 100644 index 0000000..0a8df11 Binary files /dev/null and b/TestSet/外籍车/京A3429I.jpg differ diff --git a/TestSet/外籍车/鲁AL88888.jpg b/TestSet/外籍车/鲁AL88888.jpg new file mode 100644 index 0000000..17ce459 Binary files /dev/null and b/TestSet/外籍车/鲁AL88888.jpg differ diff --git a/TestSet/大型新能源车/粤AAKL8434.jpg b/TestSet/大型新能源车/粤AAKL8434.jpg new file mode 100644 index 0000000..df5a3c3 Binary files /dev/null and b/TestSet/大型新能源车/粤AAKL8434.jpg differ diff --git a/TestSet/大型新能源车/粤BBQ9397.jpg b/TestSet/大型新能源车/粤BBQ9397.jpg new file mode 100644 index 0000000..db21277 Binary files /dev/null and b/TestSet/大型新能源车/粤BBQ9397.jpg differ diff --git a/TestSet/小型新能源汽车/京AC11112.jpg b/TestSet/小型新能源汽车/京AC11112.jpg new file mode 100644 index 0000000..cd0c95b Binary files /dev/null and b/TestSet/小型新能源汽车/京AC11112.jpg differ diff --git a/TestSet/小型新能源汽车/沪AAR0112.jpg b/TestSet/小型新能源汽车/沪AAR0112.jpg new file mode 100644 index 0000000..1b0529f Binary files /dev/null and b/TestSet/小型新能源汽车/沪AAR0112.jpg differ diff --git a/TestSet/小型汽车/浙C99229.jpg b/TestSet/小型汽车/浙C99229.jpg new file mode 100644 index 0000000..5262716 Binary files /dev/null and b/TestSet/小型汽车/浙C99229.jpg differ diff --git a/TestSet/小型汽车/粵B99999.jpg b/TestSet/小型汽车/粵B99999.jpg new file mode 100644 index 0000000..386fb0c Binary files /dev/null and b/TestSet/小型汽车/粵B99999.jpg differ diff --git a/classification_ai.py b/classification_ai.py new file mode 100644 index 0000000..2e0f80f --- /dev/null +++ b/classification_ai.py @@ -0,0 +1,61 @@ +""" + 模块作者: + AI代码结构:刘钰廷、冯雅君 + 代码优化整理:王昱博、冯昌盛 + AI模型训练/纠错:刘钰廷、冯雅君、冯昌盛 + 代码整合/打包:王昱博 + 模块用途: + 图像分类AI,用于区分车牌的具体类型 +""" + +import cv2 +from PIL import Image +from pathlib import Path +from fastai.vision.all import * +from fastai.metrics import error_rate +from fastai.learner import load_learner +from torchvision.models import resnet34 +from fastai.vision.data import ImageBlock +from fastai.vision.core import imagenet_stats +from fastai.data.block import CategoryBlock, DataBlock +from fastai.vision.augment import Resize, aug_transforms +from fastai.vision.learner import cnn_learner, vision_learner +from fastai.data.transforms import get_image_files, parent_label, RandomSplitter, Normalize + + +class ClassificationAI: + @staticmethod + def ConvertImage(cv_img: cv2.Mat) -> Image.Image: + return Image.fromarray(cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB)).resize((460, 460)) + + @classmethod + def TrainAI(cls, data_set_path: str, export_path: str) -> None: + blocks = (ImageBlock, CategoryBlock) + batch_size = 32 + dls = DataBlock( + blocks=blocks, + get_items=get_image_files, + splitter=RandomSplitter(), + get_y=parent_label, + item_tfms=Resize(460), + batch_tfms=[*aug_transforms(size=224, min_scale=0.75), Normalize.from_stats(*imagenet_stats)] + ).dataloaders(data_set_path, num_workers=4, bs=batch_size) + model = vision_learner(dls, resnet34, metrics=error_rate) + model.fine_tune(5, freeze_epochs=3) # 5 - 训练的轮次, 3 - 冻结的轮次 + model.export(Path(export_path) / 'model.pkl') + + @classmethod + def PredictImage(cls, image: cv2.Mat, model_path: str) -> tuple: + # 加载模型 + model = load_learner(model_path) + # 读取图片并转换为Tensor + img = cls.ConvertImage(image) # 读取图像文件 + # 进行预测 + pred_class, pred_idx, outputs = model.predict(img) + # 获取置信度 + # 检查输出张量的维度 + if outputs.dim() == 0: + confidence = float(outputs) + else: + confidence = float(outputs[pred_idx]) + return pred_class, confidence diff --git a/classify_model/0.0625-2.pkl b/classify_model/0.0625-2.pkl new file mode 100644 index 0000000..b7fe389 Binary files /dev/null and b/classify_model/0.0625-2.pkl differ diff --git a/classify_model/0.0625.pkl b/classify_model/0.0625.pkl new file mode 100644 index 0000000..a7fe4f5 Binary files /dev/null and b/classify_model/0.0625.pkl differ diff --git a/classify_model/0.125.pkl b/classify_model/0.125.pkl new file mode 100644 index 0000000..b7b6edd Binary files /dev/null and b/classify_model/0.125.pkl differ diff --git a/cut_image.py b/cut_image.py new file mode 100644 index 0000000..e6cc372 --- /dev/null +++ b/cut_image.py @@ -0,0 +1,66 @@ +""" + 模块作者: + 图像预处理:潘浩宇 + 轮廓寻找与切分:戴晓齐 + 代码优化/整合/打包:王昱博 + 模块用途: + 对车牌图片进行预处理和切分,找出包含车牌号的部分 +""" + +import cv2 + +class ImageCutter: + @staticmethod + # 图像去噪灰度处理,消除噪点 + def gray_guss(image): + image = cv2.GaussianBlur(image, (3, 3), 0) + gray_image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY) + return gray_image + + @classmethod + def ImagePreProcess(cls, image_path: str) -> tuple: + # 复制一张图片,在复制图上进行图像操作,保留原图 + origin_image = cv2.imread(image_path) + # 图像去噪灰度处理 + image = origin_image.copy() + # x方向上的边缘检测(增强边缘信息) + gray_image = cls.gray_guss(image) + Sobel_x = cv2.Sobel(gray_image, cv2.CV_16S, 1, 0) + absX = cv2.convertScaleAbs(Sobel_x) + image = absX + # 图像阈值化操作——获得二值化图,将像素置为0或者255。将灰度转成黑白 + ret, image = cv2.threshold(image, 0, 255, cv2.THRESH_OTSU) + # 形态学(从图像中提取对表达和描绘区域形状有意义的图像分量)——闭操作 + # 使用形状为(30,10)的矩形kernelX对图像进行偏X方向的闭运算,将图像进行X方向融合找出车牌区域。 + kernelX = cv2.getStructuringElement(cv2.MORPH_RECT, (30, 10)) + image = cv2.morphologyEx(image, cv2.MORPH_CLOSE, kernelX, iterations=1) + return origin_image, image + + @classmethod + def CutPlateRect(cls, origin_image: cv2.Mat, image: cv2.Mat) -> cv2.Mat: + # 去除细小的边缘 + # 腐蚀(erode)和膨胀(dilate) + kernelX = cv2.getStructuringElement(cv2.MORPH_RECT, (50, 1)) + kernelY = cv2.getStructuringElement(cv2.MORPH_RECT, (1, 20)) + # x方向进行闭操作(抑制暗细节) + image = cv2.dilate(image, kernelX) + image = cv2.erode(image, kernelX) + # y方向的开操作 + image = cv2.erode(image, kernelY) + image = cv2.dilate(image, kernelY) + # 中值滤波(去噪)将边缘平滑 + image = cv2.medianBlur(image, 21) + # 获得轮廓 RETR_EXTERNAL矩形的外边缘 + contours, hierarchy = cv2.findContours(image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + # 筛选 + for item in contours: + rect = cv2.boundingRect(item) + x = rect[0] + y = rect[1] + weight = rect[2] + height = rect[3] + # 根据轮廓的形状特点,确定车牌的轮廓位置并截取图像 + if (weight > (height * 3.5)) and (weight < (height * 4)): # 对长宽比例进行确定 + _image = origin_image[y:y + height, x:x + weight] # 对图片进行裁剪 + return cv2.Mat(_image) + return origin_image diff --git a/easyocr_model/craft_mlt_25k.pth b/easyocr_model/craft_mlt_25k.pth new file mode 100644 index 0000000..7461d59 Binary files /dev/null and b/easyocr_model/craft_mlt_25k.pth differ diff --git a/easyocr_model/zh_sim_g2.pth b/easyocr_model/zh_sim_g2.pth new file mode 100644 index 0000000..e7d9dad Binary files /dev/null and b/easyocr_model/zh_sim_g2.pth differ diff --git a/main.py b/main.py index 8b13789..c257a41 100644 --- a/main.py +++ b/main.py @@ -1 +1,52 @@ +""" + 主程序作者:王昱博 + 车牌识别系统: + 使用OCR技术对车牌号码进行识别 + 使用图像分类AI对车牌种类进行区分 +""" +import cv2 + +from ocr import OCR +from cut_image import ImageCutter +from classification_ai import ClassificationAI + +classify_models = ['.\\classify_model\\0.0625.pkl', '.\\classify_model\\0.0625-2.pkl', '.\\classify_model\\0.125.pkl'] + + +def train(train_set_path: str, export_path: str) -> None: + ClassificationAI.TrainAI(train_set_path, export_path) + + +def main(classify_model_index: int, image_path: str) -> None: + global classify_models + origin_image, gray_image = ImageCutter.ImagePreProcess(image_path) + lpr_text, lpr_conf, cut_image = OCR.RecognizeLicensePlate2(origin_image) + ocr_text, ocr_type = OCR.RecognizeLicensePlate(cut_image) + ai_type, ai_conf = ClassificationAI.PredictImage(cut_image, classify_models[classify_model_index]) + print(f'识别完成,以下为识别结果:\n车牌号:{lpr_text} [置信度:{lpr_conf}]\n车牌类型:\n\t{ocr_type}(OCR推测)\n\t{ai_type}(AI分类识别)\n\tAI识别置信度:{ai_conf}') + cv2.waitKey(0) + + +if __name__ == '__main__': + result = input('请选择运行模式(训练(y)/识别(n)): ') + if result == 'y' or result == 'Y': + data_path = input('输入训练集路径: ') + export_path = input('输入模型保存路径: ') + try: + train(data_path, export_path) + except Exception as e: + print(f'训练过程中发生错误: {e}') + else: + print('模型已成功训练') + finally: + print('训练结束') + elif result == 'n' or result == 'N': + model_index = input('选择使用的识别模型(1/2/3): ') + image_path = input('输入图片路径: ') + if (not model_index.isdigit()) or (int(model_index) < 1) or (int(model_index) > 3): + print('输入有误') + else: + main(int(model_index), image_path) + else: + print('输入有误') diff --git a/ocr.py b/ocr.py new file mode 100644 index 0000000..fdb9c15 --- /dev/null +++ b/ocr.py @@ -0,0 +1,46 @@ +""" + 模块作者: + 代码编写:焦雅雯 + 代码优化/整合/打包:王昱博 + 模块用途: + 使用OCR库进行车牌号识别和初步分类 +""" + +import cv2 +import easyocr +import hyperlpr3 as lpr3 + + +class OCR: + @staticmethod + def SwapChars(text: str) -> str: + text = text.replace('I', '1') + text = text.replace('O', '0') + return text + + @classmethod + def RecognizeLicensePlate(cls, image: cv2.Mat) -> tuple: + reader = easyocr.Reader(['ch_sim', 'en'], model_storage_directory='./easyocr_model') + result = reader.readtext(image) + license_plate = "" + for res in result: + license_plate += res[-2] # 如果车牌号码是两行的,按行识别出来再拼接起来 + if '\u8b66' in license_plate: + car_type = 'police' + elif '\u573a\u5185' in license_plate: + car_type = 'internal' + elif '\u6302' in license_plate: + car_type = 'bigCar' + else: + car_type = 'smallCar' + return license_plate, car_type + + @classmethod + def RecognizeLicensePlate2(cls, image: cv2.Mat): + reco = lpr3.LicensePlateCatcher() + results = reco(image) + for code, conf, _type, box in results: + x0, y0, x1, y1 = box + cut_image = image[y0:y1, x0:x1] + return code, conf, cut_image + return None, None, image diff --git a/车牌分类示例.zip b/车牌分类示例.zip new file mode 100644 index 0000000..6db560e Binary files /dev/null and b/车牌分类示例.zip differ