ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于SIFT特征匹配与HSV分割的交通标志识别MATLAB实现

基于SIFT特征匹配与HSV分割的交通标志识别MATLAB实现 简介基于SIFT特征匹配的交通标志识别系统是一份面向MATLAB开发者、人工智能与计算机视觉学习者的完整工程代码主要解决复杂背景下交通标志的检测与分类问题尤其适合课程设计、毕业设计及算法复现等场景。实现时先在HSV颜色空间设定阈值提取候选区域再结合三角形、圆形等形状精确锁定目标随后采用SIFT特征将图像划分为固定数量子块并计算各子块SIFT向量均值得到具备旋转、平移和尺度不变性的特征完成分类识别。压缩包共43个文件含14个m格式源码、1个fig格式界面文件以及jpg、png等测试图像和SIFT运行所需的exe、key文件整包仅1.01MB部署方便。已有629人学习下载使用者可直接运行GUI观察识别效果或修改阈值与特征提取策略便于理解完整识别流程。1. 复杂背景下交通标志识别为什么先用SIFT特征匹配而非端到端一张实车抓拍图里交通标志可能只占几十个像素周围有树荫、车身反光和红色招牌。你把图片丢给深度学习模型通常要先标几百张图才能微调但换一条思路——用SIFT特征匹配不训练也能在旋转、缩放和局部光照变化下找出对应标志。这套项目提供的不是玩具demo而是完整可跑的MATLAB工程包含main.m、Traffic_Iden.fig/GUI、database图片库、旋转测试样本以及sift.m、myCanny.m、recognition.m等核心模块。它的识别链路很明确先在HSV颜色空间框出候选区域再用SIFT特征描述并匹配最后通过GUI直接看到结果。适合做辅助驾驶算法验证、模式识别课程设计或想快速拿到一套可改造的视觉识别框架的工程师继续往里换特征提取器。2. HSV颜色空间分割阈值表与三角形/圆形区域筛检2.1 RGB转HSV后为什么颜色分割更稳交通标志的颜色信息是最强的先验但RGB空间里红色、蓝色和黄色对光照非常敏感。同一个红色禁令标志早晨和黄昏的R/G/B数值变化很大而色相H基本稳定在同一个区间。MATLAB的rgb2hsv把RGB归一化到01的HSV于是可以用一段固定的H、S、V阈值把目标颜色区域抠出来。这套项目里分割不是只做一个二值掩码就结束。它还依赖形状约束禁止类标志是圆形警告类是三角形先行类往往是矩形。颜色分割后先找连通域再用面积、外接矩形占比、圆形度、多边形凸包筛掉“颜色对但形状不对”的干扰。2.2 常用交通标志HSV阈值范围我在matlab命令行里试过项目自带的几张图比如noplay.png、stopcar.jpg、减速让行.jpg。需要说明HSV在MATLAB中H、S、V都归一化到[0,1]所以下面阈值全按01写避免和0255混淆。目标颜色H范围S下限V下限典型标志红10°附近0 ~ 0.030.250.1禁止驶入、停车让行红2300°360°0.95 ~ 1.000.250.1禁止停车蓝0.53 ~ 0.650.300.15指示标志、限速黄0.10 ~ 0.180.250.20警告标志深红0.80 ~ 0.970.300.05八角停车让行注意红色有两个区间是因为HSV色相环里红色跨过0点闭合。如果不合并这两个区间会丢掉一部分红色像素。S下限用来排除灰白色背景V下限用来排除暗部阴影。实拍图对比度低时可以适当把S下限降到0.2但会引入更多肤色和砖墙噪声。到这一步你拿一张noleft2.jpg运行inRange逻辑基本能看到白色候选区内包含标志和右侧红车尾灯。车尾灯也是红色所以必须进入形状筛检环节。项目中的myCanny.m就是做Canny边缘提取的和HSV掩码取交后可以更稳定地计算边缘轮廓。2.3 形态学处理与连通域筛选代码下面这段是常见的MATLAB实现和项目里recognition.m的流程相仿src imread(noplay.jpg); hsv rgb2hsv(src); % 红色双区间合并 maskR (hsv(:,:,1) 0 hsv(:,:,1) 0.03) | ... (hsv(:,:,1) 0.95 hsv(:,:,1) 1.00); maskR maskR (hsv(:,:,2) 0.25) (hsv(:,:,3) 0.10); % 开运算去小噪点闭运算补内部空洞 se strel(square, 5); mask imopen(maskR, se); mask imclose(mask, se); % 连通域属性 stats regionprops(mask, BoundingBox, Area, Perimeter, Image); candidates []; for i 1:length(stats) w stats(i).BoundingBox(3); h stats(i).BoundingBox(4); ratio w / h; fill stats(i).Area / (w * h eps); if ratio 0.6 || ratio 1.8, continue; end if fill 0.4, continue; end % 形状不饱满 candidates [candidates; stats(i).BoundingBox]; %#okAGROW end这段代码里strel(square,5)的5代表结构元边长图像分辨率大就调到79小图用3防止把细杆和标志边缘切断。fill是区域面积与外接矩形面积之比圆形和八边形的fill值通常在0.7以上窄长噪声的fill很小。三角形标志的fill略低可以降到0.4同时检查凸包角点数量是否接近3。用regionprops提取Image属性后还可以对该块单独运行myCanny.m统计边缘方向直方图圆形边缘方向均匀三角形边缘方向集中在三个角度。项目里rebuildlist.m和traverse.m本质上都是遍历图片库做这类预处理生成待检索的ROI位置清单。如果测试图上候选框超过5个优先按面积降序排序再取前3个继续做SIFT因为交通标志在多数场景里不会同时出现过多。这个“尽量少算”的策略对后续SIFT匹配的实时性帮助很大。3. SIFT关键点与分块均值特征向量从128维描述子到可匹配的Bag特征3.1 sift.m背后VLFeat的帧与描述子项目提供的sift.m和siftWin32.exe来自VLFeat的旧版封装。调用方式通常是[frames, descrs] sift(I, PeakThresh, 0.003);I需要是im2single且转灰度后的图像。frames是4×N四行分别是x坐标、y坐标、尺度、方向角descrs是128×N每一列对应一个关键点的SIFT描述子。每个描述子元素是浮点梯度统计值没有做归一化处理匹配前最好按列做L2归一化。这套代码里还有tmp.pgm和tmp.key两个中间文件——siftWin32.exe是Windows命令行程序VLFeat MATLAB接口会先把图像写成tmp.pgm再把检测结果读回tmp.key。这说明项目在旧版MATLAB上把VLFeat外部二进制接进来了。如果你想换成新版MATLAB原生方法可以直接用detectSIFTFeatures和extractFeatures替换得到的效果等价但更干净。3.2 为什么整图描述子需要分块平均仅用SIFT关键点做暴力匹配在交通标志库小样本场景下问题不大但有两处痛点。第一同一类标志的实拍图关键点数量波动很大远的图只有几十个点近的图有上千个点直接比较描述子集合困难。第二关键点没有空间约束一张图的左上角点可能和另一张图的右下角点匹配上误配率会高。项目想到的把图像分割成固定数量子块、对每个子块内SIFT向量求平均本质上是用空间金字塔的思路把局部梯度信息压缩成等长向量。比如分割成4×312块每块有128维平均描述子拼接后得到1536维特征向量。这个向量不追求像素级对齐只要求标志大致处于画面中心附近就能把关键点的空间位置粗略保留下来。3.3 分块特征构造代码与参数调法下面是我按项目逻辑写的一个可运行的分块实现兼容VLFeatfunction imgFeat blockSiftDescriptors(I, gridRow, gridCol, siftFunc) if size(I, 3) 3, I rgb2gray(I); end I im2single(I); [frames, descs] siftFunc(I, PeakThresh, 0.004, EdgeThresh, 10); descs descs ./ (sqrt(sum(descs.^2, 1)) eps); % 128xN L2归一化 [h, w] size(I); rStep max(1, floor(h / gridRow)); cStep max(1, floor(w / gridCol)); feat zeros(gridRow * gridCol, size(descs, 1)); for i 1:gridRow for j 1:gridCol y0 (i - 1) * rStep 1; y1 min(i * rStep, h); x0 (j - 1) * cStep 1; x1 min(j * cStep, w); sel frames(1, :) x0 frames(1, :) x1 ... frames(2, :) y0 frames(2, :) y1; if sum(sel) 0 feat((i - 1) * gridCol j, :) mean(descs(:, sel), 2); end end end imgFeat feat(:); imgFeat imgFeat ./ (sqrt(sum(imgFeat.^2)) eps); end这里desc/sqrt(sum)把每个描述子向量归一化为单位长度避免对比度影响。siftFunc传funciton handle方便从VLFeat切换到MATLAB原生SIFT我对分数的归一化放在外层使得每个分块向量的L2范数为1再拼接成整图特征。注意空块向量不参与匹配如果正负样本旋转过大分块完全错位空块会增多此时应增大块尺寸、减少块数。分块数量不是越大越好。参考项目测试的15.jpg、5.jpg这类像素约240×240的样本我建议用4×3或3×3。分块方式特征维度适用情况2×2512维标志占画面比例小、位置偏移大3×31152维通用默认旋转鲁棒4×31536维近景标志、结构纹理丰富5×42560维高分辨率图但空块增多后匹配不稳定选中合适分块后你的database里每张图都变成一个固定长度的行向量接下来就能用最普通的最近邻距离做匹配不需要再处理关键点集合。4. 基于特征匹配的识别主流程与GUI回调实现4.1 数据库特征如何离线生成项目里的database文件夹放着15.jpg、nogo.jpg、stop.jpg、Let.jpg等样本。识别前要先把这些图片库全部转成特征向量保存成.mat文件不能每次点按钮都重新算一遍SIFT。下面是一个离线建库的流程function buildDatabase(imgDir, outFile, gridRow, gridCol) imgs dir(fullfile(imgDir, *.jpg)); dbFeats []; dbLabels {}; for k 1:length(imgs) I imread(fullfile(imgDir, imgs(k).name)); mask extractColorRegion(I); % 第2章的颜色分割 roi getMaxBoundingBox(mask); % 取最大候选ROI if isempty(roi), continue; end crop imcrop(I, roi); feat blockSiftDescriptors(crop, gridRow, gridCol, sift); dbFeats(end1, :) feat; %#okAGROW dbLabels{end1, 1} extractLabelFromName(imgs(k).name); end save(outFile, dbFeats, dbLabels); end这里的关键点是对ROI裁剪后再提取特征而不是整图做SIFT。裁剪后标志里的文字和图形占比更大SIFT关键点集中在标志内部分块特征能描述到“禁止符号是横杠还是箭头”这类区别。extractLabelFromName可以从文件名剥离.jpg再把中文名映射为标志类型。database里的“减速让行.jpg”“停车让行.jpg”“旋转右30°.jpg”都会被转义成对应标签旋转图建议作为额外正样本加入库中。buildDatabase跑完你会得到一个.mat文件里面dbFeats的行数等于有效图片数列数等于分块特征维度。后续GUI启动时load这个文件即可。4.2 最近邻匹配与投票策略识别阶段用同一个blockSiftDescriptors函数把待识别图的ROI转成特征然后和dbFeats逐行算欧氏距离。最小距离对应的label就是识别结果function [label, score] matchBlockFeature(qFeat, dbFeats, dbLabels) dists zeros(size(dbFeats, 1), 1); for i 1:size(dbFeats, 1) d qFeat - dbFeats(i, :); dists(i) d * d; end [score, idx] min(dists); label dbLabels{idx}; end如果 qFeat 里有空块dbFeats对应位置大部分是0欧氏距离会异常偏大所以建库时要把所有图片统一缩放到相近尺寸常见做法是保持长宽比resize到P×Q再提取分块特征。项目里的recognition1.m就是在这件事上做二次封装保证ROI尺寸一致。除了全局最小距离我建议增加“分块投票”兜底把12个分块分别做最近邻每块投一票最终得票最高的label胜出。这样即使某两块被边缘阴影干扰也不会直接带偏整图判断。如果全局距离和投票结果不一致优先采信投票结果再把score和阈值比较。4.3 Traffic_Iden.m里的GUI按钮与回调GUI文件是Traffic_Iden.fig配套Traffic_Iden.m。在GUIDE中布局通常有“打开图片”按钮、“开始识别”按钮、一个axes显示图像一个text显示结果。按钮回调函数原型如下function pushbutton_open_Callback(hObject, eventdata, handles) [file, path] uigetfile({*.jpg;*.png;*.bmp, 图片文件}); if isequal(file, 0), return; end handles.I imread(fullfile(path, file)); axes(handles.axes1); imshow(handles.I); guidata(hObject, handles); end function pushbutton_recog_Callback(hObject, eventdata, handles) I handles.I; if ~isfield(handles, dbFeats) data load(database_feats.mat); handles.dbFeats data.dbFeats; handles.dbLabels data.dbLabels; guidata(hObject, handles); end mask extractColorRegion(I); roi getMaxBoundingBox(mask); crop imcrop(I, roi); feat blockSiftDescriptors(crop, 4, 3, sift); [label, score] matchBlockFeature(feat, handles.dbFeats, handles.dbLabels); set(handles.text_result, String, [label, 得分:, num2str(score)]); endguidata(hObject, handles)是MATLAB GUI里最常见的状态同步写法所有回调通过handles访问数据。很多新手在这里卡住改了handles后忘记guidata下一次回调拿到的还是旧的识别按钮永远报找不到图片。另一个坑是axes句柄如果你在GUIDE里把axes1的Tag改成了其他名字imshow之前必须保证axes(handles.axes1)处于当前坐标轴否则会覆盖空白figure。项目里recognition.m和Traffic_Iden.m的分工也很清晰recognition.m承担纯算法逻辑Traffic_Iden.m只做事件转发。别把SIFT参数写死在GUI回调里否则修改PeakThresh时还要开GUIDE找代码位置。参数表可以先放GUI初始化函数里控件Tag初始值作用edit_thresh0.004SIFT PeakThreshpopup_grid4x3分块数量text_result等待识别结果输出axes1空原图显示运行traverse.m或main.m时程序会先调用buildDatabase检查.mat是否过期再启动GUI。这样你用测试集做批量评估时直接调recognition函数就行不进GUI。5. 旋转样本验证、阈值判定与用同一套代码替换数据集的技巧5.1 用旋转图测试尺度与旋转不变性项目附带“旋转原图.jpg”“旋转左30°.jpg”“旋转右30°.jpg”就是专门用来验证SIFT特征的旋转不变性。这里有个容易忽略的细节HSV颜色分割不受旋转影响但分块特征向量会受影响因为标志旋转后原本在左上块的“减”字笔画可能跑到右上块去。解决办法是建库时就把旋转样本加进数据库让每个方向都有对应距离可度量。测试时你可以在matlab里做一组批处理angulars -30:15:30; for a angulars Ir imrotate(I, a, bilinear, crop); f blockSiftDescriptors(Ir, 4, 3, sift); dist sqrt(sum((f - refFeat).^2, 2)); fprintf(旋转%d°, L2距离%f\n, a, dist); end如果距离在旋转15°内基本持平在30°有明显跳升说明分块网格的粒度偏细改成3×3分块往往能压住这个跳升。注意imrotate默认crop会裁掉边角会让标志丢边测试时候建议先放大画布再旋转或者用loose输出后resize到固定尺寸。5.2 匹配分数阈值与“未知标志”很多下载到手的代码只返回“匹配到哪张”没有“不认识”的能力。实际场景中你传入一张非交通标志图SIFT也能强行匹配到最近的一张这是必须解决的。我一般会在matchBlockFeature外面包一层阈值判断if score 0.35 label dbLabels{idx}; else label 未知标志; end阈值要看你的特征维度和分块数不能网上说0.35就照抄。批量跑一遍测试集把正确样本和错误样本的score分布画出来选取两类分布交叉点再往右偏一点作为阈值。项目里的ee.m和extraction.m就是做这种批量评估的辅助脚本你可以结合它们的输出生成ROC式判断而不是拍脑袋定阈值。5.3 数据结构小建议tmp.key与pgmVLFeat siftWin32运行时产生tmp.pgm和tmp.key这两个文件是临时介质。在旧版MATLAB里如果当前工作目录不可写sift.m会静默报错。拿到项目后第一件事就是把这些文件移出保留确保证运行时pwd可写。新版环境可以改成vl_sift函数避免落盘。换自己的数据集时只需要保持图片库目录和识别ROI两个入口一致建库脚本可以完全复用——把database文件夹替换成你自己的标志图并修改extractLabelFromName的名字映射表交通标志识别系统就能平移成其他物体识别原型。这套基于SIFT分块描述子的做法迁移成本比重新训一轮CNN低得多。本文还有配套的精品资源点击获取
返回列表