如何使用Python和深度学习实现图像识别——从入门到实战
·
📌 前言
图像识别是计算机视觉领域最核心的应用之一,从手机相册的自动分类、自动驾驶中的目标检测,到医疗影像的辅助诊断,图像识别技术无处不在。本文将从零基础出发,带你用 Python + TensorFlow/Keras 实现一个完整的图像识别项目,涵盖数据准备、模型构建、训练、评估和预测的全流程。
🧰 一、环境准备
1.1 安装依赖
pip install numpy matplotlib opencv-python scikit-learn tensorflow
bash复制代码
建议使用 Python 3.8+,TensorFlow 2.x 版本。
1.2 验证安装
import tensorflow as tf
import cv2
import numpy as np
print(f"TensorFlow版本: {tf.__version__}")
print(f"OpenCV版本: {cv2.__version__}")
print(f"NumPy版本: {np.__version__}")
print(f"GPU可用: {tf.config.list_physical_devices('GPU')}")
🖼️ 二、图像的加载与预处理
图像预处理是整个流程中最容易被忽视但又最关键的一步。预处理不当,再好的模型也无法正确识别。
2.1 正确加载图像
import cv2
import numpy as np
import matplotlib.pyplot as plt
def load_image(image_path):
"""加载图像并转换为RGB格式"""
# OpenCV默认以BGR格式读取
image = cv2.imread(image_path)
if image is None:
raise FileNotFoundError(f"无法加载图像,请检查路径:{image_path}"
# ✅ 关键步骤:BGR → RGB
# 因为TensorFlow/Keras的预训练模型都是在RGB格式上训练的
rgb_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
return rgb_image
# 示例
image = load_image('example.jpg')
plt.imshow(image)
plt.title(f"原始图像 尺寸: {image.shape}")
plt.axis('off')
plt.show()
# 输出: 原始图像 尺寸: (1080, 1920, 3) ← 高×宽×3通道(RGB)
2.2 图像预处理流水线
def preprocess_for_model(image, target_size=(224, 224)):
"""
图像预处理流水线
参数:
image: RGB格式的numpy数组
target_size: 目标尺寸,MobileNet/VGG均要求224×224
返回:
预处理后的图像,shape为 (1, 224, 224, 3)
"""
# ✅ 步骤1:Resize — 将任意尺寸缩放到模型要求的输入尺寸
resized = cv2.resize(image, target_size)
# ✅ 步骤2:转为float32(深度学习模型要求浮点型输入)
float_image = resized.astype(np.float32)
# ✅ 步骤3:添加批次维度 — 模型期望输入为 (batch, H, W, C)
batch_image = np.expand_dims(float_image, axis=0)
# shape: (1, 224, 224, 3)
return batch_image
# 示例
processed = preprocess_for_model(image)
print(f"预处理后shape: {processed.shape}") # (1, 224, 224, 3)
💡 为什么不能转灰度图?
- 主流预训练模型(MobileNet、VGG、ResNet等)都在ImageNet数据集上训练,ImageNet是RGB彩色图像
- 颜色信息对识别至关重要(区分红苹果🟥和青苹果🟩、区分蓝天🔵和绿地🟢)
- 灰度图只有1通道,而模型输入层要求3通道,直接传入会报shape错误
🚀 三、方案一:使用预训练模型快速识别(零训练)
如果你只是想快速实现图像识别,无需自己训练模型,直接使用预训练模型是最快的方式。
from tensorflow.keras.applications import MobileNetV2
from tensorflow.keras.applications.mobilenet_v2 import preprocess_input, decode_predictions
# 加载预训练模型(首次运行会自动下载权重,约14MB)
model = MobileNetV2(weights='imagenet')
def predict_image(image_path, top_k=5):
"""使用MobileNetV2识别图像"""
# 加载并预处理图像
image = load_image(image_path)
batch_image = preprocess_for_model(image, target_size=(224, 224))
# ✅ 关键步骤:使用模型自带的preprocess_input进行归一化
# 不同模型的预处理方式不同,必须使用对应的函数!
batch_image = preprocess_input(batch_image)
# 预测
predictions = model.predict(batch_image, verbose=0)
# 解码预测结果
results = decode_predictions(predictions, top=top_k)[0]
print(f"\n🎯 图像识别结果 (Top {top_k}):")
print("-" * 50)
for i, (_, label, prob) in enumerate(results):
bar = '█' * int(prob * 30)
print(f" {i+1}. {label:<25s} {prob:>6.2%} {bar}")
print("-" * 50)
return results
# 示例
results = predict_image('example.jpg')
输出示例:
🎯 图像识别结果 (Top 5):
--------------------------------------------------
1. golden_retriever 87.32% ██████████████████████████
2. Labrador_retriever 5.18% █
3. kuvasz 2.41%
4. Great_Pyrenees 1.89%
5. clot 0.92%
--------------------------------------------------
🏗️ 四、方案二:自定义数据集训练识别模型(进阶)
预训练模型只能识别ImageNet的1000个类别。如果你的需求超出这个范围(如识别特定零件、特定病害),就需要用自己的数据训练模型。
4.1 准备数据集
假设我们要做一个猫狗分类器,目录结构如下:
dataset/
├── train/
│ ├── cat/ ← 放猫的图片
│ │ ├── cat_001.jpg
│ │ ├── cat_002.jpg
│ │ └── ...
│ └── dog/ ← 放狗的图片
│ ├── dog_001.jpg
│ ├── dog_002.jpg
│ └── ...
└── val/
├── cat/
└── dog/
4.2 使用ImageDataGenerator加载数据
from tensorflow.keras.preprocessing.image import ImageDataGenerator
# ✅ 数据增强 — 通过随机变换扩充训练数据,防止过拟合
train_datagen = ImageDataGenerator(
preprocessing_function=tf.keras.applications.mobilenet_v2.preprocess_input,
rotation_range=20, # 随机旋转±20°
width_shift_range=0.2, # 随机水平平移20%
height_shift_range=0.2, # 随机垂直平移20%
shear_range=0.2, # 随机剪切
zoom_range=0.2, # 随机缩放
horizontal_flip=True, # 随机水平翻转
fill_mode='nearest' # 填充方式
)
# 验证集不做数据增强,只做预处理
val_datagen = ImageDataGenerator(
preprocessing_function=tf.keras.applications.mobilenet_v2.preprocess_input
)
# 从目录加载数据
IMG_SIZE = (224, 224)
BATCH_SIZE = 32
train_generator = train_datagen.flow_from_directory(
'dataset/train',
target_size=IMG_SIZE,
batch_size=BATCH_SIZE,
class_mode='binary' # 二分类用binary,多分类用categorical
)
val_generator = val_datagen.flow_from_directory(
'dataset/val',
target_size=IMG_SIZE,
batch_size=BATCH_SIZE,
class_mode='binary'
)
print(f"类别映射: {train_generator.class_indices}")
# 输出: 类别映射: {'cat': 0, 'dog': 1}
4.3 构建迁移学习模型
from tensorflow.keras import layers, Model
def build_model(num_classes=1):
"""
基于MobileNetV2的迁移学习模型
- 冻结预训练层,只训练新加的分类头
- 比从头训练快10倍以上,且精度更高
"""
# 加载预训练模型,不含顶层分类头
base_model = MobileNetV2(
weights='imagenet',
include_top=False, # ✅ 去掉原始的1000类分类层
input_shape=(224, 224, 3)
)
# ✅ 冻结预训练层的权重(保留ImageNet学到的特征提取能力)
base_model.trainable = False
# 构建新的分类头
x = base_model.output
x = layers.GlobalAveragePooling2D()(x) # 全局平均池化:将7×7×1280 → 1280
x = layers.Dropout(0.3)(x) # Dropout防止过拟合
x = layers.Dense(128, activation='relu')(x) # 全连接层
x = layers.Dropout(0.3)(x)
# 输出层:二分类用sigmoid,多分类用softmax
if num_classes == 1:
outputs = layers.Dense(num_classes, activation='sigmoid')(x)
else:
outputs = layers.Dense(num_classes, activation='softmax')(x)
model = Model(inputs=base_model.input, outputs=outputs)
return model, base_model
model, base_model = build_model(num_classes=1)
model.summary()
# 查看可训练参数
total_params = model.count_params()
trainable_params = sum(tf.keras.backend.count_params(w) for w in model.trainable_weights)
print(f"\n总参数量: {total_params:,}")
print(f"可训练参数量: {trainable_params:,} ({trainable_params/total_params*100:.1f}%)")
# 输出: 可训练参数量: 165,249 (4.7%) — 只训练5%的参数!
4.4 训练模型
from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau, ModelCheckpoint
# 编译模型
model.compile(
optimizer=tf.keras.optimizers.Adam(learning_rate=1e-3),
loss='binary_crossentropy', # 二分类交叉熵
metrics=['accuracy']
)
# ✅ 回调函数 — 训练过程的智能控制
callbacks = [
# 早停:验证损失3轮不下降则停止训练
EarlyStopping(
monitor='val_loss',
patience=3,
restore_best_weights=True
),
# 学习率衰减:验证损失2轮不下降则降低学习率
ReduceLROnPlateau(
monitor='val_loss',
factor=0.5, # 学习率减半
patience=2,
min_lr=1e-7
),
# 保存最佳模型
ModelCheckpoint(
'best_model.h5',
monitor='val_accuracy',
save_best_only=True
)
]
# 开始训练
EPOCHS = 20
history = model.fit(
train_generator,
validation_data=val_generator,
epochs=EPOCHS,
callbacks=callbacks
)
4.5 训练过程可视化
def plot_training_history(history):
"""绘制训练曲线"""
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# 准确率曲线
axes[0].plot(history.history['accuracy'], 'b-', label='训练准确率', linewidth=2)
axes[0].plot(history.history['val_accuracy'], 'r--', label='验证准确率', linewidth=2)
axes[0].set_title('模型准确率', fontsize=14)
axes[0].set_xlabel('Epoch')
axes[0].set_ylabel('Accuracy')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
# 损失曲线
axes[1].plot(history.history['loss'], 'b-', label='训练损失', linewidth=2)
axes[1].plot(history.history['val_loss'], 'r--', label='验证损失', linewidth=2)
axes[1].set_title('模型损失', fontsize=14)
axes[1].set_xlabel('Epoch')
axes[1].set_ylabel('Loss')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('training_history.png', dpi=150)
plt.show()
plot_training_history(history)
4.6 微调(Fine-tuning)— 进一步提升精度
# ✅ 解冻预训练模型的最后几层,进行微调
base_model.trainable = True
# 只微调最后30层(前面的层提取通用特征,不需要调整)
for layer in base_model.layers[:-30]:
layer.trainable = False
# 用更小的学习率重新编译
model.compile(
optimizer=tf.keras.optimizers.Adam(learning_rate=1e-5), # ✅ 学习率降低100倍
loss='binary_crossentropy',
metrics=['accuracy']
)
# 继续训练
fine_tune_epochs = 10
total_epochs = len(history.history['loss']) + fine_tune_epochs
history_fine = model.fit(
train_generator,
validation_data=val_generator,
epochs=total_epochs,
initial_epoch=len(history.history['loss']),
callbacks=callbacks
)
🧪 五、模型评估与预测
5.1 在验证集上评估
# 加载最佳模型
model = tf.keras.models.load_model('best_model.h5')
val_loss, val_acc = model.evaluate(val_generator)
print(f"\n验证集损失: {val_loss:.4f}")
print(f"验证集准确率: {val_acc:.4f}")
5.2 对单张图片进行预测
def predict_single_image(image_path, model, class_names=['猫', '狗']):
"""对单张图片进行预测"""
# 加载并预处理
image = load_image(image_path)
processed = preprocess_for_model(image, target_size=(224, 224))
processed = tf.keras.applications.mobilenet_v2.preprocess_input(processed)
# 预测
prediction = model.predict(processed, verbose=0)[0][0]
# 解析结果
class_idx = 1 if prediction > 0.5 else 0
confidence = prediction if prediction > 0.5 else 1 - prediction
# 可视化
plt.imshow(image)
plt.title(f"预测: {class_names[class_idx]} ({confidence:.2%})", fontsize=16)
plt.axis('off')
plt.show()
return class_names[class_idx], confidence
# 示例
label, conf = predict_single_image('test_cat.jpg', model)
print(f"这是一只{label},置信度{conf:.2%}")
5.3 生成分类报告
from sklearn.metrics import classification_report, confusion_matrix
import seaborn as sns
# 获取验证集所有预测
val_generator.reset()
y_true = val_generator.classes
y_pred = (model.predict(val_generator, verbose=0) > 0.5).astype(int).flatten()
# 分类报告
print(classification_report(y_true, y_pred, target_names=['猫', '狗']))
# 混淆矩阵可视化
cm = confusion_matrix(y_true, y_pred)
plt.figure(figsize=(6, 5))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
xticklabels=['猫', '狗'], yticklabels=['猫', '狗'])
plt.xlabel('预测标签')
plt.ylabel('真实标签')
plt.title('混淆矩阵')
plt.show()
📦 六、模型的保存与部署
6.1 保存模型
# 方式1:保存完整模型(推荐)
model.save('cat_dog_classifier.h5')
# 方式2:保存为SavedModel格式(TensorFlow Serving部署用)
model.save('cat_dog_classifier_savedmodel')
# 方式3:只保存权重
model.save_weights('cat_dog_weights.h5')
6.2 加载模型并预测
# 加载模型
loaded_model = tf.keras.models.load_model('cat_dog_classifier.h5')
# 直接预测
label, conf = predict_single_image('new_image.jpg', loaded_model)
6.3 转换为TensorFlow Lite(移动端部署)
# 转换为TFLite格式
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT] # 量化压缩
tflite_model = converter.convert()
# 保存
with open('cat_dog_classifier.tflite', 'wb') as f:
f.write(tflite_model)
print(f"模型大小: {len(tflite_model) / 1024:.1f} KB") # 约3-5MB
🎯 七、常见问题与避坑指南
Q1:训练时显存不够怎么办?
# 方法1:减小batch_size
BATCH_SIZE = 16 # 甚至8
# 方法2:使用混合精度训练
from tensorflow.keras import mixed_precision
mixed_precision.set_global_policy('mixed_float16')
# 方法3:限制GPU显存增长
gpus = tf.config.list_physical_devices('GPU')
if gpus:
tf.config.experimental.set_memory_growth(gpus[0], True)
Q2:数据量太少怎么办?
| 方法 | 说明 | |
|---|---|---|
| 数据增强 | 随机翻转、旋转、缩放等 | |
| 迁移学习 | 使用预训练权重 | |
| 减少模型复杂度 | 减少全连接层神经元数 | |
| Dropout | 随机丢弃神经元防止过拟合 | |
| 收集更多数据 | 最根本的解决方案 |
Q3:如何选择合适的模型?
需求分析决策树:
需要实时推理?(手机/边缘设备)
└─ 是 → MobileNetV2 / EfficientNet-Lite
└─ 否 → 需要最高精度?
└─ 是 → EfficientNetB7 / Vision Transformer
└─ 否 → ResNet50 / EfficientNetB0(通用首选)
📝 八、总结
本文从环境搭建到模型部署,完整覆盖了图像识别的全流程:
数据准备 → 数据增强 → 迁移学习 → 训练 → 微调 → 评估 → 部署
↓ ↓ ↓ ↓ ↓ ↓ ↓
目录结构 ImageData MobileNetV2 fit() 解冻层 混淆矩阵 TFLite
Generator 冻结权重 回调 小学习率 分类报告
核心要点回顾:
- 预处理必须匹配模型要求 — RGB格式、正确尺寸、对应的preprocess_input
- 迁移学习是首选方案 — 站在巨人的肩膀上,少量数据也能达到高精度
- 数据增强是免费的午餐 — 不增加标注成本就能提升泛化能力
- 微调时用小学习率 — 避免破坏预训练权重
版权声明:本文为原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接。
相关推荐:
觉得有用的话,点个👍收藏吧!有问题欢迎评论区交流~
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)