简介:一份面向Python开发者的车牌识别参考项目源码,适合正在学习图像处理、模式识别或智能交通场景的读者。项目基于Python 3.6/3.7开发,集成PyQt5界面与OpenCV图像处理库,完整演示了车牌检测与识别逻辑,可帮助新手看清从图像输入到结果输出的整体流程。压缩包内含2000个文件,其中1987张jpg车辆与车牌样本用于训练或测试,7个py脚本为核心实现,另有3个xml配置、2个md说明和1个ui界面文件,整体仅25.53MB。已有310人学习该资源,内容小巧但结构完整,支持在opencv-python 3.4.3与4.2.0环境下运行。通过源码可掌握车牌定位、字符分割等关键细节,亦可在PyQt5界面上快速验证效果,适合作为二次开发或课程设计参考。
1. 车牌识别参考项目:别急着上深度学习,先搞清楚这套源码在解决什么问题
在拿到这个【源码】.zip 之前,先回答一个问题:为什么要找“参考项目”而不是直接克隆一个 YOLOv8 车检模型?答案很实际——车牌识别(License Plate Recognition, LPR)在生产环境里从来不是“一个模型”搞定的事,它是一条链路:车牌定位 → 字符分割 → 字符识别。很多开源参考项目做的正是这条完整链路,而不是某个单点算法。对刚接触 Python 视觉项目的人说,这种项目最大的价值是让你在一套代码里同时看到图像预处理、轮廓分析、模板匹配乃至简单分类器的配合方式。适合的人群很明确:课程设计、毕业设计、入门练手、想在公司内部快速搭一个 demo 验证可行性的人。这类参考项目通常不追求极致精度,但胜在结构清晰、依赖少、能在普通笔记本上跑动。
2. 从 OpenCV 到识别结果:参考项目里最常见的四条技术选型
2.1 为什么参考项目偏爱 OpenCV + 传统图像处理,而不是直接上 CNN
拿到一个车牌识别参考项目,先看它的依赖清单。常见组合是opencv-python、numpy、matplotlib、可能加一个sklearn或PIL。几乎不会上来就torch或tensorflow。这是因为参考项目的定位是“跑通链路”,不是“刷精度”。传统图像处理在车牌这种强约束场景下其实够用:车牌有固定颜色(蓝底白字、黄底黑字、绿底黑字)、固定宽高比(440×140 左右,约 3:1)、字符排列规则。把这三条先验用 OpenCV 的 HSV 颜色分割、轮廓查找、透视变换实现,比训练一个检测网络要快得多,而且代码可读性高,每步都能可视化。Python 在这类项目里最大的优势不是性能,而是生态——cv2的函数名几乎就是图像处理术语表,配合matplotlib一步步画出来,新手能清晰看到“原图 → 掩码 → 候选区 → 裁剪 → 识别”的每个中间产物。
2.2 定位阶段的常用套路:HSV 颜色空间 + 形态学操作
车牌定位是整个识别链路里翻车率最高的环节,参考项目里最常见的做法是先在 HSV 空间里筛颜色。BGR 转 HSV 用cv2.cvtColor,然后针对蓝色车牌设置阈值范围。这里有一个新手必踩的坑:OpenCV 的 HSV 范围是 H(0-180)、S(0-255)、V(0-255),不是网上很多教程写的 H(0-360)。蓝色车牌的典型范围是 H 在 100-124,S 大于 80,V 大于 80。设好范围后用cv2.inRange生成二值掩码,接着做一次闭运算(cv2.morphologyEx+cv2.MORPH_CLOSE)把车牌区域内部的字符空洞填上。闭运算的核大小通常取 5×5 到 15×15 之间,太大会把相邻车辆区域粘连在一起,太小则车牌内部的字符缝隙消不掉。定位阶段最后的收尾工作是cv2.findContours找外轮廓,再用宽高比和面积比做筛选——车牌轮廓的宽高比大约在 2.5 到 4.5 之间,面积占整图的比例需要根据图像尺寸动态算,写死一个绝对像素阈值是常见错误。
import cv2 import numpy as np def locate_plate(img): # 1. 转为HSV,便于按颜色分割 hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # 2. 蓝色车牌阈值,OpenCV 的H范围是0-180 lower_blue = np.array([100, 80, 80]) upper_blue = np.array([124, 255, 255]) mask = cv2.inRange(hsv, lower_blue, upper_blue) # 3. 闭运算:填掉字符间的空隙,让车牌连成一个整体 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15, 5)) closed = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) # 4. 找轮廓并筛选 contours, _ = cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) candidates = [] for cnt in contours: x, y, w, h = cv2.boundingRect(cnt) aspect = w / h area = w * h if 2.5 < aspect < 4.5 and area > img.shape[0] * img.shape[1] * 0.005: candidates.append((x, y, w, h)) return candidates这段代码的逻辑链路是:颜色分割得到“可能是蓝色车牌”的像素区域,形态学闭运算把这些像素连成块,最后用几何规则过滤明显不是车牌的轮廓。参数说明里最值得关注的是kernel的形状——我用的是 (15, 5),水平方向比垂直方向长,因为车牌字符是横排的,字符间隙主要在水平方向,这种非对称核能有效粘连字符而不把上下相邻的物体连进来。0.005这个面积比例阈值是经验值,如果图像是 1920×1080,那么候选区域面积需要大于约 1 万像素,对应一个 100×100 左右的块。如果场景是出入口道闸那种固定机位,这个阈值可以放宽到 0.003;如果是路边随机抓拍,建议收紧到 0.008 以上,否则车身贴纸、蓝色广告牌会大量混进来。
2.3 识别阶段用模板匹配还是轻量分类器:参考项目里两种常见做法
字符识别是第二个分水岭。参考项目里最常见的方案是模板匹配:预先准备 0-9、A-Z、以及省份简称汉字的图片模板,把分割出来的字符图缩放到与模板一致,然后算相似度。OpenCV 里用cv2.matchTemplate,归一化相关系数(TM_CCOEFF_NORMED)超过 0.6 就认为命中。这个方案的优点是零训练成本,缺点是模板的字体、粗细一旦和实际车牌不一致,识别率立刻崩。另一种做法是提取字符图片的 HOG 特征或直接拉平像素,喂给一个sklearn.svm.SVC训练。参考项目里 SVM 方案也不少见,训练数据可以自己截取真实车牌字符,也可以用公开的车牌字符数据集。SVM 的好处是对字体变化有更好的泛化能力,坏处是需要标数据。我的建议是:参考项目先跑通模板匹配,理解全链路之后,再把识别段替换成 SVM 或 LeNet 这类小网络,这样能清晰看到“换识别器”对整体精度的影响,而不至于一上来就被深度学习的环境配置劝退。
def match_char(crop, templates): # crop: 分割出的单个字符图,templates: 字符名到模板图的映射 best_score = -1 best_char = None crop_resized = cv2.resize(crop, (32, 64)) # 统一尺寸 crop_gray = cv2.cvtColor(crop_resized, cv2.COLOR_BGR2GRAY) for char, tmpl in templates.items(): tmpl_resized = cv2.resize(tmpl, (32, 64)) # TM_CCOEFF_NORMED: 归一化相关系数,对光照变化相对鲁棒 score = cv2.matchTemplate(crop_gray, tmpl_resized, cv2.TM_CCOEFF_NORMED)[0][0] if score > best_score: best_score = score best_char = char return best_char if best_score > 0.6 else '?'这里cv2.resize到统一尺寸是关键,模板匹配要求输入与模板大小一致,否则相关系数没有意义。TM_CCOEFF_NORMED计算的是亮度归一化后的相关性,能抵抗一部分光照变化,但不抵抗形变——如果字符有倾斜或透视,分数会明显下降。0.6 这个阈值需要根据实际场景校准,我自己在室内固定机位下测试,阈值放到 0.55 就能跑,但户外强光或逆光场景建议提到 0.7,宁可返回“?”也不要乱识别。注意字符分割的质量直接影响这个阶段的上限:分割出半个字符、或者一个字符被拆成两段,后面不管用什么识别器都救不回来。
2.4 中文车牌里的特殊字符:省份简称的识别是硬骨头
字母和数字一共 34 个类(I 和 O 通常不用),但中文车牌里还有 31 个省市简称,而且这些汉字的结构比字母复杂得多。模板匹配对汉字的效果尤其差——汉字笔画多,缩放后细节丢失严重,TM_CCOEFF_NORMED很容易把“京”和“沪”搞混。参考项目里常见的处理方式有两种:一是对汉字单独做更粗粒度的匹配,比如只看字符的密度分布或宽高比;二是汉字识别单独训练一个分类器,哪怕是个最朴素的 KNN 都比模板匹配靠谱。我在参考项目里见过一个取巧做法:只取车牌第一位做省份简称识别,剩余位用字母数字识别,并且对汉字区域的匹配阈值单独放宽到 0.5 以下,多返回几个候选,让后续逻辑根据上下文纠正。这种做法在“京”“沪”“苏”这类高频省份上能跑出可用的准确率,但遇到“藏”“蒙”“疆”这种笔画密集的简称,基本靠运气。如果你要复制这套方案,建议把省份简称的数据集单独收集,至少每个省份 50 张以上,并且做仿射变换增强,否则训练集和实际场景的字体差异会直接击穿识别精度。
3. 跑通参考项目的五个步骤:环境、数据、脚本、调参与验证
3.1 第一步:确认 Python 版本和 OpenCV 的安装方式
参考项目里如果没写requirements.txt,你需要自己装环境。Python 版本建议 3.8 到 3.10 之间,太新的版本有可能遇到 OpenCV 预编译包还没跟上导致装不上。安装方式直接pip install opencv-python numpy matplotlib。这里有一个常见的坑:opencv-python和opencv-contrib-python是两回事,参考项目如果用了 SIFT、ORB 这类特征提取算子,需要装的是 contrib 版本,否则cv2.SIFT_create()会直接报 AttributeError。很多入门者在这一步就卡住,怀疑是 OpenCV 没装好,其实是装错了包。如果你不确定参考项目用了什么,直接把两个都装上最省事,但要注意两个包不能同时存在,会冲突,建议先卸载再装:
pip uninstall opencv-python opencv-contrib-python -y pip install opencv-contrib-python numpy matplotlib装完验证一下:python -c "import cv2; print(cv2.__version__)"。OpenCV 4.x 版本打印出来的结果是类似4.8.1的字符串,如果你看到的是4.5.5.64这种长版本号,也没问题,那是 OpenCV 4.5.5 的对应包版本。但如果你在 Jupyter Notebook 里运行 import 直接内核崩溃,大概率是 OpenCV 和你系统里已有的库(比如 Qt 或 GTK)冲突,这时候在虚拟环境里重新装一次能避免大多数玄学问题。
3.2 第二步:准备测试数据——参考项目里带的数据集大概率不够用
参考项目自带的图片通常只有十几到几十张,只能验证“代码能跑”,不能验证“方案可用”。你需要自己找补充数据。有两个渠道:一是从网上下载公开的车牌数据集,二是在停车场、小区出入口用手机拍几张。我的经验是:至少准备三种场景的图片——白天顺光、白天逆光、夜间照明。如果参考项目只提供了一种场景的测试图,你换一张夜间图跑,识别率可能直接从 90% 掉到 30%,这不一定是你改坏了代码,而是预处理阶段的固定阈值不适应新场景。把测试数据放在test_images/目录下,写一个批量跑图脚本,统计每张图的识别结果和耗时,你会比单张调试更快找到瓶颈。
import os import cv2 test_dir = 'test_images' for filename in os.listdir(test_dir): if not filename.lower().endswith(('.jpg', '.png', '.jpeg')): continue img = cv2.imread(os.path.join(test_dir, filename)) candidates = locate_plate(img) print(f'{filename}: 候选车牌区域 {len(candidates)} 个') # 继续走分割和识别流程,记录结果3.3 第三步:调参顺序有讲究——先固定颜色阈值,再调几何筛选,最后动识别阈值
我见过很多人拿到参考项目就开始调matchTemplate的阈值,这是错误的顺序。正确顺序是:先打开一张图,把locate_plate阶段的结果画出来看,确认车牌区域有没有被正确框出来。如果没框出来,问题几乎一定在 HSV 范围或闭运算核大小,这时候调识别阈值毫无意义。框出来了,再看字符分割是否干净,最后才轮到识别器。一个有用的调试技巧是把中间结果可视化:用cv2.rectangle在原图上画框,用cv2.imshow逐帧看,或者干脆保存到debug/目录下。
3.4 第四步:字符分割——投影法是参考项目中最常见且最容易写错的环节
字符分割的经典做法是垂直投影:把二值化的车牌区域按列求和,字符所在的列投影值高,字符间隙的列投影值低,根据投影值的连续区间切分字符。看起来简单,但有两个坑。第一个是车牌边框没有被完全去掉,垂直投影时左右边框会和第一个字符粘连;第二个是中间的小圆点“·”会被误判成一个字符。参考项目里通常用cv2.findContours找每个字符的连通域来切分,但这对字符断裂(比如津字笔画分离)很敏感。更稳妥的做法是投影法 + 宽度过滤:投影切出来的每个片段,宽度太窄(小于 10 像素)的丢弃,宽度超过平均宽度 1.5 倍的认为是两个字符粘连,需要从中间再切一刀。
def split_characters(plate_binary): # plate_binary: 二值化且去边框后的车牌图 h, w = plate_binary.shape # 垂直投影:每一列的白色像素总数 col_sum = cv2.reduce(plate_binary, 0, cv2.REDUCE_SUM).flatten() chars = [] in_char = False start = 0 for i in range(w): if col_sum[i] > 0 and not in_char: in_char = True start = i elif col_sum[i] == 0 and in_char: in_char = False if i - start > 5: # 过滤掉噪点造成的狭长片段 chars.append((start, i)) # 过滤:过窄的片段丢弃,过宽的片段二次切分 widths = [end - start for start, end in chars] if not widths: return [] avg_width = sum(widths) / len(widths) refined = [] for start, end in chars: cur_width = end - start if cur_width < avg_width * 0.4: continue # 太窄,大概率是噪点或圆点 if cur_width > avg_width * 1.6: # 两个字符粘连,从中点切分 mid = (start + end) // 2 refined.append((start, mid)) refined.append((mid, end)) else: refined.append((start, end)) return refined代码里有三个关键点。第一,cv2.reduce配合REDUCE_SUM把二值图按列求和,得到的col_sum长度等于图像宽度,每个元素是该列白色像素总数。第二,连续遍历找“从无到有”和“从有到无”的边界,这是投影法切分的核心逻辑。第三,宽度过滤和粘连切分的阈值我用的是相对值——平均宽度的 0.4 倍和 1.6 倍,而不是绝对像素值,这样对不同分辨率的输入图有更好的适应性。字符分割是整条链路里最需要根据实际图片微调的环节,如果你发现识别结果总是多一位或少一位,回到这一步看投影图和切分结果,而不是去调识别器。
3.5 第五步:把参考项目跑成批量脚本,而不是一张一张试
参考项目如果只提供了单张图片的 demo 脚本,你需要自己写一个批量版,这在调参阶段能节省大量时间。批量脚本的核心是统计指标:召回率(车牌有没有被定位出来)和字符准确率(定位成功后字符识别对的比率)。这两个指标分开统计,因为定位失败和识别失败的原因完全不同。
def eval_dataset(test_dir, ground_truth): total = 0 located = 0 correct_chars = 0 total_chars = 0 for filename, true_plate in ground_truth.items(): img = cv2.imread(os.path.join(test_dir, filename)) candidates = locate_plate(img) total += 1 if not candidates: continue located += 1 # 取面积最大的候选区域做识别(假设车牌是画面中最显著的蓝色块) x, y, w, h = max(candidates, key=lambda r: r[2] * r[3]) plate_crop = img[y:y+h, x:x+w] recognized = recognize_plate(plate_crop) total_chars += len(true_plate) for a, b in zip(recognized, true_plate): if a == b: correct_chars += 1 print(f'定位召回率: {located / total:.2f}') print(f'字符准确率: {correct_chars / total_chars:.2f}')这段代码假设recognize_plate函数接收裁剪后的车牌图,返回识别出的字符串。批量化之后你会发现一个现象:定位召回率通常远高于字符准确率——蓝色车牌在 HSV 空间里太显眼了,定位不难;难的是把字符分割干净并正确识别。如果你的项目时间有限,优先把定位和分割做到位,识别部分至少能出“大部分字符正确”的结果,这在演示时是够用的。如果字符准确率低于 70%,大概率是分割阶段出了问题,多看看 debug 输出的切分图。
4. 车牌识别参考项目避坑指南:我踩过的五个坑与排查方法
4.1 坑一:OpenCV 安装后cv2.imshow报错或窗口不显示
现象:在 Linux 服务器或某些 Windows 环境下,运行cv2.imshow直接报错,或者弹出一个灰色窗口但不显示图像。
原因:OpenCV 的 GUI 功能依赖系统图形库,服务器没有显示环境,或者版本不兼容。用 Jupyter Notebook 的同学还会遇到内核卡死的问题。
解决:不用cv2.imshow,改用cv2.imwrite把中间结果写到磁盘,用图片查看器查看。我的习惯是在调试目录下命名规范地保存中间产物:01_original.jpg、02_mask.jpg、03_candidates.jpg、04_chars.jpg,这样能快速对比每个阶段的处理效果。
4.2 坑二:HSV 范围调了半小时,换张图又失效了
现象:当前测试图调好的蓝色阈值,换一张光线不同的图,车牌区域匹配不到或者匹配出大片前景。
原因:HSV 的 V 分量对光照极其敏感。顺光时 V 值可能接近 200,逆光时直接跌到 50 以下,固定阈值自然失效。
解决:分开处理颜色饱和度和亮度约束。对 V 通道做一次直方图均衡(cv2.equalizeHist),再调颜色范围;或者把蓝色检测的阈值放宽到 H 100-130、S 50-255,V 50-255,宁可多检出不放过。多检出的候选区域交给后续的宽高比和面积筛选去过滤,不要指望颜色范围一步到位。
4.3 坑三:二值化之后车牌字符糊成一团或碎成渣
现象:跑完cv2.threshold或cv2.adaptiveThreshold后,车牌字符区域一片白或者全是黑点。
原因:cv2.threshold的固定阈值遇到光照不均匀会失效;adaptiveThreshold虽然自适应,但对字符粗细敏感,blockSize 设不对就会把笔画吞掉。
解决:参考项目里如果用固定阈值,换成cv2.adaptiveThreshold,blockSize取 15 到 25 之间,C取 5 到 10 之间。如果换了自适应还是不行,检查前面颜色分割阶段得到的掩码质量——掩码如果边界模糊,二值化怎么调都没用。
4.4 坑四:夜间场景下蓝色车牌被识别成绿色
现象:白天测试正常,夜间图一跑,定位框画到车身或是路灯上,车牌本身没被框住。
原因:夜间环境光偏暖,蓝色车牌在路灯照射下色偏严重,H 值可能偏移到绿色区间。另外夜间车灯照射角度导致的反光会让车牌局部过曝,S 值下降。
解决:在 HSV 转换之前先做白平衡校正。参考项目里如果没有,可以先用一个简单的方法:把图像转换到 LAB 颜色空间,对 L 通道做 CLAHE(限制对比度自适应直方图均衡),再转回 BGR 继续流程。如果加了白平衡还是不行,老老实实为夜间场景单独标一套 HSV 阈值,运行时不修改代码,用配置文件区分白天/夜间模式。
4.5 坑五:识别结果总是少一位或多一位字符
现象:整个流程跑通了,但输出的车牌字符串长度不对,比如京A12345识别成京A1234或京A123456。
原因:字符分割时边框没有完全去除,或者车牌倾斜导致字符区域比实际偏大或偏小。第二个原因是车牌在画面中有透视形变,垂直投影的边界会偏移。
解决:先做倾斜矫正。用cv2.minAreaRect找到车牌区域的最小外接矩形,取出旋转角度,再用cv2.warpAffine把车牌转正。转正后再跑分割流程,字符宽度的波动会小很多。
def deskew(plate_crop): # 输入裁剪出的车牌区域,输出矫正后的车牌 gray = cv2.cvtColor(plate_crop, cv2.COLOR_BGR2GRAY) _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: return plate_crop cnt = max(contours, key=cv2.contourArea) rect = cv2.minAreaRect(cnt) # 返回 (中心, (宽,高), 角度) angle = rect[2] # 角度修正:OpenCV 的角度范围是 [-90, 0),需要转成正的矫正角 if angle < -45: angle = 90 + angle h, w = plate_crop.shape[:2] M = cv2.getRotationMatrix2D((w // 2, h // 2), angle, 1.0) rotated = cv2.warpAffine(plate_crop, M, (w, h), flags=cv2.INTER_CUBIC) return rotated这个函数的坑在rect[2]返回的角度语义:OpenCV 的minAreaRect返回的角度在 -90 到 0 度之间,表示矩形相对水平轴的旋转角度。当车牌顺时针倾斜时,angle可能是 -30 左右;逆时针倾斜时,angle可能小于 -45,此时需要加 90 才能得到正确的矫正方向。如果你发现矫正后车牌反而更歪了,大概率是这个角度符号没有正确处理。
5. 验证识别效果的进阶技巧:二十八省模板对比与可视化调试
当基础流程跑通后,下一步不是急着换更复杂的模型,而是把验证做扎实。我习惯做三件小事:第一,把测试集里的每张图按“识别成功 / 定位成功但识别失败 / 全部失败”分类,并保存中间结果图;第二,统计失败样本中字符级别的混淆矩阵——比如“京”被识别成“沪”出现了几次,这比只看整体准确率有用得多;第三,用matplotlib把定位框、分割边界、识别文本直接画到原图上,肉眼扫一遍就能定位问题环节。
import matplotlib.pyplot as plt def visualize_result(img, candidates, recognized_text, save_path): fig, axes = plt.subplots(1, 2, figsize=(12, 4)) axes[0].imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) for (x, y, w, h) in candidates: rect = plt.Rectangle((x, y), w, h, fill=False, edgecolor='red', linewidth=2) axes[0].add_patch(rect) axes[0].set_title(f'Recognized: {recognized_text}') # 右侧显示字符分割的结果 plate_binary = preprocess_for_split(img, candidates[0]) chars = split_characters(plate_binary) axes[1].imshow(plate_binary, cmap='gray') for i, (start, end) in enumerate(chars): axes[1].axvline(x=start, color='blue', linestyle='--') axes[1].axvline(x=end, color='red', linestyle='--') plt.tight_layout() plt.savefig(save_path, dpi=150) plt.close()这个可视化脚本的价值在于,当某张图识别错了,你能一眼看出错在哪个环节:红框没框住车牌是定位问题;框住了但分割线乱七八糟是分割问题;分割线正常但文本错误才是识别问题。参数说明里两个值得注意的地方:plt.savefig的dpi=150保证输出的图片足够清晰,方便放大查看分割边界;plt.close()在批量处理时不能省,否则内存会被一堆未关闭的 figure 对象吃满,跑几百张图之后程序会越来越慢。
最后一个进阶方向是把传统识别段替换成一个小的 CNN 分类器。参考项目如果只用了sklearn.svm,你可以用torch或tensorflow搭一个四层卷积网,输入 32×64 的灰度字符图,输出 65 个类别(31 个汉字 + 24 个字母 + 10 个数字)。我自己的经验是,CNN 在字符识别准确率上能比模板匹配提升 15-20 个百分点,尤其在汉字类别上提升最明显。但这一步不是必须的——如果你的场景是固定角度、固定光照的道闸摄像头,模板匹配加多候选排序足够用。我在一个新项目中会先跑通传统链路,确认定位和分割的瓶颈解决了,再决定要不要引入深度学习。这个顺序能避免最常见的学习路径误区——上来就训练模型,结果发现数据标注占用了 80% 的时间,而定位分割的问题根本没暴露出来。
这套方案适合什么阶段的人上手:你已经会写 Python,用过 OpenCV 的基本函数,需要一份完整链路参考来打通“图像怎么变成字符串”的认知。不适合什么人:想直接得到一个能应对各种复杂场景的工业级识别系统——那种需求应该考虑商业化 API 或者自己从头训练检测加识别的两端模型。参考项目是拐杖,不是终点,希望你借它走完第一公里,然后扔掉它,按自己的场景重新设计。希望帮到你。
本文还有配套的精品资源,点击获取