🤵♂️ 个人主页:@艾派森的个人主页
✍🏻作者简介:Python学习者
🐋 希望大家多多支持,我们一起进步!😄
如果文章对你有帮助的话,
欢迎评论 💬点赞👍🏻 收藏 📂加关注+
目录
1.项目背景
2.数据集介绍
3.技术工具
4.实验过程
4.1导入数据
4.2数据可视化
4.3特征工程
4.4构建模型
4.5训练模型
4.6模型评估
4.7模型预测
5.总结
源代码
1.项目背景
瑜伽作为一种源远流长的身心锻炼方式,在现代生活中受到了越来越广泛的关注与普及。随着健身科技与智慧医疗的发展,基于计算机视觉的人体姿态识别技术在辅助瑜伽教学、纠正动作姿势以及自主居家练习等场景中展现出极大的应用潜力。然而,瑜伽动作种类繁多且形态各异,不同动作之间往往存在肢体重叠、角度变化大以及人体关节形态复杂等问题,这给传统的图像特征提取与自动分类带来了巨大挑战。近年来,深度学习在图像识别领域的突破性进展,使得利用高阶卷积神经网络精准捕捉复杂人体姿姿态特征成为可能。本项目旨在借助先进的迁移学习技术,利用预训练的主干网络提取瑜伽姿势图像的深层特征,构建高精度的多分类模型,为自动化瑜伽动作识别与智能健身指导系统的开发提供可靠的技术支撑与实践参考。
2.数据集介绍
本实验数据集来源于Kaggle,用于识别各种瑜伽姿势的瑜伽姿势数据集。瑜伽是一套起源于古印度的身心修行方法,旨在控制和宁静心念,达到一种超脱尘世、不受世俗烦恼影响的觉知状态。印度教、佛教和耆那教中都存在着各种各样的瑜伽流派、修行方法和目标,传统瑜伽和现代瑜伽在世界各地都有实践。数据集包含约 3000 张不同瑜伽姿势的图片。提供的 Poses.json 文件包含该姿势的英文和梵文名称,以及其标志的 URL。
3.技术工具
Python版本:3.9
代码编辑器:jupyter notebook
4.实验过程
4.1导入数据
在搭建深度学习识别模型之前,首先需要对图像数据集的存储结构与类别分布进行基础环境配置与数据导入。代码通过引入 TensorFlow/Keras 框架、图像处理工具(PIL、OpenCV)以及常用的数据处理与可视化库,明确了模型训练所需的核心依赖;随后通过设置数据集的根目录路径,自动扫描并提取所有的姿势类别文件夹,筛选掉无关的 JSON 元数据配置文件,精准统计出具体的姿势分类总数以及各个类别下的样本数量分布情况。
# ---------------------------------------------------------------------- # 1. 导入项目所需的常用库与依赖包 # ---------------------------------------------------------------------- import os import numpy as np import random from glob import glob from tqdm import tqdm import matplotlib.pyplot as plt from PIL import Image import cv2 # TensorFlow / Keras 模型构建与评估模块 import tensorflow as tf from tensorflow.keras.preprocessing.image import ImageDataGenerator from tensorflow.keras.models import Sequential from tensorflow.keras.layers import GlobalAveragePooling2D, Dense, Dropout from keras.callbacks import EarlyStopping from tensorflow.keras.applications import Xception from sklearn.metrics import classification_report # ---------------------------------------------------------------------- # 2. 设置数据集根目录与提取类别信息 # ---------------------------------------------------------------------- root_path = '../input/yoga-posture-dataset' # 瑜伽姿势数据集根目录路径 class_names = sorted(os.listdir(root_path)) # 获取所有类别文件夹名称并排序 class_names.remove('Poses.json') # 移除非图像数据的元数据配置文件 n_classes = len(class_names) # 统计总类别数 # ---------------------------------------------------------------------- # 3. 统计并打印各姿势类别的样本数量分布 # ---------------------------------------------------------------------- class_dis = [len(os.listdir(os.path.join(root_path, cls))) for cls in class_names] print(f"Total Number of Classes : {n_classes}") print("+++++++++++++++++++++++++++++++++++++++\n") print(f"Classes : {class_names}") print("+++++++++++++++++++++++++++++++++++++++\n") print(f"Class Distribution : {class_dis}")通过完成对数据集目录的初步扫描与信息统计,代码成功映射出了瑜伽姿势分类的具体标签与样本规模。获取到的类别映射列表以及样本分布数据,为后续划分数据生成器、建立统一的图像尺寸规范以及配置网络输出层维度奠定了清晰的数据基础。
4.2数据可视化
数据可视化是深入理解数据集样本质量与类别结构的关键步骤。首先通过循环遍历各个瑜伽姿势文件夹,随机抽取一张姿势图像调整至统一尺寸,并在 12×4 的网格画布中进行矩阵式排布展示,直观呈现不同姿势的形态差异与视觉特征。
# ---------------------------------------------------------------------- # 1. 动态抽取并绘制各瑜伽姿势类别的代表性图像网格 # ---------------------------------------------------------------------- # 设置网格排布参数(4列,12行以容纳47个类别) num_cols = 4 num_classes = 47 num_rows = 12 plt.figure(figsize=(num_cols * 4, num_rows * 4)) # 遍历所有类别并随机抽样展示 for i, cls in enumerate(class_names): class_path = os.path.join(root_path, cls) img_path = os.path.join(class_path, random.choice(os.listdir(class_path))) img = Image.open(img_path).resize((128, 128)) plt.subplot(num_rows, num_cols, i + 1) plt.imshow(img) plt.title(cls, fontsize=15) plt.axis('off') plt.tight_layout() plt.show()这种多维度的图像展示有助于快速确认原始图像的质量与人体姿态标注的正确性,直观展现不同类别之间的视觉异同。
随后计算并统计各个类别的图像总数,按照样本量从高到低进行降序排列,利用横向条形图绘制各类别的样本分布直方图,并在柱体旁精准标注文明细数值,清晰展示数据集整体的类别平衡状况。
# ---------------------------------------------------------------------- # 2. 绘制姿势类别样本分布的横向条形图 # ---------------------------------------------------------------------- # 构建类别与图像数量的映射字典 image_counts = {cls: len(os.listdir(os.path.join(root_path, cls))) for cls in class_names} # 提取数据并按样本数量降序排列 classes = list(image_counts.keys()) counts = list(image_counts.values()) sorted_pairs = sorted(zip(classes, counts), key=lambda x: x[1], reverse=True) classes, counts = zip(*sorted_pairs) # 绘制条形图并动态调整高度 plt.figure(figsize=(12, len(classes) * 0.5)) bars = plt.barh(classes, counts, color='salmon') # 为每个条形柱添加具体的数值标签 for bar in bars: width = bar.get_width() plt.text(width + 5, bar.get_y() + bar.get_height()/2, str(int(width)), va='center', fontsize=10) # 设置坐标轴样式与网格辅助线 plt.xlabel("Image Count", fontsize=14) plt.title("Number of Images per Yoga Class", fontsize=16) plt.xticks(fontsize=10) plt.yticks(fontsize=10) plt.gca().invert_yaxis() # 反转 Y 轴使得数量最多的类别显示在最上方 plt.grid(axis='x', linestyle='--', alpha=0.5) plt.tight_layout() plt.show()直观的数据分布图表展示了各姿势样本的均衡度,为后续设定图像增强策略、选择合适的数据切分比例以及避免模型产生倾向性偏见提供了重要的参考依据。
4.3特征工程
数据预处理与增强是提升深度学习模型泛化能力、防止过拟合的关键环节。在此阶段,首先实例化图像数据生成器ImageDataGenerator,在对像素值进行1./255归一化处理的同时加入 20 度的随机旋转增强,并划分出 20% 的数据作为验证集,为后续模型训练建立统一且具备一定随机扰动的数据流管道。
# ---------------------------------------------------------------------- # 1. 配置图像数据生成器(包含归一化、数据增强与验证集划分) # ---------------------------------------------------------------------- data_gen = ImageDataGenerator( rescale=1./255, rotation_range=20, validation_split=0.2 ) # ---------------------------------------------------------------------- # 2. 从目录加载训练集与验证集数据流 # ---------------------------------------------------------------------- train_ds = data_gen.flow_from_directory( root_path, target_size=(256, 256), class_mode='sparse', subset='training' ) valid_ds = data_gen.flow_from_directory( root_path, target_size=(256, 256), class_mode='sparse', subset='validation' )通过配置flow_from_directory批量加载接口,数据集被自动调整为统一的 256×256 分辨率,并按多分类任务的需求将标签编码为稀疏整数索引。构建好的训练集与验证集数据生成器不仅节省了内存开销,还为 Xception 模型的批量输入和迭代优化提供了稳定可靠的数据支撑。
4.4构建模型
在模型构建阶段,采用迁移学习策略,选用在 ImageNet 数据集上预训练好的 Xception 架构作为特征提取主干网。首先加载去除顶层分类头的 Xception 基础模型,设定统一的输入图像尺寸,并将预训练权重进行冻结,使其在初始训练阶段不参与梯度更新;随后在其上叠加全局平均池化层、带 ReLU 激活函数的全连接层、Dropout 防过拟合正则化层以及适应 47 个瑜伽姿势类别的 Softmax 输出层;最后选用 Adam 优化器与稀疏交叉熵损失函数进行模型编译。
# ---------------------------------------------------------------------- # 1. 加载 Xception 预训练主干网络并冻结权重 # ---------------------------------------------------------------------- base_model = Xception( include_top=False, weights='imagenet', input_shape=(256, 256, 3) ) base_model.trainable = False # ---------------------------------------------------------------------- # 2. 搭建自定义分类头结构 # ---------------------------------------------------------------------- model = Sequential([ base_model, GlobalAveragePooling2D(), Dense(256, activation='relu'), Dropout(0.4), Dense(n_classes, activation='softmax') ]) # ---------------------------------------------------------------------- # 3. 编译模型并配置损失函数与评估指标 # ---------------------------------------------------------------------- model.compile( optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'] )通过冻结强力的预训练特征提取器并附加定制化的分类头,模型能够在保留高阶视觉特征提取能力的同时,大幅减少需训练的参数量。这不仅能有效加速网络收敛,还极大地避免了在特定瑜伽数据集上发生过拟合现象。
4.5训练模型
配置早停(EarlyStopping)机制可以有效防止模型因训练轮次过多而发生过拟合,提高整体拟合效率。首先设置容忍轮数为 3 的早停回调函数,确保在验证集损失连续若干轮不再下降时自动终止训练,并自动恢复模型性能最佳时的权重参数;随后通过fit方法启动训练流程,将训练集与验证集数据生成器传入模型,设定最大训练轮数为 25 轮,在回调函数的监控下开始迭代优化。
# ---------------------------------------------------------------------- # 1. 配置早停回调函数以防止模型过拟合 # ---------------------------------------------------------------------- callbacks = [EarlyStopping(patience=3, restore_best_weights=True)] # ---------------------------------------------------------------------- # 2. 启动模型训练并保存训练历史记录 # ---------------------------------------------------------------------- history = model.fit( train_ds, validation_data=valid_ds, epochs=25, callbacks=callbacks )4.6模型评估
评估环节用于全面衡量模型训练完毕后的泛化能力与分类表现。首先提取训练历史对象history中记录的准确率与损失值数据,使用双子图结构将训练集与验证集的拟合曲线进行可视化对比,直观展示模型在各个训练轮次中的收敛趋势及是否存在过拟合现象。
# ---------------------------------------------------------------------- # 1. 绘制模型训练与验证过程中的准确率和损失值变化曲线 # ---------------------------------------------------------------------- plt.figure(figsize=(12, 4)) # 绘制准确率变化对比图 plt.subplot(1, 2, 1) plt.plot(history.history['accuracy'], label='Train Acc') plt.plot(history.history['val_accuracy'], label='Val Acc') plt.title("Accuracy") plt.legend() # 绘制损失值变化对比图 plt.subplot(1, 2, 2) plt.plot(history.history['loss'], label='Train Loss') plt.plot(history.history['val_loss'], label='Val Loss') plt.title("Loss") plt.legend() plt.tight_layout() plt.show()曲线走势能够清晰反映出模型对特征的学习效率以及在验证集上的稳定性。
随后为了获取具体的分类性能指标,重新以不打乱顺序(shuffle=False)的方式加载验证集数据流,保证预测结果与真实标签严格一一对应;接着调用模型生成各样本的概率分布并计算预测类别索引,最终通过classification_report计算出每一个瑜伽姿势类别的精确率、召回率以及 F1 评分。
# ---------------------------------------------------------------------- # 2. 生成验证集预测结果并输出详细的分类评估报告 # ---------------------------------------------------------------------- from sklearn.metrics import classification_report import numpy as np # 重新加载验证数据(关闭打乱,确保索引与预测严格一致) valid_data = data_gen.flow_from_directory( root_path, target_size=(256, 256), class_mode='sparse', subset='validation', shuffle=False ) # 提取真实类别索引 true_labels = valid_data.classes # 预测样本概率分布并获取最大概率对应的类别索引 predictions = model.predict(valid_data, verbose=1) predicted_labels = np.argmax(predictions, axis=1) # 获取类别名称映射关系 class_names = list(valid_data.class_indices.keys()) # 输出各姿势类别的精确率、召回率与 F1 综合指标 print(classification_report(true_labels, predicted_labels, target_names=class_names))4.7模型预测
直观展示模型的预测效果是验证其在实际业务场景中适用性的有效手段。通过在验证集中随机抽取样本图像,进行像素归一化并扩充 Batch 维度后送入已训练好的 Xception 模型进行前向推理,实时计算出预测概率最高的类别名称;最后利用 Matplotlib 建立多图子排版,将图像与对应的真实标签(True Class)及模型预测标签(Pred Class)进行对比排版展示。
# ---------------------------------------------------------------------- # 1. 随机抽取验证集样本并进行模型预测与可视化展示 # ---------------------------------------------------------------------- plt.figure(figsize=(15, 5)) for i in range(5): idx = random.randint(0, len(valid_ds.filenames) - 1) img_path = os.path.join(valid_ds.directory, valid_ds.filenames[idx]) img = Image.open(img_path).resize((256, 256)) arr = np.array(img) / 255.0 pred = model.predict(np.expand_dims(arr, axis=0)) pred_class = idx_to_class[np.argmax(pred)] true_class = valid_ds.filenames[idx].split('/')[0] plt.subplot(1, 5, i+1) plt.imshow(img) plt.title(f"True: {true_class}\nPred: {pred_class}") plt.axis('off') plt.tight_layout() plt.show()5.总结
本实验基于包含约 3000 张图像的瑜伽姿势数据集,构建了基于 Xception 预训练网络的深度学习识别模型,实现了对 47 种复杂瑜伽姿势的自动化分类。实验过程中通过图像预处理、数据增强与迁移学习技术,有效提高了模型在复杂人体姿势上的特征提取效率。训练结果显示,模型在经过 23 轮迭代优化后,训练集准确率提升至 82.93%,验证集准确率达到了 64.46%,在 Dhanurasana、Vrksasana 及 Virabhadrasana Two 等姿态特征较为显著的类别上表现出了出色的识别性能,F1 评分均达到 0.85 以上。虽然部分样本较少或姿态高度相似的类别存在一定的识别混淆,但整体评估指标证实了该模型在瑜伽动作分类任务中的实用价值,也为后续结合姿态估计与模型轻量化改进提供了坚实的数据与实验基础。
源代码
# Common import os import numpy as np import random from glob import glob from tqdm import tqdm import matplotlib.pyplot as plt from PIL import Image import cv2 # TensorFlow / Keras import tensorflow as tf from tensorflow.keras.preprocessing.image import ImageDataGenerator from tensorflow.keras.models import Sequential from tensorflow.keras.layers import GlobalAveragePooling2D, Dense, Dropout from keras.callbacks import EarlyStopping from tensorflow.keras.applications import Xception from sklearn.metrics import classification_report # Set path root_path = '../input/yoga-posture-dataset' # List classes class_names = sorted(os.listdir(root_path)) class_names.remove('Poses.json') n_classes = len(class_names) # Count images per class class_dis = [len(os.listdir(os.path.join(root_path, cls))) for cls in class_names] # Print info print(f"Total Number of Classes : {n_classes}") print("+++++++++++++++++++++++++++++++++++++++\n") print(f"Classes : {class_names}") print("+++++++++++++++++++++++++++++++++++++++\n") print(f"Class Distribution : {class_dis}") # Set up grid num_cols = 4 num_classes = 47 num_rows = 12 # 47 classes => 12 rows (last row has fewer images) plt.figure(figsize=(num_cols * 4, num_rows * 4)) for i, cls in enumerate(class_names): class_path = os.path.join(root_path, cls) img_path = os.path.join(class_path, random.choice(os.listdir(class_path))) img = Image.open(img_path).resize((128, 128)) plt.subplot(num_rows, num_cols, i + 1) plt.imshow(img) plt.title(cls, fontsize=15) plt.axis('off') plt.tight_layout() plt.show() # Create a dictionary of class names and their image counts image_counts = {cls: len(os.listdir(os.path.join(root_path, cls))) for cls in class_names} # Prepare data classes = list(image_counts.keys()) counts = list(image_counts.values()) # Sort by count (largest first) sorted_pairs = sorted(zip(classes, counts), key=lambda x: x[1], reverse=True) classes, counts = zip(*sorted_pairs) #unzips # Create the plot plt.figure(figsize=(12, len(classes) * 0.5)) # Dynamic height based on number of classes bars = plt.barh(classes, counts, color='salmon') # Add count labels next to each bar for bar in bars: width = bar.get_width() plt.text(width + 5, bar.get_y() + bar.get_height()/2, str(int(width)), va='center', fontsize=10) # Customize axes and title plt.xlabel("Image Count", fontsize=14) plt.title("Number of Images per Yoga Class", fontsize=16) plt.xticks(fontsize=10) plt.yticks(fontsize=10) plt.gca().invert_yaxis() # Show largest class on top plt.grid(axis='x', linestyle='--', alpha=0.5) plt.tight_layout() plt.show() # ImageDataGenerator with augmentation and validation split data_gen = ImageDataGenerator( rescale=1./255, rotation_range=20, validation_split=0.2 ) # Load datasets train_ds = data_gen.flow_from_directory( root_path, target_size=(256, 256), class_mode='sparse', subset='training' ) valid_ds = data_gen.flow_from_directory( root_path, target_size=(256, 256), class_mode='sparse', subset='validation' ) # Load base model base_model = Xception( include_top=False, weights='imagenet', input_shape=(256, 256, 3) ) base_model.trainable = False # Build model model = Sequential([ base_model, GlobalAveragePooling2D(), Dense(256, activation='relu'), Dropout(0.4), Dense(n_classes, activation='softmax') ]) # Compile model model.compile( optimizer='adam', loss='sparse_categorical_crossentropy', # binary_crossentropy -> You have only 2 classes (binary classification) # categorical_crossentropy-> labels are one-hot encoded (e.g., [0, 0, 1, 0]) # sparse_categorical_crossentropy You have more than 2 classes, and labels are integers (e.g., 2) metrics=['accuracy'] ) # Train the model callbacks = [EarlyStopping(patience=3, restore_best_weights=True)] history = model.fit( train_ds, validation_data=valid_ds, epochs=25, callbacks=callbacks ) # Plot training history plt.figure(figsize=(12, 4)) plt.subplot(1, 2, 1) plt.plot(history.history['accuracy'], label='Train Acc') plt.plot(history.history['val_accuracy'], label='Val Acc') plt.title("Accuracy") plt.legend() plt.subplot(1, 2, 2) plt.plot(history.history['loss'], label='Train Loss') plt.plot(history.history['val_loss'], label='Val Loss') plt.title("Loss") plt.legend() plt.tight_layout() plt.show() from sklearn.metrics import classification_report import numpy as np # Load validation data without shuffling to match labels with predictions valid_data = data_gen.flow_from_directory( root_path, target_size=(256, 256), class_mode='sparse', subset='validation', shuffle=False ) # Get the true class labels true_labels = valid_data.classes # These are the correct class indices # Predict class probabilities predictions = model.predict(valid_data, verbose=1) # Get the predicted class index (highest probability) predicted_labels = np.argmax(predictions, axis=1) # Map class indices back to class names class_names = list(valid_data.class_indices.keys()) # Print precision, recall, and F1-score print(classification_report(true_labels, predicted_labels, target_names=class_names)) # Show 5 random predictions plt.figure(figsize=(15, 5)) for i in range(5): idx = random.randint(0, len(valid_ds.filenames) - 1) img_path = os.path.join(valid_ds.directory, valid_ds.filenames[idx]) img = Image.open(img_path).resize((256, 256)) arr = np.array(img) / 255.0 pred = model.predict(np.expand_dims(arr, axis=0)) pred_class = idx_to_class[np.argmax(pred)] true_class = valid_ds.filenames[idx].split('/')[0] plt.subplot(1, 5, i+1) plt.imshow(img) plt.title(f"True: {true_class}\nPred: {pred_class}") plt.axis('off') plt.tight_layout() plt.show()资料获取,更多粉丝福利,关注下方公众号获取