掌握计算机视觉三大基础任务:图像分类、分割与检测
计算机视觉是深度学习最具影响力的应用领域之一。在入门阶段,我们需要重点掌握三项基本的计算机视觉任务,它们是构建更复杂应用的基础。
三大基础计算机视觉任务
1. 图像分类(Image Classification)
目的:为图像指定一个或多个标签。
-
单标签分类:一张图像只能属于一个类别 -
多标签分类:找出图像所属的所有类别
实际应用:谷歌照片应用中的图像搜索功能,使用了包含20,000+类别的多标签分类模型。
2. 图像分割(Image Segmentation)
目的:将图像"分割"成不同区域,每个区域对应一个类别。
实际应用:视频通话中的虚拟背景、自动驾驶中的道路识别等。
图像分割又分为两种:
-
语义分割:将每个像素划分到语义类别(如"猫"),不区分个体实例 -
实例分割:不仅按类别分类像素,还要区分单个对象实例
3. 目标检测(Object Detection)
目的:在图像中感兴趣的目标周围绘制边界框,并给出每个框对应的类别。
实际应用:自动驾驶汽车检测车辆、行人和交通标志。
图像分割实战:宠物图像分割
下面我们通过一个实际例子,学习如何使用深度学习进行语义分割。
数据集准备
我们使用Oxford-IIIT宠物数据集,包含7,390张猫狗图片及对应的分割掩码。
import os
input_dir = "images/"
target_dir = "annotations/trimaps/"
input_img_paths = sorted([
os.path.join(input_dir, fname)
for fname in os.listdir(input_dir)
if fname.endswith(".jpg")
])
target_paths = sorted([
os.path.join(target_dir, fname)
for fname in os.listdir(target_dir)
if fname.endswith(".png") andnot fname.startswith(".")
])
理解分割掩码
分割掩码是图像分割任务的标签,它是与输入图像大小相同的单通道图像,其中每个像素值表示:
-
1:前景 -
2:背景 -
3:轮廓
构建分割模型
图像分割模型通常采用编码器-解码器结构:
from tensorflow import keras
from tensorflow.keras import layers
defget_model(img_size, num_classes):
# 编码器(下采样)
inputs = keras.Input(shape=img_size + (3,))
x = layers.Conv2D(32, 3, strides=2, activation="relu", padding="same")(inputs)
x = layers.Conv2D(64, 3, strides=2, activation="relu", padding="same")(x)
x = layers.Conv2D(128, 3, strides=2, activation="relu", padding="same")(x)
# 解码器(上采样)
x = layers.Conv2DTranspose(128, 3, activation="relu", padding="same")(x)
x = layers.Conv2DTranspose(64, 3, activation="relu", padding="same")(x)
x = layers.Conv2DTranspose(32, 3, activation="relu", padding="same")(x)
# 输出层
outputs = layers.Conv2D(num_classes, 3, activation="softmax", padding="same")(x)
return keras.Model(inputs, outputs)
model = get_model(img_size=(200, 200), num_classes=3)
关键设计要点
-
使用步幅卷积而非最大池化:保留位置信息,对分割任务至关重要 -
编码器-解码器结构:先压缩提取特征,再上采样恢复分辨率 -
Conv2DTranspose层:学习上采样操作,恢复原始图像尺寸
训练与评估
model.compile(optimizer="rmsprop", loss="sparse_categorical_crossentropy")
callbacks = [
keras.callbacks.ModelCheckpoint("oxford_segmentation.keras",
save_best_only=True)
]
history = model.fit(train_input_imgs, train_targets,
epochs=50,
callbacks=callbacks,
validation_data=(val_input_imgs, val_targets))
结果展示
训练完成后,模型能够准确地区分前景(宠物)和背景:
总结
-
图像分类:识别图像内容,"这是什么?" -
图像分割:像素级分类,"每个像素属于什么?" -
目标检测:定位并识别物体,"物体在哪里?是什么?"
掌握这三项基础任务,你就具备了解决大多数计算机视觉问题的核心能力。图像分割技术在图像编辑、自动驾驶、医学影像等领域有着广泛应用,是计算机视觉工程师必备的技能之一。
完整代码已整理发布,欢迎在技术社区交流讨论!
import os
import numpy as np
import matplotlib
matplotlib.use('Agg') # 使用非交互式后端
import matplotlib.pyplot as plt
import sys
# 设置TensorFlow日志级别
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
print("开始执行图像分割程序...")
# 检查必要的目录和文件
print("检查数据目录...")
current_dir = os.getcwd()
print(f"当前工作目录: {current_dir}")
# 根据项目规范,使用更可靠的方式定位项目根目录
# 使用当前文件的路径来确定项目根目录,而不是依赖于当前工作目录
script_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.dirname(script_dir)
print(f"脚本目录: {script_dir}")
print(f"项目根目录: {project_root}")
# 准备文件路径 - 使用相对于项目根目录的路径
input_dir = os.path.join(project_root, "images")
target_dir = os.path.join(project_root, "annotations", "trimaps")
print(f"输入图像目录: {input_dir}")
print(f"目标掩码目录: {target_dir}")
# 检查目录是否存在
ifnot os.path.exists(input_dir):
print(f"错误: 输入目录 {input_dir} 不存在")
print("请确保已下载并解压 Oxford-IIIT Pet Dataset 数据集")
print("数据集下载命令:")
print("wget http://www.robots.ox.ac.uk/~vgg/data/pets/data/images.tar.gz")
print("wget http://www.robots.ox.ac.uk/~vgg/data/pets/data/annotations.tar.gz")
print("tar -xf images.tar.gz")
print("tar -xf annotations.tar.gz")
sys.exit(1)
ifnot os.path.exists(target_dir):
print(f"错误: 目标目录 {target_dir} 不存在")
sys.exit(1)
# 获取文件列表
try:
input_img_paths = sorted([
os.path.join(input_dir, fname)
for fname in os.listdir(input_dir)
if fname.endswith(".jpg")
])
target_paths = sorted([
os.path.join(target_dir, fname)
for fname in os.listdir(target_dir)
if fname.endswith(".png") andnot fname.startswith(".")
])
print(f"找到 {len(input_img_paths)} 张输入图像")
print(f"找到 {len(target_paths)} 个目标掩码")
if len(input_img_paths) == 0or len(target_paths) == 0:
print("错误: 没有找到图像文件")
sys.exit(1)
except Exception as e:
print(f"获取文件列表时出错: {e}")
sys.exit(1)
# 限制处理的图像数量以减少内存使用
max_samples = min(500, len(input_img_paths)) # 进一步减少样本数量
input_img_paths = input_img_paths[:max_samples]
target_paths = target_paths[:max_samples]
print(f"限制处理图像数量为 {max_samples} 张以减少内存使用")
# 跳过图像显示部分,直接进入数据处理
print("跳过图像显示,直接进入数据处理...")
# 延迟导入TensorFlow,避免可能的冲突
try:
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.utils import load_img
print("TensorFlow模块导入成功")
except ImportError as e:
print(f"导入TensorFlow模块时出错: {e}")
sys.exit(1)
# 加载和处理数据的简化版本
defload_images_simple(input_img_paths, target_paths, size=(128, 128)):# 使用更小的图像尺寸
print(f"开始加载 {len(input_img_paths)} 张图像...")
input_imgs = []
targets = []
for i in range(len(input_img_paths)):
if i % 50 == 0:
print(f"已加载 {i}/{len(input_img_paths)} 张图像")
try:
# 加载输入图像
img = load_img(input_img_paths[i], target_size=size)
img_array = np.array(img, dtype="float32") / 255.0
# 加载目标掩码
mask = load_img(target_paths[i], target_size=size, color_mode="grayscale")
mask_array = np.array(mask, dtype="uint8")
# 处理标签值,确保它们在正确范围内
# 对于Oxford-IIIT Pet数据集,标签值是1,2,3,需要转换为0,1,2
mask_array = mask_array - 1
mask_array[mask_array == 255] = 2# 将边界像素设置为类别2
mask_array = np.expand_dims(mask_array, 2)
input_imgs.append(img_array)
targets.append(mask_array)
except Exception as e:
print(f"跳过图像 {input_img_paths[i]}: {e}")
continue
print(f"成功加载 {len(input_imgs)} 张图像")
return np.array(input_imgs), np.array(targets)
# 加载所有数据
try:
input_imgs, targets = load_images_simple(input_img_paths, target_paths, size=(128, 128))
if len(input_imgs) == 0:
print("错误: 没有成功加载任何图像")
sys.exit(1)
except Exception as e:
print(f"加载图像时出错: {e}")
sys.exit(1)
# 分割训练集和验证集
num_val_samples = min(100, len(input_imgs) // 5)
train_input_imgs = input_imgs[:-num_val_samples]
train_targets = targets[:-num_val_samples]
val_input_imgs = input_imgs[-num_val_samples:]
val_targets = targets[-num_val_samples:]
print(f"训练集大小: {len(train_input_imgs)}")
print(f"验证集大小: {len(val_input_imgs)}")
# 构建简化模型
defget_simple_model(img_size, num_classes):
print("正在构建简化模型...")
inputs = keras.Input(shape=img_size + (3,))
# 编码器
x = layers.Conv2D(32, 3, activation='relu', padding='same')(inputs)
x = layers.MaxPooling2D(2)(x)
x = layers.Conv2D(64, 3, activation='relu', padding='same')(x)
x = layers.MaxPooling2D(2)(x)
# 解码器
x = layers.Conv2DTranspose(64, 3, activation='relu', padding='same')(x)
x = layers.UpSampling2D(2)(x)
x = layers.Conv2DTranspose(32, 3, activation='relu', padding='same')(x)
x = layers.UpSampling2D(2)(x)
# 输出层
outputs = layers.Conv2D(num_classes, 1, activation='softmax')(x)
model = keras.Model(inputs, outputs)
print("简化模型构建完成")
return model
# 创建简化模型
model = get_simple_model(img_size=(128, 128), num_classes=3)
model.summary()
# 编译模型
print("正在编译模型...")
model.compile(
optimizer="adam", # 使用adam优化器,通常更稳定
loss="sparse_categorical_crossentropy",
metrics=['accuracy']
)
# 确保目标数据类型正确
train_targets = train_targets.astype(np.int32)
val_targets = val_targets.astype(np.int32)
# 设置回调函数
callbacks = [
keras.callbacks.ModelCheckpoint(
"simple_oxford_segmentation.keras",
save_best_only=True
),
keras.callbacks.EarlyStopping(patience=3) # 添加早停
]
# 训练模型
print("开始训练模型...")
try:
history = model.fit(
train_input_imgs, train_targets,
epochs=5, # 少量epochs进行测试
callbacks=callbacks,
batch_size=8, # 更小的batch_size
validation_data=(val_input_imgs, val_targets),
verbose=1
)
print("模型训练完成")
# 绘制训练和验证损失
if len(history.history["loss"]) > 0:
epochs = range(1, len(history.history["loss"]) + 1)
loss = history.history["loss"]
val_loss = history.history["val_loss"]
plt.figure()
plt.plot(epochs, loss, "bo-", label="Training loss")
plt.plot(epochs, val_loss, "ro-", label="Validation loss")
plt.title("Training and validation loss")
plt.legend()
plt.savefig('simple_training_loss.png', dpi=150, bbox_inches='tight')
plt.close()
print("训练损失图表已保存为 simple_training_loss.png")
# 进行预测
print("进行预测...")
if os.path.exists("simple_oxford_segmentation.keras"):
model = keras.models.load_model("simple_oxford_segmentation.keras")
# 在验证集上测试模型
test_idx = min(5, len(val_input_imgs) - 1)
test_image = val_input_imgs[test_idx]
# 预测
mask_pred = model.predict(np.expand_dims(test_image, 0), verbose=0)[0]
predicted_mask = np.argmax(mask_pred, axis=-1)
# 创建结果图表
fig, axes = plt.subplots(1, 3, figsize=(12, 4))
# 原始图像
axes[0].imshow(test_image)
axes[0].set_title("Test Image")
axes[0].axis('off')
# 预测掩码
axes[1].imshow(predicted_mask, cmap='viridis')
axes[1].set_title("Predicted Mask")
axes[1].axis('off')
# 真实掩码
axes[2].imshow(val_targets[test_idx][:, :, 0], cmap='viridis')
axes[2].set_title("Ground Truth")
axes[2].axis('off')
plt.tight_layout()
plt.savefig('simple_prediction_result.png', dpi=150, bbox_inches='tight')
plt.close()
print("预测结果图表已保存为 simple_prediction_result.png")
print("程序执行成功完成!")
except Exception as e:
print(f"训练过程中发生错误: {e}")
import traceback
traceback.print_exc()
print("程序执行遇到问题,但已尽力完成")

