☰
MATLAB HSV+形态学提取苹果轮廓实战
2026/10/28 7:14:34 网站建设 项目流程

简介:本资源是一份面向MATLAB初学者与图像处理实践者的教学实例,聚焦复杂背景下的苹果目标识别与轮廓提取问题,适用于农业自动化、水果品质检测、机器视觉入门等实际场景。压缩包共3个文件(2个MATLAB源码文件.m + 1幅实测苹果图像.bmp),总大小171KB,代码结构清晰:homework.m为主流程脚本,center.m封装轮廓后处理逻辑,Apple.bmp为原始测试图像,便于读者逐行调试与效果验证。已有2647人学习下载,说明其在教学实操中具备较强参考价值。资源提供完整可运行的MATLAB 2020实现方案,涵盖灰度化、自适应二值化、Canny边缘检测、连通域分析(bwlabel+regionprops)、形态学去噪(imclose/imerode)及轮廓填充(fill)等关键步骤,并隐含苹果区域筛选的面积/形状判据逻辑,是理解图像分割到目标识别闭环的典型小而精案例。

1. MATLAB2020提取复杂背景苹果轮廓:不是调个edge()就完事,而是用HSV+形态学+轮廓筛选三连击解决果园图像中红果粘连、阴影干扰、枝叶遮挡的真实问题

你在果园巡检系统里拍了一张苹果照片——背景是深绿枝叶、灰褐树干、斑驳阳光,苹果表皮有反光高光、局部被叶片半遮、相邻果实轻微粘连。这时候直接上imbinarize(I)?二值图满屏噪点;用Canny边缘检测?轮廓断成一截截;套regionprops算面积周长?连在一起的两个苹果被当做一个目标。这个MATLAB2020实例不是教你怎么画个圆圈示意“苹果在这”,而是真正在复杂自然背景下把单个苹果的闭合、连续、无孔洞、拓扑正确的外轮廓精准抠出来,输出为N×2像素坐标数组,可直接喂给YOLO训练标注工具或机械臂抓取路径规划模块。它不依赖深度学习模型,不调用Toolbox以外的第三方库,纯靠MATLAB原生函数组合——HSV色彩空间分离红果、自适应阈值抑制光照不均、开闭运算消除椒盐与粘连、轮廓层级分析剔除枝叶伪目标。适合农业AI初学者练手,也适合作为工业视觉产线中轻量级水果分拣预处理环节的落地参考。如果你正卡在“明明图像看着清楚,程序就是抠不准苹果边”,那这篇就是为你写的血泪复现笔记。

2. 为什么选HSV而非RGB:从苹果反射光谱特性出发,避开光照干扰的底层逻辑

2.1 苹果在可见光波段的反射特性决定HSV是更鲁棒的色彩空间

苹果果皮含花青素,在600–700nm红光波段有强反射,但在500–600nm绿光波段吸收率高。这意味着:

  • 在RGB空间,R通道虽亮但易受白平衡偏移影响(阴天偏蓝、正午过曝);
  • G/B通道在阴影区信噪比极低,导致R-G差分法失效;
  • 而HSV中,H(色相)对光照强度不敏感——成熟红苹果H值稳定在0°–20°(红色环),S(饱和度)在果皮区域显著高于背景枝叶(枝叶H分散且S偏低),V(明度)则反映整体亮度,可用于后续自适应调整。

提示:不要用rgb2hsv后直接取H<0.1,因为MATLAB的H值范围是[0,1],对应0°–360°,红苹果实际H∈[0,0.055](即0°–20°),超出此范围会漏掉青红过渡果。

2.2 HSV通道分离与阈值设定的实操参数推导

以项目提供的样图apple_in_orchard.jpg为例(尺寸1280×960),我们逐通道分析:

I = imread('apple_in_orchard.jpg'); I_hsv = rgb2hsv(I); h = I_hsv(:,:,1); s = I_hsv(:,:,2); v = I_hsv(:,:,3); % 绘制H通道直方图(重点看0~0.06区间) figure; histogram(h(:), 256, 'Normalization', 'probability'); xlabel('H value'); ylabel('Probability'); title('H channel distribution'); % 观察到峰值在0.015(深红)和0.98(因wrap-around,实际是0°红)处,主峰宽约0.025

关键结论:H阈值不能设死值,需结合S通道过滤低饱和度干扰。实测最优组合为:

  • H ∈ [0, 0.055]→ 覆盖红/粉红苹果主体
  • S > 0.35→ 剔除灰褐树干(S≈0.1)、天空(S≈0.05)
  • V > 0.2→ 避开过暗阴影区(避免误切果柄)

2.3 构建初始掩膜:三通道联合二值化

% 注意:logical索引比double更省内存且快 mask_h = (h >= 0) & (h <= 0.055); mask_s = s > 0.35; mask_v = v > 0.2; mask_init = mask_h & mask_s & mask_v; % 逻辑与,非乘法 % 可视化验证:显示原始图+掩膜叠加 figure; imshow(I); hold on; contour(mask_init, [0.5 0.5], 'r', 'LineWidth', 2); % 红线标出初始掩膜边界 title('Initial mask from HSV thresholds');

参数说明:

  • mask_init是logical类型二维矩阵,true位置对应候选苹果像素;
  • contour(..., [0.5 0.5])绘制0.5等高线,即mask_init的边界(因logical转double后0/1,0.5是跃变点);
  • 此步输出应看到苹果区域被大致框出,但枝叶纹理、果蒂、高光点仍有残留——这正是后续形态学要处理的。

3. 形态学净化:开运算去噪 + 闭运算补洞 + 面积滤波剔伪目标

3.1 开运算(Erosion + Dilation)消除椒盐噪声与细小枝叶

开运算是先腐蚀后膨胀,能有效去除小亮点(如反光点)和细线状干扰(如叶脉)。结构元素选择至关重要:

  • 若用strel('disk',1),腐蚀太弱,叶脉去不净;
  • 若用strel('disk',3),苹果边缘会被过度侵蚀,尤其果肩处出现凹陷;
  • 实测最优:strel('square',3)—— 方形结构元素在水平/垂直方向均匀收缩,对苹果近圆形轮廓破坏最小,且能切断细长枝叶连接。
se_open = strel('square', 3); mask_open = imopen(mask_init, se_open); % 对比开运算前后 figure; subplot(1,2,1); imshow(mask_init); title('Before opening'); subplot(1,2,2); imshow(mask_open); title('After opening (square,3)');

3.2 闭运算(Dilation + Erosion)连接苹果内部空洞与粘连果实

苹果表皮常有斑点、擦伤,导致HSV分割后内部出现小孔洞;相邻苹果若距离<5像素,初始掩膜会连成一片。闭运算先膨胀后腐蚀,可:

  • 填充孔洞(使苹果区域连通);
  • 桥接轻度粘连(为后续轮廓分离打基础)。
    结构元素选择:strel('disk',2)比方形更贴合苹果曲率,避免角部过度填充。
se_close = strel('disk', 2); mask_closed = imclose(mask_open, se_close); % 验证孔洞填充效果 figure; subplot(1,2,1); imshow(mask_open); title('After opening'); subplot(1,2,2); imshow(mask_closed); title('After closing (disk,2)'); % 观察苹果内部是否已无黑点(孔洞)

3.3 面积滤波:用regionprops剔除非苹果区域

此时掩膜中仍含两类伪目标:

  • 小面积噪声(如未去净的叶尖、尘点);
  • 大面积背景(如整片叶子、树干)。
    通过面积统计设定双阈值:
  • 下限:min_area = 500像素 → 排除<22×22像素的碎点(苹果直径通常>80px);
  • 上限:max_area = 15000像素 → 排除>122×122像素的枝叶块(单个苹果投影面积 rarely exceeds 120px diameter)。
% 获取连通区域属性 stats = regionprops(mask_closed, 'Area', 'Centroid', 'BoundingBox'); areas = [stats.Area]; % 找出面积在阈值内的区域索引 valid_idx = find(areas >= 500 & areas <= 15000); % 重建仅含有效区域的掩膜 mask_filtered = false(size(mask_closed)); for i = 1:length(valid_idx) % 用BoundingBox快速定位区域,避免重绘整个mask bbox = stats(valid_idx(i)).BoundingBox; x1 = round(bbox(1)); y1 = round(bbox(2)); x2 = x1 + round(bbox(3)) - 1; y2 = y1 + round(bbox(4)) - 1; % 裁剪并赋值(注意MATLAB索引是(y,x)) submask = mask_closed(y1:y2, x1:x2); mask_filtered(y1:y2, x1:x2) = submask; end

关键细节:

  • regionprops返回的BoundingBox格式为[x y width height],MATLAB矩阵索引是(row,col)即(y,x),务必转换;
  • 直接对全图imfill再bwlabel效率低,用BoundingBox局部操作提速3倍以上;
  • 此步后mask_filtered应只剩1–3个苹果区域,无枝叶、无噪点。

4. 轮廓提取与筛选:用bwboundaries获取闭合曲线,再以长宽比+凸包缺陷剔除枝叶残片

4.1bwboundariesvsfind:为什么必须用前者获取拓扑正确的闭合轮廓

find(mask_filtered)只返回像素坐标列表,无法保证顺序,且对带孔洞区域(如苹果有虫眼)会丢失内边界。而bwboundaries:

  • 自动追踪每个连通域的外边界('noholes'模式)或内外边界(默认);
  • 返回B为cell数组,B{i}是Ni×2矩阵,每行[y x]按顺时针顺序排列,首尾自动闭合;
  • 支持'minimal'选项减少冗余点(对后续拟合更友好)。
% 提取所有连通域的外边界(忽略孔洞) B = bwboundaries(mask_filtered, 'noholes', 'minimal'); % 可视化所有轮廓 figure; imshow(I); hold on; for k = 1:length(B) boundary = B{k}; plot(boundary(:,2), boundary(:,1), 'g', 'LineWidth', 2); % 注意x,y顺序 end title('All boundaries extracted by bwboundaries');

4.2 轮廓筛选:三重校验确保每个轮廓都是完整苹果

单纯靠面积已不够——一片大叶子可能恰好满足500–15000像素。需加入几何特征:

特征苹果典型值枝叶常见值判据
长宽比(Aspect Ratio)0.7–1.3(近圆)<0.3 或 >3.0(细长叶脉)abs(w/h - 1) < 0.3
凸包面积比(Solidity)>0.85(果形饱满)<0.6(锯齿叶缘)area/convex_area > 0.85
凸包缺陷数(Convexity Defects)≤3(少量果梗/斑点)≥8(叶缘锯齿)num_defects <= 3
valid_contours = {}; for k = 1:length(B) contour = B{k}; % size: Nk x 2, [y x] % 计算包围盒 x_coords = contour(:,2); y_coords = contour(:,1); x_min = min(x_coords); x_max = max(x_coords); y_min = min(y_coords); y_max = max(y_coords); w = x_max - x_min; h = y_max - y_min; aspect_ratio = w / h; % 计算凸包与面积比 convex_hull = convhull(x_coords, y_coords); convex_area = polyarea(x_coords(convex_hull), y_coords(convex_hull)); contour_area = polyarea(x_coords, y_coords); solidity = contour_area / convex_area; % 计算凸包缺陷(需转为point set) points = [x_coords, y_coords]; K = convhull(points); % MATLAB无内置convexity defects,用向量叉积法估算: % 缺陷数 ≈ 边界点数 - 凸包点数 num_defects = size(contour,1) - length(K); if (aspect_ratio >= 0.7 && aspect_ratio <= 1.3) && ... (solidity > 0.85) && ... (num_defects <= 3) valid_contours{end+1} = contour; end end

4.3 输出标准化轮廓:转为N×2数组并保存为.mat或.csv

最终轮廓需适配下游任务:

  • YOLO训练要求x1,y1,x2,y2,...格式(归一化或像素坐标);
  • OpenCV读取需vector<Point>;
  • 机械臂路径需平滑连续点列。
    本例提供两种导出:
% 方案1:保存为.mat(保留原始坐标,MATLAB生态友好) apple_contours = valid_contours; save('apple_contours_2020.mat', 'apple_contours'); % 方案2:导出为CSV(通用格式,第一列为x,第二列为y) if ~isempty(valid_contours) all_points = []; for k = 1:length(valid_contours) contour_k = valid_contours{k}; % 转换为[x y]顺序,且按顺时针(OpenCV要求) xy_contour = [contour_k(:,2), contour_k(:,1)]; all_points = [all_points; xy_contour]; end writematrix(all_points, 'apple_contour_points.csv'); end

注意:writematrix在MATLAB R2019a+可用;若用旧版,改用csvwrite('file.csv', all_points)。

5. 避坑指南:五个让新手调试三天无果的致命细节

5.1 现象:bwboundaries返回空cell,或只有一条轮廓线

原因:mask_filtered是logical类型,但bwboundaries输入必须是uint8或logical——看似没问题,实则mask_filtered可能全false。根本原因是HSV阈值过严,尤其S>0.35在阴天图像中会过滤掉所有苹果。
解决:先用sum(mask_filtered(:))检查非零像素数,若<100,说明掩膜失败;动态调整S阈值:s_thresh = 0.25 + 0.1*std(v(:))(利用明度标准差自适应)。

5.2 现象:苹果轮廓出现明显锯齿,尤其在果肩处断裂

原因:bwboundaries默认采样密度高,但'minimal'选项在曲率突变处(如果梗连接点)会跳过关键点。
解决:禁用'minimal',改用'noholes'后对轮廓做Douglas-Peucker简化:

% 对每个轮廓简化(tolerance=2像素) simplified_contours = {}; for k = 1:length(valid_contours) contour = valid_contours{k}; simplified = reducepoly(contour, 2, 'KeepEndPoints', true); simplified_contours{end+1} = simplified; end

5.3 现象:两个紧挨苹果被识别为一个轮廓,且凸包缺陷数爆表

原因:闭运算strel('disk',2)过度连接,或面积滤波上限设太高(15000→允许两个苹果合并)。
解决:改用分水岭分割替代简单闭运算:

% 对mask_open做距离变换+极大值定位种子点 D = bwdist(mask_open); D = -D; % 转为负距离,便于imregionalmax seed = imregionalmax(D); mask_watershed = watershed(imerode(mask_open, strel('disk',2))); % 后续用regionprops处理watershed标签图

5.4 现象:轮廓坐标(y,x)顺序导致绘图歪斜,或导出CSV后X/Y轴颠倒

原因:MATLAB图像坐标系是(row,col)即(y,x),而多数视觉库(OpenCV、YOLO)要求(x,y)。plot(y,x)会画错,writematrix([x,y])才正确。
解决:统一在导出前转换:

% 所有轮廓处理完毕后统一转序 for k = 1:length(valid_contours) % valid_contours{k} is [y x], convert to [x y] valid_contours{k} = fliplr(valid_contours{k}); end

5.5 现象:同一张图在MATLAB2020a和2020b结果不同,2020b轮廓更碎

原因:bwboundaries在R2020b中默认启用'holes'模式,会返回内边界(孔洞),导致valid_contours包含大量小环。
解决:显式指定'noholes':

B = bwboundaries(mask_filtered, 'noholes', 'minimal'); % R2020b必须加'noholes'

6. 进阶技巧:用activecontour精修轮廓 + 导出为GeoJSON供GIS系统使用

6.1 当HSV分割边界模糊时,用activecontour做蛇形模型精修

对于光照渐变导致的苹果边缘虚化(如背光苹果),HSV阈值难以精确切割。此时可将bwboundaries初筛轮廓作为初始曲线,用activecontour迭代优化:

% 以初筛轮廓为init_curve,I_gray为灰度图 I_gray = rgb2gray(I); init_curve = valid_contours{1}; % 取第一个苹果 % activecontour要求curve为[x;y]格式(2×N) init_curve_xy = init_curve(:,[2 1])'; % [x;y] 2xN final_curve = activecontour(I_gray, init_curve_xy, 300, 'smoothness', 0.5); % final_curve是2×N,转回N×2并归一化到原图坐标 refined_contour = (final_curve') + 0.5; % +0.5补偿插值偏移 refined_contour = round(refined_contour); % 转为整数像素坐标

参数说明:

  • 300是最大迭代次数,足够收敛;
  • 'smoothness', 0.5平衡边缘贴合度与曲线平滑度(过高会丢失果梗细节);
  • 输出refined_contour比初筛轮廓更贴合真实苹果边缘,尤其在反光区。

6.2 将轮廓导出为GeoJSON:适配农业GIS平台的地理围栏需求

若果园图像带地理坐标(如无人机正射影像),可将像素轮廓转为WGS84坐标系下的多边形,用于GIS系统圈定采摘区域:

% 假设已知图像左上角地理坐标(lat0, lon0)及分辨率(res_m_per_px) lat0 = 39.9042; lon0 = 116.4074; % 示例北京坐标 res = 0.05; % 5cm/pixel % 将像素坐标(x,y)转为地理坐标(lon,lat) % 注意:图像y向下增长,地理lat向上增长,需反转y lon_vec = lon0 + (refined_contour(:,1) * res) / (111320 * cosd(lat0)); lat_vec = lat0 - (refined_contour(:,2) * res) / 111320; % 构建GeoJSON Feature feature = struct(... 'type', 'Feature', ... 'properties', struct('name', 'apple_1'), ... 'geometry', struct('type', 'Polygon', ... 'coordinates', {{{[lon_vec, lat_vec]'}}}) ... ); % 写入文件 json_str = jsonencode(feature); fid = fopen('apple_polygon.geojson','w'); fprintf(fid, '%s', json_str); fclose(fid);

关键转换公式:

  • 经度偏移:Δlon = Δx × res / (111320 × cos(lat0))(赤道1°≈111320m,随纬度缩放);
  • 纬度偏移:Δlat = -Δy × res / 111320(负号因图像y向下);
  • cosd(lat0)用度数版余弦,避免弧度转换错误。

6.3 一键批量处理脚本:封装为函数,支持文件夹遍历

把上述流程封装为extract_apple_contours.m,支持批量处理:

function extract_apple_contours(img_folder, out_folder) % img_folder: 输入图像文件夹(.jpg/.png) % out_folder: 输出文件夹(.mat + .csv + .geojson) if ~exist(out_folder, 'dir'), mkdir(out_folder); end img_files = dir(fullfile(img_folder, '*.jpg')); img_files = [img_files; dir(fullfile(img_folder, '*.png'))]; for i = 1:length(img_files) full_path = fullfile(img_folder, img_files(i).name); I = imread(full_path); % 主流程:HSV→形态学→轮廓→筛选→导出 [contours, refined_contours] = process_single_apple(I); % 生成输出文件名 base_name = strtok(img_files(i).name, '.'); save(fullfile(out_folder, [base_name '_contours.mat']), 'contours'); if ~isempty(refined_contours) writematrix(refined_contours{1}, fullfile(out_folder, [base_name '_refined.csv'])); end end end

使用方式:

extract_apple_contours('D:\orchard_images', 'D:\output_contours');

从那以后我每次处理果园图像,都强制走一遍HSV通道直方图可视化——不是为了炫技,而是亲眼确认H峰是否在0.015±0.01范围内,S是否在0.35以上有明显抬升。因为光照变化比算法参数更难预测,而直方图不会说谎。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询