
简介这份资源是面向计算机视觉初学者与OpenCV实践者的水果识别样本集聚焦苹果、香蕉、梨子三类目标的图像分类任务可用于图像预处理、特征提取、分类器训练与测试的完整流程演练。压缩包共1624个文件以1618张jpg图像为主体另含5个xml标注文件与1个iml工程配置整体约17.82MB图像样本可直接用于构建训练集与测试集xml文件便于读取类别或标注信息。目前已有12811人学习下载说明该样本在入门级识别项目中具有较高参考价值。借助这批样本读者可实践灰度化、直方图均衡化、尺寸标准化等预处理操作并尝试SIFT、SURF、ORB等特征提取方法或结合SVM、随机森林及CNN完成分类训练同时通过旋转、缩放、裁剪等数据增强手段提升模型泛化能力适合作为课程设计、毕业设计或自学练手的数据基础。1. 从一堆水果照片到能跑的识别样本opencv水果识别样本到底在解决什么拍了一堆苹果、香蕉、梨子的照片想用 OpenCV 做个能认出它们的小系统结果卡在第一步——样本怎么组织、特征怎么提、识别结果怎么稳定。这不是个例。opencv水果识别样本(苹果、香蕉、梨子)这个标题背后真正要解决的是给你三类水果的图像构建一套可复现的样本集与识别流程让分类结果不再靠玄学。它适合两类人一是刚学完 OpenCV 基础、想找一个完整项目练手的开发者二是需要快速验证水果分拣、生鲜称重、智能货柜等场景可行性的工程师。核心难点不在算法多高深而在样本质量、颜色空间选择、特征维度控制这三件事上。我见过太多人直接拿网上零散图片跑 SIFT最后准确率在 60% 上下晃还找不到原因。这篇笔记就按我实际做过的路径把样本采集、预处理、特征提取、分类器训练和排错一条线讲透。2. 样本采集与预处理苹果、香蕉、梨子的图像怎么收才不白干2.1 三类水果的样本差异与采集数量底线苹果、香蕉、梨子在视觉上有几个天然差异点直接决定你后面特征怎么选。苹果颜色跨度大红富士、青苹果、黄元帅的色调能差出 100 个色相值香蕉成熟度不同从青绿到黄褐纹理也从光滑变斑点梨子相对统一但不同品种的果形和表面反光差异明显。如果你只收单一品种、单一光照的图片训练出来的模型换一个场景就翻车。我一般建议每类至少 200 张原始图其中 70% 用于训练、15% 验证、15% 测试。采集时注意三个变量要覆盖到光照顺光、侧光、背光各占一定比例、背景纯色桌面、木纹、超市货架、遮挡单果、双果叠放、部分遮挡。数量不够怎么办用旋转、缩放、亮度扰动做增强但增强不能替代真实场景的多样性。常见做法是先用手机拍 300 张左右按类别分文件夹存好命名用apple_001.jpg这种格式后面写脚本读取时省事。提示不要用同一张图增强出几十张来凑数验证集里出现训练集的增强版本准确率会虚高。2.2 用 OpenCV 做批量预处理的最小代码采集完的图不能直接喂给特征提取尺寸、光照、噪声都要先统一。下面这段代码是我常用的预处理模板把原始图统一到 256x256做直方图均衡化再存到处理后的目录。import cv2 import os import numpy as np # 输入输出目录按类别分子文件夹 input_root raw_fruits output_root processed_fruits target_size (256, 256) for category in os.listdir(input_root): in_dir os.path.join(input_root, category) out_dir os.path.join(output_root, category) os.makedirs(out_dir, exist_okTrue) for fname in os.listdir(in_dir): if not fname.lower().endswith((.jpg, .png, .jpeg)): continue img cv2.imread(os.path.join(in_dir, fname)) if img is None: continue # 统一尺寸用 INTER_AREA 缩小避免摩尔纹 img cv2.resize(img, target_size, interpolationcv2.INTER_AREA) # 转到 LAB 空间只对 L 通道做 CLAHE避免颜色失真 lab cv2.cvtColor(img, cv2.COLOR_BGR2LAB) l, a, b cv2.split(lab) clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8)) l clahe.apply(l) lab cv2.merge((l, a, b)) img cv2.cvtColor(lab, cv2.COLOR_LAB2BGR) # 轻度高斯滤波去噪核大小 3x3 足够 img cv2.GaussianBlur(img, (3, 3), 0) cv2.imwrite(os.path.join(out_dir, fname), img) print(预处理完成)这段代码的逻辑分四步尺寸统一保证后续特征维度一致LAB 空间分离亮度和颜色CLAHE 只增强亮度通道不会把苹果的红色拉成橙色高斯滤波去掉手机拍摄的噪点。参数上clipLimit2.0是经验值调高到 4.0 会让暗部细节过曝tileGridSize(8,8)适合 256x256 的图图更大时要相应调大。如果你发现处理后香蕉的褐色斑点被抹掉了把高斯核降到 (1,1) 或者直接去掉这步。2.3 样本划分与标签文件的生成预处理完要生成训练用的标签映射。我习惯用 CSV 存路径和类别避免后面训练时再遍历目录。import csv import random data [] for label, category in enumerate([apple, banana, pear]): cat_dir os.path.join(output_root, category) for fname in os.listdir(cat_dir): data.append([os.path.join(cat_dir, fname), label]) random.shuffle(data) split int(len(data) * 0.7) with open(train.csv, w, newline) as f: writer csv.writer(f) writer.writerows(data[:split]) with open(val.csv, w, newline) as f: writer csv.writer(f) writer.writerows(data[split:])这里label用 0、1、2 对应苹果、香蕉、梨子顺序要和后面分类器的类别名一致。random.shuffle之前要设随机种子否则每次跑出来的划分不同实验没法对比。常见坑是路径里带中文或空格OpenCV 的imread在某些版本下会返回 None所以目录名一律用英文。3. 特征提取颜色直方图、HOG 与轮廓特征怎么选怎么调3.1 颜色直方图为什么是水果识别的第一选择水果识别和通用物体识别最大的区别在于颜色信息占比极高。苹果红、香蕉黄、梨子黄绿这些在 HSV 空间里分得很开。我通常先提取 HSV 三通道的直方图H 通道 180 个 binS 和 V 各 256 个 bin然后拼接成一个长向量。但直接拼接维度太高一般会对 H 通道做 30 bin、S 和 V 各 32 bin 的量化。def color_histogram(img, h_bins30, s_bins32, v_bins32): hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV) hist_h cv2.calcHist([hsv], [0], None, [h_bins], [0, 180]) hist_s cv2.calcHist([hsv], [1], None, [s_bins], [0, 256]) hist_v cv2.calcHist([hsv], [2], None, [v_bins], [0, 256]) # 归一化消除图片尺寸影响 cv2.normalize(hist_h, hist_h) cv2.normalize(hist_s, hist_s) cv2.normalize(hist_v, hist_v) return np.concatenate([hist_h.flatten(), hist_s.flatten(), hist_v.flatten()])h_bins30是把 0 到 180 的色相分成 30 段每段 6 度足够区分红、黄、绿。如果你发现红苹果和橙子容易混把 h_bins 提到 60。归一化用cv2.normalize默认的 L2 范数让直方图总和为 1这样不同大小的水果区域贡献一致。这个特征向量长度是 30323294 维训练速度快适合做基线。3.2 HOG 特征补形状信息参数怎么设不翻车颜色直方图对形状不敏感苹果和梨子如果颜色接近就容易错。HOG 能抓边缘和梯度方向补上形状维度。OpenCV 的 HOGDescriptor 默认参数是给行人检测用的直接拿来识别水果效果一般。hog cv2.HOGDescriptor( _winSize(128, 128), _blockSize(32, 32), _blockStride(16, 16), _cellSize(16, 16), _nbins9 ) def hog_feature(img): gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) gray cv2.resize(gray, (128, 128)) return hog.compute(gray).flatten()_winSize设 128x128 是因为预处理后的图是 256x256缩小一半能减少计算量同时保留轮廓。_cellSize(16,16)比默认的 8x8 更粗水果轮廓不需要那么细的梯度。_nbins9是标准方向数不用改。HOG 特征维度是 1764和颜色直方图拼起来接近 1900 维训练 SVM 时要注意归一化否则 HOG 数值范围大会压过颜色特征。3.3 轮廓特征面积、周长、圆度怎么算才稳轮廓特征对分割质量依赖很大如果背景没去干净算出来的圆度全是噪声。我一般先用 Otsu 阈值做前景分割再找最大轮廓。def contour_feature(img): gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) _, thresh cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) contours, _ cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: return np.zeros(3) cnt max(contours, keycv2.contourArea) area cv2.contourArea(cnt) perimeter cv2.arcLength(cnt, True) circularity 4 * np.pi * area / (perimeter * perimeter 1e-6) return np.array([area / (256 * 256), perimeter / 1024, circularity])面积和周长都做了归一化除以图像尺寸避免不同分辨率影响。圆度公式里加1e-6防止除零。苹果圆度接近 0.85 到 0.95香蕉因为弯曲圆度只有 0.3 到 0.5梨子介于中间。这三个特征虽然只有 3 维但在颜色和 HOG 都模糊的时候能起决定作用。注意Otsu 阈值对光照不均的图会失效如果发现轮廓断成几块先做自适应阈值或者形态学闭运算再找轮廓。4. 分类器训练与调参SVM、KNN 和随机森林在水果样本上的实测差异4.1 特征拼接与标准化别让量纲毁了模型把颜色直方图、HOG、轮廓特征拼成一个向量后必须做标准化。颜色直方图归一化后在 0 到 1 之间HOG 值也在小范围但轮廓里的面积归一化后可能只有 0.01 量级圆度在 0 到 1 之间。如果不处理SVM 的 RBF 核会被数值大的特征主导。from sklearn.preprocessing import StandardScaler from sklearn.svm import SVC from sklearn.metrics import classification_report import pandas as pd def build_features(csv_path): df pd.read_csv(csv_path, headerNone, names[path, label]) X, y [], [] for _, row in df.iterrows(): img cv2.imread(row[path]) if img is None: continue feat np.concatenate([ color_histogram(img), hog_feature(img), contour_feature(img) ]) X.append(feat) y.append(row[label]) return np.array(X), np.array(y) X_train, y_train build_features(train.csv) X_val, y_val build_features(val.csv) scaler StandardScaler() X_train scaler.fit_transform(X_train) X_val scaler.transform(X_val) clf SVC(kernelrbf, C10, gammascale) clf.fit(X_train, y_train) y_pred clf.predict(X_val) print(classification_report(y_val, y_pred, target_names[apple, banana, pear]))StandardScaler的fit只能在训练集上做验证集用transform否则数据泄漏。SVM 的C10是我在 600 张样本上试出来的C 太小欠拟合C 太大会过拟合。gammascale是 sklearn 的默认自适应值比手动设 0.01 稳。4.2 KNN 和随机森林的对比什么场景选哪个SVM 在 1900 维特征上训练慢如果样本量涨到几千张KNN 和随机森林更实用。KNN 不用训练但预测时要算距离特征维度高时维度灾难明显。随机森林能输出特征重要性方便你判断颜色、HOG、轮廓各占多少贡献。分类器训练时间预测时间小样本准确率特征重要性SVM RBF中等快高不支持KNN k5无慢中等不支持随机森林 100 树快快高支持我实测下来600 张样本时 SVM 和随机森林准确率差 2 个百分点以内但随机森林训练只要几秒。如果你要快速迭代特征先用随机森林看重要性再决定要不要加特征。from sklearn.ensemble import RandomForestClassifier rf RandomForestClassifier(n_estimators100, max_depth15, random_state42) rf.fit(X_train, y_train) print(rf.feature_importances_[:10]) # 看前10维颜色特征贡献max_depth15防止树太深记住噪声random_state42保证每次结果可复现。特征重要性前几位如果都是颜色直方图说明你的样本颜色区分度好HOG 可以降维。4.3 交叉验证与混淆矩阵找出到底哪类水果在拖后腿单次划分验证集波动大用 5 折交叉验证看稳定区间。混淆矩阵能直接告诉你苹果被错分成梨子还是香蕉。from sklearn.model_selection import cross_val_score from sklearn.metrics import confusion_matrix scores cross_val_score(clf, X_train, y_train, cv5) print(CV accuracy:, scores.mean(), scores.std()) cm confusion_matrix(y_val, y_pred) print(cm)如果混淆矩阵里苹果和梨子互相错得多说明颜色特征在红绿之间模糊需要加纹理特征或者调整 H 通道 bin 的划分。如果香蕉和梨子错多半是成熟度导致的颜色重叠样本里要补青香蕉和熟梨子的图。5. 避坑与排查水果识别样本从 60% 到 90% 路上踩过的 5 个坑5.1 准确率虚高验证集和训练集图片重复现象训练准确率 98%验证准确率 95%换一批新图掉到 60%。原因采集时用连拍同一串香蕉的相邻帧被分到训练集和验证集模型记住了背景而不是水果。解决按拍摄批次划分同一批次的所有图只进训练集或只进验证集。用文件名的前缀分组比如batch01_开头的全放训练。5.2 颜色特征失效HSV 的 H 通道在低饱和度下噪声大现象黄色香蕉和浅绿梨子混淆严重调 H 的 bin 数没用。原因低饱和度区域 H 值不稳定稍微变光照就跳变。解决加一个饱和度阈值S 小于 40 的像素不参与 H 直方图统计或者把 S 通道的权重提高。5.3 HOG 特征维度爆炸训练慢还过拟合现象加上 HOG 后训练时间翻倍验证准确率反而降了 3 个点。原因HOG 1764 维里大部分是冗余的样本量不够时噪声被学进去。解决用 PCA 把 HOG 降到 100 维再拼接或者直接换用 LBP 纹理特征维度只有 59。5.4 轮廓提取失败背景有阴影导致 Otsu 阈值分错现象轮廓特征全是零或者圆度算出负数。原因阴影区域灰度值和水果接近Otsu 把阴影也算进前景。解决先做背景减除或者用 GrabCut 手动框选一次生成掩膜。简单场景下把图转到 HSV 用 S 通道做阈值比灰度更稳。5.5 类别不平衡苹果样本 500 张梨子只有 80 张现象梨子的召回率只有 0.4全被预测成苹果。原因SVM 的惩罚参数对多数类倾斜。解决SVC 里设class_weightbalanced或者对梨子做增强到 300 张以上。随机森林里用class_weightbalanced_subsample。6. 把识别结果跑成实时视频流一个可复用的调参习惯训练完模型只是第一步真正落地时要在视频流里逐帧识别。我一般会写一个带滑动窗口的预测函数把连续 5 帧的预测结果做多数投票避免单帧抖动导致标签跳变。from collections import deque class SmoothPredictor: def __init__(self, model, scaler, window5): self.model model self.scaler scaler self.window deque(maxlenwindow) def predict(self, frame): feat np.concatenate([ color_histogram(frame), hog_feature(frame), contour_feature(frame) ]).reshape(1, -1) feat self.scaler.transform(feat) pred self.model.predict(feat)[0] self.window.append(pred) # 多数投票 return max(set(self.window), keyself.window.count) cap cv2.VideoCapture(0) smoother SmoothPredictor(clf, scaler) while True: ret, frame cap.read() if not ret: break frame cv2.resize(frame, (256, 256)) label smoother.predict(frame) cv2.putText(frame, [apple, banana, pear][label], (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.imshow(fruit, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()window5是延迟和稳定性的折中调到 10 更稳但响应慢。max(set(...), key...)在平票时取任意一个实际用的时候可以加权重越近的帧权重越高。调参这件事我有个习惯每次只改一个参数跑完记录准确率和混淆矩阵用表格存下来。比如 H 的 bin 从 30 改到 60其他不动看苹果和梨子的混淆有没有减少。这样积累十几轮你就知道哪几个参数对这个样本集最敏感。别一次改三四个参数出了问题根本不知道是哪个引起的。这套流程跑通后换橙子、柠檬、猕猴桃也就是换样本重新训一次的事特征提取和分类框架不用动。希望帮到你。本文还有配套的精品资源点击获取