LightWeightOpenPose框架简介及踩坑过程


Author:qyan.li

Date:2022.4.17

Topic:LightWeightOpenpose框架简介及使用踩坑记录

一、前言

​ 忙碌将近两周,课程设计在磕磕绊绊中终于也算是完成,这其中真的是遇到无数的坑,当时就许诺,完成之后,一定要写篇博文记录一下。

​ 课程设计是有关姿态识别方向的,因此需要借助于一定的框架实现人体关键点的检测骨架图的绘制;开始时本来想借助于OpenPose框架,但奈何中途遇到问题太多,跑三天愣是没跑出来,无奈只好转战其他轻量级或者易于调用的框架,隔壁队友选择MediaPipe,而自己则是选择LightWeightOpenPose

​ 本博文提及的思路和解决办法,仅限参考(按照我的方法代码决对是可以跑通的,但不一定是最简单的方法),同时本博文不涉及任何原理性的讲解,实际上我也看不太懂,后面部分会有一些自己对于代码的小更改,可以参考。

二、LightWeightOpenPose框架简介

​ 自己对于LightWeightOpenPose的了解并不是特别深刻,选择它的原因也更多在于第一个看见的就是它;下面的简介来自于知乎的一篇文章,可以参考一下:

LightWeightOpenPoseIntelOpenPose的基础上,提出一种轻量版本,相对于2阶的OpenPose,其参数量只有15% ,但是性能缺相差无几(精度降低1%)。最主要的是,其模型可以在CPU上达到26fps

参考文献:轻量级OpenPose, Lightweight OpenPose - 知乎 (zhihu.com)

Tips

  1. 框架在自己电脑上运行时,速度远没有达到26fps,据我的观察,输入视频,大概也就是每秒几帧。
  2. 相比于隔壁的MediaPipe,性能差距个人感觉还是挺大的,至少从我的观察看,性能和效果都比不上隔壁的MediaPipe
  3. 相比于MediaPipe,LightWeight的优点在于它可以进行多人的关键点检测,同时可以将人物用矩形框框选出来,这对于某些情形还是非常重要的
三、LightWeightOpenPose框架运行:

github地址链接:LightWeightOpenpose框架

划重点:

​ 假设你和我一样,不想了解其内部算法的具体原理以及训练推理的具体过程,只需要以下几步即可以运行代码:

  • 配置好框架所需的具体环境:见GitHub

  • pull下框架代码,下载预训练模型:checkpoint_iter_370000.pth文件

    下载地址:https://download.01.org/opencv/openvino_training_extensions/models/human_pose_estimation/checkpoint_iter_370000.pth

  • 命令行执行cmd命令:python demo.py --checkpoint-pth checkpoint_iter_370000.pth --images xx.jpg xx.jpg

    Tips:

    1. 上述代码需保证你已经进入相应的文件路径中
    2. --images xx.jpg xx.jpg 为识别图片的执行命令,识别视频更改为--video xx.mp4
    3. 多张图片识别时直接在命令后添加即可,中间使用空格分隔

​ 看到这里,可能会有一部分同学理解不了为什么这么执行,其实这些东西在github上写的比较清楚,写在这里只是防止某些同学看不懂或者没心思看。

​ 参数设置问题可以在demo.py文件中查看:

### demo.py
parser = argparse.ArgumentParser(
        description='''Lightweight human pose estimation python demo.
                       This is just for quick results preview.
                       Please, consider c++ demo for the best performance.''')
    parser.add_argument('--checkpoint-path', type=str, required=True, help='path to the checkpoint')
    parser.add_argument('--height-size', type=int, default=256, help='network input layer height size')
    parser.add_argument('--video', type=str, default='', help='path to video file or camera id')
    parser.add_argument('--images', nargs='+', default='', help='path to input image(s)')
    parser.add_argument('--cpu', action='store_true', help='run network inference on cpu')
    parser.add_argument('--track', type=int, default=1, help='track pose id in video')
    parser.add_argument('--smooth', type=int, default=1, help='smooth pose keypoints')
    args = parser.parse_args()

​ 根源上讲,上述所有参数都可以进行设置,我们在此只是提及两个如果你想跑通代码必须设置的参数:checkpoint-path 和images或video

四、LightWeightOpenpose框架踩坑
  • GPU问题:

    ​ 假设你是拥有实验室资源的研究生或者拥GPU自重的本科生,这条可以跳过;但是假设你什么都没有,还想跑通这个框架:

    ​ 请删除demo.py中:

    if not cpu:
    	net = net.cuda()
    

    ​ 但假设白嫖Colab的学生党人士,请删除demo.pycv2.imshow('Lightweight Human Pose Estimation Python Demo', img),并自行添加保存处理后图片的imwrite函数,具体原因,可参见另一篇博文:(13条消息) 免费的GPU及Colab使用踩坑教程_隔壁李学长的博客-CSDN博客

  • 图像或视频保存问题:

    ​ 原始代码中并不存在将处理后图片保存的代码,因此需要自己添加,很简单,一句代码:

    cv2.imwrite('./saveTest.PNG',img) ## img为imshow函数传入的img
    

    针对于视频的保存问题:

    ​ 目前还没有想到好的保存方式,但在后面的博文中我应该会更新如何将若干帧图像合成视频

  • 超量图像检测问题:

    ​ 这个问题其实是由代码执行的方式所导致的问题,上述提及:借助于LightWeightOpenPose框架进行图像标注的代码执行方式为python demo.py --checkpoint-path checkpoint_iter_370000.pth --images xx.jpg xx.jpg

    ​ 借助于这种方式执行代码,图片量小可以,你可以手动输入,比如两张三张,但是假设图片量非常大呢?项目中我们数据集图片处理过后有超过一万张,这个时候手动输入肯定不现实,那我们就必须考虑借助于某种手段实现命令行命令。

    ​ 我们干不了这个工作,主要由于工作量太大,但是程序喜欢这种情形,尤其是python,特别喜欢干这种工作,这是它的强项,也是他出圈的一个重要方面,有思路,剩下就是写代码:

    ### 获取文件名列表
    import os
    FileNameLst = os.listdir("Your File Path")
    
    ### command命令生成
    with open('./Test.txt','a',encoding = 'utf-8') as f:
        '''Test.txt文件为最终存放cmd命令的文件'''
        f.write('python demo.py --checkpoint-path checkpoint_iter_370000.pth --images ')
        '''FileNameLst为一万张图片文件名列表,借助于os的listdir命令获取'''
        for item in FileNameLst:
            '''images为存放图片的文件夹'''
            f.write("./images/" + str(item) + " ")
    

    这里算是一个分界线,下面的问题有些同学可能并不会遇到(由于数据集数量的问题)

  • 命令行字符限制问题:

    ​ 借助于上述程序实现了command的命令生成,我们高高兴兴的拿着命令行命令去执行,但是又出现另外的问题:

    ​ 将上述程序生成的命令粘贴至命令行时,会发现命令总是被截断的,一开始我以为是复制粘贴不完整,但后续发现每次都是这样,经查阅资料,发现问题:命令行是存在字符限制的,具体限制的多少大家可自行查阅资料:

    ​ 那么出现问题就要解决,怎么办呢?既然命令太长,那就缩短嘛,分批次进行,不要一次性全部处理,那么一次识别多少图片呢?这个size怎么是确定呢?

    ​ 就以我的字符文件数量和文件命令方式,当我执行上述不完整的cmd命令时,发现每次执行结果为352张图片,OK,那就说明一次性执行350张图片,将我一万多张图像分为33个批次执行。

  • cmd命令执行复杂问题:

    ​ 到这里,所有阻碍我们程序运行的拦路虎都已经解决,下面讲一个简化代码执行复杂度的问题;上述生成33条命令,假设我们每次都复制粘贴至命令行手动执行,这效率有点太低,那我们在想有没有什么可以简化cmd命令执行的方法,经查阅,生成Bat文件方法。

    Bat文件网上讲解资料很多,大家可自行查阅,此处仅给出撰写格式:

    @echo off
    C:
    cd C:/Users\腻味\Desktop\PoseData\mpii_human_pose_v1  ## 目标路径
    start python demo.py --checkpoint-path xx.pth --images xx.jpg ## cmd命令
    exit
    

    Ok,又有一个解决问题的思路达成,剩下又到python的主场:

    ### 生成前32个Bat文件
    
    '''FileNameLst为一万张图片的文件名列表'''
    print(len(FileNameLst)) # 11715/350 = 33.47
    
    for i in range(33):
        with open('./Test' + str(i) + '.bat','a',encoding = 'utf-8') as f:
            f.write('@echo off' + '\n')
            f.write('C:' + '\n')
            f.write('cd C:/Users\腻味\Desktop\PoseData\mpii_human_pose_v1')
            f.write('\n')
            f.write('start ')
            f.write('python demo.py --checkpoint-path checkpoint_iter_370000.pth --images ')
            for j in range(i*350,(i+1)*350):
                f.write("./images/" + str(FileNameLst[j]) + " ")
            f.write('\n')
            f.write('exit')
            
    ### 生成最后一个Bat文件
    with open('./Test33.bat','a',encoding = 'utf-8') as f:
        f.write('@echo off' + '\n')
        f.write('C:' + '\n')
        f.write('cd C:/Users\腻味\Desktop\PoseData\mpii_human_pose_v1' + '\n')
        f.write('start ')
        f.write('python demo.py --checkpoint-path checkpoint_iter_370000.pth --images ')
        for m in range(33*350,len(FileNameLst)):
            f.write("./images/" + str(FileNameLst[m]) + " ")
        f.write('\n')
        f.write('exit')
    
  • 并行处理33个Bat文件问题:

    ​ 在搜索Bat文件时,一般都会看到关键词:批量执行,看到这,我在想:一个一个点Bat文件确实有难度,那我干脆多生成一个Bat文件,把33个小Bat文件全都放在其中,最终执行时,点一下大的Bat文件不就全解决啦。

    ​ 当然我也将想法变成现实,无一例外,同样借助于程序,但是最终执行时发现,当所有命令命令行退出执行完毕,仅仅只生成2000张图片,简化的探索失败。

    ​ 自己考虑问题在电脑应该不支持同时处理这么多的程序,但是这给我们提供一个思路,33个文件执行时,可以同时多执行几个(我当时是4个),不必线性执行,提高效率

    ​ 当然,Bat文件在编写时,应该支持线性或者并行执行,或者同时执行几个,执行完在执行其他,只是自己没有深入探索,感兴趣者可自行查阅

    总结:写到这里,写不动啦,问题总会解决的,有时候走到死胡同,可以换个思路!!!


​ 写不动啦!!!有时间再写,后续会更新些LightWeightOpenPose框架代码的小改动,以输出不同形式的图片 2022/04/17 周日


时间:2022/04/17 周日,也没剩多少东西,就不拖延啦!!!今天更完!!


五、LightWeightOpenPose输出样式微调:

​ 首先说一下,为什么要进行输出样式的微调,LightWeightOpenPose框架原始输出为在原图上的绘制人体骨骼图并进行输出,我们期待是能不能对输出图像进行一些改变:

  1. 删除背景,输出纯骨骼图
  2. 加粗原图上的人体骨骼线条,提高后续模型的分类效果

​ 由于关键点检测和骨骼图绘制部分的代码集中分布在demo.py的run_demo函数中,因此我们后续主要针对run_demo中的代码进行调整

  • LightWeightOpenPose输出纯骨骼图

    ## pose相当于原图+曲线点的集合,img相当于一张底色的图片
    for pose in current_poses:
    	pose.draw(img) # 在原有的图像上绘制pose的线条
    

    上述代码完成在原图上绘制骨骼图的操作

    1. pose中主要保存人体的关键点信息以及连接方式
    2. 借助于pose类中的自定义draw方法实现绘图,img表示原始图片

    ​ 结合上述的代码实现,可以得到输出纯骨骼图的方法:手动生成一张纯色的与原始图像大小一致的图像,将其作为draw函数传入的内部参数即可完成纯骨骼图像的输出

    ## LightWeightOpenPose输出纯骨骼图像
    t = list(img.shape)
    	tempImage = np.zeros((int(t[0]),int(t[1]),3)) # 其中必须传入tuple
        for pose in current_poses:
        	pose.draw(tempImage) # 在新生成的自定义图像上绘制pose的线条
    
  • LightWeightOpenPose输出图线条调整

    ​ 假设仔细观察run_demo函数,会发现其中存在这样一个函数:addWeighted函数,从函数的名称可以看出,此函数可以用于调整某两个事物之间的相对比例,可以尝试着修改下述代码中的参数:

    img = cv2.addWeighted(orig_img, 0.1, img, 0.9, 0)
    

    ​ 改动之后发现,参数改动的结果会带来图像上线条粗细的改变,经查阅相关资料得知:addWeighted函数可以将两张图片按照一定的比例进行融合,这也就不难解释为什么可以通过参数调整改变线条粗细

  • 总结:

    ​ 上述的调整有哪些作用嘛?其实,我做上述调整的初衷是想要增强我后续分类器的分类效果,我们后期自己搭建分类器,希望通过做上面的调整改善数据集,增强分类效果。理论上讲,应该是可以增强的,只是迫于时间原因,没有进行尝试。

  • 14
    点赞
  • 93
    收藏
    觉得还不错? 一键收藏
  • 31
    评论
### 回答1: 在lightweight OpenPose人体姿态估计网络中添加SENet注意力模块,可以参考以下步骤: 1. 首先需要在代码中导入相应的库,如下所示: ``` import torch import torch.nn as nn import torch.nn.functional as F ``` 2. 定义SENet注意力模块,代码如下: ``` class SEModule(nn.Module): def __init__(self, channels, reduction): super(SEModule, self).__init__() self.avg_pool = nn.AdaptiveAvgPool2d(1) self.fc1 = nn.Conv2d(channels, channels // reduction, kernel_size=1, bias=False) self.relu = nn.ReLU(inplace=True) self.fc2 = nn.Conv2d(channels // reduction, channels, kernel_size=1, bias=False) self.sigmoid = nn.Sigmoid() def forward(self, x): module_input = x x = self.avg_pool(x) x = self.fc1(x) x = self.relu(x) x = self.fc2(x) x = self.sigmoid(x) return module_input * x ``` 其中,`channels`表示输入的通道数,`reduction`表示缩减比例。 3. 在lightweight OpenPose人体姿态估计网络中添加SENet注意力模块,代码如下: ``` class PoseEstimationWithSENet(nn.Module): def __init__(self, num keypoints): super(PoseEstimationWithSENet, self).__init__() # 定义网络结构 # ... # 添加SENet注意力模块 self.se_block1 = SEModule(64, 16) self.se_block2 = SEModule(128, 16) self.se_block3 = SEModule(256, 16) self.se_block4 = SEModule(512, 16) # 定义输出层 # ... def forward(self, x): # 前向传播过程 # ... # 添加SENet注意力模块 x1 = self.se_block1(x1) x2 = self.se_block2(x2) x3 = self.se_block3(x3) x4 = self.se_block4(x4) # 输出结果 # ... return out ``` 其中,`num_keypoints`表示需要预测的关键点数量。 在代码中添加SENet注意力模块后,就可以对人体姿态进行更加准确的估计。 ### 回答2: 在lightweight OpenPose人体姿态估计网络中添加SENet注意力模块可以通过以下步骤完成: 1. 导入必要的库和模块,包括torch、torchvision、torch.nn等。 2. 定义SEBlock模块,它包含了SENet注意力模块的实现。SENet注意力模块主要由Squeeze操作和Excitation操作组成。 - Squeeze操作:将输入特征图进行全局池化,将每个通道的特征图转化为一个单一的数值。 - Excitation操作:使用全连接层对每个通道的特征进行映射,得到权重向量,表示每个通道的重要性。 - 最后,通过将输入特征图与权重向量进行乘法操作,得到加权后的特征图。 3. 在原始的OpenPose网络中,找到合适的位置插入SEBlock模块。一般来说,可以在每个卷积层(Conv2d)之后添加一个SEBlock模块。 4. 根据具体的网络结构,对每个卷积层后添加SEBlock模块的位置进行修改。可以通过继承nn.Module并重新定义forward函数来实现。 5. 在forward函数中,对每个卷积层后添加SEBlock模块,并将其结果作为输入传递给下一层。 6. 在训练过程中,根据需要进行参数更新和反向传播。 示例代码如下: ```python import torch import torch.nn as nn import torchvision.models as models # 定义SEBlock模块 class SEBlock(nn.Module): def __init__(self, in_channels, reduction_ratio=16): super(SEBlock, self).__init__() self.squeeze = nn.AdaptiveAvgPool2d(1) self.excitation = nn.Sequential( nn.Linear(in_channels, in_channels // reduction_ratio), nn.ReLU(inplace=True), nn.Linear(in_channels // reduction_ratio, in_channels), nn.Sigmoid() ) def forward(self, x): batch_size, channels, _, _ = x.size() squeeze = self.squeeze(x).view(batch_size, channels) excitation = self.excitation(squeeze).view(batch_size, channels, 1, 1) weighted_x = x * excitation.expand_as(x) return weighted_x # 在OpenPose网络中添加SEBlock模块的实现 class OpenPoseWithSENet(nn.Module): def __init__(self): super(OpenPoseWithSENet, self).__init__() self.backbone = models.resnet18(pretrained=True) self.se_block1 = SEBlock(64) self.se_block2 = SEBlock(128) self.se_block3 = SEBlock(256) self.se_block4 = SEBlock(512) def forward(self, x): x = self.backbone.conv1(x) x = self.backbone.bn1(x) x = self.backbone.relu(x) x = self.backbone.maxpool(x) x = self.backbone.layer1(x) x = self.se_block1(x) x = self.backbone.layer2(x) x = self.se_block2(x) x = self.backbone.layer3(x) x = self.se_block3(x) x = self.backbone.layer4(x) x = self.se_block4(x) return x # 初始化网络并进行训练 model = OpenPoseWithSENet() criterion = nn.CrossEntropyLoss() optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9) # 进行训练和测试 for epoch in range(num_epochs): # 训练代码 # 测试代码 ``` 以上是在lightweight OpenPose人体姿态估计网络中添加SENet注意力模块的简要描述和代码实现。具体实现过程可能因网络结构和需求的不同而有所变化,需要根据具体情况进行调整和修改。 ### 回答3: 在轻量级OpenPose人体姿态估计网络中,我们可以通过添加SENet注意力模块来提升网络的性能。SENet注意力模块通过自适应地学习每个通道的权重,使得网络能够更加关注重要的特征信息。 首先,在网络的每个卷积层之后,我们可以添加一个SENet注意力模块。该模块由一个全局平均池化层和两个全连接层组成。全局平均池化层将特征图压缩成一个特征向量,然后通过两个全连接层获得每个通道的权重。最后,通过乘法操作将权重应用到原始特征图中。 下面是伪代码实现: ```python import torch import torch.nn as nn import torch.nn.functional as F class SENet(nn.Module): def __init__(self, in_channels, reduction_ratio=16): super(SENet, self).__init__() self.avg_pool = nn.AdaptiveAvgPool2d(1) self.fc1 = nn.Linear(in_channels, in_channels // reduction_ratio) self.fc2 = nn.Linear(in_channels // reduction_ratio, in_channels) def forward(self, x): b, c, _, _ = x.size() y = self.avg_pool(x).view(b, c) y = F.relu(self.fc1(y)) y = torch.sigmoid(self.fc2(y)).view(b, c, 1, 1) return x * y class LightweightOpenPose(nn.Module): def __init__(self): super(LightweightOpenPose, self).__init__() # define your network architecture here self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1) self.senet1 = SENet(64) self.conv2 = nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1) self.senet2 = SENet(128) # add more layers and SENet modules here def forward(self, x): x = F.relu(self.senet1(self.conv1(x))) x = F.relu(self.senet2(self.conv2(x))) # add more forward operations here return x ``` 在这个示例中,我们只添加了两个SENet模块。你可以根据自己的需求在更多的卷积层之后添加SENet模块。同时,你还可以根据具体任务的需要来调整注意力模块中的超参数,如reduction_ratio等。这个代码只是一个示例,并不是针对具体应用定制的最优实现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 31
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值