
简介这份资源面向图像处理初学者与农业自动化方向的开发者围绕苹果成熟度检测这一典型场景提供了一套可运行的编码分析示例。包内共10个文件以8张jpg图像素材和2个m脚本为主压缩包约196KB图像素材覆盖原图、单取苹果图、灰度图、面积图、色差移除图及含青苹果样本脚本则承担图像读取、分割与成熟度判别的核心逻辑。已有275人学习下载说明该案例在入门实践中具有一定参考价值。读者可借助这些素材与脚本理解图像分割、特征提取与色彩分析如RGB、HSV模型在水果成熟度判断中的具体落地方式并在此基础上尝试引入机器学习或卷积神经网络建立更精细的成熟度模型。整体结构轻量适合作为课程实验、毕业设计或农业视觉检测项目的起步参考便于快速复现与二次扩展。1. 从一堆苹果照片到成熟度分级这个方向到底在解决什么问题果园里摘下来的苹果靠人眼一个个判断熟没熟一天下来眼睛发花、标准还飘。apple_test.rar这类图像处理项目要干的事就是把「看苹果判断成熟度」这件事交给代码输入一张苹果照片输出它属于哪个成熟阶段。核心链路是图像预处理、颜色/纹理特征提取、分类判别三步常用工具是 MATLAB 或 OpenCV属于典型的图像处理落地场景。这套方案适合谁做农业视觉检测的工程师、带图像处理课程设计的同学、想用 OpenCV 或 MATLAB 练手真实分类任务的开发者。它不追求深度学习那种大模型精度胜在流程透明、参数可调、单机就能跑通。下面按「先立原理、再动手、最后避坑」的顺序把这条链路拆开讲清楚让你能照着复现一套自己的苹果成熟度分级流程。2. 成熟度判别的底层逻辑颜色空间与特征怎么选2.1 为什么 RGB 不是首选HSV 才是苹果成熟最直观的变化是颜色青绿转黄转红。但 RGB 三个通道都和光照强相关同一颗苹果在树荫下和阳光下R 值能差出一大截直接拿 RGB 做阈值模型在阴天就翻车。HSV 把色调H、饱和度S、明度V拆开H 通道基本只反映「是什么颜色」对光照变化不敏感这是成熟度判别里最稳的一步。常见做法是把图像从 BGR 转到 HSV然后只在 H 和 S 通道上做统计。红色系苹果成熟时 H 值集中在 0° 附近或 170°~180°红色在 HSV 色环两端青色未熟果 H 值在 60°~90° 区间。这个区间不是拍脑袋定的OpenCV 里 H 的取值范围是 0~179不是 0~360换算时要注意。import cv2 import numpy as np # 读取苹果图像BGR 格式 img cv2.imread(apple.jpg) # 转 HSV注意 OpenCV 中 H 范围是 0~179S/V 是 0~255 hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # 分离三通道后续只对 H、S 做统计 h, s, v cv2.split(hsv) # 统计 H 通道直方图看主色调落在哪个区间 hist_h cv2.calcHist([h], [0], None, [180], [0, 180]) dominant_h int(np.argmax(hist_h)) print(主色调 H 值:, dominant_h)这段代码的逻辑是先把图像转到 HSV再分离通道最后用直方图找出出现最多的 H 值。calcHist的第二个参数[0]表示只统计第 0 个通道[180]是直方图的 bin 数量[0, 180]是取值范围。argmax返回峰值位置就是这颗苹果的主色调。参数上bin 数量设 180 是为了和 H 通道范围一一对应设太小会糊成一片设太大直方图会稀疏。2.2 单靠颜色不够纹理和形状补位有些苹果品种成熟前后颜色变化不明显比如黄元帅青的时候偏黄绿熟了还是黄光看 H 值区分度很低。这时候要补纹理特征。成熟苹果表皮更光滑、反光更均匀未熟果表皮粗糙、有细小斑点。常用的纹理描述子是灰度共生矩阵GLCM的对比度和能量或者直接用拉普拉斯算子的方差衡量表面粗糙度。形状也能用成熟苹果通常更饱满果形指数长轴/短轴接近 1未熟果偏长。但形状特征受拍摄角度影响大一般作为辅助不作为主判据。实操里我一般按「颜色为主、纹理为辅、形状兜底」的权重来组合颜色占 0.6纹理占 0.3形状占 0.1具体权重按品种微调。2.3 特征向量怎么拼、怎么归一化把上面几类特征拼成一个向量比如[H均值, S均值, GLCM对比度, GLCM能量, 果形指数]这就是分类器的输入。但不同特征量纲差很多H 均值在 0~180GLCM 能量在 0~1直接送进分类器数值大的特征会主导距离计算。所以必须归一化常见做法是 min-max 归一化到 [0,1]或者用 z-score 标准化。from sklearn.preprocessing import MinMaxScaler # 假设提取了 5 个特征组成特征矩阵 features np.array([[h_mean, s_mean, glcm_contrast, glcm_energy, shape_index]]) # min-max 归一化到 [0,1] scaler MinMaxScaler() features_norm scaler.fit_transform(features) print(归一化后特征:, features_norm)MinMaxScaler的公式是(x - min) / (max - min)把每个特征压到同一量纲。注意归一化的 min/max 要从训练集统计测试集用同一组参数变换否则会引入数据泄漏。这一步在 MATLAB 里对应mapminmax函数逻辑一样。3. 用 OpenCV 跑通苹果成熟度分级的最小流程3.1 图像预处理去噪、分割、只留苹果区域原始照片里有背景、枝叶、阴影直接提特征会被干扰。第一步是分割出苹果区域。最稳的做法是先转 HSV用 H 和 S 阈值做颜色分割再配合形态学操作去掉噪点。OpenCV 的inRange做阈值分割morphologyEx做开运算去小噪点、闭运算填内部空洞。# 颜色阈值分割提取苹果区域 # 红色系苹果H 在 0~10 或 170~180S 和 V 要够高 mask1 cv2.inRange(hsv, (0, 80, 60), (10, 255, 255)) mask2 cv2.inRange(hsv, (170, 80, 60), (180, 255, 255)) mask cv2.bitwise_or(mask1, mask2) # 形态学开运算去噪核大小 5x5 kernel np.ones((5, 5), np.uint8) mask cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) # 闭运算填内部空洞 mask cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) # 用掩膜提取苹果区域 apple_region cv2.bitwise_and(img, img, maskmask)inRange的下界(0, 80, 60)表示 H≥0、S≥80、V≥60上界(10, 255, 255)表示 H≤10、S/V 不限。S 下界设 80 是为了排除灰白色背景V 下界设 60 是为了排除过暗的阴影。形态学核大小 5×5 是经验值图像分辨率高就调大噪点多就多迭代几次开运算。这一步做完背景基本被清掉只剩苹果主体。3.2 特征提取从分割结果算出可分类的数字分割完在苹果区域内统计 H 均值、S 均值再算 GLCM 纹理。GLCM 用skimage的graycomatrix和graycoprops输入是灰度图距离设 1角度取 0°、45°、90°、135° 四个方向求平均。from skimage.feature import graycomatrix, graycoprops # 转灰度只保留苹果区域 gray cv2.cvtColor(apple_region, cv2.COLOR_BGR2GRAY) # 计算 GLCM距离 1四个角度 glcm graycomatrix(gray, distances[1], angles[0, np.pi/4, np.pi/2, 3*np.pi/4], levels256, symmetricTrue, normedTrue) # 提取对比度和能量四方向取平均 contrast graycoprops(glcm, contrast).mean() energy graycoprops(glcm, energy).mean() # 颜色特征苹果区域内 H、S 均值 h_mean h[mask 0].mean() s_mean s[mask 0].mean() print(fH均值{h_mean:.1f}, S均值{s_mean:.1f}, 对比度{contrast:.2f}, 能量{energy:.3f})graycomatrix的distances[1]表示相邻像素对levels256对应 8 位灰度symmetricTrue让矩阵对称normedTrue归一化成概率。graycoprops从 GLCM 里算统计量对比度反映纹理深浅能量反映纹理均匀度。h[mask 0]是布尔索引只取苹果区域的像素背景不参与统计。这几个数字拼起来就是分类器的输入。3.3 分类判别阈值法还是 KNN特征有了判别方式两条路。一是阈值法H 均值小于 30 判成熟30~60 判半熟大于 60 判未熟。优点是简单、可解释缺点是阈值要按品种调换一批苹果就得重设。二是 KNN 或 SVM拿一批已标注成熟度的苹果特征训练再对新样本分类。KNN 的 k 一般取 3 或 5特征维度低的时候效果好。from sklearn.neighbors import KNeighborsClassifier # 训练集每行一个样本的特征标签 0未熟 1半熟 2成熟 X_train np.array([[h1, s1, c1, e1], [h2, s2, c2, e2], ...]) y_train np.array([0, 1, 2, ...]) # KNN 分类器k3 knn KNeighborsClassifier(n_neighbors3) knn.fit(X_train, y_train) # 预测新样本 pred knn.predict(features_norm) print(预测成熟度等级:, pred)n_neighbors3表示看最近的 3 个邻居投票样本少的时候用 3样本多可以调到 5 或 7。特征必须先归一化再送进 KNN否则距离计算会被大量纲特征带偏。训练集每个类别至少准备 20~30 个样本太少 KNN 会不稳定。如果类别不平衡比如成熟果远多于未熟果要在fit时加weightsdistance让近邻权重更大。4. 用 MATLAB 复现同一套流程的对照写法4.1 MATLAB 里的颜色空间转换与分割MATLAB 做图像处理是很多课程设计的默认选择函数名和 OpenCV 不同但逻辑一致。rgb2hsv转 HSV注意 MATLAB 的 H 范围是 0~1不是 0~180阈值要相应除以 180。分割用逻辑索引比 OpenCV 的掩膜写法更直观。% 读取图像并转 HSV img imread(apple.jpg); hsv rgb2hsv(img); H hsv(:,:,1); S hsv(:,:,2); V hsv(:,:,3); % 红色系阈值分割H 在 0~0.056 或 0.944~1 mask (H 0.056 | H 0.944) S 0.31 V 0.24; % 形态学去噪 mask bwareaopen(mask, 100); % 去掉小于 100 像素的连通域 mask imfill(mask, holes); % 填内部空洞 % 提取苹果区域 apple bsxfun(times, img, cast(mask, like, img));bwareaopen(mask, 100)去掉面积小于 100 像素的噪点这个值按图像分辨率调分辨率高就调大。imfill(mask, holes)填封闭区域内的空洞。bsxfun做逐元素乘法把背景置零。MATLAB 的 H 阈值 0.056 对应 OpenCV 的 1010/180≈0.0560.944 对应 170换算关系要记牢不然阈值全错。4.2 MATLAB 提取纹理特征与分类MATLAB 有自带的graycomatrix和graycoprops用法和 Python 版几乎一样但参数名不同。分类可以用fitcknn或fitcsvm也可以用classify做简单判别。% 转灰度并计算 GLCM gray rgb2gray(apple); glcm graycomatrix(gray, NumLevels, 256, Offset, [0 1; -1 1; -1 0; -1 -1]); stats graycoprops(glcm, {Contrast, Energy}); contrast mean(stats.Contrast); energy mean(stats.Energy); % 颜色特征 h_mean mean(H(mask)); s_mean mean(S(mask)); % 拼特征向量 feat [h_mean, s_mean, contrast, energy]; % KNN 分类 mdl fitcknn(X_train, y_train, NumNeighbors, 3); pred predict(mdl, feat);graycomatrix的Offset参数指定四个方向的像素对[0 1]是水平[-1 1]是对角[-1 0]是垂直[-1 -1]是反对角。graycoprops返回结构体取Contrast和Energy字段求平均。fitcknn的NumNeighbors对应 Python 的n_neighbors。MATLAB 和 Python 两套流程可以互相验证同一张图跑出来的特征值应该接近差太多说明某一步的阈值或颜色空间换算错了。5. 避坑与排查成熟度分级里最容易翻车的 5 个点5.1 现象阴天拍的苹果全被判成未熟原因RGB 阈值分割在低光照下失效V 通道整体偏低inRange的 V 下界把苹果区域也滤掉了掩膜几乎为空特征值全是噪声。解决预处理阶段加一步自动亮度补偿用 CLAHE限制对比度自适应直方图均衡在 V 通道上做增强再分割。或者把 V 下界从 60 降到 40同时提高 S 下界到 100用饱和度换亮度容错。5.2 现象同一颗苹果不同角度拍成熟度判级跳变原因形状特征果形指数对拍摄角度敏感侧面拍和正面拍算出的长轴短轴差异大把分类结果带偏。解决降低形状特征权重或者干脆去掉形状特征只用颜色加纹理。如果必须用形状拍摄时固定相机角度或者用椭圆拟合代替外接矩形减少角度影响。5.3 现象KNN 训练集准确率 99%新样本一测就错原因训练集和测试集来自同一批照片特征分布高度相似模型过拟合。或者归一化参数用了全体数据统计引入了数据泄漏。解决训练集和测试集按苹果个体划分同一颗苹果的照片不能同时出现在两边。归一化的 min/max 只在训练集上统计测试集用同一组参数变换。KNN 的 k 从 3 调到 5 或 7降低对噪声的敏感。5.4 现象GLCM 算出来的对比度全是 0 或极大值原因输入灰度图的灰度级没压缩到 256 以内或者图像是 16 位深度levels参数和实际灰度范围不匹配。解决算 GLCM 前先确认图像是 8 位用cv2.convertScaleAbs或 MATLAB 的im2uint8转成 8 位。levels设 256 对应 8 位设 64 或 32 可以加速但会损失纹理细节一般 256 够用。5.5 现象MATLAB 和 Python 跑同一张图特征值对不上原因MATLAB 的 H 范围是 0~1OpenCV 是 0~179阈值没换算直接照搬分割结果完全不同。或者 MATLAB 读图默认 RGBOpenCV 默认 BGR转 HSV 前没统一通道顺序。解决MATLAB 阈值 OpenCV 阈值 / 180通道顺序在 OpenCV 里用cv2.cvtColor(img, cv2.COLOR_BGR2RGB)统一。两边跑完对比掩膜图掩膜一致了再比特征值。6. 把分级精度再提一档特征加权与交叉验证的实操技巧阈值法和 KNN 跑通之后想再提精度最有效的不是换更复杂的模型而是把特征权重调对。我一般用网格搜索找最优权重组合颜色权重从 0.4 到 0.8 按 0.1 步进纹理权重从 0.1 到 0.4形状权重固定 0.1每组权重跑 5 折交叉验证看哪组平均准确率最高。from sklearn.model_selection import cross_val_score import numpy as np best_acc 0 best_w None # 网格搜索颜色和纹理权重 for w_color in np.arange(0.4, 0.9, 0.1): for w_texture in np.arange(0.1, 0.5, 0.1): w_shape 1 - w_color - w_texture if w_shape 0: continue # 按权重缩放特征列 X_weighted X.copy() X_weighted[:, 0:2] * w_color # 颜色特征 X_weighted[:, 2:4] * w_texture # 纹理特征 X_weighted[:, 4] * w_shape # 形状特征 # 5 折交叉验证 acc cross_val_score(knn, X_weighted, y, cv5).mean() if acc best_acc: best_acc acc best_w (w_color, w_texture, w_shape) print(f最优权重: 颜色{best_w[0]}, 纹理{best_w[1]}, 形状{best_w[2]}, 准确率{best_acc:.3f})cross_val_score的cv5表示 5 折交叉验证把数据分成 5 份轮流用 4 份训练、1 份测试取平均准确率。这样评估比单次划分更稳尤其样本少的时候。权重缩放是在特征层面做的不改变 KNN 的距离计算逻辑但改变了各特征对距离的贡献。注意权重搜索的步进别太细0.1 够用太细容易过拟合到验证集。另一个技巧是特征选择5 个特征不一定都用用递归特征消除RFE看哪几个贡献最大。我跑过的苹果数据里H 均值和 GLCM 对比度通常是最重要的两个S 均值和能量次之形状指数经常被淘汰。特征少了模型更稳推理也更快。最后说个血泪经验这套流程的精度上限不在分类器在分割。分割掩膜不准后面特征全歪。我一般会先把分割结果可视化出来人眼确认苹果区域提干净了再往下走。这一步多花五分钟能省后面几小时的调参。希望帮到你。本文还有配套的精品资源点击获取