简介:数字图像处理车牌识别课程设计(MATLAB实现)配套资料,面向高校数字图像处理课程学生,提供从车牌定位、字符分割到模板匹配识别的完整实现思路与课程设计报告。压缩包共1个doc文件,整体大小2.37MB,内容涵盖设计目的、总体方案、图像预处理步骤、主要函数详解及程序代码注释,章节结构清晰,便于按步骤阅读与直接运行调试。目前已有3203人学习,内容组织与配套代码在同类课设中具有较高参考价值。资料系统梳理了图像灰度化、边缘检测、形态学腐蚀膨胀等关键环节,并针对imerode、imdilate、strel、edge等MATLAB函数的功能、用法与参数做了详细说明,还给出系统运行环境要求以及利用多幅不同光照与距离条件下的图像验证稳定性的方法。借助这份文档,读者既能理解车牌识别从采集到字符输出的完整流程,也能参照注释修改参数完成课设程序,并为数字图像处理后续学习打下基础。
1. 为什么车牌识别课程设计,识别算法反而最不重要
做车牌识别课程设计时,很多同学第一反应是去翻深度学习那套东西,但课程设计的核心舞台并不在网络结构里。真正拉开分数差距的,是前面那块不太性感的车牌定位与字符分割环节。实际跑下来你会发现,一个基于颜色特征加形态学处理的简单定位流程,往往比换一个花哨的分类器对整体识别率的影响大得多。定位切不准,后面识别网络再强也是白搭,输出的全是被裁掉一半的字符残片。
这个设计类题目的典型路线是:定位 -> 分割 -> 识别,每一步都有成熟的图像处理手段可以落地。定位用 HSV 颜色阈值加连通域筛选,分割用垂直投影,识别用模板匹配,打通这条链路后,你不仅有一个能跑的 demo,还顺手把边缘检测、形态学、连通域分析、特征匹配这些课程重点全部演示了一遍。对新手来说,这个方案能在两周内跑通;对想拿高分的同学来说,这条链路里每一个环节都有优化空间,足够你在答辩时讲满时间。本文就按这条路,把每一步的代码、参数和踩过的坑一次讲清。
2. 车牌定位:HSV 颜色阈值 + 形态学处理
2.1 为什么优先选颜色特征而不是边缘检测
车牌定位可选的切入角度不少:边缘检测、颜色特征、纹理特征、深度学习目标检测。但课程设计场景下,我最推荐颜色特征作为主线索,理由很直白:鲁棒性最好。边缘检测在车牌识别里有个著名的翻车场景,就是散热器格栅和车灯区域边缘密度极高,而且车头部分的水平边缘特别多,单纯用边缘检测会把大量非车牌区域圈进来,后续筛选条件就得越写越复杂。而车牌本身的底色,蓝底白字是最常见的情况,颜色区域在整幅图中通常是独占且集中的,用颜色阈值可以直接把候选区域缩到一个很小的集合。
MATLAB 里常用做法是在 HSV 空间做阈值,理由也简单:RGB 对光照变化太敏感。同一张蓝色车牌,在阴影下和直射阳光下,RGB 三个通道的数值可以相差 50 以上,但转到 HSV 后,H 分量(色调)的变化幅度要小得多。所以我在预处理时,第一件事永远是rgb2hsv,而不是直接对 RGB 做阈值化。
预处理还会顺带做两件事:一是把图像缩放到统一宽度,保证后续形态学操作的结构元素尺寸与图像分辨率具有可比性,避免在不同分辨率图片上出现完全不同的形态学效果。二是用中值滤波去噪,这里得选小窗口,例如medfilt2的 3x3 窗口,既能去颗粒噪声,又不会模糊字符边缘。
2.2 定位主流程的 MATLAB 实现与参数含义
先给出一份可以直接跑的定位代码。文件名locatePlate.m,输入是原图,输出是定位后的车牌区域图像:
function plateImg = locatePlate(srcImg) % ===== 1. 预处理 ===== % 统一宽度到 600,保持长宽比,避免不同分辨率导致形态学结构失效 scale = 600 / size(srcImg, 2); img = imresize(srcImg, scale); img = medfilt2(img, [3 3]); % 小窗口去噪,保留边缘细节 % ===== 2. HSV 颜色阈值 ===== hsvImg = rgb2hsv(img); H = hsvImg(:, :, 1); S = hsvImg(:, :, 2); V = hsvImg(:, :, 3); % 蓝色色相在 0.55~0.68,同时要求饱和度与亮度在合理区间 blueMask = (H > 0.55) & (H < 0.68) & (S > 0.35) & (V > 0.15); % ===== 3. 形态学处理 ===== se1 = strel('rectangle', [9 9]); closed = imclose(blueMask, se1); % 填充车牌区域内部字符缝隙 se2 = strel('rectangle', [3 3]); opened = imopen(closed, se2); % 去除孤立小噪点 % ===== 4. 连通域筛选 ===== cc = bwconncomp(opened); stats = regionprops(cc, 'BoundingBox', 'Area'); candidates = []; for i = 1:numel(stats) w = stats(i).BoundingBox(3); h = stats(i).BoundingBox(4); ratio = w / h; areaRatio = stats(i).Area / (w * h); % 车牌长宽比约 3~4,字符区域占外接矩形比例适中 if ratio > 2 && ratio < 5 && areaRatio > 0.3 candidates = [candidates; stats(i).BoundingBox]; %#ok<AGROW> end end if isempty(candidates) error('未定位到车牌区域,请检查颜色阈值'); end % 若多个候选,取面积最大 [~, idx] = max(candidates(:, 3) .* candidates(:, 4)); bbox = candidates(idx, :); plateImg = imcrop(img, bbox); end这段代码里值得解释的参数有三个。一是imclose的结构元素[9 9],这一步的目的不是去噪,而是把车牌内部字符之间的空隙用形态学闭运算填上。9 像素的矩形在 600 宽度尺度下,恰好能跨过字符间隙。如果这个值太小,字符之间无法连通,车牌区域会被拆成多个碎块;如果太大,容易把车身其他蓝色区域并进来,候选框会扩大。二是 HSV 阈值中S > 0.35这个约束,它排除了偏灰白的浅蓝色区域,比如某些车身的淡蓝色喷漆。三是areaRatio > 0.3这个填充率判定,纯粹矩形框的车牌区域,字符和背景的分布决定了这个值一般落在 0.4 到 0.8 之间,如果太低,说明矩形框里大部分是空洞,大概率是误检。
2.3 定位环节的验证技巧:可视化中间结果
定位是整条链路里最需要调试的一环,而调试的第一步不是改参数,是看中间结果。我习惯在代码里临时插入figure显示blueMask、closed、opened这三步的输出,对比每一级图像的变化。这里有个实用经验:如果blueMask里车牌区域本身都不完整,问题出在 HSV 阈值;如果blueMask完整但opened后区域破碎,问题出在形态学参数;如果前两步都正常,候选框却偏大或偏小,问题出在长宽比和填充率约束的配合上。
测试图片的选择也有讲究。最开始调试时,先找一张背景简单、车身颜色与车牌差异大的正面照片,跑通后再换复杂场景。常见做法是至少准备三组测试:晴天正面、侧面 45 度、略微遮挡场景。如果侧面场景定位失败,调整长宽比下限,把范围放宽到 1.8 到 5.5 即可,但要注意这同时会增加误检率。在定位这一环上,我的建议是「参数宁可放宽,不要收紧」,因为后续字符分割和识别环节还能过滤一部分错误,但车牌区域没框进来就是直接判死刑。
3. 字符分割:垂直投影法
3.1 为什么字符分割是识别率的分水岭
很多同学把时间花在识别算法上,但我做下来感觉整个项目最难调的其实是符号分割。车牌字符是印刷体、字体统一、间距规则、没有连笔,看起来分割很简单,实际跑起来问题相当多。定位框稍微切到车牌的螺钉、边框,又经过灰度化、二值化,还有光照不均的残留,投影图会多出大量噪声。这时候如果直接按投影值做切割,识别部分还没来得及工作,就已经把字符切错了。
传统但可靠的分割方案是垂直投影法。二值化图像中,白色字符会在一部分列上有密集的像素累积,而字符间隙的投影值接近于零,投影图会形成一个个明显的脉冲峰。按这个波形对字符列区间做切分,就是课程设计里最常见的做法。我对这个方案的定位是:它不一定是最优解,但在课程设计这个场景下,好解释、好调试、好答辩。
另一个需要注意的点是二值化阈值选择。车牌的蓝底白字在灰度图上表现为中间暗、字符亮,对比度比较理想。用graythresh做大津法全局阈值通常表现不错,但如果目标图片光照不均匀,就需要考虑局部自适应二值化。具体判断方式不难:在灰度图上画一条横线,观察车牌区域的灰度值波动,如果波动幅度超过 30 个灰度级,建议改用局部阈值。
3.2 垂直投影分割的 MATLAB 实现
以下是字符分割的完整实现。文件名splitChars.m,输入是定位后的车牌彩色图,输出是所有字符子图:
function charCells = splitChars(plateImg) % ===== 1. 灰度化与二值化 ===== gray = rgb2gray(plateImg); % 大津法全局阈值;若光照不均可换用局部阈值 bw = imbinarize(gray, graythresh(gray)); % 确保车牌字符为白色,背景为黑色,便于投影 if mean(bw(:)) > 0.5 bw = ~bw; end % ===== 2. 去除上下边框:水平投影 ===== rowProj = sum(bw, 2); validRows = find(rowProj > 3); % 阈值 3,过滤微弱噪点 bw = bw(min(validRows):max(validRows), :); % ===== 3. 去除左右边框与铆钉:垂直投影 + 宽度约束 ===== colProj = sum(bw, 1); % 排除投影值过长区域即边框干扰 colProj(colProj > 0.6 * size(bw, 1)) = 0; % ===== 4. 垂直投影切分字符 ===== isChar = colProj > 0; diffIdx = diff([0, isChar, 0]); startCols = find(diffIdx == 1); endCols = find(diffIdx == -1) - 1; % 宽度约束:过窄的视为噪声(铆钉/划痕) imgW = size(bw, 2); minCharW = round(imgW * 0.05); charRanges = []; for i = 1:numel(startCols) segW = endCols(i) - startCols(i) + 1; if segW >= minCharW charRanges = [charRanges; startCols(i), endCols(i)]; %#ok<AGROW> end end % ===== 5. 按列区间裁剪 ===== charCells = cell(size(charRanges, 1), 1); for i = 1:size(charRanges, 1) charCells{i} = imcrop(bw, [charRanges(i,1), 1, ... charRanges(i,2)-charRanges(i,1)+1, size(bw,1)]); end end注意以上代码中的两个关键设计。一是colProj(colProj > 0.6 * size(bw, 1)) = 0,这行解决的是左右边框问题:如果图像中残留车牌边框,边框对应的列投影值几乎等于整列高度,明显高于字符区域。利用投影值的高度差异直接把边框列从投影序列中抹掉,比形态学方法更简洁。二是最小宽度阈值minCharW取图像宽度的 5%,它过滤掉的是螺钉、铆钉和边框碎片。但要注意,如果这个值设得过大,会把车牌第二位字母和第三位字母之间的连续字符误伤,这时需要检查的是定位框是否切得太紧,而不是盲目调低阈值。
3.3 分割结果的判定方法
分割完成后,别急着进入识别环节,先用一个快速脚本检查分割质量。我习惯于把每个charCells{i}输出到一张小图上,横向拼接并显示序号。判定分割是否合格只需要三个标准:是否正好是 7 个字符(新能源车牌是 8 个)、字符宽度是否均匀、是否有明显被切割的字符残片。如果有字符粘连,常见原因是定位框倾斜,车牌字符有轻微旋转,垂直投影时两个字符的投影区间连成一片,这种情况下就要对定位结果做旋转校正,而不是改分割参数。如果有字符断裂,例如「京」字中间被切断,通常是因为二值化时笔画亮度不均,低频部分被判定为背景,可以尝试降低 Otsu 阈值或改用局部阈值处理。
4. 字符识别:模板匹配
4.1 模板匹配为什么在这个场景里够用
车牌字符集本身是封闭的:汉字 31 个左右、大写字母 24 个左右、数字 10 个,总共几十个类。每个字符都是规整的印刷体,没有手写识别那种形变问题。在这样一个受限场景里,模板匹配完全够用,而且代码量小、运行快、答辩好讲。CNN 方案适合数据集较大、需要泛化能力的产品化场景,但课程设计里,模板匹配的定位是「用最少资源解决封闭集识别问题」,这个理由在答辩时反而是一个亮点。
模板匹配的度量方式有两种主流选择:相关性系数和欧氏距离。相关性系数对光照变化更不敏感,因为它在计算时对区域做了归一化;欧氏距离更直观,但容易受亮度影响。字符都是二值化后的黑白图,光照影响其实已经基本被消除,两种方式差距不大。我更推荐相关性系数,理由不是精度更高,而是因为它天然给出一个归一化分数,可以用来做识别置信度判断,便于在答辩中展示「这次识别有多确定」。
4.2 模板匹配的 MATLAB 实现与阈值讨论
以下是配合上述分割结果使用的识别代码。文件名recognizeChars.m:
function plateStr = recognizeChars(charCells, templatePath) % 模板文件需先构建并保存为 templates.mat S = load(templatePath); names = fieldnames(S); results = []; for i = 1:numel(charCells) charImg = imresize(charCells{i}, [20 12]); % 统一尺寸 charImg = im2double(charImg(:)'); % 拉成行向量 scores = zeros(numel(names), 1); for j = 1:numel(names) tpl = S.(names{j}); % 计算归一化相关系数 tmpScore = corr2(charImg, tpl); scores(j) = tmpScore; end [bestScore, idx] = max(scores); % 置信度低于 0.5 视为可疑结果,后续可用规则修正 if bestScore < 0.5 results = [results, '?']; %#ok<AGROW> else results = [results, names{idx}(1)]; %#ok<AGROW> end end plateStr = results; end这里有一个容易被忽视的细节:模板匹配的性能其实在代码外,也就是模板库的构建质量。我踩过的坑是模板与测试图预处理不一致,比如模板用了不同字体或不同字符宽高比。构建模板库的常见做法是,找几张质量好的车牌图,用前面相同的分割流程切出字符,手动筛选并命名保存。每一类模板最好取三个以上样本,比如同一个数字 5 要多保留几种粗细或倾斜程度的变体,识别时把多个模板里的最高分作为该字符的最终得分,这一点对识别率的提升比换算法更有效。
另一个值得注意的点是模板字符和实际字符的分辨率匹配。imresize(charCells{i}, [20 12])中 12 代表宽度 12 像素、20 代表高度 20 像素,用于匹配输入字符与模板的统一大小。宽度设太低会造成信息损失,设太高则会增加计算量,但在图片分辨率较高时模板需要同步重新截取。如果不匹配,主要表现为模板匹配得分偏低,这时候优先重新截取模板而不是调整插值方法。相关性系数小于 0.5 的结果要标记为「?」,保留中间结果并人工复核。这类低置信度字符往往出现在汉字区域,因为汉字结构复杂、笔画密集,对二值化质量更敏感。
4.3 规则修正:识别完并不代表完工
模板匹配的输出经常会有一两个字符的错误,尤其是「0」和「O」、「1」和「I」、「D」和「0」这类高度相似的组合。针对这种情况,可以在识别结果之后加一个规则修正层,最常见的是车牌结构规则:第一位必须匹配某个汉字(比如「京」「沪」「粤」等),第二位的字母不能是「I」「O」等少数几个字母,数字位不能出现字母。用这几条规则对识别结果再检查一遍,结合模板匹配的次优分数做替换,往往能把整串识别率提升 3 到 5 个百分点。
% 规则修正示例:第二位不应出现 I/O,若出现且次优是数字,则替换 if ismember(plateStr(2), 'IO') % 重新取第二位的次优匹配 [~, order] = sort(scores, 'descend'); for k = 2:numel(order) cand = names{order(k)}(1); if ~ismember(cand, 'IO') plateStr(2) = cand; break; end end end这段规则代码不需要很复杂,它的作用是把模板匹配的结果从「候选字符」变成「符合车牌规范的候选字符」。要注意边界情况:如果第一位汉字识别出来是「?」或明显不合理,规则层无法修复,问题的根因往往在分割阶段,汉字笔画在二值化时断裂了。所以规则修正只能做减法,不能做加法,它无法解决根源问题,但能让已经识别出来的字符组合变得合理。
5. 避坑:车牌识别课程设计的 8 个典型翻车点
5.1 边缘检测把散热器格栅当成车牌
现象:定位框框住了车头的散热器格栅或车灯区域,这些区域在边缘密度上和车牌非常接近。
原因:格栅的横条边缘非常密集,在边缘图上形成大面积高响应区域,连通域特征与车牌接近。
解决:一是避免把 edge 检测结果作为主定位依据,改以颜色特征为主;二是对候选框过滤参数加「填充率」约束,格栅区域是栅栏结构,空洞多、填充率低,areaRatio > 0.3能很好地把这类区域排除;三是如果必须在答辩中展示边缘方法,那就把边缘方法定位为「候选区域生成」,颜色方法做最终裁定,两个结果做交集。
5.2 HSV 阈值把车身蓝色区域框进来了
现象:定位结果包括车身侧面、车顶等大面积蓝色区域。
原因:蓝色车身的 HSV 范围与蓝色车牌有重叠,尤其是较深的天蓝和部分运动车型的蓝色车漆。
解决:最有效的办法是按形态学特征做一轮过滤,在 HSV 阈值之外加一个面积与长宽比的联合约束:车牌区域外接矩形的长宽比紧凑,而车身区域通常是细长条或位于图像边缘的大色块,直接按长宽比和最小面积两个参数过滤。还有一种情况是车身反光导致色相偏移,这属于光照问题,建议在原图上先试一下白平衡预处理,简单的做法是除以灰色世界假设下的各通道均值。
5.3 字符分割时铆钉被切出来
现象:分割结果中,第一个字符前出现一个窄条图像,识别结果为「1」或「I」。
原因:牌照固定铆钉在二值化图上表现为圆形高亮区域,垂直投影图上表现为一个高度较矮但宽度很小的峰。
解决:分割流程中的宽度约束minCharW = round(imgW * 0.05)就是干这个用的,铆钉的宽度通常不足字符宽度的 1/3,会被过滤。如果仍然被切出来,则将宽度约束改成「与整张图的字符宽度中位数比较」,低于中位数 60% 的统统丢弃,这个规则对铆钉和边框碎片的过滤能力更强。
5.4 模板匹配里「0」总是识别成「O」
现象:数字 0 的位置全输出字母 O,算法似乎完全不区分。
原因:模板库里同时存在数字 0 和字母 O,二者在 12 像素宽、20 像素高的二值图上几乎形同,相关性系数无法区分。
解决:在模板设计阶段就避免收录这种「超相似对」,或给「O」类模板附加车牌规则约束:车牌第二位不允许出现 O,因此直接跳过对「O」的匹配。更彻底的做法是对二值化后的字符计算孔洞特征,数字 0 的孔洞面积占比更接近规范圆形,这个特征可以在得分相等时作为仲裁依据,但要小心字体缺角导致孔洞不封闭。
5.5 光照不均导致同一个字符在二值化后被切断
现象:字符分割没问题,但识别得分全部偏低,仔细看字符图像,部分笔画轮廓是断的。
原因:大津法全局阈值对光照不均匀没有自适应能力,字符的一部分像素被判定为背景。
解决:改用局部自适应阈值imbinarize(gray, 'adaptive'),它的核心参数是局部邻域大小,推荐设置为车牌字符平均宽度的 1.5 到 2 倍。邻域太小会在笔画内部产生空洞,邻域太大就退化为全局阈值。字符宽度可以粗略地用「投影宽度估计」来计算,不需要精确值。这一条对夜间和阴影场景几乎是必选项。
5.6 GUI 里点了按钮没反应
现象:在 GUI 上把识别代码接进回调函数后,点击按钮时 UI 卡住或没有输出。
原因:回调函数里直接同步运行整条识别链路,GUI 主循环被阻塞,或者变量在回调之间没有正确传递。
解决:课程设计场景中最实用的做法是把识别流程封装成一个函数,回调中只调用该函数并刷新显示控件。常见做法是在OpeningFcn中把原图存入handles,回调中通过handles取图,用guidata(hObject, handles)保存修改后的数据。如果 UI 仍然卡顿,把耗时操作拆到timer或parfeval里,但课程设计一般不需要做到这一步,最简单的改进是把预览图缩小再显示,避免同时处理大矩阵显示和识别计算。
5.7 GUI 中按钮回调与函数命名冲突
现象:代码报错内容指向function参数个数不足,或回调函数无法识别自定义函数文件。
原因:GUI 自动生成的回调函数内直接引用了自定义函数名,但该函数未添加到 MATLAB 路径中,或是函数名与 GUI 回调名重名覆盖。
解决:在项目根目录建立一个utils文件夹,把所有自定义函数放进去,右键该文件夹选择「添加到路径”,再将 GUI 的*.m文件放在根目录,启动时调用addpath('utils')。前提是命名不能与 MATLAB 自带函数冲突,例如不要用imshow、sum这类已有函数名。
5.8 换了机器跑不出同样的结果
现象:同样的代码在宿舍电脑上识别率很高,换到教室的机器上就各种报错或结果不一致。
原因:多半是路径问题。imread使用了相对路径,当前目录非项目根目录时报错;或者模板库.mat文件里保存了绝对路径。
解决:在所有imread、load调用前先切换目录,常见做法是cd(fileparts(mfilename('fullpath'))),这一行写在主脚本开头,确保工作目录始终是脚本所在目录。模板库路径也建议改为相对路径拼接。这看起来是个小问题,但如果答辩时演示切换机器失败,很可能前面的工程能力展示全部白费,我见过不止一个人在这里翻车。
6. 往答辩加分项做:批量验证与识别过程可视化
最后一个环节,建议不要急着收工,做两件能明显提高完成度的事:一是写一个离线批量测试脚本,用 10 到 20 张测试图统计识别率,二是做一个识别过程的中间可视化。理由很实在:答辩时评委不会在意你调了多牛的网络结构,他们更关心「你的方案在不同条件下分别是什么表现」以及「遇到失败时怎么定位问题」。
批量测试脚本的核心是系统化地统计识别结果。我建议把每一张图的真值写在一个元胞数组里,运行后输出每张图的识别结果、是否完全正确、哪些字符识别错、置信度列表。具体实现思路如下:
% batchTest.m 示意 testFiles = {'img01.jpg', 'img02.jpg', 'img03.jpg'}; groundTruth = {'京A12345', '沪B67890', '粤C24680'}; for i = 1:numel(testFiles) img = imread(testFiles{i}); plate = locatePlate(img); chars = splitChars(plate); result = recognizeChars(chars, 'templates.mat'); % 与真值对比,统计正确字符数 correct = sum(result == groundTruth{i}); fprintf('文件 %s:识别为 %s,正确 %d/%d\n', ... testFiles{i}, result, correct, numel(groundTruth{i})); end批量测试得到的识别率,要按场景分类记录,比如分别记录白天正面、侧面、夜间光照不足的识别率。不建议把混合场景统一成一个准确率,因为答辩时评委大概率会追问「那个晚上的图你是怎么处理的」,分场景统计能顺着回答展示你在预处理上做过的调整。
可视化方面的建议是做一个figure,分四格同步显示原图、HSV 阈值掩膜、垂直投影曲线和最终识别结果。垂直投影曲线可以用plot(colProj)直接画出来,在曲线图上标出分割点位置,这是最能直观展示「你确实理解了分割原理」的方式。项目做到这一步,本质上已经从「能跑」升级为「能讲清楚每一步为什么这么设计、边界在哪里」。
再多谈一句学习路线。这个题目想做到「能答辩、禁得起追问」,光跑通代码是不够的。建议把定位、分割、识别三块核心代码各独立成函数,把阈值参数尽量集中在文件头部的变量区,答辩时改了哪个参数、效果怎么变化,演示一遍比任何背稿都有效。写清楚函数间接口比让代码在单个脚本里一直往向下推进重要得多,因为分割环节要微调时,能单独运行分割函数才能真正快速迭代。
我最初做这个题目时也走过弯路,把时间大量花在换识别算法上,后来发现瓶颈一直在定位和分割这两步,调整预处理方案后识别率提升比换算法更明显。有段时间为了追求高识别率,我为每一类的多个模板都设了独立权重,结果调试成本高到爆炸,最后改成「同权重多模板取最高分」,效果反而更稳定,也更简洁。如果你也要做这个题目,我建议不要贪多,把颜色定位到投影分割到模板匹配这条线做深,胜过铺太多探索性内容。希望帮到你。
后补一句习惯:没收尾前,把工作目录下的*.png中间过程图全部截图保存,答辩前统一整理成一张流程图贴到 PPT 里,这种图表比任何大段原理文字都有说服力,关键是这些图是你自己代码跑出来的真实输出,不存在造假问题。
本文还有配套的精品资源,点击获取