MATLAB颜色识别实战:HSV阈值分割与目标定位统计 📅 发布时间:2026/9/13 16:26:11 👁 浏览次数: 简介面向计算机相关专业学生的MATLAB课程设计资料针对图像颜色识别与处理任务提供完整可运行的工程方案适合作为大作业或毕设初期演示参考。压缩包中共有7个文件包括.m格式的主程序源码、.fig格式的GUI交互界面、.jpg格式的测试样本图像、.md格式的说明文档以及一个存放额外数据的.zip压缩包整体大小仅197KB携带和部署都很方便。项目基于图形界面设计实现颜色识别系统源码已通过运行测试能够完成颜色区域分割、颜色特征提取等核心功能测试图像涵盖不同颜色形状场景便于直观验证算法效果配套文档对设计思路和操作流程作了说明可在此基础上针对具体需求修改或扩展。目前已有70人学习下载内容难度适中既适合初学者模仿学习也能满足课程设计、作业提交等实战需求。1. 从“找颜色”到“量化颜色”MATLAB颜色识别比想象中多两步这个标题看起来是课程设计实际做的时候会发现在MATLAB里完成“找出红色区域”这种任务难点从来不在“调用什么函数”而在把一张真实照片里的红色目标变成能写进报告里的“坐标、面积、占比”。因为真实场景中的光照、阴影、反光会让同一个物体的RGB值差别很大直接做像素相等比较基本会失败。日常做法是把图像从RGB转换到HSV颜色空间用色相H做主判据、饱和度S和亮度V做约束得到二值掩码再用形态学操作整理成可统计的目标区域。标题里源码、文档、数据三件套落到技术上其实就是颜色空间转换、阈值分割、形态学后处理、区域统计这四步。下面按这条主线展开适合做图像处理课程设计也适合在MATLAB里做颜色视觉预研的工程师参考。2. 颜色空间选型为什么RGB直接做颜色识别会失败HSV怎么解决2.1 RGB空间里的“同一颜色”在不同光照下根本不是同一个RGB值先用一个具体场景说明问题。一个橙子在室内日光灯下橙色区域的RGB大致是(220, 130, 40)同一颗橙子转到阴影处可能变成(150, 80, 25)如果表皮有高光局部甚至会接近(255, 220, 180)。在这三种情况下如果你用“R在200到240且G在100到160且B在20到60”这样的RGB规则去圈橙子阴影部分一定会漏检高光部分则完全不满足条件。RGB三个通道的本质是颜色的三刺激值亮度信息被平均摊在三个通道里。当环境光强度改变时三个通道的数值会整体上移或下移你想找一个能同时覆盖三种光照的RGB盒状区域几乎做不到。这不是阈值选得不好而是这个坐标系本身就不适合“按颜色分区域”的任务。2.2 HSV的三个通道到底在描述什么为什么H分量稳定HSV把颜色拆成色相H、饱和度S、亮度V三个独立坐标。H描述颜色的本质用0到1的角度来表示从红到橙、黄、绿、青、蓝、品红再到红的一圈S描述颜色有多“纯”S越接近1颜色越鲜艳S接近0就是灰V描述亮度。这个拆法的意义在于光照强度变化主要影响V对H的影响很小对S的影响也有限所以以H为主判据做分割天然比RGB抗光照变化。不过H分量有个必须记住的边界饱和度接近0的像素H值会变得很不稳定一个灰色像素会因为很小的噪声在H上乱跳纯黑或纯白区域的S和V没有区分度也不该参与颜色判断。所以在构造掩码时S下限和V的上下限不是可有可无的约束而是保证H可信的前提。2.3 MATLAB里RGB转HSV的最小实现img imread(fruit.png); % 读取原始RGB图像 hsvImg rgb2hsv(img); % 转换到HSV空间 H hsvImg(:,:,1); % 色相, 范围[0,1] S hsvImg(:,:,2); % 饱和度, 范围[0,1] V hsvImg(:,:,3); % 亮度, 范围[0,1] figure; subplot(1,3,1); imshow(H); title(H通道); subplot(1,3,2); imshow(S); title(S通道); subplot(1,3,3); imshow(V); title(V通道);这段代码是后面所有分割逻辑的基础。rgb2hsv是MATLAB图像处理工具箱Image Processing Toolbox提供的函数输入可以是uint8的RGB图输出是double类型的HSV图三个通道都归一化到0到1所以后续写阈值时千万不要按0到255的去写这是最常见的出错点。用imshow单独显示H通道时看到的是灰度图越亮表示H值越接近1配合颜色条能更快理解H的分布规律。注意rgb2hsv输出是double类型取值范围0到1。按0~255的直觉写阈值会直接得到一张全黑或全白的掩码而且报错信息不会提示你这里有问题。2.4 三个常用颜色空间的对比与选型颜色空间通道含义对光照鲁棒性MATLAB函数适合场景RGB红/绿/蓝三刺激值差亮度干扰大无需转换原图显示、存储HSV色相/饱和度/亮度H较稳rgb2hsv按颜色分割目标Lab亮度绿红轴蓝黄轴a/b轴也较稳rgb2lab颜色相似度计算这里补一个选型建议如果只需要“把红色和蓝色分开”HSV足够如果需要评估“两个颜色有多接近”Lab空间的欧氏距离更合适。本项目的核心任务是分割出颜色区域并统计面积、位置使用HSV是较为稳妥的默认选择代码量也最小。后面所有实现都基于HSV进行。3. 基于HSV阈值的MATLAB颜色识别从掩码到目标统计3.1 单色目标提取的最小流程转换、阈值、掩码颜色识别的核心动作是把“找到红色像素”翻译成一段逻辑表达式然后输出一个二值掩码。下面是识别红色水果的完整最小代码注释里标出了三个关键设计点。img imread(fruit.png); hsvImg rgb2hsv(img); H hsvImg(:,:,1); S hsvImg(:,:,2); V hsvImg(:,:,3); % 红色在H环上跨过0/1边界, 所以要同时覆盖0附近和1附近两个区间 redMask (H 0.06 | H 0.94) ... % 色相属于红色系 (S 0.30) ... % 排除灰白像素 (V 0.10 V 0.95); % 去掉纯黑/纯白高光点 figure; subplot(1,2,1); imshow(img); subplot(1,2,2); imshow(redMask);逻辑说明|和在MATLAB里分别做或、与运算配合括号明确优先级。红色的H值在0附近同时H1等价于H0所以必须写两个区间用或连接S下限0.30是为了把纸箱、白色桌面这类低饱和背景直接排除掉V下限0.10去掉阴影里的死黑点V上限0.95去掉镜面高光里接近白色的像素。这三个边界不是固定的后面第5章会讲标定方法。需要特别注意H 0.06 | H 0.94这种写法在MATLAB里会产生一个与H同尺寸的逻辑矩阵逻辑矩阵可以直接用imshow显示也可以保存成单通道掩码文件后续的形态学处理和区域统计都以它为输入。3.2 形态学后处理开运算去掉噪点闭运算补上空洞阈值出来的掩码通常带两类问题背景里零星散布的孤立噪点以及目标内部因为高光或纹理产生的空洞。直接拿这样的掩码去做面积统计结果会偏得离谱。常见做法是先后做一次闭运算再做一次开运算最后用imfill填洞。se strel(disk, 5); % 半径为5的圆盘结构元 redMask imclose(redMask, se); % 先闭运算, 减少内部空洞 redMask imopen(redMask, se); % 再开运算, 去除孤立噪点 redMask imfill(redMask, holes); % 填充剩余目标内部空洞strel(disk, 5)生成一个半径5像素的圆盘结构元半径大小决定了操作的“力度”。闭运算会先把窄缝和空洞填上开运算再把细小的噪声点抹掉。顺序上先闭后开比反过来更合理先修复目标内部的结构再去清理背景上的杂点否则开运算会把本来就残缺的目标边界越抹越薄。结构元半径取图像尺寸的1/100到1/200左右是一个可参考的初始值后面按实际效果调。3.3 多颜色识别用一张颜色映射表扩展检测能力单色识别跑通之后下一个需求通常是同时识别红、绿、蓝、黄等多种颜色。工程上我一般会维护一张颜色阈值表再用循环逐色生成掩码。下面是阈值表和循环调用的骨架颜色H范围S下限说明红0~0.06 或 0.94~10.30跨越H环的0/1边界黄0.10~0.220.35与橙色的边界在0.08~0.10附近绿0.28~0.420.30黄绿边界在0.22~0.28之间蓝0.55~0.700.30青色区域与蓝靠近0.55% 颜色映射表: 名称, H下界, H上界, S下界, V下界, V上界 colorSpec { red, 0.00, 0.06, 0.30, 0.10, 0.95; green, 0.28, 0.42, 0.30, 0.10, 0.95; blue, 0.55, 0.70, 0.30, 0.10, 0.95; yellow,0.10, 0.22, 0.35, 0.20, 0.95; }; maskAll zeros(size(img,1), size(img,2)); % 累积掩码 for k 1:size(colorSpec, 1) hL colorSpec{k,2}; hU colorSpec{k,3}; sL colorSpec{k,4}; vL colorSpec{k,5}; vU colorSpec{k,6}; if hL hU mask (H hL H hU); else % 跨0区间: 红色这类 mask (H hL | H hU); end mask mask (S sL) (V vL V vU); mask imclose(mask, se); mask imopen(mask, se); mask imfill(mask, holes); maskAll(mask) k; % 用标签编号填充累积掩码 end imshow(label2rgb(maskAll)); % 标签矩阵转伪彩色显示这段逻辑的关键是maskAll(mask) k这一行它把每类颜色的目标用不同的数值编号填进同一个标签矩阵后面的区域统计可以直接基于这个带标签的矩阵进行。label2rgb把标签矩阵转成伪彩色图能一眼看出每个区域被归到了哪一类。注意如果两个颜色的H区间设计得有重叠后处理的标签会覆盖前面的标签所以颜色映射表中的区间不要交叉这也是上表存在的意义之一。实际项目里应该把colorSpec放到独立配置文件里方便标定时单独修改。3.4 用regionprops把掩码变成可写进报告的统计量有了干净的掩码接下来要回答“目标在哪、有多大”的问题。MATLAB的regionprops是对连通域做统计的标准入口一次调用就能拿到面积、质心、外接矩形、长轴长度等几十个属性足以支撑课程设计里“识别定位计数”的完整输出需求。stats regionprops(maskAll, Area, Centroid, BoundingBox, ... MajorAxisLength, MinorAxisLength); targetCount numel(stats); fprintf(检测到 %d 个目标\n, targetCount); for k 1:targetCount area stats(k).Area; cx stats(k).Centroid(1); cy stats(k).Centroid(2); bb stats(k).BoundingBox; fprintf(目标%d: 面积%d, 质心(%.1f, %.1f), 宽x高%.0fx%.0f\n, ... k, area, cx, cy, bb(3), bb(4)); end % 把结果叠加显示到原图上 figure; imshow(img); hold on; for k 1:targetCount rectangle(Position, stats(k).BoundingBox, EdgeColor, y, LineWidth, 2); plot(stats(k).Centroid(1), stats(k).Centroid(2), r, MarkerSize, 10); end hold off;regionprops返回的是结构体数组每个元素对应一个连通域。Area是像素个数不是物理面积要换算成实际面积需要结合相机标定得到的像素与毫米比例。Centroid返回[x, y]两个分量注意x是列方向、y是行方向画图时不要写反。BoundingBox的格式是[x, y, w, h]x和y是外接矩形的左上角刚好能直接喂给rectangle函数画框。整个stats结构体还可以用struct2table直接转成表格方便写到CSV或Excel里这一招在做批量实验时尤其省事。如果发现同一个目标被regionprops拆成了好几块说明前面的形态学去噪力度不够优先回去调结构元半径而不是在这里做区域合并。4. 参数标定与批量输出把颜色识别从“跑通”变成“可交付”4.1 阈值怎么来用impixel对采样点做统计标定很多人在这一步卡住阈值是拍脑袋定的换张图片就失效。常见做法是不要凭感觉写阈值而是直接在图上点选目标区域收集采样点的HSV值再按均值和标准差算出区间。交互式取点的函数是impixel。以下是标定流程的代码骨架img imread(fruit.png); figure; imshow(img); title(点选目标颜色区域选好后按回车); pixRGB impixel(img); % 在图上点选, 每次点击取一个像素 pixHSV rgb2hsv(pixRGB); % 每行是一个采样点 Hvals pixHSV(:,1); Svals pixHSV(:,2); Vvals pixHSV(:,3); fprintf(H: 均值%.3f 标准差%.3f | S: %.3f±%.3f | V: %.3f±%.3f\n, ... mean(Hvals), std(Hvals), mean(Svals), std(Svals), mean(Vvals), std(Vvals)); % 以均值±2倍标准差作为建议区间 hLow max(0, mean(Hvals) - 2*std(Hvals)); hHigh min(1, mean(Hvals) 2*std(Hvals));逻辑说明impixel在图像上交互取点取完按回车返回RGB矩阵每一行是一个点。rgb2hsv处理这个矩阵后每一行对应一个样本的HSV值。按“均值±2倍标准差”给出的范围初看会偏保守因为颜色分布往往不是标准正态但作为一个起点完全够用之后在这个基础上放宽或收紧。注意H是环状变量如果均值在0附近直接加减会把范围算到负数或超过1的地方算法上要先绕环处理不过那是标定工具要解决的问题了。4.2 掩码可视化把分割结果叠回原图快速定位误检每次调完阈值都需要一个直观的反馈。我习惯把掩码的轮廓画到原图上轮廓以内是高亮目标轮廓以外是背景误检漏检一眼就能发现。figure; imshow(img); hold on; boundary bwperim(mask, 8); % 提取掩码边界 [y, x] find(boundary); % 边界点的行列坐标 plot(x, y, c., MarkerSize, 2); % 用青色点画出轮廓 hold off;bwperim提取的是掩码的边界像素第二个参数8表示按8邻域判断连通。find把边界像素的坐标取出来x和y分别是列、行。如果发现轮廓圈到了目标旁边的阴影就往S下限方向收如果目标中间出现大面积空洞就检查V上限是否太低把高光区排除了。这个可视化步骤建议封装成一个函数每次调整阈值后直接调用。4.3 批量处理全部数据循环读图、汇总结果、写表格课程设计里的“全部数据”通常会有一批测试图像处理脚本必须能循环读入、逐张处理、最后汇总成一张统计表。下面是基于dir的批量处理框架每张图的结果累积到表格里再一次性写出。folder test_images; files dir(fullfile(folder, *.png)); results table(); % 汇总表 for k 1:numel(files) img imread(fullfile(folder, files(k).name)); mask detectColors(img); % 封装好的识别函数, 返回标签矩阵 stats regionprops(mask, Area, Centroid); nObj numel(stats); % 用行的形式追加到表格 tmp table({files(k).name}, nObj, mean([stats.Area]), ... VariableNames, {ImageName, ObjectCount, MeanArea}); results [results; tmp]; end writetable(results, result.csv);ImageNameObjectCountMeanAreafruit_01.png3452.3fruit_02.png5381.0fruit_03.png2677.5这段代码的前提是把前面的颜色识别流程封装成一个detectColors(img)函数输入原始图像、输出标签矩阵或掩码这样批量脚本才能保持简洁。[stats.Area]在MATLAB里会把多个结构体的字段拼成一个数值数组mean对它求平均就是所有目标的平均面积。每张图的行结果用table逐行追加最后writetable导出CSVExcel直接能打开。上表是导出文件长这样的示意报告里需要的表格和图表数据就全有了。配套文档一般也是在这个阶段整理函数接口说明、颜色阈值参数表、中间结果截图和定量指标四部分批量处理的结果表格正好是“实验结果分析”章节的数据来源。4.4 进阶用App Designer做一个阈值调节界面如果题目要求“可视化界面”MATLAB自带的App Designer是成本最低的路子。核心思路是把阈值处理逻辑放进一个独立函数界面上的滑块只负责更新这个函数的参数。这里给出回调函数的骨架function hueSliderChanged(app, ~) hLow app.HueLowSlider.Value; hHigh app.HueHighSlider.Value; sLow app.SaturationSlider.Value; mask createMaskFromRange(app.CurrentImage, hLow, hHigh, sLow); imshow(mask, Parent, app.MaskAxes); end界面上的每个滑块对应一个参数滑块变化回调里重新生成掩码并刷新显示性能足够满足单张图的交互需求。这个方案的优势是不需要学GUIDE组件拖拽加回调即可完成视觉效果也比传统figure方案好得多。如果你要把整个流程变成可命令行调用的脚本只需把回调里的createMaskFromRange函数写好界面层和数据层就完全解耦了。5. 验收技巧与三个必调参数让MATLAB颜色识别在测试集上稳定复现5.1 从“肉眼看着像”到“数值达标”用IoU和F1验收课程设计如果要得高分只放几张效果图是不够的。自己准备十几张图手工粗略标注出目标区域作为真值然后和程序输出比对。gt imread(label_01.png); % 人工标注的真值掩码 mask detectColors(imread(img_01.png)); intersect sum(mask gt, all); union sum(mask | gt, all); iou intersect / union; % IoU, 通常0.6以上算可接受 tp sum(mask gt, all); fp sum(mask ~gt, all); fn sum(~mask gt, all); precision tp / (tp fp); recall tp / (tp fn); f1 2 * precision * recall / (precision recall); fprintf(IoU%.3f Precision%.3f Recall%.3f F1%.3f\n, iou, precision, recall, f1);注意这里gt若是RGB图要先转成二值掩码。mask gt的按位与就是预测和真值重叠的像素集合统计这些像素个数就能算出IoU、P、R、F1四个常用指标。这些数字比“看起来很像”更能说服评审老师也能帮你自己判断哪一类图像最容易漏检。5.2 按这个顺序调三个参数绝大多数误检都能收敛参数参考初始值调大后的表现调小后的表现调参原因H带宽0.08~0.12相邻色系混入目标内部出现空白色相阈值是主判据S下限0.25~0.40深色/阴影下目标丢失灰白背景误判为目标决定低饱和区域能否参与结构元半径3~7小目标被抹平噪点残留影响掩码的干净程度调参顺序建议固定为先调H带宽直到目标主要区域被覆盖再调S下限把背景中的灰白区域清出去最后调结构元半径把零星噪点和内部空洞收拾干净。每次只改一个参数用第4章的叠加轮廓图看效果不要同时动多个参数否则出了问题无法定位。拿一张之前漏检的图先只调H带宽再看S下限最后动结构元半径三步走完再统计一次F1结果大概率比拍脑袋调参要高出一截。本文还有配套的精品资源点击获取