Pytorch图像分类实战笔记|Datawhale组队学习

学习计划

Pytorch图像分类实战笔记|Datawhale组队学习_第1张图片

Task01:构建自己的图像分类数据集

第一节配套的代码:(按照顺序学习)

Pytorch图像分类实战笔记|Datawhale组队学习_第2张图片

1.1配置环境

tqdm库官方说明:https://github.com/tqdm/tqdm#documentation

简而言之,tqdm是 Python 进度条库,可以在 Python长循环中添加一个进度提示信息。

代码例子:

from tqdm import tqdm
from time import sleep

for char in tqdm(range(0,5)):
    sleep(2)

'''
更简便的写法
'''
from tqdm import trange
for i in trange(0,5):
    sleep(2)

显示结果:

补充阅读:https://blog.csdn.net/wxd1233/article/details/118371404

opencv-python 处理图像

requests Python HTTP库

1.2图像采集

爬虫知识,略

1.3注意事项

  1. 删除无关图片(手动删)

  1. 类别均衡

  1. 多样化、代表性、一致性

Pytorch图像分类实战笔记|Datawhale组队学习_第3张图片
  1. 删除多余文件夹、文件

文件夹:__MACOSX,.DS_Store,.ipynb_checkpoints;

文件:gif格式的、非三通道图片(三通道图可以是彩色图,可以是灰度模式的图像。三通道分别指RGB通道)

1.4数据集信息描述

图像尺寸、比例分布

#第一步,导入库
import os
import numpy as np
import pandas as pd
import cv2
from tqdm import tqdm

import matplotlib.pyplot as plt
%matplotlib inline

# 第二步,指定数据集路径
dataset_path = 'fruit81_full'
os.chdir(dataset_path)
os.listdir()

df = pd.DataFrame()
for fruit in tqdm(os.listdir()): # 遍历每个类别    
    os.chdir(fruit)
    for file in os.listdir(): # 遍历每张图像
        try:
            img = cv2.imread(file)
            df = df.append({'类别':fruit, '文件名':file, '图像宽':img.shape[1], '图像高':img.shape[0]}, ignore_index=True)
        except:
            print(os.path.join(fruit, file), '读取错误')
    os.chdir('../')
os.chdir('../')

#查看结果
df
Pytorch图像分类实战笔记|Datawhale组队学习_第4张图片

#可视化图像尺寸分布

from scipy.stats import gaussian_kde
from matplotlib.colors import LogNorm

x = df['图像宽']
y = df['图像高']

xy = np.vstack([x,y])
z = gaussian_kde(xy)(xy)

# Sort the points by density, so that the densest points are plotted last
idx = z.argsort()
x, y, z = x[idx], y[idx], z[idx]

plt.figure(figsize=(10,10))
# plt.figure(figsize=(12,12))
plt.scatter(x, y, c=z,  s=5, cmap='Spectral_r')
# plt.colorbar()
# plt.xticks([])
# plt.yticks([])

plt.tick_params(labelsize=15)

xy_max = max(max(df['图像宽']), max(df['图像高']))
plt.xlim(xmin=0, xmax=xy_max)
plt.ylim(ymin=0, ymax=xy_max)

plt.ylabel('height', fontsize=25)
plt.xlabel('width', fontsize=25)

plt.savefig('图像尺寸分布.pdf', dpi=120, bbox_inches='tight')

plt.show()
Pytorch图像分类实战笔记|Datawhale组队学习_第5张图片

1.5划分数据集

CV是图像数据集,无法用Pytorch自动划分。只能使用基础语法创建目录,移动文件。

主要思路:

将数据集统一命名为数字-->获得文件名列表full_dataset-->打乱列表random_dataset-->set train_rate = 0.8,train_dataset = random_dataset[:int(full_dataset)*0.8],test_dataset =random_dataset[int(full_dataset)*0.8:] -->移动文件。

第一步,导入所需要的库。

import os
import shutil
import random
import pandas as pd

第二步,获得full_dataset,并随即打乱

# 读取该类别的所有图像文件名
old_dir = os.path.join(dataset_path, fruit)
images_filename = os.listdir(old_dir)
random.shuffle(images_filename)

第三步,开始划分

# 划分训练集和测试集
testset_numer = int(len(images_filename) * test_frac) # 测试集图像个数
testset_images = images_filename[:testset_numer]      # 获取拟移动至 test 目录的测试集图像文件名
trainset_images = images_filename[testset_numer:]     # 获取拟移动至 train 目录的训练集图像文件名

第四步,开始移动

# 移动图像至 test 目录
for image in testset_images:
    old_img_path = os.path.join(dataset_path, fruit, image)         # 获取原始文件路径
    new_test_path = os.path.join(dataset_path, 'val', fruit, image) # 获取 test 目录的新文件路径
    shutil.move(old_img_path, new_test_path) # 移动文件

# 移动图像至 train 目录
for image in trainset_images:
    old_img_path = os.path.join(dataset_path, fruit, image)           # 获取原始文件路径
    new_train_path = os.path.join(dataset_path, 'train', fruit, image) # 获取 train 目录的新文件路径
    shutil.move(old_img_path, new_train_path) # 移动文件

第五步,删除原有的文件目录

    # 删除旧文件夹
    assert len(os.listdir(old_dir)) == 0 # 确保旧文件夹中的所有图像都被移动走
    shutil.rmtree(old_dir) # 删除文件夹

第六步,df

# 工整地输出每一类别的数据个数
print('{:^18} {:^18} {:^18}'.format(fruit, len(trainset_images), len(testset_images)))

# 保存到表格中
df = df.append({'class':fruit, 'trainset':len(trainset_images), 'testset':len(testset_images)}, ignore_index=True)

第七步,第二步到第六步加个遍历,因为数据集不止这一类。

for fruit in classes: # 遍历每个类别
    ...

第八步

# 重命名数据集文件夹
shutil.move(dataset_path, dataset_name+'_split')

# 数据集各类别数量统计表格,导出为 csv 文件
df['total'] = df['trainset'] + df['testset']
df.to_csv('数据量统计.csv', index=False)

Task02预训练图像分类模型预测

task02的代码,按顺序进行学习

Pytorch图像分类实战笔记|Datawhale组队学习_第6张图片

2.1配置环境

mmcv-full

MMCV 是一个面向计算机视觉的基础库,它支持了很多开源项目。

MMCV 提供了如下众多功能:

  • 通用的 IO 接口

  • 图像和视频处理

  • 图像和标注结果可视化

  • 常用小工具(进度条,计时器等)

  • 基于 PyTorch 的通用训练框架

  • 多种 CNN 网络结构

  • 高质量实现的常见 CUDA 算子

mmcv-full: 完整版,包含所有的特性以及丰富的开箱即用的 CUDA 算子。注意完整版本可能需要更长时间来编译。

mmcv: 精简版,不包含 CUDA 算子但包含其余所有特性和功能,类似 MMCV 1.0 之前的版本。如果你不需要使用 CUDA 算子的话,精简版可以作为一个考虑选项。

根据torch和cuda以及mmcv版本在官网中查找对应命令下载

https://mmcv.readthedocs.io/zh_CN/latest/get_started/installation.html
import torch
print(torch.__version__)
print(torch.version.cuda)
pip install mmcv-full==1.7.0 -f https://download.openmmlab.com/mmcv/dist/cu102/torch1.6/index.html

其他我在这个项目之前就安装过了,不赘述。

2.2预测单张图像

第一步,导入所需要的库

import os

import cv2

import pandas as pd
import numpy as np

import torch

import matplotlib.pyplot as plt
%matplotlib inline

from torchvision import models
# 有 GPU 就用 GPU,没有就用 CPU
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')

第二步,查看有多少模型

dir(models)
['AlexNet',
 'DenseNet',
 'GoogLeNet',
 'GoogLeNetOutputs',
 'Inception3',
 'InceptionOutputs',
 'MNASNet',
 'MobileNetV2',
 'ResNet',
 'ShuffleNetV2',
 'SqueezeNet',
 'VGG',
 '_GoogLeNetOutputs',
 '_InceptionOutputs',
 '__builtins__',
 '__cached__',
 '__doc__',
 '__file__',
 '__loader__',
 '__name__',
 '__package__',
 '__path__',
 '__spec__',
 '_utils',
 'alexnet',
 'densenet',
 'densenet121',
 'densenet161',
 'densenet169',
 'densenet201',
 'detection',
 'googlenet',
 'inception',
 'inception_v3',
 'mnasnet',
 'mnasnet0_5',
 'mnasnet0_75',
 'mnasnet1_0',
 'mnasnet1_3',
 'mobilenet',
 'mobilenet_v2',
 'quantization',
 'resnet',
 'resnet101',
 'resnet152',
 'resnet18',
 'resnet34',
 'resnet50',
 'resnext101_32x8d',
 'resnext50_32x4d',
 'segmentation',
 'shufflenet_v2_x0_5',
 'shufflenet_v2_x1_0',
 'shufflenet_v2_x1_5',
 'shufflenet_v2_x2_0',
 'shufflenetv2',
 'squeezenet',
 'squeezenet1_0',
 'squeezenet1_1',
 'utils',
 'vgg',
 'vgg11',
 'vgg11_bn',
 'vgg13',
 'vgg13_bn',
 'vgg16',
 'vgg16_bn',
 'vgg19',
 'vgg19_bn',
 'video',
 'wide_resnet101_2',
 'wide_resnet50_2']

第三步,载入预训练图像分类模型

model = models.resnet18(pretrained=True) 

# model = models.resnet152(pretrained=True) 效果更好,更精准

model = model.eval()
model = model.to(device)#使用GPU处理

model.eval()的作用:模型中有BatchNormalization和Dropout,在预测时使用model.eval()后会将其关闭以免影响预测结果。(反正记得用就对了)

第四步,图像预处理

from torchvision import transforms

torchvision是PyTorch的计算机视觉工具包,包含了一些与CV相关的处理。其中transforms尝用作图像预处理的,如数据中心化标准化缩放裁剪等。

https://zhuanlan.zhihu.com/p/200876072这篇文章介绍了使用transform22种图片预处理方法。

# 测试集图像预处理-RCTN:缩放裁剪、转 Tensor、归一化
test_transform = transforms.Compose([transforms.Resize(256),
                                     transforms.CenterCrop(224),
                                     transforms.ToTensor(),
                                     transforms.Normalize(
                                         mean=[0.485, 0.456, 0.406], 
                                         std=[0.229, 0.224, 0.225])
                                    ])

第五步,开始预测

首先,载入测试图像

# 用 pillow 载入
from PIL import Image

img_path = 'test_img/ca.jpeg'
img_pil = Image.open(img_path)
Pytorch图像分类实战笔记|Datawhale组队学习_第7张图片
input_img = test_transform(img_pil) # 预处理
input_img = input_img.unsqueeze(0).to(device)#不知道这一步是为了什么
# 执行前向预测,得到所有类别的 logit 预测分数
pred_logits = model(input_img) 
import torch.nn.functional as F
pred_softmax = F.softmax(pred_logits, dim=1) # 对 logit 分数做 softmax 运算

预测结果分析:各类别置信度柱状图

plt.figure(figsize=(8,4))

x = range(1000)
y = pred_softmax.cpu().detach().numpy()[0]

ax = plt.bar(x, y, alpha=0.5, width=0.3, color='yellow', edgecolor='red', lw=3)
plt.ylim([0, 1.0]) # y轴取值范围
# plt.bar_label(ax, fmt='%.2f', fontsize=15) # 置信度数值

plt.xlabel('Class', fontsize=20)
plt.ylabel('Confidence', fontsize=20)
plt.tick_params(labelsize=16) # 坐标文字大小
plt.title(img_path, fontsize=25)

plt.show()
Pytorch图像分类实战笔记|Datawhale组队学习_第8张图片

取出置信度最大的前5个

n = 5
top_n = torch.topk(pred_softmax, n)
pred_ids = top_n[1].cpu().detach().numpy().squeeze()
confs = top_n[0].cpu().detach().numpy().squeeze()
Pytorch图像分类实战笔记|Datawhale组队学习_第9张图片

values是置信度,indices是对应的id

载入csv

df = pd.read_csv('imagenet_class_index.csv')
idx_to_labels = {}
for idx, row in df.iterrows():
    idx_to_labels[row['ID']] = [row['wordnet'], row['class']]
Pytorch图像分类实战笔记|Datawhale组队学习_第10张图片
# 用 opencv 载入原图
img_bgr = cv2.imread(img_path)

for i in range(n):
    class_name = idx_to_labels[pred_ids[i]][1] # 获取类别名称
    confidence = confs[i] * 100 # 获取置信度
    text = '{:<15} {:>.4f}'.format(class_name, confidence)
    print(text)
  
Pytorch图像分类实战笔记|Datawhale组队学习_第11张图片

将结果写在图片上后是这样:

2.3预测视频文件

看一下导入的库,知道每个库的作用就行了。

import os
import time
import shutil
import tempfile
from tqdm import tqdm

import cv2
from PIL import Image

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
plt.rcParams['axes.unicode_minus']=False  # 用来正常显示负号
plt.rcParams['font.sans-serif']=['SimHei']  # 用来正常显示中文标签
import gc

import torch
import torch.nn.functional as F
from torchvision import models

import mmcv

# 有 GPU 就用 GPU,没有就用 CPU
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
print('device:', device)

主要思路:

  1. 利用mmcv把视频的每一帧提取出来,放在临时文件夹里

  1. 循环运行【1.2】的代码

  1. 将第二步的输出结果合并成一个视频

这一块补充知识:https://blog.csdn.net/ViatorSun/article/details/108815413

是关于opencv和PIL格式的转换

2.4预测摄像头

准备工作

import os

import numpy as np
import pandas as pd

import cv2 # opencv-python
from PIL import Image, ImageFont, ImageDraw
from tqdm import tqdm # 进度条

import matplotlib.pyplot as plt
%matplotlib inline

import torch
import torch.nn.functional as F
from torchvision import models

# 有 GPU 就用 GPU,没有就用 CPU
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
print('device:', device)

# 导入中文字体,指定字号
font = ImageFont.truetype('SimHei.ttf', 32)

载入预训练图片分类模型

model = models.resnet18(pretrained=True)
model = model.eval()
model = model.to(device)

载入ImageNet 1000图像分类标签

# 载入ImageNet 1000图像分类标签
df = pd.read_csv('imagenet_class_index.csv')
idx_to_labels = {}
for idx, row in df.iterrows():
    idx_to_labels[row['ID']] = row['Chinese']

图片预处理

from torchvision import transforms

# 测试集图像预处理-RCTN:缩放裁剪、转 Tensor、归一化
test_transform = transforms.Compose([transforms.Resize(256),
                                     transforms.CenterCrop(224),
                                     transforms.ToTensor(),
                                     transforms.Normalize(
                                         mean=[0.485, 0.456, 0.406], 
                                         std=[0.229, 0.224, 0.225])
                                    ])

获取摄像头一帧的图片,输出img_bgr(bgr格式)

# 导入opencv-python
import cv2
import time

# 获取摄像头,传入0表示获取系统默认摄像头
cap = cv2.VideoCapture(1)

# 打开cap
cap.open(0)

time.sleep(1)

success, img_bgr = cap.read()
    
# 关闭摄像头
cap.release()

# 关闭图像窗口
cv2.destroyAllWindows()

实现array到image的转化

img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB) # BGR转RGB

img_pil = Image.fromarray(img_rgb)
#plt.imshow(img_rgb)

对单个图片的预处理

input_img = test_transform(img_pil).unsqueeze(0).to(device) # 预处理
pred_logits = model(input_img) # 执行前向预测,得到所有类别的 logit 预测分数
pred_softmax = F.softmax(pred_logits, dim=1) # 对 logit 分数做 softmax 运算

n = 5
top_n = torch.topk(pred_softmax, n) # 取置信度最大的 n 个结果
pred_ids = top_n[1].cpu().detach().numpy().squeeze() # 解析出类别
confs = top_n[0].cpu().detach().numpy().squeeze() # 解析出置信度
draw = ImageDraw.Draw(img_pil) 
# 在图像上写字
for i in range(len(confs)):
    pred_class = idx_to_labels[pred_ids[i]]
    text = '{:<15} {:>.3f}'.format(pred_class, confs[i])
    # 文字坐标,中文字符串,字体,rgba颜色
    draw.text((50, 100 + 50 * i), text, font=font, fill=(255, 0, 0, 1))
img = np.array(img_pil) # PIL 转 array

将上面的代码块进行合并,就可以定义一个处理帧的函数。

格式转化很容易搞混。上面提到的这篇博客(https://blog.csdn.net/ViatorSun/article/details/108815413)需要收藏,以备不时之需。

# 处理帧函数
def process_frame(img):
    
    # 记录该帧开始处理的时间
    start_time = time.time()
    
    img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # BGR转RGB
    img_pil = Image.fromarray(img_rgb) # array 转 PIL
    input_img = test_transform(img_pil).unsqueeze(0).to(device) # 预处理
    pred_logits = model(input_img) # 执行前向预测,得到所有类别的 logit 预测分数
    pred_softmax = F.softmax(pred_logits, dim=1) # 对 logit 分数做 softmax 运算
    
    top_n = torch.topk(pred_softmax, 5) # 取置信度最大的 n 个结果
    pred_ids = top_n[1].cpu().detach().numpy().squeeze() # 解析预测类别
    confs = top_n[0].cpu().detach().numpy().squeeze() # 解析置信度
    
    # 使用PIL绘制中文
    draw = ImageDraw.Draw(img_pil) 
    # 在图像上写字
    for i in range(len(confs)):
        pred_class = idx_to_labels[pred_ids[i]]
        text = '{:<15} {:>.3f}'.format(pred_class, confs[i])
        # 文字坐标,中文字符串,字体,bgra颜色
        draw.text((50, 100 + 50 * i),  text, font=font, fill=(255, 0, 0, 1))
    img = np.array(img_pil) # PIL 转 array
    img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) # RGB转BGR
    
    # 记录该帧处理完毕的时间
    end_time = time.time()
    # 计算每秒处理图像帧数FPS
    FPS = 1/(end_time - start_time)  
    # 图片,添加的文字,左上角坐标,字体,字体大小,颜色,线宽,线型
    img = cv2.putText(img, 'FPS  '+str(int(FPS)), (50, 80), cv2.FONT_HERSHEY_SIMPLEX, 2, (255, 0, 255), 4, cv2.LINE_AA)
    return img

最后一步,调用摄像头获取每帧(模板,以后直接套用)。

# 调用摄像头逐帧实时处理模板
# 不需修改任何代码,只需修改process_frame函数即可
# 同济子豪兄 2021-7-8

# 导入opencv-python
import cv2
import time

# 获取摄像头,传入0表示获取系统默认摄像头
cap = cv2.VideoCapture(1)

# 打开cap
cap.open(0)

# 无限循环,直到break被触发
while cap.isOpened():
    # 获取画面
    success, frame = cap.read()
    if not success:
        print('Error')
        break
    
    ## !!!处理帧函数
    frame = process_frame(frame)
    
    # 展示处理后的三通道图像
    cv2.imshow('my_window',frame)

    if cv2.waitKey(1) in [ord('q'),27]: # 按键盘上的q或esc退出(在英文输入法下)
        break
    
# 关闭摄像头
cap.release()

# 关闭图像窗口
cv2.destroyAllWindows()

2.5总结

把每个代码之间的关系滤清了,逻辑是通了但是没搞懂底层。中间遇到一些问题,比如cuda failed,还有图片格式转化搞不清。

你可能感兴趣的:(python,pytorch,分类)