Multi-task learning of facial landmarks and attributes with Tensorflow(一)

本文详细介绍了如何对深度学习MTFL人脸数据库进行数据预处理与增强,包括图片尺寸调整、坐标标注、属性标记以及数据集划分等步骤。通过此过程,可以有效提高模型训练的质量和效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

数据集下载链接:

http://mmlab.ie.cuhk.edu.hk/projects/TCDCN.html

github地址:

https://github.com/Mimsyy/Deep-learning-MTFL 

Multi-Task Facial Landmark (MTFL人脸数据库),这个数据库包括了12,995张人脸图片,每张图片都被做了一些标注。包括
(1)5个人脸特征点,包括左眼、右眼、鼻子、左嘴角、右嘴角。
(2)标记性别、微笑、眼镜、姿态。


标记数据格式

数据库被分成两个部分:training和tesing,标记的信息全部存放在txt文件中(traning和testing文件)。
文件中的每一行为一张人脸图片的标记信息,他的格式如下:
image path x1...x5,y1..y5 gender smile wearing glasses head pose
--x1...x5,y1...y5: 标记了左眼、右眼、鼻子、左嘴角、右嘴角的坐标位置。
--gender: 1代表男, 2代表女
--smile: 1代表微笑, 2代表不微笑。
--glasses: 1代表戴眼镜, 2代表没戴眼镜。
--head pose: 1 for left profile,2 for left, 3 for frontal, 4 for right, 5 for right profile


对数据预处理文件的解读

from PIL import Image
import os
import numpy as np
#数据集存放地址
folder = os.path.abspath(os.path.join("./", os.pardir)+"/MTFL") + "/"
#最终要将图全部resize成这个大小150*150
finalSize = 150
counter = 0
infoFiles = ["training.txt", "testing.txt"]
validation_counter = 0
#建立一个validation.txt文件用于存放验证数据信息
val_file = open(folder + "validation.txt","a")
#"training.txt", "testing.txt"中的信息进行处理
for idx in range(len(infoFiles)):
    info = infoFiles[idx]
    #当idx=0时读取training.txt,当idx=1时读取testing.txt
    f = open(folder+info,"r")
    #打开一个新的文件,用于写入信息
    fnew = open("tmp", 'a')
    #当读取training.txt信息时,创建aug_training.txt文件
    if idx == 0:
        fnew_augmented = open(folder+"aug_"+info,'a')
    lines = f.readlines()
    for line in lines:
        line = line.strip("\n ").split(" ")
        #获取到图片名称
        imgName = line[0].replace("\\", "/")
        if imgName == "":
            break
        #打开图片,读取图片的宽高
        img = Image.open(folder+imgName, mode='r')
        originalWidth = img.width
        originalHeight = img.height
        if (originalWidth != originalHeight):
            # Skip non-square images,若高宽不一致则跳过
            img.close()
            continue

        # Resize and check RGB,将图片resize到150*150
        if(originalWidth != finalSize):
            img = img.resize((finalSize, finalSize), Image.ANTIALIAS)    # Image.ANTIALIAS代表高质量

            # Check if img is RGB or greyscale,若图片不是彩色图,则跳过
            pixels = list(img.getdata())
            width, height = img.size
            pixelsArray = np.asarray([pixels[i * width:(i + 1) * width] for i in range(height)])
            if len(pixelsArray.shape) != 3:
                # img is greyscale, skip it
                img.close()
                continue

        coords = []
        # 图片的缩放比例,若大小没有被调整该系数为1,若图像大小调整,同比缩放坐标数据,记录在coords中line[i][1]-line[i][10]
        # 对应x1,x2,x3,x4,x5,y1,y2,y3,y4,y5: 标记了左眼、右眼、鼻子、左嘴角、右嘴角的坐标位置
        coordsScaleFactor = float(finalSize) / float(originalWidth)
        for i in range(1,11):
            coords.append(float(line[i])*coordsScaleFactor)
        #属性gender,smile,glasses,head pose分别对应每行line[i][11]-line[i][14]
        attributes = np.array([int(line[i]) for i in range(11,15)])

        #对testing.txt进行读取,生成1000个用于验证的图片信息于validation.txt中
        if(idx == 1 and validation_counter <= 1000):
            #line[i][0]
            val_file.write(imgName)
            #写入特征点信息
            for coord in coords:
                val_file.write(" " + str(coord))
            #写入属性信息
            for attribute in attributes:
                val_file.write(" " + str(attribute - 1)) # Subtract 1 for better indexing
            val_file.write("\n")
            validation_counter += 1
        # '''
        #         --gender: 1代表男, 2代表女
        #         --smile: 1代表微笑, 2代表不微笑。
        #         --glasses: 1代表戴眼镜, 2代表没戴眼镜。
        #         --head pose: 1 for left profile,2 for left, 3 for frontal, 4 for right, 5 for right profile
        #         attribute - 1:是为了使索引从0开始,方便训练
        # '''
        #剩下的写入fnew中用于训练
        else:
            # Write resized img to file
            fnew.write(imgName)
            for coord in coords:
                fnew.write(" " + str(coord))
            for attribute in attributes:
                fnew.write(" " + str(attribute - 1)) # Subtract 1 for better indexing
            fnew.write("\n")

        # Mirror the image if it's not part of test data
        if idx == 0:
            # Get the new img name,生成翻转图像的名称./MTFL/lfw_5590/Aaron_Eckhart_0001.jpg-->./MTFL/lfw_5590/Aaron_Eckhart_0001_transl.jpg
            splitName = imgName.split('.')
            imgNameTransp = splitName[0] + '_transl.' + splitName[1]

            # Mirror the image and save it
            #左右翻转图像,存图
            imgTransp = img.copy().transpose(Image.FLIP_LEFT_RIGHT)
            imgTransp.save(folder+imgNameTransp)
            imgTransp.close()
            #生成对应翻转图像特征点及属性特征
            coordsTransp = [0 for i in range(10)]
            # Translate x-coords for eyes, nose, and mouth
            coordsTransp[0] = 150 - coords[1]    #左眼睛x1
            coordsTransp[1] = 150 - coords[0]    #右眼睛x2
            coordsTransp[2] = 150 - coords[2]    #鼻子x3
            coordsTransp[3] = 150 - coords[4]    #左嘴角X4
            coordsTransp[4] = 150 - coords[3]    #右嘴角x5
            # Translate y-coords for eyes, nose, and mouth
            coordsTransp[5] = coords[6]          #左眼睛y1
            coordsTransp[6] = coords[5]          #右眼睛y2
            coordsTransp[7] = coords[7]          #鼻子y3
            coordsTransp[8] = coords[9]          #左嘴角y4
            coordsTransp[9] = coords[8]          #右嘴角y5
            # Translate attributes  属性变换gender,smile,glasses,head pose
            attributesTransp = np.array([int(line[i]) for i in range(11,15)])
            attributesTransp[3] = 6 - attributesTransp[3] # Translate head:1 for left profile,2 for left, 3 for frontal, 4 for right, 5 for right profile

            # Write resized old img to augmented file,写入原图的图像信息
            fnew_augmented.write(imgName)
            for coord in coords:
                fnew_augmented.write(" " + str(coord))
            for attribute in attributes:
                fnew_augmented.write(" " + str(attribute - 1))
            fnew_augmented.write("\n")

            # Write mirrored img to augmented file,写入翻转后的图像信息
            fnew_augmented.write(imgNameTransp)
            for coord in coordsTransp:
                fnew_augmented.write(" " + str(coord))
            for attribute in attributesTransp:
                fnew_augmented.write(" " + str(attribute - 1))
            fnew_augmented.write("\n")

        # Save resized img 存的都是resize到150*150的图片
        img.save(folder+imgName)
        img.close()
        
        counter = counter + 1
        if counter % 1000 == 0:
            print(counter,"files processed")
    #对"training.txt"/"testing.txt"中的每一行读完之后,就关闭该文件
    f.close()
    #对"training.txt"/"testing.txt"中的每一行数据进行操作完成,写入完毕后,就关闭该"tmp"文件
    fnew.close()
    #关闭aug_training.txt
    if idx == 0:
        fnew_augmented.close()
    #删除之前的"training.txt"/"testing.txt"
    os.remove(folder+info)
    #将tmp重命名为"training.txt"/"testing.txt"
    os.rename("tmp",folder+info)
#都循环完毕后,关闭validation.txt
val_file.close()

 

<think>我们正在将Multi-Task-Facial(多任务面部任务)整合到YOLOv11模型和WIDERFACE数据集的训练中。YOLOv11是个目标检测模型,而WIDERFACE是个大规模人脸检测数据集。Multi-Task-Facial通常包括人脸检测、关键点定位、表情识别、属性识别等多个任务。目标:在YOLOv11模型的基础上,扩展为多任务模型,同时完成人脸检测(主任务)以及其他面部相关任务(如关键点检测、表情分类等)。步骤:1.**数据准备**:-WIDERFACE数据集主要用于人脸检测,它包含大量的人脸图像和边界框标注。但Multi-Task-Facial需要更多的标注信息(如关键点、表情、属性等)。因此,我们需要对WIDERFACE数据集进行补充标注,或者将WIDERFACE与其他包含多任务标注的数据集(如CelebA,AFLW等)合并使用。-数据格式统:将多任务标注整合到个统的标注文件中(例如,每个图像对应个标注文件,包含人脸框、关键点坐标、表情标签等)。2.**修改模型架构(YOLOv11)**:-YOLOv11本身是个单任务模型(检测)。我们需要在其主干网络(backbone)之后添加多个任务特定的分支(heads)。-具体设计:-共享主干网络:使用YOLOv11的主干网络(如CSPDarknet)提取共享特征。-任务分支:-任务1:人脸检测分支(YOLOv11原有的检测头,输出边界框和置信度)。-任务2:人脸关键点检测分支(输出例如68个关键点的坐标)。-任务3:表情分类分支(输出表情类别概率)。-任务4:属性分类分支(如性别、年龄等,每个属性个二分类或多分类输出)。-注意:每个分支可以是个或多个卷积层。3.**损失函数**:-总损失是多任务损失的加权和:$$L_{total}=w_{det}\cdotL_{det}+w_{landmark}\cdotL_{landmark}+w_{expr}\cdotL_{expr}+w_{attr}\cdotL_{attr}$$-其中:-$L_{det}$:人脸检测损失,采用YOLOv11原有的损失(包括边界框坐标损失、置信度损失、分类损失)。-$L_{landmark}$:关键点损失,通常使用平滑L1损失或均方误差(MSE)。-$L_{expr}$:表情分类损失,使用交叉熵损失。-$L_{attr}$:属性分类损失,每个属性使用交叉熵损失,然后取平均。-权重选择:可以手动调整,也可以使用动态权重调整方法(如根据任务不确定性[^1])。4.**训练策略**:-联合训练:同时训练所有任务。-数据加载:在数据加载时,需要同时加载所有任务的标注。-训练技巧:-由于不同任务的学习难度和收敛速度不同,可以考虑任务式提前停止[^5]:当某个任务在验证集上性能饱和时,冻结该任务分支,继续训练其他任务。-梯度裁剪:避免不同任务梯度量级差异过大导致训练不稳定。-学习率调整:使用余弦退火等动态调整学习率。5.**评估**:-分别评估每个任务:-人脸检测:使用mAP(meanAveragePrecision)评估。-关键点检测:使用NME(NormalizedMeanError)评估。-表情识别:使用准确率(Accuracy)评估。-属性分类:使用准确率或F1分数评估。6.**推理优化**:-在推理时,我们可以选择只运行需要的任务分支。例如,如果只需要人脸检测,那么只需要运行检测分支;如果需要全部任务,则运行所有分支。关键挑战:-数据集:WIDERFACE没有提供除检测框以外的标注,因此需要额外标注或融合其他数据集。-模型复杂度:多任务模型会增加计算量和参数量,需要考虑模型轻量化或剪枝。代码结构示例(PyTorch伪代码):```pythonclassMultiTaskYOLO(nn.Module):def__init__(self,backbone,num_classes,num_landmarks,num_exprs,num_attrs):super().__init__()self.backbone=backbone#YOLOv11的主干#检测头(YOLO原有的检测头)self.det_head=YOLOHead(...)#关键点头(输出68个关键点,每个点2个坐标)self.landmark_head=nn.Sequential(nn.Conv2d(backbone_out_channels,256,3,padding=1),nn.ReLU(),nn.Conv2d(256,num_landmarks*2,1))#表情分类头(全局平均池化后接全连接层)self.expr_head=nn.Sequential(nn.AdaptiveAvgPool2d(1),nn.Flatten(),nn.Linear(backbone_out_channels,num_exprs))#属性分类头(可以多个属性共享个头,也可以每个属性单独个头)self.attr_head=nn.ModuleList([nn.Sequential(nn.AdaptiveAvgPool2d(1),nn.Flatten(),nn.Linear(backbone_out_channels,num_classes_attr[i]))foriinrange(num_attrs)])defforward(self,x):features=self.backbone(x)det_out=self.det_head(features)landmark_out=self.landmark_head(features)expr_out=self.expr_head(features)attr_outs=[head(features)forheadinself.attr_head]returndet_out,landmark_out,expr_out,attr_outs```训练循环中的损失计算示例:```python#假设每个任务的损失函数已经定义loss_det=det_criterion(det_pred,det_target)loss_landmark=landmark_criterion(landmark_pred,landmark_target)loss_expr=expr_criterion(expr_pred,expr_target)loss_attr=0foriinrange(num_attrs):loss_attr+=attr_criterion(attr_preds[i],attr_targets[:,i])loss_attr/=num_attrs#总损失,权重可以根据需要调整total_loss=w_det*loss_det+w_landmark*loss_landmark+w_expr*loss_expr+w_attr*loss_attr```注意事项:-在训练前,需要确保每个任务都有对应的标注数据。对于部分缺失标注的数据,可以采用掩码机制跳过该任务的损失计算。-由于WIDERFACE数据集规模很大,补充标注的工作量巨大,可以考虑使用迁移学习:先在完整标注的小数据集上预训练多任务模型,再用WIDERFACE的检测标注进行微调(此时其他任务分支的标注缺失,可以冻结这些分支,只训练检测分支)。引用说明:-动态权重调整(如根据任务不确定性)可参考[^1]。-任务式提前停止策略可参考[^5]。相关问题:1.如何为WIDERFACE数据集添加人脸关键点、表情等标注?2.多任务训练中,如何平衡不同任务之间的损失权重?3.在YOLOv11多任务模型中,如何优化推理速度?4.如何处理训练数据中部分任务标注缺失的情况?</think>### 在YOLOv11模型中结合WIDER FACE数据集实现Multi-Task-Facial训练的步骤 #### **1. 数据准备与标注转换** - **WIDER FACE数据集处理**: - 下载并解压WIDER FACE数据集(包含32,203张图像和393,703个人脸标注) - 转换标注格式为YOLO格式(归化坐标): $$ x_{center} = \frac{x + w/2}{img_w}, \quad y_{center} = \frac{y + h/2}{img_h} $$ $$ w_{norm} = \frac{w}{img_w}, \quad h_{norm} = \frac{h}{img_h} $$ - 生成`train.txt`和`val.txt`文件列表 - **多任务标注扩展**: - 为WIDER FACE补充关键点/表情/属性标注(使用预训练模型自动标注或人工标注) - 创建多任务标注文件(每行格式): ``` image_path x1,y1,x2,y2,conf,landmark_x1,landmark_y1,...,expr_id,attr1,attr2,... ``` #### **2. 模型架构修改** - **主干网络共享**: ```python # YOLOv11主干网络(CSPDarknet) backbone = CSPDarknet(depth_multiple=1.0, width_multiple=1.0) ``` - **多任务分支扩展**: ```python # 检测分支(原始YOLO头) detect_head = Detect(nc=1) # 仅人脸检测,nc=1 # 关键点分支(68个点) landmark_head = nn.Sequential( Conv(1024, 256, 3), nn.Flatten(), nn.Linear(256*7*7, 136) # 68*2=136 ) # 表情分支(7类基本表情) expr_head = nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Flatten(), nn.Linear(1024, 7) ) ``` #### **3. 损失函数设计** - **多任务加权损失**: $$ \mathcal{L}_{total} = \lambda_{det}\mathcal{L}_{det} + \lambda_{landmark}\mathcal{L}_{landmark} + \lambda_{expr}\mathcal{L}_{expr} $$ 其中: - $\mathcal{L}_{det}$:YOLO检测损失(CIoU + 置信度)[^1] - $\mathcal{L}_{landmark}$:平滑L1关键点损失 - $\mathcal{L}_{expr}$:表情分类交叉熵损失 - **动态权重调整**(推荐): $$ \lambda_i = \frac{1}{2\sigma_i^2}, \quad \mathcal{L}_{total} = \sum_i \frac{\mathcal{L}_i}{\sigma_i^2} + \log \sigma_i $$ 通过可学习的$\sigma_i$自动平衡任务权重[^2] #### **4. 训练流程实现** 1. **数据加载**: ```python dataset = MultiTaskDataset( img_dir="WIDER_train/images", annot_file="multitask_annotations.txt", transforms=augmentations # 包含Mosaic, MixUp等 ) ``` 2. **训练循环**: ```python for imgs, targets in dataloader: # targets: [batch_idx, cls, box, landmarks, expr] det_out, landmark_out, expr_out = model(imgs) # 计算多任务损失 loss_det = compute_yolo_loss(det_out, targets[..., :6]) loss_landmark = smooth_l1(landmark_out, targets[..., 6:142]) loss_expr = F.cross_entropy(expr_out, targets[..., 142]) # 动态加权 total_loss = loss_det/(2*sigma_det**2) + loss_landmark/(2*sigma_landmark**2) + loss_expr/(2*sigma_expr**2) total_loss += torch.log(sigma_det*sigma_landmark*sigma_expr) total_loss.backward() optimizer.step() ``` #### **5. 关键训练技巧** - **渐进式训练**: 1. 第阶段:仅训练检测分支(冻结其他分支) 2. 第二阶段:联合训练所有分支 3. 第三阶段:微调任务特定分支 - **梯度处理**: ```python # 梯度裁剪防止冲突 torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=10.0) # 梯度归化(可选) for param in model.parameters(): if param.grad is not None: param.grad.data = param.grad.data / (param.grad.data.norm() + 1e-8) ``` - **数据增强策略**: - 基础增强:随机裁剪、色彩抖动、旋转(±30°) - 高级增强:CutMix, GridMask - **关键点增强**:弹性形变(确保关键点同步变换) #### **6. 评估与验证** - **分任务评估指标**: | 任务 | 指标 | 说明 | |--------------|-----------------------|--------------------------| | 人脸检测 | mAP@0.5 | WIDER FACE官方评估标准 | | 关键点检测 | NME (Normalized Mean Error) | $$ \frac{1}{N}\sum_i \frac{\|pred_i - gt_i\|_2}{d_{interocular}} $$ | | 表情识别 | 准确率(Accuracy) | 7类表情分类准确率 | --- ### 部署优化建议 - **推理加速**: ```python # 仅启用必要分支 model.set_task('detection') # 仅运行检测头 detections = model(imgs) # 对检测结果运行其他任务 for bbox in detections: landmark = model.run_head('landmark', roi_crop(img, bbox)) ``` - **模型轻量化**: - 使用通道剪枝压缩共享主干网络 - 量化INT8推理(TensorRT部署) --- ### 相关问题 1. 如何在WIDER FACE数据集上自动生成多任务标注? 2. YOLOv11多任务训练中如何解决不同任务收敛速度不致的问题? 3. 动态损失权重$\sigma$的初始化值应该如何设置? 4. 多任务模型中如何平衡检测精度与关键点精度? [^1]: 检测损失包含CIoU损失、置信度损失和分类损失,是人脸检测的基础 [^2]: 动态权重调整通过可学习的$\sigma$参数自动平衡多任务损失权重
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值