MATLAB交通标志识别系统:基于CNN与HSV颜色分割的完整实现 📅 发布时间:2026/9/15 20:30:39 👁 浏览次数: 简介面向计算机、电子信息、数学等专业学生的课程设计与毕业设计需求这套基于Matlab的交通标志识别系统资源自带GUI操作界面源码、图片数据集和说明文档齐全。资源包共36个文件整体约17.78MB涵盖m源码脚本、mat模型与数据文件、jpg/png/bmp交通标志样本、txt说明文档及fig界面文件既有可直接运行的程序也有训练好的识别模型和图片库。目前已有658人学习/浏览数据集中覆盖指示、警示、禁止三类交通标志并附有不同颜色通道模型的精度数据GUI支持选择图片并显示识别结果便于对照源码理解完整流程。说明文档配合清晰的文件组织能够帮助快速定位各功能模块理解从样本预处理、模型训练到GUI集成的实现路径同时也有较好的扩展空间适合继续做算法改进、数据集扩充和功能优化为毕业设计写作与系统演示提供支撑。1. 交通标志识别系统的拆解思路从数据集构成到识别流程打开这个rar第一眼看到的是三个带颜色的网络权重文件和一堆散落的脚本、图片。这套基于Matlab实现的交通标志识别系统解决的是一件很具体的事输入一张含交通标志的照片输出标志的语义类别并且通过GUI操作界面让整个过程可见。和现在流行的YOLO方案不同它没有目标检测的边框回归而是先用颜色把红、蓝、黄三类标志分开再用三个小型CNN分别识别。这个思路在毕业设计和课程设计里很常见优点是训练快、代码可读性好缺点是要求标志在画面中有一定占比。理解了这个前提后面看mcnn.m和Traffic_Iden.m才不会觉得结构奇怪。适合的人群是计算机、电子信息工程、数学等专业做课程设计或毕业设计的学生把它当作二次开发的起点而不是拿来即用的商业产品。2. 数据集组织与颜色分割预处理从三类文件夹到HSV掩膜2.1 数据目录与标签映射资源里的图片直接按“禁止类、警示类、指示类”三个文件夹存放里面混着png、jpg、bmp三种格式。文件名一部分带语义比如“18_靠左侧道路行驶015.bmp”另一部分则是“0185.png”这类纯编号。这种组织方式维护起来直观但直接喂给imageDatastore时文件夹名只能生成大类标签不能支撑细粒度识别。所以在训练之前我会先写一个加载函数把三类的图片路径收集起来同时建立从文件名到最终类别名的映射表对于没有语义关键词的文件用大类名兜底至少保证数据能跑通。function [imgs, labels] load_traffic_images(rootPath) folders {禁止类, 警示类, 指示类}; imgs {}; labels {}; for i 1:length(folders) folderPath fullfile(rootPath, folders{i}); files dir(fullfile(folderPath, *.png)); files [files; dir(fullfile(folderPath, *.jpg))]; files [files; dir(fullfile(folderPath, *.bmp))]; for j 1:length(files) imgs{end1} fullfile(folderPath, files(j).name); %#okAGROW labels{end1} folders{i}; %#okAGROW end end end这段代码把三种常见位图格式全部扫出来存的是绝对路径而不是图像矩阵避免一次性载入大量高分辨率图片把内存占满。labels直接用文件夹名后面的细粒度拆分可以在这个基础上按文件名关键字重新赋值。资源中redname.mat、bluename.mat、yellowname.mat里保存的通常就是每个颜色桶对应的细粒度类别名字符串数组加载后用categorical转换即可。2.2 颜色空间选择与HSV阈值分割为什么这套系统要分别保存rednet_cnn.mat、bluenet_cnn.mat、yellownet_cnn.mat三个模型因为交通标志的底色有很强的颜色先验红色表示禁止或者部分警告蓝色表示指示黄色表示警告。直接在RGB空间分割颜色亮度变化会让阈值很难稳定我一般会转HSV把亮度信息剥离出来用H分量做主判断再用S和V过滤掉低饱和度和过暗的区域。红色在HSV色相环上比较特殊H0附近被一分为二所以掩膜条件要写成两个区间取或。下面的示例函数返回一个二值掩膜用于后续裁剪标志区域function mask extract_color_mask(img, colorType) hsvImg rgb2hsv(img); H hsvImg(:, :, 1); S hsvImg(:, :, 2); V hsvImg(:, :, 3); switch colorType case red mask ((H 0.05) | (H 0.95)) (S 0.3) (V 0.2); case blue mask (H 0.55) (H 0.70) (S 0.3) (V 0.2); case yellow mask (H 0.10) (H 0.20) (S 0.3) (V 0.2); end mask imclose(imopen(mask, strel(disk, 3)), strel(disk, 5)); end这里用strel构造圆盘形结构元素开运算去掉孤立噪点闭运算填补标志图形内部的细小裂缝。结构元素半径需要根据图片分辨率调整如果你用的是手机拍摄的大图半径3到5可能不够我一般先统一缩放到宽度约400像素再做分割。HSV的参考区间如下表注意这些值只适合作为起点换一批照片后必须重新标定。颜色H 范围S 最小V 最小备注红色0.05 或 0.950.300.20红色在色相环两端蓝色0.55~0.700.300.20覆盖深蓝和浅蓝黄色0.10~0.200.300.20与橙色交界容易混2.3 尺寸统一与数据增强CNN的输入尺寸必须固定。资源里的png、jpg、bmp分辨率参差不齐常见做法是从原图中按掩膜外接矩形裁剪出标志区域缩放到64x64或32x32。缩放时最好保持宽高比缺的部分用黑色或灰色填充否则圆形标志会被压成椭圆引入不必要的变形。对于几百张规模的毕业设计数据集直接训练CNN很容易过拟合。数据增强是必须的我常用Matlab的augmentedImageDatastore做在线增强这样不会在磁盘上生成额外的图片副本augImds augmentedImageDatastore([64 64], trainImgs, trainLabels, ... DataAugmentation, imageDataAugmenter(... RandRotation, [-15 15], ... RandXTranslation, [-4 4], ... RandYTranslation, [-4 4], ... RandScale, [0.9 1.1]));这段代码里RandRotation控制在±15度是因为交通标志是标准化图形旋转幅度太大会让模型学到不存在的姿态。RandScale控制在0.9到1.1模拟拍摄距离的小幅变化。增强参数不是越大越好过多的人为扰动反而会让验证集精度异常波动这一点在后面的调试部分还会提到。3. 基于颜色专属CNN的模型训练与推理redmodle、bluemodle、yellowmodle的分工3.1 网络结构选型为什么用小型CNN而不是YOLO现在聊到交通标志识别很多人第一反应是YOLO。但这个资源里没有目标框标注所有图片只有分类标签YOLO依赖的box监督信号完全缺失。另一个原因是毕业设计运行环境一般就是普通笔记本YOLO系列哪怕是轻量版训练一轮的时间也远超小CNN。所以这里用三个小型CNN分别负责红、蓝、黄三类是数据条件和算力约束下的合理选型。这套“multi-CNN”结构从mcnn.m这个文件名也能猜出来三个网络结构一致只是训练数据不同。我的习惯是用两个卷积层加两个全连接参数总量控制在百万以内。参考结构如下表层类型参数输出尺寸1卷积8个3x3padding same64x64x82ReLU 最大池化池化2x2 stride 232x32x83卷积16个3x3padding same32x32x164ReLU 最大池化池化2x2 stride 216x16x165全连接64646全连接类别数N这个深度对交通标志识别足够因为颜色分割已经把大部分背景剔除网络真正要学的是标志内部的箭头、数字和符号组合。如果后面想提高准确率优先加第三个卷积层而不是加大全连接全连接层越大越容易过拟合。3.2 redmodle.m / bluemodle.m / yellowmodle.m 的训练参数设置资源中的redmodle.m、bluemodle.m、yellowmodle.m是三个独立训练脚本分别读取红色、蓝色、黄色数据集并生成对应的net_cnn.mat。这里分享一个我在写这类训练脚本时的标准做法先按标签分层划分训练集和验证集再定义网络层和训练选项最后保存网络。rng(2024); [trainSet, valSet] splitEachLabel(augImds, 0.8, randomized); layers [ imageInputLayer([64 64 3], Normalization, none) convolution2dLayer(3, 8, Padding, same) reluLayer() maxPooling2dLayer(2, Stride, 2) convolution2dLayer(3, 16, Padding, same) reluLayer() maxPooling2dLayer(2, Stride, 2) fullyConnectedLayer(64) reluLayer() fullyConnectedLayer(numClasses) softmaxLayer() classificationLayer()]; options trainingOptions(sgdm, ... InitialLearnRate, 0.001, ... MiniBatchSize, 32, ... MaxEpochs, 20, ... ValidationData, valSet, ... Plots, training-progress); net trainNetwork(trainSet, layers, options); save(rednet_cnn.mat, net);splitEachLabel加randomized参数是为了打乱样本顺序避免同类图片连续出现导致一个batch内全是同一类。InitialLearnRate用0.001是sgdm的常规起点如果你的数据集只有几十张建议降到0.0005并适当增加epoch。训练完成后保存的是net变量本身GUI加载时直接load(rednet_cnn.mat, net)就能用。三个脚本唯一的区别是数据源和保存文件名所以复制修改时留意别把red保存成blue。3.3 推理流程与精度矩阵的生成模型训练完下一步是在测试集上算精度。资源里的redprecision.mat、blueprecision.mat、yellowprecision.mat应该就是不同颜色模型的评估结果。我一般会保存整体精度和每类精度而不是只存一个标量这样答辩时能说清楚哪个类别拖了后腿。function [acc, perClassAcc, C] evaluate_model(net, testImgs, testLabels) pred classify(net, testImgs); C confusionmat(testLabels, pred); acc sum(diag(C)) / sum(C, all); perClassAcc diag(C) ./ sum(C, 2); save(redprecision.mat, acc, perClassAcc, C); endconfusionmat输出的是混淆矩阵每一行代表真实类别每一列代表预测类别。如果某一行精确率明显低于其他行说明该类样本太少或者类内差异太大。perClassAcc是逐类精度注意当某一类在测试集中一个样本都没有时sum(C,2)会得到0导致NaN所以要在保存前检查测试集标签的分布是否均衡。4. GUI操作界面与识别链路整合从Traffic_Iden.fig到mcnn.m主控4.1 GUI布局与回调函数设计Traffic_Iden.fig和Traffic_Iden.m是配套的GUI文件前者存界面布局后者存回调函数。这是传统guide方式生成的工程Matlab新版本打开.fig时会提示用guide命令编辑但不要重新生成直接双击进入布局编辑器即可。界面至少要有三个部分显示原图的axes、显示预处理结果的axes、触发识别的按钮和显示文本。控件的Tag属性是回调函数定位控件的唯一依据布局时就要把Tag改成有意义的名称比如btnLoad、btnRecognize、txtResult而不是默认的pushbutton1。我的界面控件习惯如下表控件Tag类型作用axesOriginalaxes显示原始图片axesProcessedaxes显示裁剪和预处理后的标志区域btnLoadpushbutton打开文件选择对话框btnRecognizepushbutton执行识别流程txtResulttext显示识别类别和置信度回调函数的签名是统一的以加载图片按钮为例function btnLoad_Callback(hObject, eventdata, handles) [filename, pathname] uigetfile({*.jpg;*.png;*.bmp, 图片文件}); if isequal(filename, 0), return; end imgPath fullfile(pathname, filename); axes(handles.axesOriginal); imshow(imgPath); handles.currentImgPath imgPath; guidata(hObject, handles); enduigetfile返回两个值用户取消时filename是0所以要提前返回。imshow直接作用于handles.axesOriginal指定的坐标区。最后一行guidata非常关键它把更新后的handles写回GUI的handles结构否则btnRecognize的回调里读不到currentImgPath这个字段。4.2 mcnn.m主控逻辑颜色判断与模型选择mcnn.m负责把预处理、颜色判断、推理串起来。阅读这个脚本建议从recognize_from_gui这个入口函数开始看它接受图片路径和一个包含三个模型的models结构体返回识别结果结构体。function result recognize_from_gui(imgPath, models) I imread(imgPath); areas [ sum(extract_color_mask(I, red), all), sum(extract_color_mask(I, blue), all), sum(extract_color_mask(I, yellow), all)]; [~, idx] max(areas); colorNames {red, blue, yellow}; colorType colorNames{idx}; [label, score] predict_traffic_sign(I, colorType, models); result struct(ColorType, colorType, Label, label, Score, score); end这段代码先分别统计三种颜色掩膜的像素数哪个区域面积最大就交给对应的颜色模型。逻辑很朴素适合标志区域占画面主导的场景。如果图片里同时出现多个不同颜色的标志max(areas)只会选中一个这个局限可以在说明文档.txt里注明答辩时老师问了也好解释。predict_traffic_sign是实际推理函数注意模型参数从外部传入function [label, score] predict_traffic_sign(I, colorType, models) I imresize(I, [64 64]); switch colorType case red [label, score] classify(models.red.net, I); case blue [label, score] classify(models.blue.net, I); case yellow [label, score] classify(models.yellow.net, I); end endclassify输出的label是categorical类型score是概率向量。这里直接用imresize压缩整张图而不是裁剪标志区域是因为掩膜面积最大的颜色基本对应主标志整图缩放后标志仍然占主导。如果你的GUI里裁剪了区域就把这段改成先裁剪再resize。4.3 在GUI启动时加载模型与相对路径坑模型加载放在按钮回调里是最差的实践因为每点一次识别按钮都要读一次几十MB的mat文件。我习惯在GUI的OpeningFcn里一次性加载三个模型存到handles.modelsfunction Traffic_Iden_OpeningFcn(hObject, eventdata, handles, varargin) handles.models load_all_models(); handles.currentImgPath ; guidata(hObject, handles); end function models load_all_models() models.red load(rednet_cnn.mat, net); models.blue load(bluenet_cnn.mat, net); models.yellow load(yellownet_cnn.mat, net); endload_all_models返回的结构体里每个字段都是一个带net字段的结构体所以上面predict时用models.red.net。这里要提醒另一个坑load中的相对路径是相对于当前工作目录的GUI双击运行时工作目录经常不在源码目录。保险做法是用mfilename(fullpath)先取到脚本绝对路径再拼接模型文件名这样无论从哪个目录启动都不会找不到文件。注意如果最终用Application Compiler打包成exe需要把三个net_cnn.mat和三个precision.mat作为附加资源加入否则运行时会报“找不到文件”。5. 精度评估与调试技巧从precision文件到多尺度投票5.1 从redprecision.mat这类文件反推问题redprecision.mat这类文件答辩时老师一定会问“精度怎么算的”。我的习惯是保存成结构体包含整体精度、每类精度和测试样本数三项。如果某类精度特别低先别急着调网络看该类样本数和图片质量。样本太少时精度低是正常的优先补数据而不是加层。混淆矩阵里如果“禁止”类样本被大量分到“警示”类多半是颜色分割阶段把红色的两个子类混在一起了而不是CNN的问题。5.2 常见误判与阈值调整第一个常见误判红色和深橙色在HSV中H值靠得很近。预处理时我会把红色H区间从两端各收窄0.01同时把S下限提高到0.35减少橙色背景的干扰。第二个常见误判识别结果置信度不高但系统仍给出一个标签。默认classify会取分数最高的类别哪怕它只有30%的概率。我一般在GUI展示时加一个置信度门槛低于阈值显示“无法识别”[~, scores] predict(net, I); [maxScore, idx] max(scores); if maxScore 0.6 resultLabel 无法识别; else resultLabel classNames{idx}; end阈值0.6不是拍脑袋定的我通常先在验证集上画出置信度分布选一个能把“正确且高置信”和“错误但高置信”分开的点。如果你发现很多正确样本分数也在0.6以下就降到0.5同时检查是不是数据增强过度了。5.3 一个提高鲁棒性的小技巧多尺度投票最后一个调试技巧适合在答辩演示前用成本很低但效果明显。交通标志在照片中大小不一模型对训练时见过的尺度敏感测试图一旦比训练图大很多或小很多精度会掉。我通常会把测试图缩放到几个不同尺寸分别识别然后投票决定最终类别。function finalLabel multi_scale_vote(I, net, classNames) scales [0.8, 1.0, 1.2]; votes zeros(length(classNames), 1); for k 1:length(scales) resizedI imresize(I, scales(k)); resizedI imresize(resizedI, [64 64]); [~, scores] predict(net, resizedI); [~, idx] max(scores); votes(idx) votes(idx) 1; end [~, finalIdx] max(votes); finalLabel classNames(finalIdx); end这段代码里imresize了两次第一次按比例缩放模拟不同拍摄距离第二次强制拉回网络输入尺寸。两次缩放叠加后等效于在原图上做不同细节保留的重采样起到了类似数据增强的作用。投票数太少时可能平票我会在votes累加时同时累加scores按总分打破平局。这个技巧对GUI代码的改动只有一行把classify换成multi_scale_vote即可。本文还有配套的精品资源点击获取