用Python和Scikit-learn给人民币‘看相’:一个颜色矩+SVM的纸币面额识别小项目
用Python打造人民币智能识别系统:从颜色特征到SVM模型的实战解析
走在街头巷尾,你是否注意过自动售货机如何精准识别投入的纸币?这背后隐藏的正是计算机视觉与机器学习的精妙结合。今天,我们将用Python构建一个能"看懂"人民币面额的智能系统,整个过程就像教孩子认识不同颜色和大小的玩具一样直观有趣。
1. 项目准备与环境搭建
在开始之前,我们需要准备好Python环境和必要的工具库。这个项目特别适合刚学完Python基础,想要尝试实际应用的开发者。以下是我们的技术栈:
- Python 3.8+:推荐使用Anaconda管理环境
- Pillow (PIL):处理图像的基本操作
- NumPy:数值计算和数组处理
- scikit-learn:机器学习模型实现
- Matplotlib(可选):可视化分析
安装依赖只需一行命令:
pip install pillow numpy scikit-learn matplotlib提示:如果遇到权限问题,可以尝试添加
--user参数或在虚拟环境中安装。
我们的数据集包含6种面额的人民币图片(1元、5元、10元、20元、50元、100元),每种面额有40张不同角度和正反面的照片,总共240张。这些图片已经按照"面值_序号.png"的格式命名,例如"5_23.png"表示第23张5元人民币照片。
2. 图像预处理:为机器学习准备"食材"
就像烹饪前需要洗净切配食材一样,图像预处理是机器学习项目成功的关键第一步。我们需要确保所有图片规格统一,并且排除干扰因素。
2.1 标准化图像尺寸
不同照片可能因为拍摄角度和距离产生尺寸差异,我们需要统一调整为相同大小:
from PIL import Image def resize_image(image_path, target_size=(60, 60)): img = Image.open(image_path) return img.resize(target_size)2.2 提取中心区域
纸币边缘可能因为拍摄角度产生变形,我们更关注中心区域的颜色特征:
def crop_center(image, crop_width=100, crop_height=100): width, height = image.size left = (width - crop_width)/2 top = (height - crop_height)/2 right = (width + crop_width)/2 bottom = (height + crop_height)/2 return image.crop((left, top, right, bottom))2.3 颜色通道分离
人民币不同面额最直观的区别就是颜色,我们需要分别处理RGB三个通道:
def split_channels(image): return image.split() # 返回(R, G, B)三个通道3. 特征工程:颜色矩的魔力
特征工程是将原始数据转换为机器学习模型能理解的形式的过程。对于图像识别,颜色矩(Color Moments)是一种简单有效的特征提取方法。
3.1 理解颜色矩
颜色矩包含三个层次的统计特征:
- 一阶矩(均值):颜色的平均强度
- 二阶矩(标准差):颜色的变化幅度
- 三阶矩(偏度):颜色分布的不对称性
对于RGB图像,每个通道计算这三个矩,总共得到9个特征值。
3.2 计算颜色矩
以下是计算颜色矩的Python实现:
import numpy as np def calculate_moments(channel): # 一阶矩(均值) mean = np.mean(channel) # 二阶矩(标准差) std = np.std(channel) # 三阶矩(偏度) skewness = np.mean((channel - mean)**3) ** (1/3) return mean, std, skewness3.3 构建特征矩阵
我们需要为每张图片计算RGB三个通道的颜色矩,并组合成特征向量:
def extract_features(image): r, g, b = split_channels(image) r_mean, r_std, r_skew = calculate_moments(np.array(r)) g_mean, g_std, g_skew = calculate_moments(np.array(g)) b_mean, b_std, b_skew = calculate_moments(np.array(b)) return [r_mean, r_std, r_skew, g_mean, g_std, g_skew, b_mean, b_std, b_skew]4. 构建SVM分类模型
支持向量机(SVM)在小样本、高维度分类问题上表现优异,非常适合我们的纸币识别任务。
4.1 数据准备
首先加载所有图片并提取特征:
import os def load_dataset(image_folder): features = [] labels = [] for filename in os.listdir(image_folder): if filename.endswith('.png'): # 从文件名获取面额标签 label = filename.split('_')[0] labels.append(label) # 处理图像并提取特征 img_path = os.path.join(image_folder, filename) img = Image.open(img_path) img = resize_image(img) img = crop_center(img) feature = extract_features(img) features.append(feature) return np.array(features), np.array(labels)4.2 训练测试分割
使用scikit-learn的train_test_split划分数据集:
from sklearn.model_selection import train_test_split X, y = load_dataset('人民币图片/') X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42)4.3 SVM模型训练
不同核函数对结果有显著影响,我们比较几种常见选择:
| 核函数类型 | 适用场景 | 训练速度 | 预测速度 |
|---|---|---|---|
| linear | 线性可分 | 快 | 快 |
| poly | 非线性 | 中等 | 中等 |
| rbf | 非线性 | 慢 | 中等 |
| sigmoid | 特定场景 | 中等 | 中等 |
from sklearn.svm import SVC from sklearn.metrics import accuracy_score # 使用RBF核函数 svm_model = SVC(kernel='rbf', C=1.0, gamma='scale') svm_model.fit(X_train, y_train) # 评估模型 train_acc = accuracy_score(y_train, svm_model.predict(X_train)) test_acc = accuracy_score(y_test, svm_model.predict(X_test)) print(f"训练集准确率: {train_acc:.2%}") print(f"测试集准确率: {test_acc:.2%}")4.4 模型优化技巧
提高模型性能的几个实用方法:
- 特征标准化:SVM对特征尺度敏感,使用StandardScaler
- 类别权重:样本不均衡时设置class_weight='balanced'
- 网格搜索:寻找最优的C和gamma参数组合
from sklearn.preprocessing import StandardScaler from sklearn.pipeline import make_pipeline # 创建包含标准化的管道 svm_pipeline = make_pipeline( StandardScaler(), SVC(kernel='rbf', C=1.0, gamma='scale') ) svm_pipeline.fit(X_train, y_train)5. 实际应用中的挑战与解决方案
在理想实验室环境下模型可能表现良好,但实际应用中会遇到各种挑战:
5.1 光照条件变化
不同光线环境下拍摄的照片颜色差异很大:
解决方案:
- 使用HSV颜色空间替代RGB
- 增加光照归一化步骤
- 收集更多不同光照条件下的训练数据
5.2 拍摄角度倾斜
纸币不是正对相机时,颜色特征会失真:
解决方案:
- 应用透视变换校正角度
- 提取多个区域的特征而不仅是中心区域
- 使用SIFT或SURF等局部特征
5.3 纸币磨损和污渍
流通中的纸币会有不同程度的磨损:
解决方案:
- 增加数据增强,模拟磨损效果
- 结合纹理特征而不仅依赖颜色
- 使用更鲁棒的分类算法如随机森林
5.4 实时性要求
实际应用可能需要实时识别:
优化策略:
# 简化特征计算 def fast_features(image): r, g, b = split_channels(image) return [np.mean(r), np.mean(g), np.mean(b)] # 仅使用均值- 减少特征维度
- 选择计算量小的模型(如线性SVM)
- 使用Cython或Numba加速关键代码
6. 项目扩展与进阶方向
完成基础版本后,可以考虑以下扩展方向提升系统能力:
6.1 多特征融合
除了颜色矩,可以加入:
- 纹理特征:LBP、灰度共生矩阵
- 形状特征:边缘检测、霍夫变换
- 深度学习特征:使用预训练CNN提取高级特征
6.2 模型集成
结合多种模型的优势:
- 训练随机森林作为第二分类器
- 使用投票机制综合SVM和RF的结果
- 堆叠(Stacking)不同模型的预测结果
6.3 端到端部署
将模型应用到实际场景:
from flask import Flask, request, jsonify app = Flask(__name__) @app.route('/predict', methods=['POST']) def predict(): file = request.files['image'] img = Image.open(file.stream) # 预处理和特征提取 feature = extract_features(img) # 预测 pred = svm_model.predict([feature]) return jsonify({'denomination': pred[0]}) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)6.4 移动端集成
使用以下工具将模型部署到移动设备:
- TensorFlow Lite:轻量级推理框架
- Core ML(iOS)或ML Kit(Android)
- ONNX Runtime:跨平台模型部署
在实际测试中,我发现模型的性能很大程度上取决于训练数据的多样性。曾经在一个项目中,因为训练集缺少特定光照条件下的样本,导致模型在实际应用中准确率骤降。后来通过数据增强和收集更多真实场景数据解决了这个问题。
