MATLAB GUI水面漂浮物垃圾分类检测:从HSV分割到SVM分类实战
发布时间:2026/9/13 14:24:19来源:尧图网络
简介MATLAB GUI漂浮物垃圾分类检测项目是一套可直接运行的图形界面程序面向计算机、通信、人工智能、自动化等专业学生和从业者。项目以漂浮物图像为输入融合图像预处理、特征提取与分类器设计可帮助用户完成从样本导入、模型训练到分类结果展示的完整流程同时适配课程设计、课程大作业或毕业设计等场景初学者可结合源码学习有基础者也能在此基础上扩展分类类别或改进算法。项目基于GUIDE或App Designer构建图形界面覆盖图像上传、预处理、特征提取、分类器训练与结果输出等环节具有较高的模块可读性样本集包含塑料袋、塑料瓶、花朵、橘子等常见漂浮物类别可用于验证不同特征与分类器的效果。压缩包共39个文件包含32张jpg训练样本、m主程序、fig界面文件、mat训练模型以及doc导入说明等整体仅1.56MB结构直观便于快速部署。已有50人学习浏览。利用该资源可省去自行搭建GUI和训练环境的时间直接对照界面代码理解分类流程并能通过替换样本图片拓展检测对象。1. MATLAB GUI漂浮物垃圾分类检测拿到程序后先看这条主线水面图像里的漂浮物最难处理的不是“识别”而是“从反光和水波纹里把目标完整分割出来”。这套 MATLAB GUI 项目把预处理、ROI 定位、特征提取和分类器推理封装在一个可视界面里启动 piaofuwu.m加载 net.mat选择图片点击分类按钮结果直接显示在界面标签上。它内置了红色塑料袋、蓝色瓶子、黄色花朵、橘子等类别样本适合刚接触 matlab 图像处理的在校生对照完整链路学习也适合有经验的人直接拿 GUI 做原型演示或课程设计。与传统深度学习方案不同它走的是 HSV 分割加经典分类器的路线这也意味着模型体积小、训练时长可控、中间每一步都能单独观察输出结果。2. 图像预处理与 ROI 定位finddomain.m 的边界提取思路2.1 为什么先做色彩空间转换再设阈值水面图像的反光会让同一物体出现截然不同的 RGB 数值。红色塑料袋直接受光时是亮红阴影里就变成暗红带紫蓝色瓶身倒影在水面上会掺入天空的灰色。直接对 RGB 三分量写阈值要么把目标滤掉要么把反光全部圈进来。常见做法是先转 HSV将颜色信息拆成色相、饱和度、明度三个互不干扰的通道再分别设阈值。H 通道描述“是什么颜色”S 通道描述“颜色有多纯”V 通道只负责亮度。对水面场景S 和 V 联合使用能有效区分彩色目标与灰白水体。function mask waterSegmentation(img) hsv rgb2hsv(img); % 转换到 HSV通道范围均为 0~1 h hsv(:, :, 1); s hsv(:, :, 2); v hsv(:, :, 3); % 红色色相在 0 附近或越过 0.97 后靠近红饱和度和亮度都要够 mask_red (h 0.03 | h 0.97) (s 0.4) (v 0.1); % 蓝色色相约在 0.5~0.7饱和度明显高于水面 mask_blue (h 0.5 h 0.7) (s 0.35) (v 0.1); mask mask_red | mask_blue; end这组阈值不是拍脑袋写的。实际调试时我会先用imtool(img)打开图像点几个目标区域的颜色值把每个样本的 H、S、V 记录下来再回填到阈值里比凭经验快得多。H 的红色区间要特别注意红色的 H 在 0 附近但 HSV 的色相是环形所以必须用h 0.03 | h 0.97这种形式同时覆盖两端。S 阈值抬高到 0.4 会把水面反光中的灰色像素筛掉代价是偏粉的淡色物体可能漏检所以后续还需要边缘信息补回来。2.2 finddomain.m 里的连通域与边缘组合资源里的 finddomain.m 并不负责最终分割它的作用是在颜色分割失效时用边缘信息找回目标区域。具体思路是先转灰度用 Canny 提取轮廓再用膨胀和填充把断裂的轮廓线拼成闭合区域。这套方法对高亮反光区域反而有效因为物体与水面的交界处往往存在明显的灰度跳变即使彩色信息被反光冲淡灰度边缘仍然稳定。gray rgb2gray(img); % 灰度图用于边缘检测 edge_map edge(gray, canny, [0.1 0.2]); % Canny 低阈值为 0.1、高阈值为 0.2 edge_map imdilate(edge_map, strel(disk, 3)); % 先膨胀让不连续边缘连接 filled imfill(edge_map, holes); % 填充闭合区域内部 candidates bwareaopen(filled, 200); % 面积小于 200 像素的碎片丢弃Canny 的双阈值[0.1 0.2]控制边缘敏感度。低阈值 0.1 决定哪些弱边缘被保留高阈值 0.2 决定哪些强边缘必须保留。水面波纹会生成大量细碎边缘如果发现候选区域太多把低阈值抬到 0.15 能明显减少波纹干扰。imdilate的结构元用disk半径 3半径太小断口接不上半径太大相邻物体容易粘连。这个函数和 2.1 节的颜色分割是互补关系颜色分割提供区域内部的像素集合边缘填充提供闭合目标的整体轮廓两个结果做交集或并集都可以作为后续形态学操作的输入。2.3 形态学净化与候选区域筛选从颜色或边缘得到的 mask 还带着孤立噪点和缺口。直接送去提取特征会让形状特征失真比如圆度会被缺口拉低面积会被噪点顶高。此时需要先闭运算填充孔洞再开运算去除毛刺。闭运算是先膨胀后腐蚀适合补上物体内部的小孔开运算是先腐蚀后膨胀适合切断细长的连接点。se strel(disk, 5); mask_clean imclose(mask, se); % 闭运算填充孔洞保留目标形状 mask_clean imopen(mask_clean, se); % 开运算去除向外突出的毛刺 mask_clean bwareaopen(mask_clean, 500); % 去掉小于 500 像素的连通域 stats regionprops(mask_clean, BoundingBox, Area); imshow(img); hold on; for k 1:numel(stats) rectangle(Position, stats(k).BoundingBox, ... EdgeColor, r, LineWidth, 1.5); end这里的disk半径 5 是经验起点实际要根据图像分辨率调整。图像尺寸越大单个物体占的像素越多结构元半径也应该相应放大否则形态学操作起不到净化的作用。bwareaopen的 500 像素阈值用于过滤小水花和噪点但如果你的拍摄视角比较远目标本身就很小这个值可能需要降到 200。区域框画出来后再看目标是否完整这比直接看数字结果更容易定位问题。结构元半径适用场景副作用3近距离特写目标占画面比例大对细长条物体可能保留不全5中距离常规水面监控两个紧挨物体可能合并8远距离小目标为主容易把相邻垃圾粘连成一个区域3. 特征提取与分类器训练从训练样本到 net.mat3.1 特征工程颜色、形状与纹理的组合漂浮物分类不能只看颜色。红色塑料袋和黄色花朵在 H 通道都可能偏暖此时形状能提供区分橘子和蓝色瓶子都是近圆形颜色又能把二者分开。因此这套项目把特征抽取分成了三组HSV 统计特征、几何特征和灰度纹理特征。统计特征用均值加标准差既保留中心趋势也保留像素分布范围几何特征用面积、周长和圆度纹理特征使用灰度共生矩阵的对比度。function feats extractFeatures(mask, hsv_img, gray_img) % 颜色特征H/S/V 的均值和标准差共 6 维 h hsv_img(:,:,1); s hsv_img(:,:,2); v hsv_img(:,:,3); feats(1) mean(h(mask)); feats(2) std(h(mask)); feats(3) mean(s(mask)); feats(4) std(s(mask)); feats(5) mean(v(mask)); feats(6) std(v(mask)); % 形状特征面积、周长、圆度共 3 维 stats regionprops(mask, Area, Perimeter); area stats.Area; perim stats.Perimeter; feats(7) area; feats(8) perim; feats(9) 4 * pi * area / perim^2; % 圆度圆为 1细长条趋近 0 % 纹理特征灰度共生矩阵对比度1 维 glcm graycomatrix(gray_img, NumLevels, 8, Offset, [2 0]); feats(10) graycoprops(glcm, Contrast); end这段函数要求mask是逻辑数组否则h(mask)索引会报错。特征向量的顺序必须在训练和推理时保持一致一旦调整过某个特征的位置旧的 net.mat 就要重新训练。灰度共生矩阵的NumLevels我习惯设为 8因为漂浮物表面通常不是精细纹理8 级灰度足够捕捉塑料袋褶皱和果皮表面的差异Offset选[2 0]表示水平方向距离 2 像素对旋转不敏感的需求可以再加[0 2]、[1 1]等方向但特征维度会膨胀小样本下反而容易过拟合。3.2 分类器选型SVM 与神经网络怎么取舍训练样本里有 26 张左右图像单类样本数很少。这种规模下支持向量机比神经网络更容易收敛也容易解释调整方向。fitcecoc是 MATLAB 里做多分类 SVM 的推荐函数它内部把多类别拆成多个二分类器默认使用一对一编码。神经网络的优势是决策边界更灵活但如果没有足够验证集调超参数很容易在训练集上表现好、测试集上崩掉。这个项目里的 net.mat 无论存放的是 SVM 模型还是 BP 网络在 GUI 中的加载和调用方式都是相同的load进来然后用predict或sim做推理。分类器适用场景训练时长超参数敏感度fitcecocSVM样本数几十到几百特征维数 10 左右秒级较低主要调核函数和 BoxConstraintfeedforwardnetBP样本数几百以上特征与类别非线性强分钟级较高需调隐层节点和训练轮数如果你的机器上没有安装统计和机器学习工具箱fitcecoc会直接报错。提前运行ver(stats)确认是否存在。如果只是做演示也可以用fitcdiscr判别分析替代但精度通常不如 SVM。3.3 训练脚本提取特征、训练模型、保存 net.mat完整的训练流程是把所有训练样本读取、预处理、提取特征组装成特征矩阵 X 和标签向量 Y然后调用分类器训练。文件清单里的“训练样本”目录包含 0.jpg 到 25.jpg命名本身没有类别信息所以要在脚本里用文件名映射或者目录结构区分。常见做法是在每个类别子目录下放对应图片用父文件夹名作为标签。files dir(fullfile(训练样本, *.jpg)); X []; Y {}; for i 1:length(files) img imread(fullfile(files(i).folder, files(i).name)); mask waterSegmentation(img); % 与 GUI 中完全相同的预处理 gray rgb2gray(img); hsv rgb2hsv(img); feats extractFeatures(mask, hsv, gray); X [X; feats]; Y [Y; getLabel(files(i).name)]; % 根据文件名前缀映射类别 end net fitcecoc(X, Y); % 训练多分类 SVM save(net.mat, net);训练前务必确认 X 中没有 NaN 行。常见原因是某一类图像的颜色阈值把整个目标过滤掉导致 mask 为空mean(mask)返回 NaN。可以在循环里加一段检查如果sum(mask(:)) 100打印出对应文件名并跳过或手动调整该图的阈值。fitcecoc默认核函数是线性若特征之间的量纲差异大建议改成KernelFunction, rbf但 RBF 核需要额外调BoxConstraint否则容易过拟合。保存 net.mat 时只保存分类器对象不要连图片和中间变量一起写进去减小文件体积。4. GUI 代码框架与回调实现piaofuwu.fig 的前后台联动4.1 handles 结构体与界面对象挂载GUIDE 生成的 piaofuwu.fig 保存界面布局piaofuwu.m 管理初始化与回调。打开函数OpeningFcn里handles是所有界面对象的集合也是回调函数之间传递数据的中心。分类器应该只加载一次不要在每个按钮回调里反复读 net.mat否则点击分类时会卡顿。界面对象需要保证 tag 唯一比如图像显示区域设为axes1结果文本设为textResult加载按钮设为btnLoad分类按钮设为btnClassify。function piaofuwu_OpeningFcn(hObject, eventdata, handles, varargin) handles.output hObject; if exist(net.mat, file) load(net.mat, net); handles.net net; % 分类器挂到 handles 上 else handles.net []; % 没有模型时分类按钮不可用 end guidata(hObject, handles); % 必须调用 guidata 更新 endguidata(hObject, handles)是把 handles 写回图形对象的 UserData后续回调通过handles.net访问模型时才能拿到最新内容。初次接触 GUIDE 的人经常忘记这步结果回调里看到的是空 handles。另一个容易忽略的点是工作目录load(net.mat, net)用的是相对路径如果从命令行直接运行piaofuwu而当前目录不是工程根目录会报文件不存在。4.2 核心回调加载图像与输出分类结果分类回调是整套程序的出口。先加载图像到handles.img再在分类按钮里调用预处理、特征提取和预测。这里的关键是训练脚本和 GUI 回调必须共用同一套函数分割阈值和特征顺序也保持一致。如果训练时用的分离脚本GUI 里又复制了一份不同参数的函数结果会很混乱。function btnLoad_Callback(hObject, eventdata, handles) [file, path] uigetfile({*.jpg;*.png;*.bmp, 水面图像}); if isequal(file, 0) return; % 用户点击取消 end img imread(fullfile(path, file)); axes(handles.axes1); % 切换到图像显示区域 imshow(img); handles.img img; guidata(hObject, handles); end function btnClassify_Callback(hObject, eventdata, handles) if isempty(handles.net) || ~isfield(handles, img) set(handles.textResult, String, 请先加载模型和图像); return; end img handles.img; mask waterSegmentation(img); feats extractFeatures(mask, rgb2hsv(img), rgb2gray(img)); label predict(handles.net, feats); % label 是字符向量或分类数组 set(handles.textResult, String, [分类结果, char(label)]); endpredict返回的结果可能是categorical类型直接用char(label)能把它转成普通字符串避免在set时出现转换错误。isfield(handles, img)用来判断是否已经加载过图像防止用户没选图就点分类导致程序崩溃。界面上的静态文本 tag 要和回调里写的handles.textResult完全一致否则 set 会报无效参数名。4.3 Unt.p 的封装与 net.mat 加载细节Unt.p是 MATLAB 的 pcode 文件由pcode Unt.m生成。调用方式与原 .m 完全相同例如mask Unt(img);但源码被加密无法用type Unt查看内容。这种封装很适合课程设计提交场景避免局部算法直接被复制同时保持主函数和各回调可维护。需要注意的是 pcode 与 MATLAB 版本绑定高版本生成的 .p 文件在低版本 MATLAB 里通常无法运行反过来低版本生成的 .p 在高版本里虽然能运行但会丢失高版本性能优化。net.mat 的加载失败要先确认工具箱支持执行which fitcecoc或which feedforwardnet有正常路径输出才说明工具箱已经安装。问题现象常见原因排查方向Unable to read MAT-file网络传输损坏或版本不兼容重新拷贝 net.mat检查 MATLAB 版本Undefined function fitcecoc缺少统计和机器学习工具箱运行ver(stats)查看Index exceeds matrix dimensions特征维数与 net.mat 训练时不符核对 extractFeatures 的输出长度调用 Unt 报错pcode 版本高于当前 MATLAB让原作者在相同版本生成 .p 文件5. 新场景验证与调参从 25 张训练图开始拿到这套程序第一步不是扩大训练集而是验证当前分割参数在你自己水面图像上是否成立。把 0.jpg 到 25.jpg 分成训练集 20 张、测试集 5 张用 tic/toc 包住预处理和分类观察三类指标。指标计算方式建议基线分割 IoU预测 mask 与手工标注 mask 的交并比大于 0.7单类召回率该类正确识别数 / 该类实际数量红袋大于 0.8橘子大于 0.85单张耗时tic/toc 测量 GUI 回调小于 1 秒召回率低先查 mask 是否过分割也就是目标被切碎后面积小于bwareaopen的阈值IoU 低则把 2.3 节结构元半径从 5 改到 3保留更多边缘细节。特征里出现 NaN 时打开 mask 单通道看看是不是全黑尤其是深色背景下的橘子V 阈值调到 0.05 通常能明显改善。换到水池或河道场景时反光强度会变化S 阈值可以放宽到 0.3 但没有排除静水高光区域的把握时还是应该多跑几张图看分割结果。一个稳妥的做法是所有可调阈值和核半径写进同一个结构体params在 OpeningFcn 里初始化再给 GUI 增加一个调参回调。运行时改阈值不用注释代码直接拖动滑块或输入数值就能实时更新params实验记录里也能写清楚“当前参数组合得到的准确率”。这样反复实验时不会出现改了代码忘了改回原值的情况课程设计答辩时也能直接展示参数对结果的影响过程。本文还有配套的精品资源点击获取
网站建设高端定制企业官网